From 0d88fec4bf591816f638d3620d20f837de32a495 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Aug 2024 17:32:07 +0200 Subject: [PATCH 001/349] #UTILS - added rear door check for CH-47 --- Moose Development/Moose/Utilities/Utils.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 033157ee3..d347cc082 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2319,8 +2319,8 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " cargo door is open") return true end - - if type_name == "CH-47Fbl1" and (unit:getDrawArgumentValue(86) > 0) then + + if type_name == "CH-47Fbl1" and (unit:getDrawArgumentValue(86) > 0.5) then BASE:T(unit_name .. " rear cargo door is open") return true end From 5ffeecc3336b786cb7b9f3a53d071120db2cf3ff Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Aug 2024 17:32:21 +0200 Subject: [PATCH 002/349] #STORAGE - added enumerators --- Moose Development/Moose/Wrapper/Storage.lua | 26 ++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 6f154c575..9e750d779 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -147,9 +147,33 @@ STORAGE.Liquid = { DIESEL = 3, } +--- Liquid Names for the static cargo resource table. +-- @type STORAGE.LiquidName +-- @field #number JETFUEL "jet_fuel". +-- @field #number GASOLINE "gasoline". +-- @field #number MW50 "methanol_mixture". +-- @field #number DIESEL "diesel". +STORAGE.LiquidName = { + GASOLINE = "gasoline", + DIESEL = "diesel", + MW50 = "methanol_mixture", + JETFUEL = "jet_fuel", +} + +--- Storage types. +-- @type STORAGE.Type +-- @field #number WEAPONS weapons. +-- @field #number LIQUIDS liquids. Also see #list<#STORAGE.Liquid> for types of liquids. +-- @field #number AIRCRAFT aircraft. +STORAGE.Type = { + WEAPONS = "weapons", + LIQUIDS = "liquids", + AIRCRAFT = "aircrafts", +} + --- STORAGE class version. -- @field #string version -STORAGE.version="0.0.2" +STORAGE.version="0.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list From 7fa01b9e3c49b93800be806c093b93eac2df8523 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Aug 2024 17:32:46 +0200 Subject: [PATCH 003/349] #STATIC - added access to STORAGE object if set (cargo static types) --- Moose Development/Moose/Wrapper/Static.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 916b78a84..6cf1f28e5 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -330,3 +330,12 @@ function STATIC:FindAllByMatching( Pattern ) return GroupsFound end + +--- Get the Wrapper.Storage#STORAGE object of an static if it is used as cargo and has been set up as storage object. +-- @param #STATIC self +-- @return Wrapper.Storage#STORAGE Storage or `nil` if not fund or set up. +function STATIC:GetStaticStorage() + local name = self:GetName() + local storage = STORAGE:NewFromStaticCargo(name) + return storage +end From d86f47136174f19b7fc48efc7755535006f55b93 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Aug 2024 17:33:22 +0200 Subject: [PATCH 004/349] #SPAWNSTATIC - added functions to manage cargo static resource maps --- Moose Development/Moose/Core/SpawnStatic.lua | 83 +++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index f6a082ddf..965f86f00 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -189,6 +189,7 @@ function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID) self.InitStaticCategory=StaticCategory self.CountryID=CountryID or country.id.USA self.SpawnTemplatePrefix=self.InitStaticType + self.TemplateStaticUnit = {} self.InitStaticCoordinate=COORDINATE:New(0, 0, 0) self.InitStaticHeading=0 @@ -196,6 +197,61 @@ function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID) return self end +--- (Internal/Cargo) Init the resource table for STATIC object that should be spawned containing storage objects. +-- NOTE that you have to init many other parameters as the resources. +-- @param #SPAWNSTATIC self +-- @param #number CombinedWeight The weight this cargo object should have (some have fixed weights!), defaults to 1kg. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:_InitResourceTable(CombinedWeight) + if not self.TemplateStaticUnit.resourcePayload then + self.TemplateStaticUnit.resourcePayload = { + ["weapons"] = {}, + ["aircrafts"] = {}, + ["gasoline"] = 0, + ["diesel"] = 0, + ["methanol_mixture"] = 0, + ["jet_fuel"] = 0, + } + end + self:InitCargo(true) + self:InitCargoMass(CombinedWeight or 1) + return self +end + +--- (User/Cargo) Add to resource table for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. +-- @param #SPAWNSTATIC self +-- @param #string Type Type of cargo. Known types are: STORAGE.Type.WEAPONS, STORAGE.Type.LIQUIDS, STORAGE.Type.AIRCRAFT. Liquids are fuel. +-- @param #string Name Name of the cargo type. Liquids can be STORAGE.LiquidName.JETFUEL, STORAGE.LiquidName.GASOLINE, STORAGE.LiquidName.MW50 and STORAGE.LiquidName.DIESEL. The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. Aircraft go by their typename. +-- @param #number Amount of tons (liquids) or number (everything else) to add. +-- @param #number CombinedWeight Combined weight to be set to this static cargo object. NOTE - some static cargo objects have fixed weights! +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:AddCargoResource(Type,Name,Amount,CombinedWeight) + if not self.TemplateStaticUnit.resourcePayload then + self:_InitResourceTable(CombinedWeight) + end + if Type == STORAGE.Type.LIQUIDS and type(Name) == "string" then + self.TemplateStaticUnit.resourcePayload[Name] = Amount + else + self.TemplateStaticUnit.resourcePayload[Type] = { + [Name] = { + ["amount"] = Amount, + } + } + end + UTILS.PrintTableToLog(self.TemplateStaticUnit) + return self +end + +--- (User/Cargo) Resets resource table to zero for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. +-- Handy if you spawn from cargo statics which have resources already set. +-- @param #SPAWNSTATIC self +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:ResetCargoResources() + self.TemplateStaticUnit.resourcePayload = nil + self:_InitResourceTable() + return self +end + --- Initialize heading of the spawned static. -- @param #SPAWNSTATIC self -- @param Core.Point#COORDINATE Coordinate Position where the static is spawned. @@ -317,6 +373,25 @@ function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle) return self end +--- Allows to place a CallFunction hook when a new static spawns. +-- The provided method will be called when a new group is spawned, including its given parameters. +-- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned. +-- @param #SPAWNSTATIC self +-- @param #function SpawnCallBackFunction The function to be called when a group spawns. +-- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:OnSpawnStatic( SpawnCallBackFunction, ... ) + self:F( "OnSpawnStatic" ) + + self.SpawnFunctionHook = SpawnCallBackFunction + self.SpawnFunctionArguments = {} + if arg then + self.SpawnFunctionArguments = arg + end + + return self +end + --- Spawn a new STATIC object. -- @param #SPAWNSTATIC self -- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template. @@ -488,7 +563,7 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- ED's dirty way to spawn FARPS. Static=coalition.addGroup(CountryID, -1, TemplateGroup) - -- Currently DCS 2.8 does not trigger birth events if FAPRS are spawned! + -- Currently DCS 2.8 does not trigger birth events if FARPS are spawned! -- We create such an event. The airbase is registered in Core.Event local Event = { id = EVENTS.Birth, @@ -503,6 +578,12 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) self:T2({Template=Template}) Static=coalition.addStaticObject(CountryID, Template) end + + -- If there is a SpawnFunction hook defined, call it. + if self.SpawnFunctionHook then + -- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. + self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic, unpack(self.SpawnFunctionArguments)) + end return mystatic end From 084c172e9339b16da2b600ba38d18a11aa752d94 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Aug 2024 17:33:39 +0200 Subject: [PATCH 005/349] #SPAWN - Added StopRepeat() --- Moose Development/Moose/Core/Spawn.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 5e8621921..1c2610f27 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -199,6 +199,7 @@ -- -- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed. -- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp. +-- * @{#SPAWN.StopRepeat}(): This method is used to stop the repeater. -- -- ### Link-16 Datalink STN and SADL IDs (limited at the moment to F15/16/18/AWACS/Tanker/B1B, but not the F15E for clients, SADL A10CII only) -- @@ -1411,6 +1412,30 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) return self end +--- Stop the SPAWN InitRepeat function (EVENT handler for takeoff, land and engine shutdown) +-- @param #SPAWN self +-- @return #SPAWN self +-- @usage +-- local spawn = SPAWN:New("Template Group") +-- :InitRepeatOnEngineShutDown() +-- local plane = spawn:Spawn() -- it is important that we keep the SPAWN object and do not overwrite it with the resulting GROUP object by just calling :Spawn() +-- +-- -- later on +-- spawn:StopRepeat() +function SPAWN:StopRepeat() + if self.Repeat then + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + end + if self.RepeatOnEngineShutDown then + self:UnHandleEvent(EVENTS.EngineShutdown) + end + self.Repeat = false + self.RepeatOnEngineShutDown = false + self.RepeatOnLanding = false + return self +end + do -- AI methods --- Turns the AI On or Off for the @{Wrapper.Group} when spawning. From 554809764f4bd9a4d15153d57f91fc7122c51802 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Aug 2024 17:33:53 +0200 Subject: [PATCH 006/349] #AIRBASE - Added Kola airbases --- Moose Development/Moose/Wrapper/Airbase.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 23d82593a..4d9e068a1 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -721,11 +721,12 @@ AIRBASE.Sinai = { --- Airbases of the Kola map -- -- * AIRBASE.Kola.Banak --- * AIRBASE.Kola.Bas_100 -- * AIRBASE.Kola.Bodo -- * AIRBASE.Kola.Jokkmokk -- * AIRBASE.Kola.Kalixfors +-- * AIRBASE.Kola.Kallax -- * AIRBASE.Kola.Kemi_Tornio +-- * AIRBASE.Kola.Kirkenes -- * AIRBASE.Kola.Kiruna -- * AIRBASE.Kola.Monchegorsk -- * AIRBASE.Kola.Murmansk_International @@ -733,11 +734,12 @@ AIRBASE.Sinai = { -- * AIRBASE.Kola.Rovaniemi -- * AIRBASE.Kola.Severomorsk_1 -- * AIRBASE.Kola.Severomorsk_3 +-- * AIRBASE.Kola.Vidsel +-- * AIRBASE.Kola.Vuojarvi -- -- @field Kola AIRBASE.Kola = { ["Banak"] = "Banak", - ["Bas_100"] = "Bas 100", ["Bodo"] = "Bodo", ["Jokkmokk"] = "Jokkmokk", ["Kalixfors"] = "Kalixfors", @@ -749,6 +751,10 @@ AIRBASE.Kola = { ["Rovaniemi"] = "Rovaniemi", ["Severomorsk_1"] = "Severomorsk-1", ["Severomorsk_3"] = "Severomorsk-3", + ["Vuojarvi"] = "Vuojarvi", + ["Kirkenes"] = "Kirkenes", + ["Kallax"] = "Kallax", + ["Vidsel"] = "Vidsel", } --- Airbases of the Afghanistan map From f53bd8f11af03990045b5fd57334ca71ed081993 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Aug 2024 17:35:06 +0200 Subject: [PATCH 007/349] #CTLD * Added spawning crates behind the CH-47 * Added functionality to retain resource maps of cargo statics * Added door check for cargo crate operations --- Moose Development/Moose/Ops/CTLD.lua | 112 ++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 0975935ff..b243f3e95 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update July 2024 +-- Last Update Aug 2024 do @@ -46,6 +46,7 @@ do -- @field #string Subcategory Sub-category name. -- @field #boolean DontShowInMenu Show this item in menu or not. -- @field Core.Zone#ZONE Location Location (if set) where to get this cargo item. +-- @field #table ResourceMap Resource Map information table if it has been set for static cargo items. -- @extends Core.Base#BASE --- @@ -122,14 +123,31 @@ CTLD_CARGO = { self.Mark = nil self.Subcategory = Subcategory or "Other" self.DontShowInMenu = DontShowInMenu or false + self.ResourceMap = nil if type(Location) == "string" then Location = ZONE:New(Location) end self.Location = Location return self end - - --- Query Location. + + --- Add Resource Map information table + -- @param #CTLD_CARGO self + -- @param #table ResourceMap + -- @return #CTLD_CARGO self + function CTLD_CARGO:SetStaticResourceMap(ResourceMap) + self.ResourceMap = ResourceMap + return self + end + + --- Get Resource Map information table + -- @param #CTLD_CARGO self + -- @return #table ResourceMap + function CTLD_CARGO:GetStaticResourceMap() + return self.ResourceMap + end + + --- Query Location. -- @param #CTLD_CARGO self -- @return Core.Zone#ZONE location or `nil` if not set function CTLD_CARGO:GetLocation() @@ -1251,7 +1269,7 @@ CTLD.UnitTypeCapabilities = { ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH-58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, - ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 30, cargoweightlimit = 8000}, + ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 8000}, } --- CTLD class version. @@ -2418,7 +2436,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) return self end -- spawn crates in front of helicopter - local IsHerc = self:IsHercules(Unit) -- Herc + local IsHerc = self:IsHercules(Unit) -- Herc, Bronco and Hook load from behind + local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number @@ -2440,7 +2459,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) local rheading = 0 local angleOffNose = 0 local addon = 0 - if IsHerc then + if IsHerc or IsHook then -- spawn behind the Herc addon = 180 end @@ -2490,17 +2509,25 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) dist = dist - (20 + math.random(1,10)) local width = width / 2 local Offy = math.random(-width,width) - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) :InitLinkToUnit(Ship,dist,Offy,0) - :Spawn(270,cratealias) + if isstatic then + local map=cargotype:GetStaticResourceMap() + spawnstatic.TemplateStaticUnit.resourcePayload = map + end + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) else - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) :InitCoordinate(cratecoord) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) - :Spawn(270,cratealias) + if isstatic then + local map=cargotype:GetStaticResourceMap() + spawnstatic.TemplateStaticUnit.resourcePayload = map + end + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) end local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() @@ -2510,9 +2537,13 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) if drop then --CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory) realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) + local map=cargotype:GetStaticResourceMap() + realcargo:SetStaticResourceMap(map) table.insert(droppedcargo,realcargo) else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) + local map=cargotype:GetStaticResourceMap() + realcargo:SetStaticResourceMap(map) end table.insert(self.Spawned_Cargo, realcargo) end @@ -2562,11 +2593,15 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord) basetype = cratetemplate end self.CrateCounter = self.CrateCounter + 1 - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) :InitCoordinate(cratecoord) - :Spawn(270,cratealias) + if isstatic then + local map = cargotype:GetStaticResourceMap() + spawnstatic.TemplateStaticUnit.resourcePayload = map + end + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() self.CargoCounter = self.CargoCounter + 1 @@ -2744,6 +2779,13 @@ function CTLD:_LoadCratesNearby(Group, Unit) local cratelimit = capabilities.cratelimit -- #number local grounded = not self:IsUnitInAir(Unit) local canhoverload = self:CanHoverLoad(Unit) + + -- Door check + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) + if not self.debug then return self end + end + --- cases ------------------------------- -- Chopper can\'t do crates - bark & return -- Chopper can do crates - @@ -3058,7 +3100,7 @@ function CTLD:_ListInventory(Group, Unit) return self end ---- (Internal) Function to check if a unit is a Hercules C-130. +--- (Internal) Function to check if a unit is a Hercules C-130 or a Bronco. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome @@ -3070,6 +3112,17 @@ function CTLD:IsHercules(Unit) end end +--- (Internal) Function to check if a unit is a CH-47 +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #boolean Outcome +function CTLD:IsHook(Unit) + if string.find(Unit:GetTypeName(),"CH.47") then + return true + else + return false + end +end --- (Internal) Function to set troops positions of a template to a nice circle -- @param #CTLD self @@ -3122,8 +3175,9 @@ function CTLD:_UnloadTroops(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) - if IsHerc then + local IsHerc = self:IsHercules(Unit) + local IsHook = self:IsHook(Unit) + if IsHerc and (not IsHook) then -- no hover but airdrop here hoverunload = self:IsCorrectFlightParameters(Unit) end @@ -3252,10 +3306,16 @@ function CTLD:_UnloadCrates(Group, Unit) end end end + -- Door check + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) + if not self.debug then return self end + end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters local IsHerc = self:IsHercules(Unit) - if IsHerc then + local IsHook = self:IsHook(Unit) + if IsHerc and (not IsHook) then -- no hover but airdrop here hoverunload = self:IsCorrectFlightParameters(Unit) end @@ -3949,8 +4009,14 @@ function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Locatio self.CargoCounter = self.CargoCounter + 1 local type = CTLD_CARGO.Enum.STATIC local template = STATIC:FindByName(Name,true):GetTypeName() + local unittemplate = _DATABASE:GetStaticUnitTemplate(Name) + local ResourceMap = nil + if unittemplate and unittemplate.resourcePayload then + ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload) + end -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) + cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) return self end @@ -3965,8 +4031,14 @@ function CTLD:GetStaticsCargoFromTemplate(Name,Mass) self.CargoCounter = self.CargoCounter + 1 local type = CTLD_CARGO.Enum.STATIC local template = STATIC:FindByName(Name,true):GetTypeName() + local unittemplate = _DATABASE:GetStaticUnitTemplate(Name) + local ResourceMap = nil + if unittemplate and unittemplate.resourcePayload then + ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload) + end -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1) + cargo:SetStaticResourceMap(ResourceMap) --table.insert(self.Cargo_Statics,cargo) return cargo end @@ -5942,7 +6014,9 @@ end cargotemplates = UTILS.Split(cargotemplates,";") injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) elseif cargotype == CTLD_CARGO.Enum.STATIC or cargotype == CTLD_CARGO.Enum.REPAIR then - injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + local map=cargotype:GetStaticResourceMap() + injectstatic:SetStaticResourceMap(map) end if injectstatic then self:InjectStatics(dropzone,injectstatic) @@ -6298,6 +6372,8 @@ function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name, _pos) self.CTLD.Spawned_Crates[self.CTLD.CrateCounter] = theStatic local newCargo = CTLD_CARGO:New(self.CTLD.CargoCounter, theCargo.Name, theCargo.Templates, theCargo.CargoType, true, false, theCargo.CratesNeeded, self.CTLD.Spawned_Crates[self.CTLD.CrateCounter], true, theCargo.PerCrateMass, nil, theCargo.Subcategory) + local map=theCargo:GetStaticResourceMap() + newCargo:SetStaticResourceMap(map) table.insert(self.CTLD.Spawned_Cargo, newCargo) newCargo:SetWasDropped(true) From 63dc9b2e8ee505f7f258d154825aa770e7438054 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 6 Aug 2024 18:19:35 +0200 Subject: [PATCH 008/349] #CTLD - Dyn CArgo Spawns Handling --- Moose Development/Moose/Core/Database.lua | 37 +++++++ Moose Development/Moose/Ops/CTLD.lua | 120 +++++++++++++++++---- Moose Development/Moose/Wrapper/Static.lua | 16 ++- 3 files changed, 154 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 184525502..602f9d469 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1211,6 +1211,43 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category return self end +--- Get a generic static cargo group template from scratch for dynamic cargo spawns register. Does not register the template! +-- @param #DATABASE self +-- @param #string Name Name of the static. +-- @param #string Typename Typename of the static. Defaults to "container_cargo". +-- @param #number Mass Mass of the static. Defaults to 0. +-- @param #number Coalition Coalition of the static. Defaults to coalition.side.BLUE. +-- @param #number Country Country of the static. Defaults to country.id.GERMANY. +-- @return #table Static template table. +function DATABASE:_GetGenericStaticCargoGroupTemplate(Name,Typename,Mass,Coalition,Country) + local StaticTemplate = {} + StaticTemplate.name = Name or "None" + StaticTemplate.units = { [1] = { + name = Name, + resourcePayload = { + ["weapons"] = {}, + ["aircrafts"] = {}, + ["gasoline"] = 0, + ["diesel"] = 0, + ["methanol_mixture"] = 0, + ["jet_fuel"] = 0, + }, + ["mass"] = Mass or 0, + ["category"] = "Cargos", + ["canCargo"] = true, + ["type"] = Typename or "container_cargo", + ["rate"] = 100, + ["y"] = 0, + ["x"] = 0, + ["heading"] = 0, + }} + StaticTemplate.CategoryID = "static" + StaticTemplate.CoalitionID = Coalition or coalition.side.BLUE + StaticTemplate.CountryID = Country or country.id.GERMANY + UTILS.PrintTableToLog(StaticTemplate) + return StaticTemplate +end + --- Get static group template. -- @param #DATABASE self -- @param #string StaticName Name of the static. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index b243f3e95..96524d47b 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1274,7 +1274,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.56" +CTLD.version="1.0.57" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1841,7 +1841,7 @@ end -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData function CTLD:_EventHandler(EventData) - self:T(string.format("%s Event = %d",self.lid, EventData.id)) + self:I(string.format("%s Event = %d",self.lid, EventData.id)) local event = EventData -- Core.Event#EVENTDATA if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then local _coalition = event.IniCoalition @@ -1865,12 +1865,37 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end return - elseif event.id == EVENTS.PlayerLeaveUnit then + elseif event.id == EVENTS.PlayerLeaveUnit or event.id == EVENTS.UnitLost then -- remove from pilot table local unitname = event.IniUnitName or "none" self.CtldUnits[unitname] = nil self.Loaded_Cargo[unitname] = nil self.MenusDone[unitname] = nil + elseif event.id == EVENTS.Birth and event.IniObjectCategory == 6 and string.match(event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then + --UTILS.PrintTableToLog(event) + --------------- + -- New dynamic cargo system Handling + -------------- + local function RegisterDynamicCargo() + local static = _DATABASE:AddStatic(event.IniUnitName) + if static then + static.DCSCargoObject = event.IniDCSUnit + local Mass = event.IniDCSUnit:getCargoWeight() + local country = event.IniDCSUnit:getCountry() + local template = _DATABASE:_GetGenericStaticCargoGroupTemplate(event.IniUnitName,event.IniTypeName,Mass,event.IniCoalition,country) + _DATABASE:_RegisterStaticTemplate(template,event.IniCoalition,"static",country) + self:I("**** Ground crew created static cargo added: "..event.IniUnitName .." | Weight in kgs: "..Mass) + local cargotype = self:AddStaticsCargo(event.IniUnitName,Mass,1,nil,true) + self.CrateCounter = self.CrateCounter + 1 + self.Spawned_Crates[self.CrateCounter] = static + cargotype.Positionable = static + table.insert(self.Spawned_Cargo, cargotype) + end + end + self:ScheduleOnce(0.5,RegisterDynamicCargo) + --------------- + -- End new dynamic cargo system Handling + -------------- end return self end @@ -2604,7 +2629,7 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord) self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() - self.CargoCounter = self.CargoCounter + 1 + --self.CargoCounter = self.CargoCounter + 1 cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] table.insert(self.Spawned_Cargo, cargotype) return self @@ -2631,8 +2656,8 @@ end function CTLD:_ListCratesNearby( _group, _unit) self:T(self.lid .. " _ListCratesNearby") local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(_group,_unit, finddist,true) -- #table - if number > 0 then + local crates,number,loadedbygc,indexgc = self:_FindCratesNearby(_group,_unit, finddist,true) -- #table + if number > 0 or indexgc > 0 then local text = REPORT:New("Crates Found Nearby:") text:Add("------------------------------------------------------------") for _,_entry in pairs (crates) do @@ -2649,6 +2674,19 @@ function CTLD:_ListCratesNearby( _group, _unit) text:Add(" N O N E") end text:Add("------------------------------------------------------------") + if indexgc > 0 then + text:Add("Probably ground crew loaded (F8)") + for _,_entry in pairs (loadedbygc) do + local entry = _entry -- #CTLD_CARGO + local name = entry:GetName() --#string + local dropped = entry:WasDropped() + if dropped then + text:Add(string.format("Dropped crate for %s, %dkg",name, entry.PerCrateMass)) + else + text:Add(string.format("Crate for %s, %dkg",name, entry.PerCrateMass)) + end + end + end self:_SendMessage(text:Text(), 30, true, _group) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) @@ -2722,8 +2760,10 @@ end -- @param Wrapper.Unit#UNIT _unit Unit -- @param #number _dist Distance -- @param #boolean _ignoreweight Find everything in range, ignore loadable weight --- @return #table Table of crates +-- @return #table Crates Table of crates -- @return #number Number Number of crates found +-- @return #table CratesGC Table of crates possibly loaded by GC +-- @return #number NumberGC Number of crates possibly loaded by GC function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) self:T(self.lid .. " _FindCratesNearby") local finddist = _dist @@ -2731,7 +2771,9 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) local existingcrates = self.Spawned_Cargo -- #table -- cycle local index = 0 + local indexg = 0 local found = {} + local LoadedbyGC = {} local loadedmass = 0 local unittype = "none" local capabilities = {} @@ -2744,20 +2786,47 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates - local staticid = cargo:GetID() local weight = cargo:GetMass() -- weight in kgs of this cargo + local staticid = cargo:GetID() self:T(self.lid .. " Found cargo mass: " .. weight) - if static and static:IsAlive() then - local staticpos = static:GetCoordinate() + local cargoalive = false -- TODO dyn cargo spawn workaround + local dcsunit = nil + local dcsunitpos = nil + if static.DCSCargoObject then + dcsunit = Unit.getByName(static.StaticName) + if dcsunit then + cargoalive = dcsunit:isExist() ~= nil and true or false + end + if cargoalive == true then + local dcsvec3 = dcsunit:getPoint() or dcsunit:getPosition().p or {x=0,y=0,z=0} + self:I({dcsvec3 = dcsunit:getPoint(), dcspos = dcsunit:getPosition().p}) + if dcsvec3 then + dcsunitpos = COORDINATE:New(dcsvec3.x,dcsvec3.z,dcsvec3.y) + end + end + end + if static and (static:IsAlive() or cargoalive) then + local staticpos = static:GetCoordinate() or dcsunitpos + --- Testing + local landheight = staticpos:GetLandHeight() + local agl = staticpos.y-landheight + agl = UTILS.Round(agl,2) + local GCloaded = agl > 0 and true or false + --- Testing local distance = self:_GetDistance(location,staticpos) - if distance <= finddist and static and (weight <= maxloadable or _ignoreweight) then + self:I({name=static:GetName(),agl=agl,GCloaded=GCloaded,distance=string.format("%.2f",distance or 0)}) + if (not GCloaded) and distance <= finddist and static and (weight <= maxloadable or _ignoreweight) then index = index + 1 table.insert(found, staticid, cargo) maxloadable = maxloadable - weight end + if GCloaded == true and distance < 10 and static then + indexg = indexg + 1 + table.insert(LoadedbyGC,staticid, cargo) + end end end - return found, index + return found, index, LoadedbyGC, indexg end --- (Internal) Function to get and load nearby crates. @@ -2963,7 +3032,9 @@ function CTLD:_ListCargo(Group, Unit) local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo local loadedmass = self:_GetUnitCargoMass(Unit) -- #number local maxloadable = self:_GetMaxLoadableMass(Unit) - if self.Loaded_Cargo[unitname] then + local finddist = self.CrateDistance or 35 + local _,_,loadedgc,loadedno = self:_FindCratesNearby(Group,Unit,finddist,true) + if self.Loaded_Cargo[unitname] or loadedno > 0 then local no_troops = loadedcargo.Troopsloaded or 0 local no_crates = loadedcargo.Cratesloaded or 0 local cargotable = loadedcargo.Cargo or {} -- #table @@ -2985,7 +3056,7 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add(" -- CRATES --") local cratecount = 0 - for _,_cargo in pairs(cargotable) do + for _,_cargo in pairs(cargotable or {}) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then @@ -2996,6 +3067,18 @@ function CTLD:_ListCargo(Group, Unit) if cratecount == 0 then report:Add(" N O N E") end + if loadedno > 0 then + report:Add("------------------------------------------------------------") + report:Add(" -- CRATES loaded via F8 --") + for _,_cargo in pairs(loadedgc or {}) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) then + report:Add(string.format("Crate: %s size 1",cargo:GetName())) + loadedmass = loadedmass + cargo:GetMass() + end + end + end report:Add("------------------------------------------------------------") report:Add("Total Mass: ".. loadedmass .. " kg. Loadable: "..maxloadable.." kg.") local text = report:Text() @@ -4004,6 +4087,7 @@ end -- @param #string SubCategory Name of sub-category (optional). -- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. -- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. +-- @return #CTLD_CARGO CargoObject function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) self:T(self.lid .. " AddStaticsCargo") self.CargoCounter = self.CargoCounter + 1 @@ -4018,7 +4102,7 @@ function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Locatio local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) - return self + return cargo end --- User function - Get a *generic* static-type loadable as #CTLD_CARGO object. @@ -5404,7 +5488,9 @@ end -- Events self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:HandleEvent(EVENTS.UnitLost, self._EventHandler) + self:HandleEvent(EVENTS.Birth, self._EventHandler) self:__Status(-5) -- AutoSave @@ -6404,8 +6490,6 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct if offload_cargo == true or ParatrooperGroupSpawn == true then if ParatrooperGroupSpawn == true then - --self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) - --self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5) self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) else self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 6cf1f28e5..456cd779a 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -178,7 +178,7 @@ end -- @param #STATIC self -- @return DCS static object function STATIC:GetDCSObject() - local DCSStatic = StaticObject.getByName( self.StaticName ) + local DCSStatic = StaticObject.getByName( self.StaticName ) if DCSStatic then return DCSStatic @@ -339,3 +339,17 @@ function STATIC:GetStaticStorage() local storage = STORAGE:NewFromStaticCargo(name) return storage end + +--- Get the Cargo Weight of a static object in kgs. Returns -1 if not found. +-- @param #STATIC self +-- @return #number Mass Weight in kgs. +function STATIC:GetCargoWeight() + local DCSObject = StaticObject.getByName(self.StaticName ) + local mass = -1 + if DCSObject then + mass = DCSObject:getCargoWeight() or 0 + local masstxt = DCSObject:getCargoDisplayName() or "none" + BASE:I("GetCargoWeight "..tostring(mass).." MassText "..masstxt) + end + return mass +end From 922725297638d774714155a9c72c9ecd6bf5f021 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 8 Aug 2024 19:53:59 +0200 Subject: [PATCH 009/349] #Chinhook --- Moose Development/Moose/Ops/CTLD.lua | 61 +++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 96524d47b..bb69df9c8 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -770,10 +770,37 @@ do -- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. -- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. -- my_ctld.RadioSoundFC3 = "beacon.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. --- --- ## 2.1 User functions +-- my_ctld.enableChinhookGCLoading = true -- this will effectively suppress the crate load and drop menus for CTLD for the Chinhook -- --- ### 2.1.1 Adjust or add chopper unit-type capabilities +-- ## 2.1 CH-47 Chinhook support +-- +-- The Chinhook comes with the option to use the ground crew menu to load and unload cargo into the Helicopter itself for better immersion. As well, it can sling-load cargo from ground. The cargo you can actually **create** +-- from this menu is limited to contain items from the airbase or FARP's resources warehouse and can take a number of shapes (static shapes in the category of cargo) independent of their contents. If you unload this +-- kind of cargo with the ground crew, the contents will be "absorbed" into the airbase or FARP you landed at, and the cargo static will be removed after ca 2 mins. +-- +-- ## 2.1.1 Moose CTLD created crate cargo +-- +-- Given the correct shape, Moose created cargo can be either loaded with the ground crew or via the F10 CTLD menu. **It is strongly recommend to either use the ground crew or CTLD to load/unload cargo**. Mix and match will not work here. +-- Static shapes loadable *into* the Chinhook are at the time of writing: +-- +-- * Ammo crate (type "ammo_cargo") +-- * M117 bomb crate (type name "m117_cargo") +-- * Dual shell fuel barrels (type name "barrels") +-- * UH-1H net (type name "uh1h_cargo") +-- +-- All other kinds of cargo can be sling-loaded. +-- +-- ## 2.1.2 Recommended settings +-- +-- my_ctld.basetype = "ammo_cargo" +-- my_ctld.forcehoverload = false -- no hover autoload, leads to cargo complications with ground crew created cargo items +-- my_ctld.pilotmustopendoors = true -- crew must open back loading door 50% (horizontal) or more +-- my_ctld.enableslingload = true -- will set cargo items as sling-loadable +-- my_ctld.enableChinhookGCLoading = true -- will effectively suppress the crate load and drop menus for CTLD for the Chinhook +-- +-- ## 2.2 User functions +-- +-- ### 2.2.1 Adjust or add chopper unit-type capabilities -- -- Use this function to adjust what a heli type can or cannot do: -- @@ -798,8 +825,12 @@ do -- ["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats -- ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats -- ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, +-- ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, +-- ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, +-- ["OH-58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, +-- ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 8000}, -- --- ### 2.1.2 Activate and deactivate zones +-- ### 2.2.2 Activate and deactivate zones -- -- Activate a zone: -- @@ -811,7 +842,7 @@ do -- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: -- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) -- --- ## 2.1.3 Limit and manage available resources +-- ## 2.2.3 Limit and manage available resources -- -- When adding generic cargo types, you can effectively limit how many units can be dropped/build by the players, e.g. -- @@ -1274,7 +1305,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.57" +CTLD.version="1.0.58" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1453,6 +1484,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self.movecratesbeforebuild = true self.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} + -- Chinhook + self.enableChinhookGCLoading = true + local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CTLD_%s_Persist.csv",AliaS) @@ -3865,6 +3899,8 @@ function CTLD:_RefreshF10Menus() local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitTypeCapabilities local cantroops = capabilities.troops local cancrates = capabilities.crates + local isHook = self:IsHook(_unit) + local nohookswitch = not (isHook and self.enableChinhookGCLoading) -- top menu local topmenu = MENU_GROUP:New(_group,"CTLD",nil) local toptroops = nil @@ -3921,8 +3957,10 @@ function CTLD:_RefreshF10Menus() local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() end -- sub menu crates management - if cancrates then - local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) + if cancrates then + if nohookswitch then + local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) + end local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) local packmenu = MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) @@ -3990,11 +4028,14 @@ function CTLD:_RefreshF10Menus() end listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) local removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) - local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local unloadmenu + if nohookswitch then + unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + end if not self.nobuildmenu then local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() - else + elseif unloadmenu then unloadmenu:Refresh() end end From bf7c49708f789dab3f15d8812f6ecf31cb751758 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 9 Aug 2024 10:41:21 +0200 Subject: [PATCH 010/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index bb69df9c8..e32720028 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1643,6 +1643,8 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #string ZoneName Name of the Zone where the Troops have been RTB'd. + -- @param Core.Zone#ZONE_Radius ZoneObject of the Zone where the Troops have been RTB'd. --- FSM Function OnAfterTroopsPickedUp. -- @function [parent=#CTLD] OnAfterTroopsPickedUp @@ -3361,7 +3363,7 @@ function CTLD:_UnloadTroops(Group, Unit) end -- cargotable loop else -- droppingatbase self:_SendMessage("Troops have returned to base!", 10, false, Group) - self:__TroopsRTB(1, Group, Unit) + self:__TroopsRTB(1, Group, Unit, zonename, zone) end -- cleanup load list local loaded = {} -- #CTLD.LoadedCargo @@ -5794,8 +5796,10 @@ end -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #string ZoneName Name of the Zone where the Troops have been RTB'd. + -- @param Core.Zone#ZONE_Radius ZoneObject of the Zone where the Troops have been RTB'd. -- @return #CTLD self - function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) + function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit, ZoneName, ZoneObject) self:T({From, Event, To}) return self end From 29bb204e6dadd4d55c84d3198c5c2ba5e7bd26b3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Aug 2024 09:52:43 +0200 Subject: [PATCH 011/349] xx --- Moose Development/Moose/Wrapper/Static.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 456cd779a..557d117cc 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -349,7 +349,7 @@ function STATIC:GetCargoWeight() if DCSObject then mass = DCSObject:getCargoWeight() or 0 local masstxt = DCSObject:getCargoDisplayName() or "none" - BASE:I("GetCargoWeight "..tostring(mass).." MassText "..masstxt) + --BASE:I("GetCargoWeight "..tostring(mass).." MassText "..masstxt) end return mass end From 501fafcea600d902aec1aa445fa9af08ad239280 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Aug 2024 18:16:26 +0200 Subject: [PATCH 012/349] xx --- Moose Development/Moose/Wrapper/Airbase.lua | 34 +++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 4d9e068a1..4759ebf2f 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -655,66 +655,96 @@ AIRBASE.SouthAtlantic={ -- -- * AIRBASE.Sinai.Abu_Rudeis -- * AIRBASE.Sinai.Abu_Suwayr +-- * AIRBASE.Sinai.Al_Bahr_al_Ahmar -- * AIRBASE.Sinai.Al_Ismailiyah +-- * AIRBASE.Sinai.Al_Khatatbah -- * AIRBASE.Sinai.Al_Mansurah +-- * AIRBASE.Sinai.Al_Rahmaniyah_Air_Base -- * AIRBASE.Sinai.As_Salihiyah -- * AIRBASE.Sinai.AzZaqaziq -- * AIRBASE.Sinai.Baluza -- * AIRBASE.Sinai.Ben_Gurion +-- * AIRBASE.Sinai.Beni_Suef -- * AIRBASE.Sinai.Bilbeis_Air_Base -- * AIRBASE.Sinai.Bir_Hasanah +-- * AIRBASE.Sinai.Birma_Air_Base +-- * AIRBASE.Sinai.Borj_El_Arab_International_Airport -- * AIRBASE.Sinai.Cairo_International_Airport -- * AIRBASE.Sinai.Cairo_West -- * AIRBASE.Sinai.Difarsuwar_Airfield -- * AIRBASE.Sinai.El_Arish -- * AIRBASE.Sinai.El_Gora +-- * AIRBASE.Sinai.El_Minya -- * AIRBASE.Sinai.Fayed +-- * AIRBASE.Sinai.Gebel_El_Basur_Air_Base -- * AIRBASE.Sinai.Hatzerim -- * AIRBASE.Sinai.Hatzor +-- * AIRBASE.Sinai.Hurghada_International_Airport -- * AIRBASE.Sinai.Inshas_Airbase +-- * AIRBASE.Sinai.Jiyanklis_Air_Base -- * AIRBASE.Sinai.Kedem -- * AIRBASE.Sinai.Kibrit_Air_Base +-- * AIRBASE.Sinai.Kom_Awshim -- * AIRBASE.Sinai.Melez -- * AIRBASE.Sinai.Nevatim -- * AIRBASE.Sinai.Ovda --- * AIRBASE.Sinai.Palmahim +-- * AIRBASE.Sinai.Palmachim +-- * AIRBASE.Sinai.Quwaysina -- * AIRBASE.Sinai.Ramon_Airbase +-- * AIRBASE.Sinai.Ramon_International_Airport -- * AIRBASE.Sinai.Sde_Dov +-- * AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport -- * AIRBASE.Sinai.St_Catherine -- * AIRBASE.Sinai.Tel_Nof +-- * AIRBASE.Sinai.Wadi_Abu_Rish -- * AIRBASE.Sinai.Wadi_al_Jandali -- -- @field Sinai AIRBASE.Sinai = { ["Abu_Rudeis"] = "Abu Rudeis", ["Abu_Suwayr"] = "Abu Suwayr", + ["Al_Bahr_al_Ahmar"] = "Al Bahr al Ahmar", ["Al_Ismailiyah"] = "Al Ismailiyah", + ["Al_Khatatbah"] = "Al Khatatbah", ["Al_Mansurah"] = "Al Mansurah", + ["Al_Rahmaniyah_Air_Base"] = "Al Rahmaniyah Air Base", ["As_Salihiyah"] = "As Salihiyah", ["AzZaqaziq"] = "AzZaqaziq", ["Baluza"] = "Baluza", ["Ben_Gurion"] = "Ben-Gurion", + ["Beni_Suef"] = "Beni Suef", ["Bilbeis_Air_Base"] = "Bilbeis Air Base", ["Bir_Hasanah"] = "Bir Hasanah", + ["Birma_Air_Base"] = "Birma Air Base", + ["Borj_El_Arab_International_Airport"] = "Borj El Arab International Airport", ["Cairo_International_Airport"] = "Cairo International Airport", ["Cairo_West"] = "Cairo West", ["Difarsuwar_Airfield"] = "Difarsuwar Airfield", ["El_Arish"] = "El Arish", ["El_Gora"] = "El Gora", + ["El_Minya"] = "El Minya", ["Fayed"] = "Fayed", + ["Gebel_El_Basur_Air_Base"] = "Gebel El Basur Air Base", ["Hatzerim"] = "Hatzerim", ["Hatzor"] = "Hatzor", + ["Hurghada_International_Airport"] = "Hurghada International Airport", ["Inshas_Airbase"] = "Inshas Airbase", + ["Jiyanklis_Air_Base"] = "Jiyanklis Air Base", ["Kedem"] = "Kedem", ["Kibrit_Air_Base"] = "Kibrit Air Base", + ["Kom_Awshim"] = "Kom Awshim", ["Melez"] = "Melez", ["Nevatim"] = "Nevatim", ["Ovda"] = "Ovda", - ["Palmahim"] = "Palmahim", + ["Palmachim"] = "Palmachim", + ["Quwaysina"] = "Quwaysina", ["Ramon_Airbase"] = "Ramon Airbase", + ["Ramon_International_Airport"] = "Ramon International Airport", ["Sde_Dov"] = "Sde Dov", + ["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport", ["St_Catherine"] = "St Catherine", ["Tel_Nof"] = "Tel Nof", + ["Wadi_Abu_Rish"] = "Wadi Abu Rish", ["Wadi_al_Jandali"] = "Wadi al Jandali", } From 5b32d39309bb514be0a16bfb85baa8368bfe02f2 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:28:00 +0200 Subject: [PATCH 013/349] Align w master changes (#2163) * Update Base.lua (#2159) Performance tuning - the BASE:I, F, T calls rank very high in overall number of calls taken from Moose. Ensure only the minimum number of actions based on trace state and level is taken * Update Spawn.lua (#2161) Updating this class as it calls BASE I,F,T a lot. Making it less noisy for some performance tuning --- Moose Development/Moose/Core/Base.lua | 14 +- Moose Development/Moose/Core/Spawn.lua | 341 +++++++++++++------------ 2 files changed, 178 insertions(+), 177 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index c4be4ef30..fa3d4fcfd 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -26,7 +26,7 @@ -- @module Core.Base -- @image Core_Base.JPG -local _TraceOnOff = true +local _TraceOnOff = false -- default to no tracing local _TraceLevel = 1 local _TraceAll = false local _TraceClass = {} @@ -1200,7 +1200,7 @@ end -- @param Arguments A #table or any field. function BASE:F( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1215,7 +1215,7 @@ end -- @param Arguments A #table or any field. function BASE:F2( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 2 then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1230,7 +1230,7 @@ end -- @param Arguments A #table or any field. function BASE:F3( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 3 then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1274,7 +1274,7 @@ end -- @param Arguments A #table or any field. function BASE:T( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1289,7 +1289,7 @@ end -- @param Arguments A #table or any field. function BASE:T2( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 2 then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1304,7 +1304,7 @@ end -- @param Arguments A #table or any field. function BASE:T3( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 3 then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 1c2610f27..345f24983 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -318,7 +318,7 @@ SPAWN.Takeoff = { -- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME. function SPAWN:New( SpawnTemplatePrefix ) local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN - self:F( { SpawnTemplatePrefix } ) + --self:F( { SpawnTemplatePrefix } ) local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) if TemplateGroup then @@ -374,7 +374,7 @@ end -- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME. function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) + --self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) if TemplateGroup then @@ -527,7 +527,7 @@ end -- function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix, NoMooseNamingPostfix ) local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } ) + --self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } ) --if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then --BASE:I( "ERROR: in function NewFromTemplate, required parameter SpawnAliasPrefix is not set" ) --return nil @@ -603,7 +603,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) -- function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) - self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) + --self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) self.SpawnInitLimit = true self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. @@ -624,7 +624,7 @@ end -- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided create new unit names. -- @return #SPAWN self function SPAWN:InitKeepUnitNames( KeepUnitNames ) - self:F() + --self:F() self.SpawnInitKeepUnitNames = false @@ -638,7 +638,7 @@ end -- @param #boolean LateActivated (optional) If true, the spawned groups are late activated. -- @return #SPAWN self function SPAWN:InitLateActivated( LateActivated ) - self:F() + --self:F() self.LateActivated = LateActivated or true @@ -652,7 +652,7 @@ end -- @param #number TerminalType (Optional) The terminal type. -- @return #SPAWN self function SPAWN:InitAirbase( AirbaseName, Takeoff, TerminalType ) - self:F() + --self:F() self.SpawnInitAirbase = AIRBASE:FindByName( AirbaseName ) @@ -680,7 +680,7 @@ end -- Spawn:InitHeading( 100, 150 ) -- function SPAWN:InitHeading( HeadingMin, HeadingMax ) - self:F() + --self:F() self.SpawnInitHeadingMin = HeadingMin self.SpawnInitHeadingMax = HeadingMax @@ -748,7 +748,7 @@ end -- -- @return #SPAWN self function SPAWN:InitCountry( Country ) - self:F() + --self:F() self.SpawnInitCountry = Country @@ -760,7 +760,7 @@ end -- @param #number Category Category id. -- @return #SPAWN self function SPAWN:InitCategory( Category ) - self:F() + --self:F() self.SpawnInitCategory = Category @@ -772,7 +772,7 @@ end -- @param #string Livery Livery name. Note that this is not necessarily the same name as displayed in the mission editor. -- @return #SPAWN self function SPAWN:InitLivery( Livery ) - self:F( { livery = Livery } ) + --self:F( { livery = Livery } ) self.SpawnInitLivery = Livery @@ -784,7 +784,7 @@ end -- @param #string Skill Skill, possible values "Average", "Good", "High", "Excellent" or "Random". -- @return #SPAWN self function SPAWN:InitSkill( Skill ) - self:F( { skill = Skill } ) + --self:F( { skill = Skill } ) if Skill:lower() == "average" then self.SpawnInitSkill = "Average" elseif Skill:lower() == "good" then @@ -805,7 +805,7 @@ end -- @param #number Octal The octal number (digits 1..7, max 5 digits, i.e. 1..77777) to set the STN to. Every STN needs to be unique! -- @return #SPAWN self function SPAWN:InitSTN(Octal) - self:F( { Octal = Octal } ) + --self:F( { Octal = Octal } ) self.SpawnInitSTN = Octal or 77777 local num = UTILS.OctalToDecimal(Octal) if num == nil or num < 1 then @@ -823,7 +823,7 @@ end -- @param #number Octal The octal number (digits 1..7, max 4 digits, i.e. 1..7777) to set the SADL to. Every SADL needs to be unique! -- @return #SPAWN self function SPAWN:InitSADL(Octal) - self:F( { Octal = Octal } ) + --self:F( { Octal = Octal } ) self.SpawnInitSADL = Octal or 7777 local num = UTILS.OctalToDecimal(Octal) if num == nil or num < 1 then @@ -841,7 +841,7 @@ end -- @param #number MPS The speed in MPS to use. -- @return #SPAWN self function SPAWN:InitSpeedMps(MPS) -self:F( { MPS = MPS } ) +--self:F( { MPS = MPS } ) if MPS == nil or tonumber(MPS)<0 then MPS=125 end @@ -854,7 +854,7 @@ end -- @param #number Knots The speed in knots to use. -- @return #SPAWN self function SPAWN:InitSpeedKnots(Knots) -self:F( { Knots = Knots } ) +--self:F( { Knots = Knots } ) if Knots == nil or tonumber(Knots)<0 then Knots=300 end @@ -867,7 +867,7 @@ end -- @param #number KPH The speed in KPH to use. -- @return #SPAWN self function SPAWN:InitSpeedKph(KPH) - self:F( { KPH = KPH } ) + --self:F( { KPH = KPH } ) if KPH == nil or tonumber(KPH)<0 then KPH=UTILS.KnotsToKmph(300) end @@ -881,7 +881,7 @@ end -- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group. -- @return #SPAWN self function SPAWN:InitRadioCommsOnOff( switch ) - self:F( { switch = switch } ) + --self:F( { switch = switch } ) self.SpawnInitRadio = switch or true return self end @@ -891,7 +891,7 @@ end -- @param #number frequency The frequency in MHz. -- @return #SPAWN self function SPAWN:InitRadioFrequency( frequency ) - self:F( { frequency = frequency } ) + --self:F( { frequency = frequency } ) self.SpawnInitFreq = frequency @@ -903,7 +903,7 @@ end -- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. -- @return #SPAWN self function SPAWN:InitRadioModulation( modulation ) - self:F( { modulation = modulation } ) + --self:F( { modulation = modulation } ) if modulation and modulation:lower() == "fm" then self.SpawnInitModu = radio.modulation.FM else @@ -948,7 +948,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) -- function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) - self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) + --self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) self.SpawnRandomizeRoute = true self.SpawnRandomizeRouteStartPoint = SpawnStartPoint @@ -970,7 +970,7 @@ end -- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius ) - self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } ) + --self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } ) self.SpawnRandomizePosition = RandomizePosition or false self.SpawnRandomizePositionOuterRadius = OuterRadius or 0 @@ -996,7 +996,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeUnits( true, 500, 50 ) -- function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) - self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) + --self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) self.SpawnRandomizeUnits = RandomizeUnits or false self.SpawnOuterRadius = OuterRadius or 0 @@ -1021,7 +1021,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitSetUnitRelativePositions(Positions) -- function SPAWN:InitSetUnitRelativePositions(Positions) - self:F({self.SpawnTemplatePrefix, Positions}) + --self:F({self.SpawnTemplatePrefix, Positions}) self.SpawnUnitsWithRelativePositions = true self.UnitsRelativePositions = Positions @@ -1041,7 +1041,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitSetUnitAbsolutePositions(Positions) -- function SPAWN:InitSetUnitAbsolutePositions(Positions) - self:F({self.SpawnTemplatePrefix, Positions}) + --self:F({self.SpawnTemplatePrefix, Positions}) self.SpawnUnitsWithAbsolutePositions = true self.UnitsAbsolutePositions = Positions @@ -1070,7 +1070,7 @@ end -- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) - self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) + --self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) local temptable = {} for _,_temp in pairs(SpawnTemplatePrefixTable) do @@ -1112,7 +1112,7 @@ end -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) -- function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) local setnames = SpawnTemplateSet:GetSetNames() self:InitRandomizeTemplate(setnames) @@ -1142,7 +1142,7 @@ end -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -- function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) -- R2.3 - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce() @@ -1156,7 +1156,7 @@ end -- @param #number Grouping Indicates the maximum amount of units in the group. -- @return #SPAWN function SPAWN:InitGrouping( Grouping ) -- R2.2 - self:F( { self.SpawnTemplatePrefix, Grouping } ) + --self:F( { self.SpawnTemplatePrefix, Grouping } ) self.SpawnGrouping = Grouping @@ -1179,7 +1179,7 @@ end -- :SpawnScheduled( 5, .5 ) -- function SPAWN:InitRandomizeZones( SpawnZoneTable ) - self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) + --self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) local temptable = {} for _,_temp in pairs(SpawnZoneTable) do @@ -1226,7 +1226,7 @@ end -- @param Core.Point#COORDINATE Coordinate The position to spawn from -- @return #SPAWN self function SPAWN:InitPositionCoordinate(Coordinate) - self:T( { self.SpawnTemplatePrefix, Coordinate:GetVec2()} ) + self:T2( { self.SpawnTemplatePrefix, Coordinate:GetVec2()} ) self:InitPositionVec2(Coordinate:GetVec2()) return self end @@ -1236,10 +1236,10 @@ end -- @param DCS#Vec2 Vec2 The position to spawn from -- @return #SPAWN self function SPAWN:InitPositionVec2(Vec2) - self:T( { self.SpawnTemplatePrefix, Vec2} ) + self:T2( { self.SpawnTemplatePrefix, Vec2} ) self.SpawnInitPosition = Vec2 self.SpawnFromNewPosition = true - self:T("MaxGroups:"..self.SpawnMaxGroups) + self:T2("MaxGroups:"..self.SpawnMaxGroups) for SpawnGroupID = 1, self.SpawnMaxGroups do self:_SetInitialPosition( SpawnGroupID ) end @@ -1264,7 +1264,7 @@ end -- :InitRepeatOnEngineShutDown() -- function SPAWN:InitRepeat() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) self.Repeat = true self.RepeatOnEngineShutDown = false @@ -1286,7 +1286,7 @@ end -- :Spawn() -- function SPAWN:InitRepeatOnLanding() - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) self:InitRepeat() self.RepeatOnEngineShutDown = false @@ -1308,7 +1308,7 @@ end -- :InitRepeatOnEngineShutDown() -- :Spawn() function SPAWN:InitRepeatOnEngineShutDown() - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) self:InitRepeat() self.RepeatOnEngineShutDown = true @@ -1328,13 +1328,13 @@ end -- Spawn_Helicopter:InitCleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. -- function SPAWN:InitCleanUp( SpawnCleanUpInterval ) - self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) + --self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) self.SpawnCleanUpInterval = SpawnCleanUpInterval self.SpawnCleanUpTimeStamps = {} local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup } ) + self:T2( { "CleanUp Scheduler:", SpawnGroup } ) self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) return self @@ -1357,7 +1357,7 @@ end -- :InitArray( 90, 10, 100, 50 ) -- function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) - self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) + --self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. @@ -1367,7 +1367,7 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) local SpawnYIndex = 0 for SpawnGroupID = 1, self.SpawnMaxGroups do - self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) + self:T2( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) self.SpawnGroups[SpawnGroupID].Visible = true self.SpawnGroups[SpawnGroupID].Spawned = false @@ -1499,9 +1499,10 @@ end -- Delay methods --- Hide the group on the map view (visible to game master slots!). -- @param #SPAWN self +-- @param #boolean OnOff Defaults to true -- @return #SPAWN The SPAWN object -function SPAWN:InitHiddenOnMap() - self.SpawnHiddenOnMap = true +function SPAWN:InitHiddenOnMap(OnOff) + self.SpawnHiddenOnMap = OnOff == false and false or true return self end @@ -1526,7 +1527,7 @@ end -- @param #SPAWN self -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:Spawn() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) if self.SpawnInitAirbase then return self:SpawnAtAirbase( self.SpawnInitAirbase, self.SpawnInitTakeoff, nil, self.SpawnInitTerminalType ) @@ -1542,7 +1543,7 @@ end -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:ReSpawn( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) if not SpawnIndex then SpawnIndex = 1 @@ -1601,12 +1602,12 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if aliveunits ~= self.AliveUnits then self.AliveUnits = aliveunits - self:T("***** self.AliveUnits accounting failure! Corrected! *****") + self:T2("***** self.AliveUnits accounting failure! Corrected! *****") end set= nil - self:T( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) + self:T2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) if self:_GetSpawnIndex( SpawnIndex ) then @@ -1621,12 +1622,12 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate local SpawnZone = self.SpawnGroups[self.SpawnIndex].SpawnZone - self:T( SpawnTemplate.name ) + self:T2( SpawnTemplate.name ) if SpawnTemplate then local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) - self:T( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) + self:T2( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) -- If RandomizePosition, then Randomize the formation in the zone band, keeping the template. if self.SpawnRandomizePosition then @@ -1638,7 +1639,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) for UnitID = 1, #SpawnTemplate.units do SpawnTemplate.units[UnitID].x = SpawnTemplate.units[UnitID].x + (RandomVec2.x - CurrentX) SpawnTemplate.units[UnitID].y = SpawnTemplate.units[UnitID].y + (RandomVec2.y - CurrentY) - self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + self:T2( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -1654,18 +1655,18 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) numTries = numTries + 1 inZone = SpawnZone:IsVec2InZone(RandomVec2) - --self:T("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!") - --self:T(SpawnZone) + --self:T2("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!") + --self:T2(SpawnZone) end end if (not inZone) then - self:T("Could not place unit within zone and within radius!") + self:T2("Could not place unit within zone and within radius!") RandomVec2 = SpawnZone:GetRandomVec2() end end SpawnTemplate.units[UnitID].x = RandomVec2.x SpawnTemplate.units[UnitID].y = RandomVec2.y - self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + self:T2( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -1821,7 +1822,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) end if self.SpawnHiddenOnMap then - SpawnTemplate.hidden=true + SpawnTemplate.hidden=self.SpawnHiddenOnMap end -- Set country, coalition and category. @@ -1903,7 +1904,7 @@ end -- -- Between these two values, a random amount of seconds will be chosen for each new spawn of the helicopters. -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):SpawnScheduled( 600, 0.5 ) function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation, WithDelay ) - self:F( { SpawnTime, SpawnTimeVariation } ) + --self:F( { SpawnTime, SpawnTimeVariation } ) local SpawnTime = SpawnTime or 60 local SpawnTimeVariation = SpawnTimeVariation or 0.5 @@ -1924,7 +1925,7 @@ end -- @param #SPAWN self -- @return #SPAWN function SPAWN:SpawnScheduleStart() - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) self.SpawnScheduler:Start() return self @@ -1934,7 +1935,7 @@ end -- @param #SPAWN self -- @return #SPAWN function SPAWN:SpawnScheduleStop() - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) self.SpawnScheduler:Stop() return self @@ -1959,7 +1960,7 @@ end -- :SpawnScheduled( 300, 0.3 ) -- function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) - self:F( "OnSpawnGroup" ) + --self:F( "OnSpawnGroup" ) self.SpawnFunctionHook = SpawnCallBackFunction self.SpawnFunctionArguments = {} @@ -2019,7 +2020,7 @@ end -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) -- function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- R2.2, R2.4 - self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) + --self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() @@ -2033,14 +2034,14 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT EmergencyAirSpawn = true end - self:F( { SpawnIndex = self.SpawnIndex } ) + --self:F( { SpawnIndex = self.SpawnIndex } ) if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then -- Get group template. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - self:F( { SpawnTemplate = SpawnTemplate } ) + --self:F( { SpawnTemplate = SpawnTemplate } ) if SpawnTemplate then @@ -2049,10 +2050,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local GroupAlive = self:GetGroupFromIndex( self.SpawnIndex ) - self:F( { GroupAlive = GroupAlive } ) + --self:F( { GroupAlive = GroupAlive } ) -- Debug output - self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) + self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) -- Template group, unit and its attributes. local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) @@ -2081,7 +2082,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Get airbase ID and category. local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() - self:F( { AirbaseCategory = AirbaseCategory } ) + --self:F( { AirbaseCategory = AirbaseCategory } ) -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then @@ -2101,7 +2102,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Check if we spawn on ground. local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) - self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) -- Check where we actually spawn if we spawn on ground. local spawnonship = false @@ -2155,7 +2156,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) --[[ @@ -2168,18 +2169,18 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if ishelo then if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) + self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Not enough helo ports. Let's try also other terminal types. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) + self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- No terminal type specified. We try all spots except shelters. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) + self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2188,23 +2189,23 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if termtype == nil then if isbomber or istransport or istanker or isawacs then -- First we fill the potentially bigger spots. - self:T( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) + self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Now we try the smaller ones. - self:T( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) + self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else - self:T( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) + self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- Terminal type explicitly given. - self:T( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) + self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2214,12 +2215,12 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Debug: Get parking data. --[[ local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) - self:T(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) + self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) for _,_spot in pairs(parkingdata) do - self:T(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", + self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) end - self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) + self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) ]] -- Set this to true if not enough spots are available for emergency air start. @@ -2332,7 +2333,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn on ship. We take only the position of the ship. SpawnTemplate.units[UnitID].x = PointVec3.x -- TX @@ -2341,7 +2342,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT else - self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x @@ -2353,7 +2354,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT else - self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. SpawnTemplate.units[UnitID].x = TX @@ -2370,8 +2371,8 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT end -- Debug output. - self:T( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) - self:T( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) + self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) + self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -2415,7 +2416,7 @@ end -- @param #SPAWN.Takeoff Takeoff (Optional) Takeoff type, i.e. either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is Hot. -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. function SPAWN:SpawnAtParkingSpot( Airbase, Spots, Takeoff ) - self:F( { Airbase = Airbase, Spots = Spots, Takeoff = Takeoff } ) + --self:F( { Airbase = Airbase, Spots = Spots, Takeoff = Takeoff } ) -- Ensure that Spots parameter is a table. if type( Spots ) ~= "table" then @@ -2453,7 +2454,7 @@ function SPAWN:SpawnAtParkingSpot( Airbase, Spots, Takeoff ) self:T2( { spot = spot } ) if spot and spot.Free then - self:T( string.format( "Adding parking spot ID=%d TermType=%d", spot.TerminalID, spot.TerminalType ) ) + self:T2( string.format( "Adding parking spot ID=%d TermType=%d", spot.TerminalID, spot.TerminalType ) ) table.insert( Parkingdata, spot ) end @@ -2481,7 +2482,7 @@ end -- @return #nil Nothing is returned! function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) - self:F( { SpawnIndex = SpawnIndex, SpawnMaxGroups = self.SpawnMaxGroups } ) + --self:F( { SpawnIndex = SpawnIndex, SpawnMaxGroups = self.SpawnMaxGroups } ) -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() @@ -2501,7 +2502,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex local GroupAlive = self:GetGroupFromIndex( SpawnIndex ) -- Debug output - self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) + self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) -- Template group, unit and its attributes. local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) @@ -2525,7 +2526,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Get airbase ID and category. local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() - self:F( { AirbaseCategory = AirbaseCategory } ) + --self:F( { AirbaseCategory = AirbaseCategory } ) -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then @@ -2545,7 +2546,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Check if we spawn on ground. local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) - self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) -- Check where we actually spawn if we spawn on ground. local spawnonship = false @@ -2587,7 +2588,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) --[[ @@ -2600,18 +2601,18 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex if ishelo then if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) + self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Not enough helo ports. Let's try also other terminal types. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) + self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- No terminal type specified. We try all spots except shelters. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) + self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2623,23 +2624,23 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- TODO: Some attributes are "Helicopters", "Bombers", "Transports", "Battleplanes". Need to check it out. if isbomber or istransport then -- First we fill the potentially bigger spots. - self:T( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) + self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Now we try the smaller ones. - self:T( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) + self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else - self:T( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) + self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- Terminal type explicitly given. - self:T( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) + self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2654,7 +2655,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) end - self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) + self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) ]] -- Set this to true if not enough spots are available for emergency air start. @@ -2714,7 +2715,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex SpawnTemplate.parked = true for UnitID = 1, nunits do - self:F( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:F( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] @@ -2732,7 +2733,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn on ship. We take only the position of the ship. SpawnTemplate.units[UnitID].x = PointVec3.x -- TX @@ -2741,7 +2742,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex else - self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x @@ -2753,7 +2754,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex else - self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. SpawnTemplate.units[UnitID].x = TX @@ -2844,7 +2845,7 @@ end -- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), AIRBASE.TerminalType.OpenBig ) -- function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, R2.4, R2.5 - self:F( { self.SpawnTemplatePrefix, SpawnAirbase, TerminalType } ) + --self:F( { self.SpawnTemplatePrefix, SpawnAirbase, TerminalType } ) self:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, 1 ) @@ -2867,7 +2868,7 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) self:T2( PointVec3 ) @@ -2883,7 +2884,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) if SpawnTemplate then - self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) + self:T2( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) local TemplateHeight = SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil @@ -2895,7 +2896,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) -- Translate the position of the Group Template to the Vec3. for UnitID = 1, #SpawnTemplate.units do - -- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + -- self:T2( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) local UnitTemplate = SpawnTemplate.units[UnitID] local SX = UnitTemplate.x or 0 local SY = UnitTemplate.y or 0 @@ -2908,7 +2909,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) if SpawnTemplate.CategoryID ~= Group.Category.SHIP then SpawnTemplate.units[UnitID].alt = Vec3.y or TemplateHeight end - self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end SpawnTemplate.route.points[1].x = Vec3.x SpawnTemplate.route.points[1].y = Vec3.z @@ -2935,7 +2936,7 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) return self:SpawnFromVec3( Coordinate:GetVec3(), SpawnIndex ) end @@ -2956,7 +2957,7 @@ end -- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) -- function SPAWN:SpawnFromPointVec3( PointVec3, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) return self:SpawnFromVec3( PointVec3:GetVec3(), SpawnIndex ) end @@ -2982,7 +2983,7 @@ end -- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 ) -- function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, Vec2, MinHeight, MaxHeight, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, Vec2, MinHeight, MaxHeight, SpawnIndex } ) local Height = nil @@ -3014,7 +3015,7 @@ end -- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) -- function SPAWN:SpawnFromPointVec2( PointVec2, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) return self:SpawnFromVec2( PointVec2:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end @@ -3040,7 +3041,7 @@ end -- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 ) -- function SPAWN:SpawnFromUnit( HostUnit, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then return self:SpawnFromVec2( HostUnit:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) @@ -3068,7 +3069,7 @@ end -- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 ) -- function SPAWN:SpawnFromStatic( HostStatic, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostStatic, MinHeight, MaxHeight, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, HostStatic, MinHeight, MaxHeight, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then return self:SpawnFromVec2( HostStatic:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) @@ -3108,7 +3109,7 @@ end -- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 ) -- function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } ) if Zone then if RandomizeGroup then @@ -3158,7 +3159,7 @@ end -- @param #number SpawnIndex Is the number of the Group that is to be spawned. -- @return #string SpawnGroupName function SPAWN:SpawnGroupName( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) local SpawnPrefix = self.SpawnTemplatePrefix if self.SpawnAliasPrefix then @@ -3167,10 +3168,10 @@ function SPAWN:SpawnGroupName( SpawnIndex ) if SpawnIndex then local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex ) - self:T( SpawnName ) + self:T2( SpawnName ) return SpawnName else - self:T( SpawnPrefix ) + self:T2( SpawnPrefix ) return SpawnPrefix end @@ -3190,7 +3191,7 @@ end -- end -- function SPAWN:GetFirstAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) for SpawnIndex = 1, self.SpawnCount do local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) @@ -3217,7 +3218,7 @@ end -- end -- function SPAWN:GetNextAliveGroup( SpawnIndexStart ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) SpawnIndexStart = SpawnIndexStart + 1 for SpawnIndex = SpawnIndexStart, self.SpawnCount do @@ -3243,7 +3244,7 @@ end -- end -- function SPAWN:GetLastAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) for SpawnIndex = self.SpawnCount, 1, -1 do -- Added local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) @@ -3264,7 +3265,7 @@ end -- @param #number SpawnIndex The index of the group to return. -- @return Wrapper.Group#GROUP self function SPAWN:GetGroupFromIndex( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) if not SpawnIndex then SpawnIndex = 1 @@ -3322,7 +3323,7 @@ end --- Get the index from a given group. -- The function will search the name of the group for a #, and will return the number behind the #-mark. function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) + --self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 ) local Index = tonumber( IndexString ) @@ -3334,7 +3335,7 @@ end --- Return the last maximum index that can be used. function SPAWN:_GetLastIndex() - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + --self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) return self.SpawnMaxGroups end @@ -3342,7 +3343,7 @@ end --- Initalize the SpawnGroups collection. -- @param #SPAWN self function SPAWN:_InitializeSpawnGroups( SpawnIndex ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) + --self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) if not self.SpawnGroups[SpawnIndex] then self.SpawnGroups[SpawnIndex] = {} @@ -3386,7 +3387,7 @@ end --- Gets the CountryID of the Group with the given SpawnPrefix function SPAWN:_GetGroupCountryID( SpawnPrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } ) local TemplateGroup = Group.getByName( SpawnPrefix ) @@ -3404,7 +3405,7 @@ end -- @param #string SpawnTemplatePrefix -- @return @SPAWN self function SPAWN:_GetTemplate( SpawnTemplatePrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) local SpawnTemplate = nil @@ -3413,7 +3414,7 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix ) end local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template - self:F( { Template = Template } ) + --self:F( { Template = Template } ) SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) @@ -3435,7 +3436,7 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) -- if not self.SpawnTemplate then -- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) @@ -3466,7 +3467,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 if self.SpawnGrouping then local UnitAmount = #SpawnTemplate.units - self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } ) + --self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } ) if UnitAmount > self.SpawnGrouping then for UnitID = self.SpawnGrouping + 1, UnitAmount do SpawnTemplate.units[UnitID] = nil @@ -3498,7 +3499,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 if SpawnInitKeepUnitIFF == false then UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) - self:T( { UnitPrefix, Rest } ) + self:T2( { UnitPrefix, Rest } ) --else --UnitPrefix=SpawnTemplate.units[UnitID].name end @@ -3692,7 +3693,7 @@ end -- @param #number SpawnIndex The index of the group to be spawned. -- @return #SPAWN function SPAWN:_RandomizeRoute( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) if self.SpawnRandomizeRoute then local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate @@ -3712,7 +3713,7 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) SpawnTemplate.route.points[t].alt = nil end - self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) + self:T2( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) end end @@ -3726,7 +3727,7 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_RandomizeTemplate( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) if self.SpawnRandomizeTemplate then self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[math.random( 1, #self.SpawnTemplatePrefixTable )] @@ -3755,15 +3756,15 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_SetInitialPosition( SpawnIndex ) - self:T( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) + self:T2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) if self.SpawnFromNewPosition then - self:T( "Preparing Spawn at Vec2 ", self.SpawnInitPosition ) + self:T2( "Preparing Spawn at Vec2 ", self.SpawnInitPosition ) local SpawnVec2 = self.SpawnInitPosition - self:T( { SpawnVec2 = SpawnVec2 } ) + self:T2( { SpawnVec2 = SpawnVec2 } ) local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate @@ -3773,11 +3774,11 @@ function SPAWN:_SetInitialPosition( SpawnIndex ) SpawnTemplate.route.points[1].x = SpawnTemplate.route.points[1].x or 0 SpawnTemplate.route.points[1].y = SpawnTemplate.route.points[1].y or 0 - self:T( { Route = SpawnTemplate.route } ) + self:T2( { Route = SpawnTemplate.route } ) for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x @@ -3788,7 +3789,7 @@ function SPAWN:_SetInitialPosition( SpawnIndex ) UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... -- SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.route.points[1].x = SpawnVec2.x @@ -3806,31 +3807,31 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_RandomizeZones( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) if self.SpawnRandomizeZones then local SpawnZone = nil -- Core.Zone#ZONE_BASE while not SpawnZone do - self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) + self:T2( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) local ZoneID = math.random( #self.SpawnZoneTable ) - self:T( ZoneID ) + self:T2( ZoneID ) SpawnZone = self.SpawnZoneTable[ZoneID]:GetZoneMaybe() end - self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) + self:T2( "Preparing Spawn in Zone", SpawnZone:GetName() ) local SpawnVec2 = SpawnZone:GetRandomVec2() - self:T( { SpawnVec2 = SpawnVec2 } ) + self:T2( { SpawnVec2 = SpawnVec2 } ) local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate self.SpawnGroups[SpawnIndex].SpawnZone = SpawnZone - self:T( { Route = SpawnTemplate.route } ) + self:T2( { Route = SpawnTemplate.route } ) for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x @@ -3841,7 +3842,7 @@ function SPAWN:_RandomizeZones( SpawnIndex ) UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... -- SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.x = SpawnVec2.x SpawnTemplate.y = SpawnVec2.y @@ -3854,7 +3855,7 @@ function SPAWN:_RandomizeZones( SpawnIndex ) end function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) -- Translate local TranslatedX = SpawnX @@ -3896,11 +3897,11 @@ end -- @param #number SpawnIndex Spawn index. -- @return #number self.SpawnIndex function SPAWN:_GetSpawnIndex( SpawnIndex ) - self:T( { template=self.SpawnTemplatePrefix, SpawnIndex=SpawnIndex, SpawnMaxGroups=self.SpawnMaxGroups, SpawnMaxUnitsAlive=self.SpawnMaxUnitsAlive, AliveUnits=self.AliveUnits, TemplateUnits=#self.SpawnTemplate.units } ) + self:T2( { template=self.SpawnTemplatePrefix, SpawnIndex=SpawnIndex, SpawnMaxGroups=self.SpawnMaxGroups, SpawnMaxUnitsAlive=self.SpawnMaxUnitsAlive, AliveUnits=self.AliveUnits, TemplateUnits=#self.SpawnTemplate.units } ) if (self.SpawnMaxGroups == 0) or (SpawnIndex <= self.SpawnMaxGroups) then if (self.SpawnMaxUnitsAlive == 0) or (self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive) or self.UnControlled == true then - self:T( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) + self:T2( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then self.SpawnCount = self.SpawnCount + 1 SpawnIndex = self.SpawnCount @@ -3924,17 +3925,17 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnBirth( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) + self:T2( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then self.AliveUnits = self.AliveUnits + 1 - self:T( "Alive Units: " .. self.AliveUnits ) + self:T2( "Alive Units: " .. self.AliveUnits ) end end end @@ -3945,8 +3946,8 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnDeadOrCrash( EventData ) - self:T( "Dead or crash event ID "..tostring(EventData.id or 0)) - self:T( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") ) + self:T2( "Dead or crash event ID "..tostring(EventData.id or 0)) + self:T2( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") ) --if EventData.id == EVENTS.Dead then return end @@ -3958,12 +3959,12 @@ function SPAWN:_OnDeadOrCrash( EventData ) local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "Dead event: " .. EventPrefix } ) - self:T(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) + self:T2( { "Dead event: " .. EventPrefix } ) + self:T2(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) and self.AliveUnits > 0 then self.AliveUnits = self.AliveUnits - 1 end - self:T( "New Alive Units: " .. self.AliveUnits ) + self:T2( "New Alive Units: " .. self.AliveUnits ) end end end @@ -3973,15 +3974,15 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnTakeOff( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "TakeOff event: " .. EventPrefix } ) + self:T2( { "TakeOff event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then - self:T( "self.Landed = false" ) + self:T2( "self.Landed = false" ) SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false ) end end @@ -3993,19 +3994,19 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnLand( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "Land event: " .. EventPrefix } ) + self:T2( { "Land event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then -- TODO: Check if this is the last unit of the group that lands. SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true ) if self.RepeatOnLanding then local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + self:T2( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) -- self:ReSpawn( SpawnGroupIndex ) -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. @@ -4022,19 +4023,19 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnEngineShutDown( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "EngineShutdown event: " .. EventPrefix } ) + self:T2( { "EngineShutdown event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then -- todo: test if on the runway local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" ) if Landed and self.RepeatOnEngineShutDown then local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + self:T2( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) -- self:ReSpawn( SpawnGroupIndex ) -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) @@ -4060,10 +4061,10 @@ end -- @param #SPAWN self -- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() - self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) + --self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + self:T2( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) local IsHelo = false @@ -4080,7 +4081,7 @@ function SPAWN:_SpawnCleanUpScheduler() self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] - self:T( { SpawnUnitName, Stamp } ) + self:T2( { SpawnUnitName, Stamp } ) if Stamp.Vec2 then if (SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1) or IsHelo then @@ -4088,7 +4089,7 @@ function SPAWN:_SpawnCleanUpScheduler() if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + self:T2( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) --self:ReSpawn( SpawnCursor ) SCHEDULER:New( nil, self.ReSpawn, { self, SpawnCursor }, 3 ) Stamp.Vec2 = nil @@ -4117,7 +4118,7 @@ function SPAWN:_SpawnCleanUpScheduler() SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + self:T2( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) end From 402e72f728cead44a9abfe5e36eae83a6fed9f69 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Aug 2024 16:58:42 +0200 Subject: [PATCH 014/349] CTLD small fix --- Moose Development/Moose/Ops/CTLD.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index d416f9a67..346e64c08 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3240,7 +3240,7 @@ end -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:IsHook(Unit) - if string.find(Unit:GetTypeName(),"CH.47") then + if Unit and string.find(Unit:GetTypeName(),"CH.47") then return true else return false From 093a60050b1476359f28f892fa62c361b13b45bd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 13 Aug 2024 09:49:48 +0200 Subject: [PATCH 015/349] xx --- Moose Development/Moose/Core/Point.lua | 8 ++++---- Moose Development/Moose/Ops/ATIS.lua | 24 +++++++++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index c5272252e..3bf9c0dd1 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2669,9 +2669,9 @@ do -- COORDINATE local date=UTILS.GetDCSMissionDate() -- Debug output. - --self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) - if InSeconds then + if InSeconds or type(sunrise) == "string" then return sunrise else return UTILS.SecondsToClock(sunrise, true) @@ -2837,9 +2837,9 @@ do -- COORDINATE local date=UTILS.GetDCSMissionDate() -- Debug output. - --self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) - if InSeconds then + if InSeconds or type(sunrise) == "string" then return sunrise else return UTILS.SecondsToClock(sunrise, true) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index e835c8885..a5b577dde 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1975,17 +1975,23 @@ function ATIS:onafterBroadcast( From, Event, To ) local hours = self.gettext:GetEntry("HOURS",self.locale) local sunrise = coord:GetSunrise() - sunrise = UTILS.Split( sunrise, ":" ) - local SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] ) - if self.useSRS then - SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours ) + local SUNRISE = "no time" + if tostring(sunrise) ~= "N/R" then + sunrise = UTILS.Split( sunrise, ":" ) + SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] ) + if self.useSRS then + SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours ) + end end - + local sunset = coord:GetSunset() - sunset = UTILS.Split( sunset, ":" ) - local SUNSET = string.format( "%s%s", sunset[1], sunset[2] ) - if self.useSRS then - SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours ) + local SUNSET = "no time" + if tostring(sunset) ~= "N/S" then + sunset = UTILS.Split( sunset, ":" ) + SUNSET = string.format( "%s%s", sunset[1], sunset[2] ) + if self.useSRS then + SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours ) + end end --------------------------------- From 0ee30532eedfdf95ea996a3b03761b9df89e4f60 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 13 Aug 2024 10:30:36 +0200 Subject: [PATCH 016/349] #ATIS - Polar circle fixes --- Moose Development/Moose/Core/Point.lua | 4 ++-- Moose Development/Moose/Ops/ATIS.lua | 6 ++++-- Moose Development/Moose/Utilities/Utils.lua | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 3bf9c0dd1..fd990d7fb 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2669,7 +2669,7 @@ do -- COORDINATE local date=UTILS.GetDCSMissionDate() -- Debug output. - self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + --self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%s sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), tonumber(sunrise) or "0", Tdiff)) if InSeconds or type(sunrise) == "string" then return sunrise @@ -2837,7 +2837,7 @@ do -- COORDINATE local date=UTILS.GetDCSMissionDate() -- Debug output. - self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + --self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%s sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), tostring(sunrise) or "0", Tdiff)) if InSeconds or type(sunrise) == "string" then return sunrise diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index a5b577dde..7b0d71961 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1975,8 +1975,9 @@ function ATIS:onafterBroadcast( From, Event, To ) local hours = self.gettext:GetEntry("HOURS",self.locale) local sunrise = coord:GetSunrise() + --self:I(sunrise) local SUNRISE = "no time" - if tostring(sunrise) ~= "N/R" then + if tostring(sunrise) ~= "N/S" and tostring(sunrise) ~= "N/R" then sunrise = UTILS.Split( sunrise, ":" ) SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] ) if self.useSRS then @@ -1985,8 +1986,9 @@ function ATIS:onafterBroadcast( From, Event, To ) end local sunset = coord:GetSunset() + --self:I(sunset) local SUNSET = "no time" - if tostring(sunset) ~= "N/S" then + if tostring(sunset) ~= "N/S" and tostring(sunset) ~= "N/R" then sunset = UTILS.Split( sunset, ":" ) SUNSET = string.format( "%s%s", sunset[1], sunset[2] ) if self.useSRS then diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index d347cc082..ef92696e6 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1221,7 +1221,7 @@ function UTILS.SecondsToClock(seconds, short) end -- Seconds - local seconds = tonumber(seconds) + local seconds = tonumber(seconds) or 0 -- Seconds of this day. local _seconds=seconds%(60*60*24) @@ -2122,9 +2122,9 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal) local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude)) if rising and cosH > 1 then - return "N/R" -- The sun never rises on this location on the specified date + return "N/S" -- The sun never rises on this location on the specified date elseif cosH < -1 then - return "N/S" -- The sun never sets on this location on the specified date + return "N/R" -- The sun never sets on this location on the specified date end -- Finish calculating H and convert into hours From 60d68161c458017ac790e235a13254e09918d1f2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 13 Aug 2024 19:07:57 +0200 Subject: [PATCH 017/349] #CTLD #DYNAMICCARGO --- Moose Development/Moose/Core/Database.lua | 16 +- Moose Development/Moose/Core/Event.lua | 1 + Moose Development/Moose/Ops/CTLD.lua | 208 +++++++++++++----- .../Moose/Wrapper/DynamicCargo.lua | 149 +++++++++---- 4 files changed, 277 insertions(+), 97 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 5ce11f221..e2874853e 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -179,16 +179,20 @@ end -- @param #boolean force -- @return Wrapper.Unit#UNIT The added unit. function DATABASE:AddUnit( DCSUnitName, force ) - - if not self.UNITS[DCSUnitName] or force == true then + + local DCSunitName = DCSUnitName + + if type(DCSunitName) == "number" then DCSunitName = string.format("%d",DCSUnitName) end + + if not self.UNITS[DCSunitName] or force == true then -- Debug info. - self:T( { "Add UNIT:", DCSUnitName } ) + self:T( { "Add UNIT:", DCSunitName } ) -- Register unit - self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName) + self.UNITS[DCSunitName]=UNIT:Register(DCSunitName) end - return self.UNITS[DCSUnitName] + return self.UNITS[DCSunitName] end @@ -1451,7 +1455,7 @@ function DATABASE:_RegisterDynamicGroup(Groupname) -- Add unit. self:I(string.format("Register Unit: %s", tostring(DCSUnitName))) - self:AddUnit( DCSUnitName, true ) + self:AddUnit( tostring(DCSUnitName), true ) end else diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 3997fbdf5..4b993d7a4 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1530,6 +1530,7 @@ function EVENT:onEvent( Event ) if Event.dynamiccargo then Event.IniDynamicCargo = Event.dynamiccargo Event.IniDynamicCargoName = Event.IniDynamicCargo.StaticName + Event.IniPlayerName = Event.IniDynamicCargo.Owner or string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+") end -- Zone object. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 346e64c08..6959d908c 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -78,6 +78,7 @@ CTLD_CARGO = { -- @field #string REPAIR -- @field #string ENGINEERS -- @field #string STATIC + -- @field #string GCLOADABLE CTLD_CARGO.Enum = { VEHICLE = "Vehicle", -- #string vehicles TROOPS = "Troops", -- #string troops @@ -86,6 +87,7 @@ CTLD_CARGO = { REPAIR = "Repair", -- #string repair ENGINEERS = "Engineers", -- #string engineers STATIC = "Static", -- #string statics + GCLOADABLE = "GC_Loadable", -- #string dynamiccargo } --- Function to create new CTLD_CARGO object. @@ -770,7 +772,7 @@ do -- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. -- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. -- my_ctld.RadioSoundFC3 = "beacon.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. --- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop menus for CTLD for the Chinook +-- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIc types for CTLD for the Chinook -- -- ## 2.1 CH-47 Chinook support -- @@ -1203,6 +1205,7 @@ CTLD = { wpZones = {}, dropOffZones = {}, pickupZones = {}, + DynamicCargo = {}, } ------------------------------ @@ -1307,7 +1310,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.58" +CTLD.version="1.1.12" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1909,28 +1912,98 @@ function CTLD:_EventHandler(EventData) self.CtldUnits[unitname] = nil self.Loaded_Cargo[unitname] = nil self.MenusDone[unitname] = nil - elseif event.id == EVENTS.Birth and event.IniObjectCategory == 6 and string.match(event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then - --UTILS.PrintTableToLog(event) + --elseif event.id == EVENTS.NewDynamicCargo and event.IniObjectCategory == 6 and string.match(event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then + elseif event.id == EVENTS.NewDynamicCargo then + self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) --------------- - -- New dynamic cargo system Handling + -- New dynamic cargo system Handling NEW -------------- - local function RegisterDynamicCargo() - local static = _DATABASE:AddStatic(event.IniUnitName) - if static then - static.DCSCargoObject = event.IniDCSUnit - local Mass = event.IniDCSUnit:getCargoWeight() - local country = event.IniDCSUnit:getCountry() - local template = _DATABASE:_GetGenericStaticCargoGroupTemplate(event.IniUnitName,event.IniTypeName,Mass,event.IniCoalition,country) - _DATABASE:_RegisterStaticTemplate(template,event.IniCoalition,"static",country) - self:I("**** Ground crew created static cargo added: "..event.IniUnitName .." | Weight in kgs: "..Mass) - local cargotype = self:AddStaticsCargo(event.IniUnitName,Mass,1,nil,true) - self.CrateCounter = self.CrateCounter + 1 - self.Spawned_Crates[self.CrateCounter] = static - cargotype.Positionable = static - table.insert(self.Spawned_Cargo, cargotype) + self.DynamicCargo[event.IniDynamicCargoName] = event.IniDynamicCargo + --------------- + -- End new dynamic cargo system Handling + -------------- + elseif event.id == EVENTS.DynamicCargoLoaded then + self:T(self.lid.."GC Loaded Event "..event.IniDynamicCargoName) + --------------- + -- New dynamic cargo system Handling LOADING + -------------- + local dcargo = event.IniDynamicCargo -- Wrapper.DynamicCargo#DYNAMICCARGO + -- get client/unit object + local client = CLIENT:FindByPlayerName(dcargo.Owner) + if client and client:IsAlive() then + -- add to unit load list + local unitname = client:GetName() or "none" + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} end + loaded.Cratesloaded = loaded.Cratesloaded+1 + table.insert(loaded.Cargo,dcargo) + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + local Group = client:GetGroup() + self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName), 10, false, Group) + self:__CratesPickedUp(1, Group, client, dcargo) end - --self:ScheduleOnce(0.5,RegisterDynamicCargo) + --------------- + -- End new dynamic cargo system Handling + -------------- + elseif event.id == EVENTS.DynamicCargoUnloaded then + self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) + --------------- + -- New dynamic cargo system Handling UNLOADING + -------------- + local dcargo = event.IniDynamicCargo -- Wrapper.DynamicCargo#DYNAMICCARGO + -- get client/unit object + local client = CLIENT:FindByPlayerName(dcargo.Owner) + if client and client:IsAlive() then + -- add to unit load list + local unitname = client:GetName() or "none" + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + loaded.Cratesloaded = loaded.Cratesloaded - 1 + if loaded.Cratesloaded < 0 then loaded.Cratesloaded = 0 end + -- TODO zap cargo from list + local Loaded = {} + for _,_item in pairs (loaded.Cargo or {}) do + self:T(self.lid.."UNLOAD checking: ".._item:GetName()) + self:T(self.lid.."UNLOAD state: ".. tostring(_item:WasDropped())) + if _item and _item:GetType() == CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName ~= _item:GetName() and not _item:WasDropped() then + table.insert(Loaded,_item) + else + table.insert(Loaded,_item) + end + end + loaded.Cargo = nil + loaded.Cargo = Loaded + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + self.Loaded_Cargo[unitname] = loaded + end + local Group = client:GetGroup() + self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName), 10, false, Group) + self:__CratesDropped(1,Group,client,{dcargo}) + end + --------------- + -- End new dynamic cargo system Handling + -------------- + elseif event.id == EVENTS.DynamicCargoRemoved then + self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) + --------------- + -- New dynamic cargo system Handling REMOVE + -------------- + self.DynamicCargo[event.IniDynamicCargoName] = nil --------------- -- End new dynamic cargo system Handling -------------- @@ -2173,7 +2246,7 @@ end function CTLD:_FindRepairNearby(Group, Unit, Repairtype) self:T(self.lid .. " _FindRepairNearby") - --self:I({Group:GetName(),Unit:GetName(),Repairtype}) + --self:T({Group:GetName(),Unit:GetName(),Repairtype}) local unitcoord = Unit:GetCoordinate() -- find nearest group of deployed groups @@ -2191,7 +2264,7 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) end end - --self:I("Distance: ".. nearestDistance) + --self:T("Distance: ".. nearestDistance) -- found one and matching distance? if nearestGroup == nil or nearestDistance > self.EngineerSearch then @@ -2225,7 +2298,7 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) -- walk through generics and find matching type local Cargotype = nil for k,v in pairs(self.Cargo_Crates) do - --self:I({groupname,v.Templates,Repairtype}) + --self:T({groupname,v.Templates,Repairtype}) if matchstring(groupname,v.Templates) and matchstring(groupname,Repairtype) then Cargotype = v -- #CTLD_CARGO break @@ -2235,7 +2308,7 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) if Cargotype == nil then return nil, nil else - --self:I({groupname,Cargotype}) + --self:T({groupname,Cargotype}) return nearestGroup, Cargotype end @@ -2713,7 +2786,7 @@ function CTLD:_ListCratesNearby( _group, _unit) end text:Add("------------------------------------------------------------") if indexgc > 0 then - text:Add("Probably ground crew loaded (F8)") + text:Add("Probably ground crew loadable (F8)") for _,_entry in pairs (loadedbygc) do local entry = _entry -- #CTLD_CARGO local name = entry:GetName() --#string @@ -2817,7 +2890,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) local capabilities = {} local maxmass = 2000 local maxloadable = 2000 - local IsNoHook = not self:IsHook(_unit) + local IsHook = self:IsHook(_unit) if not _ignoreweight then maxloadable = self:_GetMaxLoadableMass(_unit) end @@ -2828,9 +2901,10 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) local weight = cargo:GetMass() -- weight in kgs of this cargo local staticid = cargo:GetID() self:T(self.lid .. " Found cargo mass: " .. weight) - local cargoalive = false -- TODO dyn cargo spawn workaround - local dcsunit = nil - local dcsunitpos = nil + --local cargoalive = false -- TODO dyn cargo spawn workaround + --local dcsunit = nil + --local dcsunitpos = nil + --[[ if static and static.DCSCargoObject then dcsunit = Unit.getByName(static.StaticName) if dcsunit then @@ -2844,26 +2918,37 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) end end end - if static and (static:IsAlive() or cargoalive) then - local staticpos = static:GetCoordinate() or dcsunitpos + --]] + if static and static:IsAlive() then --or cargoalive) then + local restricthooktononstatics = self.enableChinookGCLoading and IsHook + self:T(self.lid .. " restricthooktononstatics: " .. tostring(restricthooktononstatics)) + local cargoisstatic = cargo:GetType() == CTLD_CARGO.Enum.STATIC and true or false + self:T(self.lid .. " Cargo is static: " .. tostring(cargoisstatic)) + local restricted = cargoisstatic and restricthooktononstatics + self:T(self.lid .. " Loading restricted: " .. tostring(restricted)) + local staticpos = static:GetCoordinate() --or dcsunitpos + --[[ --- Testing local landheight = staticpos:GetLandHeight() local agl = staticpos.y-landheight agl = UTILS.Round(agl,2) local GCloaded = agl > 0 and true or false - if IsNoHook == true then GCloaded = false end + if IsNoHook == true then GCloaded = false end + --]] --- Testing local distance = self:_GetDistance(location,staticpos) - self:T({name=static:GetName(),IsNoHook=IsNoHook,agl=agl,GCloaded=GCloaded,distance=string.format("%.2f",distance or 0)}) - if (not GCloaded) and distance <= finddist and static and (weight <= maxloadable or _ignoreweight) then + --self:T({name=static:GetName(),IsHook=IsHook,agl=agl,GCloaded=GCloaded,distance=string.format("%.2f",distance or 0)}) + --if (not restricthooktononstatics) and distance <= finddist and static and (weight <= maxloadable or _ignoreweight) then + self:T(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) + if distance <= finddist and (weight <= maxloadable or _ignoreweight) and restricted == false then index = index + 1 table.insert(found, staticid, cargo) maxloadable = maxloadable - weight + --elseif restricthooktononstatics and distance < 10 and static then + --indexg = indexg + 1 + --table.insert(LoadedbyGC,staticid, cargo) end - if GCloaded == true and distance < 10 and static then - indexg = indexg + 1 - table.insert(LoadedbyGC,staticid, cargo) - end + end end return found, index, LoadedbyGC, indexg @@ -2929,10 +3014,10 @@ function CTLD:_LoadCratesNearby(Group, Unit) if number == 0 and self.hoverautoloading then return self -- exit elseif number == 0 then - self:_SendMessage("Sorry no loadable crates nearby or max cargo weight reached!", 10, false, Group) + self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!", 10, false, Group) return self -- exit elseif numberonboard == cratelimit then - self:_SendMessage("Sorry no fully loaded!", 10, false, Group) + self:_SendMessage("Sorry, we are fully loaded!", 10, false, Group) return self -- exit else -- go through crates and load @@ -3024,9 +3109,13 @@ function CTLD:_GetUnitCargoMass(Unit) if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) end - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then loadedmass = loadedmass + cargo.PerCrateMass end + if type == CTLD_CARGO.Enum.GCLOADABLE then + local mass = cargo:GetCargoWeight() + loadedmass = loadedmass+mass + end end end return loadedmass @@ -3073,8 +3162,8 @@ function CTLD:_ListCargo(Group, Unit) local loadedmass = self:_GetUnitCargoMass(Unit) -- #number local maxloadable = self:_GetMaxLoadableMass(Unit) local finddist = self.CrateDistance or 35 - local _,_,loadedgc,loadedno = self:_FindCratesNearby(Group,Unit,finddist,true) - if self.Loaded_Cargo[unitname] or loadedno > 0 then + --local _,_,loadedgc,loadedno = self:_FindCratesNearby(Group,Unit,finddist,true) + if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 local no_crates = loadedcargo.Cratesloaded or 0 local cargotable = loadedcargo.Cargo or {} -- #table @@ -3099,17 +3188,22 @@ function CTLD:_ListCargo(Group, Unit) for _,_cargo in pairs(cargotable or {}) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE) and (not cargo:WasDropped() or self.allowcratepickupagain) then report:Add(string.format("Crate: %s size 1",cargo:GetName())) cratecount = cratecount + 1 end + if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then + report:Add(string.format("GC loaded Crate: %s size 1",cargo:GetName())) + cratecount = cratecount + 1 + end end if cratecount == 0 then report:Add(" N O N E") end + --[[ if loadedno > 0 then report:Add("------------------------------------------------------------") - report:Add(" -- CRATES loaded via F8 --") + report:Add(" -- CRATES loaded via Ground Crew --") for _,_cargo in pairs(loadedgc or {}) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum @@ -3119,6 +3213,7 @@ function CTLD:_ListCargo(Group, Unit) end end end + --]] report:Add("------------------------------------------------------------") report:Add("Total Mass: ".. loadedmass .. " kg. Loadable: "..maxloadable.." kg.") local text = report:Text() @@ -3453,7 +3548,7 @@ function CTLD:_UnloadCrates(Group, Unit) for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and (not cargo:WasDropped() or self.allowcratepickupagain) then + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and (not cargo:WasDropped() or self.allowcratepickupagain) then -- unload crates self:_GetCrates(Group, Unit, cargo, 1, true) cargo:SetWasDropped(true) @@ -3474,6 +3569,10 @@ function CTLD:_UnloadCrates(Group, Unit) table.insert(loaded.Cargo,_cargo) loaded.Troopsloaded = loaded.Troopsloaded + size end + if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + size + end end self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded @@ -3906,7 +4005,8 @@ function CTLD:_RefreshF10Menus() local cantroops = capabilities.troops local cancrates = capabilities.crates local isHook = self:IsHook(_unit) - local nohookswitch = not (isHook and self.enableChinookGCLoading) + --local nohookswitch = not (isHook and self.enableChinookGCLoading) + local nohookswitch = true -- top menu local topmenu = MENU_GROUP:New(_group,"CTLD",nil) local toptroops = nil @@ -5537,7 +5637,11 @@ end self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) self:HandleEvent(EVENTS.UnitLost, self._EventHandler) - self:HandleEvent(EVENTS.Birth, self._EventHandler) + --self:HandleEvent(EVENTS.Birth, self._EventHandler) + self:HandleEvent(EVENTS.NewDynamicCargo, self._EventHandler) + self:HandleEvent(EVENTS.DynamicCargoLoaded, self._EventHandler) + self:HandleEvent(EVENTS.DynamicCargoUnloaded, self._EventHandler) + self:HandleEvent(EVENTS.DynamicCargoRemoved, self._EventHandler) self:__Status(-5) -- AutoSave @@ -5625,9 +5729,11 @@ end -- @return #CTLD self function CTLD:onafterStop(From, Event, To) self:T({From, Event, To}) - self:UnhandleEvent(EVENTS.PlayerEnterAircraft) - self:UnhandleEvent(EVENTS.PlayerEnterUnit) - self:UnhandleEvent(EVENTS.PlayerLeaveUnit) + self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + self:UnHandleEvent(EVENTS.PlayerEnterUnit) + self:UnHandleEvent(EVENTS.PlayerLeaveUnit) + self:UnHandleEvent(EVENTS.UnitLost) + self:UnHandleEvent(EVENTS.Shot) return self end diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 82b9074bb..6665f782e 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -30,6 +30,8 @@ -- @field #table DCS#Vec3 LastPosition. -- @field #number Interval Check Interval. 20 secs default. -- @field #boolean testing +-- @field Core.Timer#TIMER timer Timmer to run intervals +-- @field #string Owner The playername who has created, loaded or unloaded this cargo. Depends on state. -- @extends Wrapper.Positionable#POSITIONABLE --- *The capitalist cannot store labour-power in warehouses after he has bought it, as he may do with the raw material.* -- Karl Marx @@ -48,6 +50,8 @@ DYNAMICCARGO = { ClassName = "DYNAMICCARGO", verbose = 0, testing = true, + Interval = 10, + } --- Liquid types. @@ -109,17 +113,18 @@ DYNAMICCARGO.AircraftTypes = { --- Helo types possible. -- @type DYNAMICCARGO.AircraftDimensions DYNAMICCARGO.AircraftDimensions = { + -- CH-47 model start coordinate is quite exactly in the middle of the model, so half values here ["CH-47Fbl1"] = { - ["width"] = 8, + ["width"] = 4, ["height"] = 6, - ["length"] = 22, + ["length"] = 11, ["ropelength"] = 30, }, } --- DYNAMICCARGO class version. -- @field #string version -DYNAMICCARGO.version="0.0.1" +DYNAMICCARGO.version="0.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -137,7 +142,7 @@ DYNAMICCARGO.version="0.0.1" -- @return #DYNAMICCARGO self function DYNAMICCARGO:Register(CargoName) - -- Inherit everything from BASE class. + -- Inherit everything from a BASE class. local self=BASE:Inherit(self, POSITIONABLE:New(CargoName)) -- #DYNAMICCARGO self.StaticName = CargoName @@ -146,7 +151,7 @@ function DYNAMICCARGO:Register(CargoName) self.CargoState = DYNAMICCARGO.State.NEW - self.Interval = 10 + self.Interval = DYNAMICCARGO.Interval or 10 local DCSObject = self:GetDCSObject() @@ -157,7 +162,10 @@ function DYNAMICCARGO:Register(CargoName) self.lid = string.format("DYNAMICCARGO %s", CargoName) - self:ScheduleOnce(self.Interval,DYNAMICCARGO._UpdatePosition,self) + self.Owner = string.match(CargoName,"^(.+)|%d%d:%d%d|PKG%d+") or "None" + + self.timer = TIMER:New(DYNAMICCARGO._UpdatePosition,self) + self.timer:Start(self.Interval,self.Interval) if not _DYNAMICCARGO_HELOS then _DYNAMICCARGO_HELOS = SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart() @@ -181,6 +189,35 @@ end -- User API Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Get last know owner (player) name of this DYNAMICCARGO +-- @param #DYNAMICCARGO self +-- @return DCS#Vec3 Position in 3D space +function DYNAMICCARGO:GetLastPosition() + return self.Owner +end + +--- [CTLD] Get number of crates this DYNAMICCARGO consists of. Always one. +-- @param #DYNAMICCARGO self +-- @return #number crate number, always one +function DYNAMICCARGO:GetCratesNeeded() + return 1 +end + +--- [CTLD] Get this DYNAMICCARGO drop state. True if DYNAMICCARGO.State.UNLOADED +-- @param #DYNAMICCARGO self +-- @return #boolean Dropped +function DYNAMICCARGO:WasDropped() + return self.CargoState == DYNAMICCARGO.State.UNLOADED and true or false +end + +--- [CTLD] Get CTLD_CARGO.Enum type of this DYNAMICCARGO +-- @param #DYNAMICCARGO self +-- @return #string Type, only one at the moment is CTLD_CARGO.Enum.GCLOADABLE +function DYNAMICCARGO:GetType() + return CTLD_CARGO.Enum.GCLOADABLE +end + + --- Find last known position of this DYNAMICCARGO -- @param #DYNAMICCARGO self -- @return DCS#Vec3 Position in 3D space @@ -290,6 +327,51 @@ end -- Private Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- [Internal] _Get Possible Player Helo Nearby +-- @param #DYNAMICCARGO self +-- @param Core.Point#COORDINATE pos +-- @param #boolean loading If true measure distance for loading else for unloading +-- @return #boolean Success +-- @return Wrapper.Client#CLIENT Helo +-- @return #string PlayerName +function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) + local set = _DYNAMICCARGO_HELOS:GetAliveSet() + local success = false + local Helo = nil + local Playername = nil + for _,_helo in pairs (set or {}) do + local helo = _helo -- Wrapper.Client#CLIENT + local name = helo:GetPlayerName() or "None" + self:T(self.lid.." Checking: "..name) + local hpos = helo:GetCoordinate() + -- TODO Unloading via sling load? + local inair = hpos.y-hpos:GetLandHeight() > 4 and true or false + local typename = helo:GetTypeName() + if hpos and typename and inair == false then + local dimensions = DYNAMICCARGO.AircraftDimensions[typename] + if dimensions then + local delta2D = hpos:Get2DDistance(pos) + local delta3D = hpos:Get3DDistance(pos) + if self.testing then + self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) + self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) + end + if loading~=true and delta2D > dimensions.length or delta2D > dimensions.width or delta3D > dimensions.ropelength then + success = true + Helo = helo + Playername = name + end + if loading == true and delta2D < dimensions.length or delta2D < dimensions.width or delta3D < dimensions.ropelength then + success = true + Helo = helo + Playername = name + end + end + end + end + return success,Helo,Playername +end + --- [Internal] Update internal states. -- @param #DYNAMICCARGO self -- @return #DYNAMICCARGO self @@ -298,15 +380,22 @@ function DYNAMICCARGO:_UpdatePosition() if self:IsAlive() then local pos = self:GetCoordinate() if self.testing then - self:I(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z)) - self:I(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z)) + self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z)) + self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z)) end if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then - -- moved + --------------- + -- LOAD Cargo + --------------- if self.CargoState == DYNAMICCARGO.State.NEW then - self:I(self.lid.." moved! NEW -> LOADED") + local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true) + self:T(self.lid.." moved! NEW -> LOADED by "..playername) self.CargoState = DYNAMICCARGO.State.LOADED + self.Owner = playername _DATABASE:CreateEventDynamicCargoLoaded(self) + --------------- + -- UNLOAD Cargo + --------------- elseif self.CargoState == DYNAMICCARGO.State.LOADED then -- TODO add checker if we are in flight somehow -- ensure not just the helo is moving @@ -317,49 +406,29 @@ function DYNAMICCARGO:_UpdatePosition() agl = UTILS.Round(agl,2) self:T(self.lid.." AGL: "..agl or -1) local isunloaded = true + local client + local playername if count > 0 and (agl > 0 or self.testing) then self:T(self.lid.." Possible alive helos: "..count or -1) if agl ~= 0 or self.testing then - isunloaded = false - local set = _DYNAMICCARGO_HELOS:GetAliveSet() - for _,_helo in pairs (set or {}) do - local helo = _helo -- Wrapper.Client#CLIENT - self:I(self.lid.." Checking: "..helo:GetPlayerName()) - local hpos = helo:GetCoordinate() - hpos:MarkToAll("Helo position",true,"helo") - pos:MarkToAll("Cargo position",true,"cargo") - local typename = helo:GetTypeName() - if hpos then - local dimensions = DYNAMICCARGO.AircraftDimensions[typename] - if dimensions then - hpos:SmokeOrange() - local delta2D = hpos:Get2DDistance(pos) - local delta3D = hpos:Get3DDistance(pos) - if self.testing then - self:I(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) - self:I(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) - end - if delta2D > dimensions.length or delta2D > dimensions.width or delta3D > dimensions.ropelength then - isunloaded = true - end - end - end - end + isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false) end if isunloaded then - self:I(self.lid.." moved! LOADED -> UNLOADED") + self:T(self.lid.." moved! LOADED -> UNLOADED by "..playername) self.CargoState = DYNAMICCARGO.State.UNLOADED + self.Owner = playername _DATABASE:CreateEventDynamicCargoUnloaded(self) end end end self.LastPosition = pos end - -- come back laters - self:ScheduleOnce(self.Interval,DYNAMICCARGO._UpdatePosition,self) else - -- we are dead - self:I(self.lid.." dead! " ..self.CargoState.."-> REMOVED") + --------------- + -- REMOVED Cargo + --------------- + if self.timer and self.timer:IsRunning() then self.timer:Stop() end + self:T(self.lid.." dead! " ..self.CargoState.."-> REMOVED") self.CargoState = DYNAMICCARGO.State.REMOVED _DATABASE:CreateEventDynamicCargoRemoved(self) end From 81653cf28907c470f86cca496709c5ca73f47f56 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 13 Aug 2024 19:12:38 +0200 Subject: [PATCH 018/349] Update Modules.lua --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index bf3cee7fa..969dde2e6 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -49,6 +49,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Marker.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Weapon.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Net.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Storage.lua' ) +__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/DynamicCargo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/Cargo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoUnit.lua' ) From 0ee0eb3162aa422c05bee84e37e088ba63d44043 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 13 Aug 2024 19:13:23 +0200 Subject: [PATCH 019/349] Update Moose.files --- Moose Setup/Moose.files | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 367203aa1..ab22084a2 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -47,6 +47,7 @@ Wrapper/Marker.lua Wrapper/Weapon.lua Wrapper/Net.lua Wrapper/Storage.lua +Wrapper/DynamicCargo.lua Functional/Scoring.lua Functional/CleanUp.lua From c82359f87b1d55e988fd293b3896a8ae98ec99b7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 14 Aug 2024 11:34:57 +0200 Subject: [PATCH 020/349] #range --- Moose Development/Moose/Core/Set.lua | 2 ++ Moose Development/Moose/Functional/Range.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index b597c97f9..1d189d639 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -4792,10 +4792,12 @@ do -- SET_CLIENT local UnitCategory = 0 if ClientCategoryID==nil and MClient:IsExist() then ClientCategoryID,UnitCategory=MClient:GetCategory() + --self:T3("Applying Category Workaround .. Outcome: Obj is "..tostring(ClientCategoryID).." Unit is "..tostring(UnitCategory)) self:T3( { "Category:", UnitCategory, self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and UnitCategory and self.FilterMeta.Categories[CategoryName] == UnitCategory then MClientCategory = true end + --self:T3("Filter Outcome is "..tostring(MClientCategory)) else self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and ClientCategoryID and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 984dbed7e..769598b9c 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -4109,7 +4109,7 @@ function RANGE:_GetPlayerUnitAndName( _unitName ) -- Get DCS unit from its name. local DCSunit = Unit.getByName( _unitName ) - if DCSunit then + if DCSunit and DCSunit.getPlayerName then local playername = DCSunit:getPlayerName() local unit = UNIT:Find( DCSunit ) From 64bae078770fe111966fcb8b1c554896bdd79006 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 14 Aug 2024 15:42:26 +0200 Subject: [PATCH 021/349] #ATIS French Translations --- Moose Development/Moose/Ops/ATIS.lua | 62 ++++++++++++++++++++++++++- Moose Development/Moose/Sound/SRS.lua | 10 ++--- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 7b0d71961..9953be1e9 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -882,6 +882,66 @@ ATIS.Messages = { FARP = "Farp", DELIMITER = "Punto", -- decimal delimiter }, + -- French messages thanks to @Wojtech and Bing + FR = { + HOURS = "Heures", + TIME = "Temps", + NOCLOUDINFO = "Informations sur la couverture nuageuse non disponibles", + OVERCAST = "Ciel couvert", + BROKEN = "Nuages fragmentés", + SCATTERED = "Nuages épars", + FEWCLOUDS = "Nuages rares", + NOCLOUDS = "Clair", + AIRPORT = "Aéroport", + INFORMATION ="Information", + SUNRISEAT = "Levé du soleil à %s heure locale", + SUNSETAT = "Couché du soleil à %s heure locale", + WINDFROMMS = "Vent du %s pour %s mètres par seconde", + WINDFROMKNOTS = "Vent du %s pour %s noeuds", + GUSTING = "Rafale de vent", + VISIKM = "Visibilité %s kilomètres", + VISISM = "Visibilité %s Miles", + RAIN = "Pluie", + TSTORM = "Orage", + SNOW = "Neige", + SSTROM = "Tempête de neige", + FOG = "Brouillard", + DUST = "Poussière", + PHENOMENA = "Phénomène météorologique", + CLOUDBASEM = "Couverture nuageuse de %s à %s mètres", + CLOUDBASEFT = "Couverture nuageuse de %s à %s pieds", + TEMPERATURE = "Température", + DEWPOINT = "Point de rosée", + ALTIMETER = "Altimètre", + ACTIVERUN = "Décollages piste", + ACTIVELANDING = "Atterrissages piste", + LEFT = "Gauche", + RIGHT = "Droite", + RWYLENGTH = "Longueur de piste", + METERS = "Mètre", + FEET = "Pieds", + ELEVATION = "Hauteur", + TOWERFREQ = "Fréquences de la tour", + ILSFREQ = "Fréquences ILS", + OUTERNDB = "Fréquences Outer NDB", + INNERNDB = "Fréquences Inner NDB", + VORFREQ = "Fréquences VOR", + VORFREQTTS = "Fréquences V O R", + TACANCH = "Canal TACAN %d", + RSBNCH = "Canal RSBN", + PRMGCH = "Canal PRMG", + ADVISE = "Informez le contrôle que vous avez copié l'information", + STATUTE = "Statute Miles", + DEGREES = "Degré celcius", + FAHRENHEIT = "Degré Fahrenheit", + INCHHG = "Pouces de mercure", + MMHG = "Millimètres de mercure", + HECTO = "Hectopascals", + METERSPER = "Mètres par seconde", + TACAN = "TAKAN", + FARP = "FARPE", + DELIMITER = "Décimal", -- decimal delimiter + } } --- @@ -1061,7 +1121,7 @@ end -- @return #ATIS self function ATIS:_InitLocalization() self:T(self.lid.."_InitLocalization") - self.gettext = TEXTANDSOUND:New("AWACS","en") -- Core.TextAndSound#TEXTANDSOUND + self.gettext = TEXTANDSOUND:New("ATIS","en") -- Core.TextAndSound#TEXTANDSOUND self.locale = "en" for locale,table in pairs(self.Messages) do local Locale = string.lower(tostring(locale)) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 853d17520..b8a1f9b4f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -271,11 +271,11 @@ MSRS.Voices = { ["David"] = "Microsoft David Desktop", -- en-US ["Zira"] = "Microsoft Zira Desktop", -- en-US ["Hortense"] = "Microsoft Hortense Desktop", --fr-FR - ["de-DE-Hedda"] = "Microsoft Hedda Desktop", -- de-DE - ["en-GB-Hazel"] = "Microsoft Hazel Desktop", -- en-GB - ["en-US-David"] = "Microsoft David Desktop", -- en-US - ["en-US-Zira"] = "Microsoft Zira Desktop", -- en-US - ["fr-FR-Hortense"] = "Microsoft Hortense Desktop", --fr-FR + ["de_DE_Hedda"] = "Microsoft Hedda Desktop", -- de-DE + ["en_GB_Hazel"] = "Microsoft Hazel Desktop", -- en-GB + ["en_US_David"] = "Microsoft David Desktop", -- en-US + ["en_US_Zira"] = "Microsoft Zira Desktop", -- en-US + ["fr_FR_Hortense"] = "Microsoft Hortense Desktop", --fr-FR }, MicrosoftGRPC = { -- en-US/GB voices only as of Jan 2024, working ones if using gRPC and MS, if voice packs are installed --["Hedda"] = "Hedda", -- de-DE From e61856197a8c75e9ed913a19cdbbe5fe726c607b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 Aug 2024 12:01:32 +0200 Subject: [PATCH 022/349] #DynamicCargo - fix for FARP/Ship heights --- Moose Development/Moose/Core/Database.lua | 33 +++++++++++++++---- .../Moose/Wrapper/DynamicCargo.lua | 19 +++++++---- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index be0e058f9..bb5d65987 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -856,12 +856,16 @@ end -- @param #boolean Force (optional) Force registration of client. -- @return Wrapper.Client#CLIENT The client object. function DATABASE:AddClient( ClientName, Force ) - - if not self.CLIENTS[ClientName] or Force == true then - self.CLIENTS[ClientName] = CLIENT:Register( ClientName ) + + local DCSUnitName = ClientName + + if type(DCSUnitName) == "number" then DCSUnitName = string.format("%d",ClientName) end + + if not self.CLIENTS[DCSUnitName] or Force == true then + self.CLIENTS[DCSUnitName] = CLIENT:Register( DCSUnitName ) end - return self.CLIENTS[ClientName] + return self.CLIENTS[DCSUnitName] end @@ -901,9 +905,11 @@ end --- Adds a player based on the Player Name in the DATABASE. -- @param #DATABASE self function DATABASE:AddPlayer( UnitName, PlayerName ) - + + if type(UnitName) == "number" then UnitName = string.format("%d",UnitName) end + if PlayerName then - self:T( { "Add player for unit:", UnitName, PlayerName } ) + self:I( { "Add player for unit:", UnitName, PlayerName } ) self.PLAYERS[PlayerName] = UnitName self.PLAYERUNITS[PlayerName] = self:FindUnit( UnitName ) self.PLAYERSJOINED[PlayerName] = PlayerName @@ -911,6 +917,21 @@ function DATABASE:AddPlayer( UnitName, PlayerName ) end +--- Get a PlayerName by UnitName from PLAYERS in DATABASE. +-- @param #DATABASE self +-- @return #string PlayerName +-- @return Wrapper.Unit#UNIT PlayerUnit +function DATABASE:_FindPlayerNameByUnitName(UnitName) + if UnitName then + for playername,unitname in pairs(self.PLAYERS) do + if unitname == UnitName and self.PLAYERUNITS[playername] and self.PLAYERUNITS[playername]:IsAlive() then + return playername, self.PLAYERUNITS[playername] + end + end + end + return nil +end + --- Deletes a player from the DATABASE based on the Player Name. -- @param #DATABASE self function DATABASE:DeletePlayer( UnitName, PlayerName ) diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 6665f782e..335687f9a 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -49,7 +49,7 @@ DYNAMICCARGO = { ClassName = "DYNAMICCARGO", verbose = 0, - testing = true, + testing = false, Interval = 10, } @@ -124,7 +124,7 @@ DYNAMICCARGO.AircraftDimensions = { --- DYNAMICCARGO class version. -- @field #string version -DYNAMICCARGO.version="0.0.3" +DYNAMICCARGO.version="0.0.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -171,6 +171,11 @@ function DYNAMICCARGO:Register(CargoName) _DYNAMICCARGO_HELOS = SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart() end + if self.testing then + BASE:TraceOn() + BASE:TraceClass("DYNAMICCARGO") + end + return self end @@ -341,11 +346,13 @@ function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) local Playername = nil for _,_helo in pairs (set or {}) do local helo = _helo -- Wrapper.Client#CLIENT - local name = helo:GetPlayerName() or "None" + local name = helo:GetPlayerName() or _DATABASE:_FindPlayerNameByUnitName(helo:GetName()) or "None" self:T(self.lid.." Checking: "..name) local hpos = helo:GetCoordinate() -- TODO Unloading via sling load? - local inair = hpos.y-hpos:GetLandHeight() > 4 and true or false + --local inair = hpos.y-hpos:GetLandHeight() > 4.5 and true or false -- Standard FARP is 4.5m + local inair = helo:InAir() + self:T(self.lid.." InAir: AGL/InAir: "..hpos.y-hpos:GetLandHeight().."/"..tostring(inair)) local typename = helo:GetTypeName() if hpos and typename and inair == false then local dimensions = DYNAMICCARGO.AircraftDimensions[typename] @@ -389,7 +396,7 @@ function DYNAMICCARGO:_UpdatePosition() --------------- if self.CargoState == DYNAMICCARGO.State.NEW then local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true) - self:T(self.lid.." moved! NEW -> LOADED by "..playername) + self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername)) self.CargoState = DYNAMICCARGO.State.LOADED self.Owner = playername _DATABASE:CreateEventDynamicCargoLoaded(self) @@ -414,7 +421,7 @@ function DYNAMICCARGO:_UpdatePosition() isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false) end if isunloaded then - self:T(self.lid.." moved! LOADED -> UNLOADED by "..playername) + self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) self.CargoState = DYNAMICCARGO.State.UNLOADED self.Owner = playername _DATABASE:CreateEventDynamicCargoUnloaded(self) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 863f03e0f..e75f1f874 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1500,7 +1500,7 @@ function UNIT:InAir(NoHeloCheck) local UnitCategory = DCSUnit:getDesc().category -- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not. - -- This is a workaround since DCS currently does not acknoledge that helos land on buildings. + -- This is a workaround since DCS currently does not acknowledge that helos land on buildings. -- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier! if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER and (not NoHeloCheck) then local VelocityVec3 = DCSUnit:getVelocity() From c7201580d65968340c7a2fad8568a8b0207b729a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 Aug 2024 17:43:30 +0200 Subject: [PATCH 023/349] xx --- Moose Development/Moose/Functional/Range.lua | 26 +++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 769598b9c..2a4fb2611 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -1811,7 +1811,7 @@ function RANGE:OnEventBirth( EventData ) if not EventData.IniPlayerName then return end local _unitName = EventData.IniUnitName - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName, EventData.IniPlayerName ) self:T3( self.lid .. "BIRTH: unit = " .. tostring( EventData.IniUnitName ) ) self:T3( self.lid .. "BIRTH: group = " .. tostring( EventData.IniGroupName ) ) @@ -1967,7 +1967,9 @@ end -- @param #number attackAlt Attack altitude. -- @param #number attackVel Attack velocity. function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackVel) - + + if not playerData then return end + -- Get closet target to last position. local _closetTarget = nil -- #RANGE.BombTarget local _distance = nil @@ -1984,13 +1986,13 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV -- Coordinate of impact point. local impactcoord = weapon:GetImpactCoordinate() - -- Check if impact happened in range zone. + -- Check if impact happened in range zone.+ local insidezone = self.rangezone:IsCoordinateInZone( impactcoord ) -- Smoke impact point of bomb. - if playerData.smokebombimpact and insidezone then - if playerData.delaysmoke then + if playerData and playerData.smokebombimpact and insidezone then + if playerData and playerData.delaysmoke then timer.scheduleFunction( self._DelayedSmoke, { coord = impactcoord, color = playerData.smokecolor }, timer.getTime() + self.TdelaySmoke ) else impactcoord:Smoke( playerData.smokecolor ) @@ -2115,7 +2117,7 @@ function RANGE:OnEventShot( EventData ) local _unitName = EventData.IniUnitName -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName, EventData.IniPlayerName ) -- Distance Player-to-Range. Set this to larger value than the threshold. local dPR = self.BombtrackThreshold * 2 @@ -2127,11 +2129,13 @@ function RANGE:OnEventShot( EventData ) end -- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons. - if _track and dPR <= self.BombtrackThreshold and _unit and _playername then + if _track and dPR <= self.BombtrackThreshold and _unit and _playername and self.PlayerSettings[_playername] then -- Player data. local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData - + + if not playerData then return end + -- Attack parameters. local attackHdg=_unit:GetHeading() local attackAlt=_unit:GetHeight() @@ -4099,8 +4103,8 @@ end -- @return Wrapper.Unit#UNIT Unit of player. -- @return #string Name of the player. -- @return #boolean If true, group has > 1 player in it -function RANGE:_GetPlayerUnitAndName( _unitName ) - self:F2( _unitName ) +function RANGE:_GetPlayerUnitAndName( _unitName, PlayerName ) + self:I( _unitName ) if _unitName ~= nil then @@ -4111,7 +4115,7 @@ function RANGE:_GetPlayerUnitAndName( _unitName ) if DCSunit and DCSunit.getPlayerName then - local playername = DCSunit:getPlayerName() + local playername = DCSunit:getPlayerName() or PlayerName or "None" local unit = UNIT:Find( DCSunit ) self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } ) From 823930de40c304c1bd31296bc822a713b575af7c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 Aug 2024 09:27:16 +0200 Subject: [PATCH 024/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6959d908c..3011aacb9 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -782,7 +782,7 @@ do -- -- ## 2.1.1 Moose CTLD created crate cargo -- --- Given the correct shape, Moose created cargo can be either loaded with the ground crew or via the F10 CTLD menu. **It is strongly recommend to either use the ground crew or CTLD to load/unload cargo**. Mix and match will not work here. +-- Given the correct shape, Moose created cargo can be either loaded with the ground crew or via the F10 CTLD menu. **It is strongly recommend to either use the ground crew or CTLD to load/unload Moose created cargo**. Mix and match will not work here. -- Static shapes loadable *into* the Chinook are at the time of writing: -- -- * Ammo crate (type "ammo_cargo") @@ -1593,7 +1593,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo crate. + -- @param #CTLD_CARGO Cargo Cargo crate. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew loaded! -- @return #CTLD self --- FSM Function OnBeforeTroopsDeployed. @@ -1615,7 +1615,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew unloaded! -- @return #CTLD self --- FSM Function OnBeforeCratesBuild. @@ -1681,7 +1681,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo crate. + -- @param #CTLD_CARGO Cargo Cargo crate. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew loaded! -- @return #CTLD self --- FSM Function OnAfterTroopsDeployed. @@ -1703,7 +1703,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew unloaded! -- @return #CTLD self --- FSM Function OnAfterCratesBuild. @@ -5758,7 +5758,7 @@ end -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo crate. + -- @param #CTLD_CARGO Cargo Cargo crate. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew loaded! -- @return #CTLD self function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) self:T({From, Event, To}) @@ -5840,7 +5840,7 @@ end -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew unloaded! -- @return #CTLD self function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) self:T({From, Event, To}) From b95f12449e8353cd78f5d4af3152cea82eace756 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 Aug 2024 15:21:21 +0200 Subject: [PATCH 025/349] xx --- Moose Development/Moose/Core/Set.lua | 569 ++++++++++++++++-- .../Moose/Wrapper/DynamicCargo.lua | 52 +- 2 files changed, 569 insertions(+), 52 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 1d189d639..3e69bae04 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -88,8 +88,7 @@ do -- SET_BASE Index = {}, Database = nil, CallScheduler = nil, - TimeInterval = nil, - YieldInterval = nil, + } --- Filters @@ -506,19 +505,6 @@ do -- SET_BASE return self end - --- Define the SET iterator **"yield interval"** and the **"time interval"**. - -- @param #SET_BASE self - -- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. - -- @param #number TimeInterval Sets the time in seconds when the main logic will resume the iterator loop. The default time is 0.001 seconds. - -- @return #SET_BASE self - function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) - - self.YieldInterval = YieldInterval - self.TimeInterval = TimeInterval - - return self - end - --- Define the SET iterator **"limit"**. -- @param #SET_BASE self -- @param #number Limit Defines how many objects are evaluated of the set as part of the Some iterators. The default is 1. @@ -2003,12 +1989,12 @@ do -- @param Wrapper.Group#GROUP MGroup The group that is checked for inclusion. -- @return #SET_GROUP self function SET_GROUP:IsIncludeObject( MGroup ) - self:F2( MGroup ) + --self:F2( MGroup ) local MGroupInclude = true if self.Filter.Alive == true then local MGroupAlive = false - self:F( { Active = self.Filter.Active } ) + --self:F( { Active = self.Filter.Active } ) if MGroup and MGroup:IsAlive() then MGroupAlive = true end @@ -2017,7 +2003,7 @@ do if self.Filter.Active ~= nil then local MGroupActive = false - self:F( { Active = self.Filter.Active } ) + --self:F( { Active = self.Filter.Active } ) if self.Filter.Active == false or (self.Filter.Active == true and MGroup:IsActive() == true) then MGroupActive = true end @@ -2027,7 +2013,7 @@ do if self.Filter.Coalitions and MGroupInclude then local MGroupCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MGroup:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:T3( { "Coalition:", MGroup:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MGroup:GetCoalition() then MGroupCoalition = true end @@ -2038,7 +2024,7 @@ do if self.Filter.Categories and MGroupInclude then local MGroupCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) + --self:T3( { "Category:", MGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MGroup:GetCategory() then MGroupCategory = true end @@ -2049,7 +2035,7 @@ do if self.Filter.Countries and MGroupInclude then local MGroupCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MGroup:GetCountry(), CountryName } ) + --self:T3( { "Country:", MGroup:GetCountry(), CountryName } ) if country.id[CountryName] == MGroup:GetCountry() then MGroupCountry = true end @@ -2060,7 +2046,7 @@ do if self.Filter.GroupPrefixes and MGroupInclude then local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do - self:T3( { "Prefix:", string.find( MGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) + --self:T3( { "Prefix:", string.find( MGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) if string.find( MGroup:GetName(), GroupPrefix:gsub( "-", "%%-" ), 1 ) then MGroupPrefix = true end @@ -2085,7 +2071,7 @@ do MGroupInclude = MGroupInclude and MGroupFunc end - self:T2( MGroupInclude ) + --self:T2( MGroupInclude ) return MGroupInclude end @@ -3239,7 +3225,7 @@ do -- SET_UNIT -- @param Wrapper.Unit#UNIT MUnit -- @return #SET_UNIT self function SET_UNIT:IsIncludeObject( MUnit ) - self:F2( {MUnit} ) + --self:F2( {MUnit} ) local MUnitInclude = false @@ -3258,7 +3244,7 @@ do -- SET_UNIT if self.Filter.Coalitions and MUnitInclude then local MUnitCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:F( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:F( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then MUnitCoalition = true end @@ -3269,7 +3255,7 @@ do -- SET_UNIT if self.Filter.Categories and MUnitInclude then local MUnitCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) + --self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then MUnitCategory = true end @@ -3280,7 +3266,7 @@ do -- SET_UNIT if self.Filter.Types and MUnitInclude then local MUnitType = false for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) + --self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) if TypeName == MUnit:GetTypeName() then MUnitType = true end @@ -3291,7 +3277,7 @@ do -- SET_UNIT if self.Filter.Countries and MUnitInclude then local MUnitCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) + --self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) if country.id[CountryName] == MUnit:GetCountry() then MUnitCountry = true end @@ -3302,7 +3288,7 @@ do -- SET_UNIT if self.Filter.UnitPrefixes and MUnitInclude then local MUnitPrefix = false for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do - self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) + --self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) if string.find( MUnit:GetName(), UnitPrefix, 1 ) then MUnitPrefix = true end @@ -3313,10 +3299,10 @@ do -- SET_UNIT if self.Filter.RadarTypes and MUnitInclude then local MUnitRadar = false for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do - self:T3( { "Radar:", RadarType } ) + --self:T3( { "Radar:", RadarType } ) if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. - self:T3( "RADAR Found" ) + --self:T3( "RADAR Found" ) end MUnitRadar = true end @@ -3327,7 +3313,7 @@ do -- SET_UNIT if self.Filter.SEAD and MUnitInclude then local MUnitSEAD = false if MUnit:HasSEAD() == true then - self:T3( "SEAD Found" ) + --self:T3( "SEAD Found" ) MUnitSEAD = true end MUnitInclude = MUnitInclude and MUnitSEAD @@ -3337,7 +3323,7 @@ do -- SET_UNIT if self.Filter.Zones and MUnitInclude then local MGroupZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do - self:T3( "Zone:", ZoneName ) + --self:T3( "Zone:", ZoneName ) if MUnit:IsInZone(Zone) then MGroupZone = true end @@ -3350,7 +3336,7 @@ do -- SET_UNIT MUnitInclude = MUnitInclude and MUnitFunc end - self:T2( MUnitInclude ) + --self:T2( MUnitInclude ) return MUnitInclude end @@ -3698,7 +3684,7 @@ do -- SET_STATIC self:_FilterStart() self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) end return self @@ -4753,7 +4739,7 @@ do -- SET_CLIENT -- @param Wrapper.Client#CLIENT MClient -- @return #SET_CLIENT self function SET_CLIENT:IsIncludeObject( MClient ) - self:F2( MClient ) + --self:F2( MClient ) local MClientInclude = true @@ -4776,12 +4762,12 @@ do -- SET_CLIENT if ClientCoalitionID==nil and MClient:IsAlive()~=nil then ClientCoalitionID=MClient:GetCoalition() end - self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and ClientCoalitionID and self.FilterMeta.Coalitions[CoalitionName] == ClientCoalitionID then MClientCoalition = true end end - self:T( { "Evaluated Coalition", MClientCoalition } ) + --self:T( { "Evaluated Coalition", MClientCoalition } ) MClientInclude = MClientInclude and MClientCoalition end @@ -4799,20 +4785,20 @@ do -- SET_CLIENT end --self:T3("Filter Outcome is "..tostring(MClientCategory)) else - self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) + --self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and ClientCategoryID and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then MClientCategory = true end end end - self:T( { "Evaluated Category", MClientCategory } ) + --self:T( { "Evaluated Category", MClientCategory } ) MClientInclude = MClientInclude and MClientCategory end if self.Filter.Types and MClientInclude then local MClientType = false for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) + --self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) if TypeName == MClient:GetTypeName() then MClientType = true end @@ -4833,26 +4819,26 @@ do -- SET_CLIENT MClientCountry = true end end - self:T( { "Evaluated Country", MClientCountry } ) + --self:T( { "Evaluated Country", MClientCountry } ) MClientInclude = MClientInclude and MClientCountry end if self.Filter.ClientPrefixes and MClientInclude then local MClientPrefix = false for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do - self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) + --self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) if string.find( MClient.UnitName, ClientPrefix, 1 ) then MClientPrefix = true end end - self:T( { "Evaluated Prefix", MClientPrefix } ) + --self:T( { "Evaluated Prefix", MClientPrefix } ) MClientInclude = MClientInclude and MClientPrefix end if self.Filter.Zones and MClientInclude then local MClientZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do - self:T3( "Zone:", ZoneName ) + --self:T3( "Zone:", ZoneName ) local unit = MClient:GetClientGroupUnit() if unit and unit:IsInZone(Zone) then MClientZone = true @@ -4870,7 +4856,7 @@ do -- SET_CLIENT MClientPlayername = true end end - self:T( { "Evaluated Playername", MClientPlayername } ) + --self:T( { "Evaluated Playername", MClientPlayername } ) MClientInclude = MClientInclude and MClientPlayername end @@ -4883,7 +4869,7 @@ do -- SET_CLIENT MClientCallsigns = true end end - self:T( { "Evaluated Callsign", MClientCallsigns } ) + --self:T( { "Evaluated Callsign", MClientCallsigns } ) MClientInclude = MClientInclude and MClientCallsigns end @@ -4893,7 +4879,7 @@ do -- SET_CLIENT end end - self:T2( MClientInclude ) + --self:T2( MClientInclude ) return MClientInclude end @@ -8504,3 +8490,490 @@ do -- SET_SCENERY end end + +-- TODO SET_DYNAMICCARGO + +do -- SET_DYNAMICCARGO + + --- + -- @type SET_DYNAMICCARGO + -- @field #table Filter Table of filters. + -- @field #table Set Table of objects. + -- @field #table Index Table of indices. + -- @field #table List Unused table. + -- @field Core.Scheduler#SCHEDULER CallScheduler. + -- @field #SET_DYNAMICCARGO.Filters Filter Filters. + -- @field #number ZoneTimerInterval. + -- @field Core.Timer#TIMER ZoneTimer Timer for active filtering of zones. + -- @extends Core.Set#SET_BASE + + --- + -- @type SET_DYNAMICCARGO.Filters + -- @field #string Coalitions + -- @field #string Types + -- @field #string Countries + -- @field #string StaticPrefixes + -- @field #string Zones + + --- The @{Core.Set#SET_DYNAMICCARGO} class defines the functions that define a collection of objects form @{Wrapper.DynamicCargo#DYNAMICCARGO}. + -- A SET provides iterators to iterate the SET. + -- + -- @field #SET_DYNAMICCARGO SET_DYNAMICCARGO + SET_DYNAMICCARGO = { + ClassName = "SET_DYNAMICCARGO", + Filter = {}, + Set = {}, + List = {}, + Index = {}, + Database = nil, + CallScheduler = nil, + Filter = { + Coalitions = nil, + Types = nil, + Countries = nil, + StaticPrefixes = nil, + Zones = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + } + }, + ZoneTimerInterval = 20, + ZoneTimer = nil, + } + + --- Creates a new SET_DYNAMICCARGO object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO + -- @usage + -- -- Define a new SET_DYNAMICCARGO Object. This DBObject will contain a reference to all alive Statics. + -- DBObject = SET_DYNAMICCARGO:New() + function SET_DYNAMICCARGO:New() + + --- Inherits from BASE + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.DYNAMICCARGO ) ) -- Core.Set#SET_DYNAMICCARGO + + return self + end + + --- + -- @param #SET_DYNAMICCARGO self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DCargo + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:IsIncludeObject( DCargo ) + --self:F2( DCargo ) + local DCargoInclude = true + + if self.Filter.Coalitions then + local DCargoCoalition = false + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + self:I( { "Coalition:", DCargo:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == DCargo:GetCoalition() then + DCargoCoalition = true + end + end + DCargoInclude = DCargoInclude and DCargoCoalition + end + + if self.Filter.Types then + local DCargoType = false + for TypeID, TypeName in pairs( self.Filter.Types ) do + self:I( { "Type:", DCargo:GetTypeName(), TypeName } ) + if TypeName == DCargo:GetTypeName() then + DCargoType = true + end + end + DCargoInclude = DCargoInclude and DCargoType + end + + if self.Filter.Countries then + local DCargoCountry = false + for CountryID, CountryName in pairs( self.Filter.Countries ) do + self:I( { "Country:", DCargo:GetCountry(), CountryName } ) + if country.id[CountryName] == DCargo:GetCountry() then + DCargoCountry = true + end + end + DCargoInclude = DCargoInclude and DCargoCountry + end + + if self.Filter.StaticPrefixes then + local DCargoPrefix = false + for StaticPrefixId, StaticPrefix in pairs( self.Filter.StaticPrefixes ) do + self:I( { "Prefix:", string.find( DCargo:GetName(), StaticPrefix, 1 ), StaticPrefix } ) + if string.find( DCargo:GetName(), StaticPrefix, 1 ) then + DCargoPrefix = true + end + end + DCargoInclude = DCargoInclude and DCargoPrefix + end + + if self.Filter.Zones then + local DCargoZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + self:I( "In zone: "..ZoneName ) + if DCargo and DCargo:IsInZone(Zone) then + DCargoZone = true + end + end + DCargoInclude = DCargoInclude and DCargoZone + end + + self:I( DCargoInclude ) + return DCargoInclude + end + + --- Builds a set of dynamic cargo of defined coalitions. + -- Possible current coalitions are red, blue and neutral. + -- @param #SET_DYNAMICCARGO self + -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterCoalitions( Coalitions ) + if not self.Filter.Coalitions then + self.Filter.Coalitions = {} + end + if type( Coalitions ) ~= "table" then + Coalitions = { Coalitions } + end + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + return self + end + + --- Builds a set of dynamic cargo of defined dynamic cargo type names. + -- @param #SET_DYNAMICCARGO self + -- @param #string Types Can take those type name strings known within DCS world. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterTypes( Types ) + if not self.Filter.Types then + self.Filter.Types = {} + end + if type( Types ) ~= "table" then + Types = { Types } + end + for TypeID, Type in pairs( Types ) do + self.Filter.Types[Type] = Type + end + return self + end + + --- [User] Add a custom condition function. + -- @function [parent=#SET_DYNAMICCARGO] FilterFunction + -- @param #SET_DYNAMICCARGO self + -- @param #function ConditionFunction If this function returns `true`, the object is added to the SET. The function needs to take a DYNAMICCARGO object as first argument. + -- @param ... Condition function arguments if any. + -- @return #SET_DYNAMICCARGO self + -- @usage + -- -- Image you want to exclude a specific DYNAMICCARGO from a SET: + -- local cargoset = SET_DYNAMICCARGO:New():FilterCoalitions("blue"):FilterFunction( + -- -- The function needs to take a DYNAMICCARGO object as first - and in this case, only - argument. + -- function(dynamiccargo) + -- local isinclude = true + -- if dynamiccargo:GetName() == "Exclude Me" then isinclude = false end + -- return isinclude + -- end + -- ):FilterOnce() + -- BASE:I(cargoset:Flush()) + + --- Builds a set of dynamic cargo of defined countries. + -- Possible current countries are those known within DCS world. + -- @param #SET_DYNAMICCARGO self + -- @param #string Countries Can take those country strings known within DCS world. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterCountries( Countries ) + if not self.Filter.Countries then + self.Filter.Countries = {} + end + if type( Countries ) ~= "table" then + Countries = { Countries } + end + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + return self + end + + --- Builds a set of DYNAMICCARGOs that contain the given string in their name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all names that **contain** the string. LUA Regex applies. + -- @param #SET_DYNAMICCARGO self + -- @param #string Prefixes The string pattern(s) that need to be contained in the dynamic cargo name. Can also be passed as a `#table` of strings. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterPrefixes( Prefixes ) + if not self.Filter.StaticPrefixes then + self.Filter.StaticPrefixes = {} + end + if type( Prefixes ) ~= "table" then + Prefixes = { Prefixes } + end + for PrefixID, Prefix in pairs( Prefixes ) do + self.Filter.StaticPrefixes[Prefix] = Prefix + end + return self + end + + --- Builds a set of DYNAMICCARGOs that contain the given string in their name. + -- **Attention!** LUA Regex applies! + -- @param #SET_DYNAMICCARGO self + -- @param #string Patterns The string pattern(s) that need to be contained in the dynamic cargo name. Can also be passed as a `#table` of strings. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterNamePattern( Patterns ) + return self:FilterPrefixes(Patterns) + end + + --- Builds a set of DYNAMICCARGOs that are in state DYNAMICCARGO.State.LOADED (i.e. is on board of a Chinook). + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterIsLoaded() + self:FilterFunction( + function(cargo) + if cargo and cargo.CargoState and cargo.CargoState == DYNAMICCARGO.State.LOADED then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of DYNAMICCARGOs that are in state DYNAMICCARGO.State.LOADED (i.e. was on board of a Chinook previously and is now unloaded). + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterIsUnloaded() + self:FilterFunction( + function(cargo) + if cargo and cargo.CargoState and cargo.CargoState == DYNAMICCARGO.State.UNLOADED then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of DYNAMICCARGOs that are in state DYNAMICCARGO.State.NEW (i.e. new and never loaded into a Chinook). + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterIsNew() + self:FilterFunction( + function(cargo) + if cargo and cargo.CargoState and cargo.CargoState == DYNAMICCARGO.State.NEW then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of DYNAMICCARGOs that are owned at the moment by this player name. + -- @param #SET_DYNAMICCARGO self + -- @param #string PlayerName + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterCurrentOwner(PlayerName) + self:FilterFunction( + function(cargo) + if cargo and cargo.Owner and string.find(cargo.Owner,PlayerName,1,true) then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of dynamic cargo in zones. + -- @param #SET_DYNAMICCARGO self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + self.Filter.Zones[zonename] = Zone + end + return self + end + + --- Starts the filtering. + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterStart() + if _DATABASE then + self:HandleEvent( EVENTS.NewDynamicCargo, self._EventHandlerDCAdd ) + self:HandleEvent( EVENTS.DynamicCargoRemoved, self._EventHandlerDCRemove ) + if self.Filter.Zones then + self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self) + local timing = self.ZoneTimerInterval or 30 + self.ZoneTimer:Start(timing,timing) + end + self:_FilterStart() + end + + return self + end + + --- Stops the filtering. + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterStop() + if _DATABASE then + self:UnHandleEvent( EVENTS.NewDynamicCargo) + self:UnHandleEvent( EVENTS.DynamicCargoRemoved ) + if self.ZoneTimer and self.ZoneTimer:IsRunning() then + self.ZoneTimer:Stop() + end + end + + return self + end + + --- [Internal] Private function for use of continous zone filter + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:_ContinousZoneFilter() + local Database = _DATABASE.DYNAMICCARGO + for ObjectName, Object in pairs( Database ) do + if self:IsIncludeObject( Object ) and self:IsNotInSet(Object) then + self:Add( ObjectName, Object ) + elseif (not self:IsIncludeObject( Object )) and self:IsInSet(Object) then + self:Remove(ObjectName) + end + end + + return self + end + + --- Handles the events for the Set. + -- @param #SET_DYNAMICCARGO self + -- @param Core.Event#EVENTDATA Event + function SET_DYNAMICCARGO:_EventHandlerDCAdd( Event ) + if Event.IniDynamicCargo and Event.IniDynamicCargoName then + if not _DATABASE.DYNAMICCARGO[Event.IniDynamicCargoName] then + _DATABASE:AddDynamicCargo( Event.IniDynamicCargoName ) + end + local ObjectName, Object = self:FindInDatabase( Event ) + if Object and self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + end + end + + return self + end + + --- Handles the remove event for dynamic cargo set. + -- @param #SET_DYNAMICCARGO self + -- @param Core.Event#EVENTDATA Event + function SET_DYNAMICCARGO:_EventHandlerDCRemove( Event ) + if Event.IniDCSUnitName then + local ObjectName, Object = self:FindInDatabase( Event ) + if ObjectName then + self:Remove( ObjectName ) + end + end + + return self + end + + --- Handles the Database to check on any event that Object exists in the Database. + -- This is required, because sometimes the _DATABASE event gets called later than the SET_DYNAMICCARGO event or vise versa! + -- @param #SET_DYNAMICCARGO self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the DYNAMICCARGO + -- @return Wrapper.DynamicCargo#DYNAMICCARGO The DYNAMICCARGO object + function SET_DYNAMICCARGO:FindInDatabase( Event ) + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] + end + + --- Set filter timer interval for FilterZones if using active filtering with FilterStart(). + -- @param #SET_DYNAMICCARGO self + -- @param #number Seconds Seconds between check intervals, defaults to 30. **Caution** - do not be too agressive with timing! Objects are usually not moving fast enough + -- to warrant a check of below 10 seconds. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterZoneTimer(Seconds) + self.ZoneTimerInterval = Seconds or 30 + return self + end + + --- This filter is N/A for SET_DYNAMICCARGO + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterDeads() + return self + end + + --- This filter is N/A for SET_DYNAMICCARGO + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterCrashes() + return self + end + + --- Returns a list of current owners (playernames) indexed by playername from the SET. + -- @param #SET_DYNAMICCARGO self + -- @return #list<#string> Ownerlist + function SET_DYNAMICCARGO:GetOwnerNames() + local owners = {} + self:ForEach( + function(cargo) + if cargo and cargo.Owner then + table.insert(owners, cargo.Owner, cargo.Owner) + end + end + ) + return owners + end + + --- Returns a list @{Wrapper.Storage#STORAGE} objects from the SET indexed by cargo name. + -- @param #SET_DYNAMICCARGO self + -- @return #list Storagelist + function SET_DYNAMICCARGO:GetStorageObjects() + local owners = {} + self:ForEach( + function(cargo) + if cargo and cargo.warehouse then + table.insert(owners, cargo.StaticName, cargo.warehouse) + end + end + ) + return owners + end + + --- Returns a list of current owners (Wrapper.Client#CLIENT objects) indexed by playername from the SET. + -- @param #SET_DYNAMICCARGO self + -- @return #list<#string> Ownerlist + function SET_DYNAMICCARGO:GetOwnerClientObjects() + local owners = {} + self:ForEach( + function(cargo) + if cargo and cargo.Owner then + local client = CLIENT:FindByPlayerName(cargo.Owner) + if client then + table.insert(owners, cargo.Owner, client) + end + end + end + ) + return owners + end + + +end diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 335687f9a..63f341009 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -124,7 +124,7 @@ DYNAMICCARGO.AircraftDimensions = { --- DYNAMICCARGO class version. -- @field #string version -DYNAMICCARGO.version="0.0.4" +DYNAMICCARGO.version="0.0.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -194,13 +194,57 @@ end -- User API Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Get last know owner (player) name of this DYNAMICCARGO +--- Get last known owner name of this DYNAMICCARGO -- @param #DYNAMICCARGO self --- @return DCS#Vec3 Position in 3D space -function DYNAMICCARGO:GetLastPosition() +-- @return #string Owner +function DYNAMICCARGO:GetLastOwner() return self.Owner end +--- Returns true if the cargo is new and has never been loaded into a Helo. +-- @param #DYNAMICCARGO self +-- @return #boolean Outcome +function DYNAMICCARGO:IsNew() + if self.CargoState and self.CargoState == DYNAMICCARGO.State.NEW then + return true + else + return false + end +end + +--- Returns true if the cargo been loaded into a Helo. +-- @param #DYNAMICCARGO self +-- @return #boolean Outcome +function DYNAMICCARGO:IsLoaded() + if self.CargoState and self.CargoState == DYNAMICCARGO.State.LOADED then + return true + else + return false + end +end + +--- Returns true if the cargo has been unloaded from a Helo. +-- @param #DYNAMICCARGO self +-- @return #boolean Outcome +function DYNAMICCARGO:IsUnloaded() + if self.CargoState and self.CargoState == DYNAMICCARGO.State.REMOVED then + return true + else + return false + end +end + +--- Returns true if the cargo has been removed. +-- @param #DYNAMICCARGO self +-- @return #boolean Outcome +function DYNAMICCARGO:IsRemoved() + if self.CargoState and self.CargoState == DYNAMICCARGO.State.UNLOADED then + return true + else + return false + end +end + --- [CTLD] Get number of crates this DYNAMICCARGO consists of. Always one. -- @param #DYNAMICCARGO self -- @return #number crate number, always one From 4ca4d2e211be3505a3dc757f666d3d6e53a3efee Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 Aug 2024 15:45:43 +0200 Subject: [PATCH 026/349] xx --- Moose Development/Moose/Modules_local.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index 15c6bcba5..5a6b6c0e9 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -115,7 +115,6 @@ __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' ) From cf322c121e8a8e4078441292a41c8d8e037023b1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 17 Aug 2024 11:37:26 +0200 Subject: [PATCH 027/349] xx --- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Ops/CTLD.lua | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index b8edf725c..7e5ee2944 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -27,7 +27,7 @@ -- * @{#SET_CARGO}: Defines a collection of @{Cargo.Cargo}s filtered by filter criteria. -- * @{#SET_ZONE}: Defines a collection of @{Core.Zone}s filtered by filter criteria. -- * @{#SET_SCENERY}: Defines a collection of @{Wrapper.Scenery}s added via a filtered @{#SET_ZONE}. --- * @{#SET_DYNAMICCARGO}: Defines a collection of @{Wrapper.DynamicCargo}s added via a filtered @{#SET_ZONE}. +-- * @{#SET_DYNAMICCARGO}: Defines a collection of @{Wrapper.DynamicCargo}s filtered by filter criteria. -- -- These classes are derived from @{#SET_BASE}, which contains the main methods to manage the collections. -- diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3011aacb9..8cbbc45e3 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -801,6 +801,7 @@ do -- my_ctld.enableChinookGCLoading = true -- will effectively suppress the crate load and drop menus for CTLD for the Chinook -- my_ctld.movecratesbeforebuild = false -- cannot detect movement of crates at the moment -- my_ctld.nobuildinloadzones = true -- don't build where you load. +-- my_ctld.ChinookTroopCircleRadius = 5 -- Radius for troops dropping in a nice circle. Adjust to your planned squad size for the Chinook. -- -- ## 2.2 User functions -- @@ -1206,6 +1207,7 @@ CTLD = { dropOffZones = {}, pickupZones = {}, DynamicCargo = {}, + ChinookTroopCircleRadius = 5, } ------------------------------ @@ -3434,6 +3436,8 @@ function CTLD:_UnloadTroops(Group, Unit) randomcoord:Translate(offset,Angle,nil,true) end local tempcount = 0 + local ishook = self:IsHook(Unit) + if ishook then tempcount = self.ChinookTroopCircleRadius or 5 end -- 10m circle for the Chinook for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 tempcount = tempcount+1 From 86ed8fe9e574a081ad30c97ff32f5008ba18cf34 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 18 Aug 2024 13:06:13 +0200 Subject: [PATCH 028/349] #ATIS cover "NEWRAINPRESET4" --- Moose Development/Moose/Ops/ATIS.lua | 35 +++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 9953be1e9..ac2a5ed07 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -2137,7 +2137,7 @@ function ATIS:onafterBroadcast( From, Event, To ) local cloudbase = clouds.base local cloudceil = clouds.base + clouds.thickness local clouddens = clouds.density - + -- Cloud preset (DCS 2.7) local cloudspreset = clouds.preset or "Nothing" @@ -2168,6 +2168,39 @@ function ATIS:onafterBroadcast( From, Event, To ) else precepitation = 3 -- snow end + elseif cloudspreset:find( "RainyPreset4" ) then + -- Overcast + Rain + clouddens = 5 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset5" ) then + -- Overcast + Rain + clouddens = 5 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset6" ) then + -- Overcast + Rain + clouddens = 5 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + -- NEWRAINPRESET4 + elseif cloudspreset:find( "NEWRAINPRESET4" ) then + -- Overcast + Rain + clouddens = 5 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end elseif cloudspreset:find( "RainyPreset" ) then -- Overcast + Rain clouddens = 9 From 43ffad02beec1ae3e8f83b65f48ba0e968a171d3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 18 Aug 2024 19:33:24 +0200 Subject: [PATCH 029/349] xx --- Moose Development/Moose/Core/Set.lua | 10 ++++++---- Moose Development/Moose/Wrapper/Group.lua | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 7e5ee2944..7d349c8e8 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -2003,12 +2003,13 @@ do if self.Filter.Categories and MGroupInclude then local MGroupCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - --self:T3( { "Category:", MGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) + --self:I( { "Category:", MGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MGroup:GetCategory() then MGroupCategory = true end end MGroupInclude = MGroupInclude and MGroupCategory + --self:I("Is Included: "..tostring(MGroupInclude)) end if self.Filter.Countries and MGroupInclude then @@ -2025,12 +2026,13 @@ do if self.Filter.GroupPrefixes and MGroupInclude then local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do - --self:T3( { "Prefix:", string.find( MGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MGroup:GetName(), GroupPrefix:gsub( "-", "%%-" ), 1 ) then + --self:I( { "Prefix:", MGroup:GetName(), GroupPrefix } ) + if string.find( string.lower(MGroup:GetName()), string.lower(GroupPrefix), 1 ) or string.find( string.lower(MGroup:GetName()), string.lower(GroupPrefix), 1, true ) then MGroupPrefix = true end end MGroupInclude = MGroupInclude and MGroupPrefix + --self:I("Is Included: "..tostring(MGroupInclude)) end if self.Filter.Zones and MGroupInclude then @@ -2050,7 +2052,7 @@ do MGroupInclude = MGroupInclude and MGroupFunc end - --self:T2( MGroupInclude ) + --self:I( MGroupInclude ) return MGroupInclude end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index bd27b6212..d7d378291 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -516,7 +516,7 @@ function GROUP:GetCategory() local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) + --self:I( GroupCategory ) return GroupCategory end From 21ec234dd347f4c217aed76c5d88e61923ca2e20 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 18 Aug 2024 19:38:43 +0200 Subject: [PATCH 030/349] xx --- Moose Development/Moose/Core/Set.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 7d349c8e8..918399108 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -9009,6 +9009,5 @@ do -- SET_DYNAMICCARGO ) return owners end - end From 43f1c6fcb1c2e59ac37b63cd0b82cef4338ca28b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 20 Aug 2024 10:48:08 +0200 Subject: [PATCH 031/349] xx --- Moose Development/Moose/Ops/CSAR.lua | 24 ++++++++++++++---------- Moose Development/Moose/Ops/CTLD.lua | 6 +++++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index b6f747260..2e4627d79 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update July 2024 +-- Last Update Aug 2024 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -136,6 +136,7 @@ -- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection. Requires mycsar.enableForAI to be set to true. --shagrat -- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. -- mycsar.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane +-- mycsar.CreateRadioBeacons = true -- set to false to disallow creating ADF radio beacons. -- -- ## 3. Results -- @@ -256,6 +257,7 @@ CSAR = { topmenuname = "CSAR", ADFRadioPwr = 1000, PilotWeight = 80, + CreateRadioBeacons = true, } --- Downed pilots info. @@ -297,7 +299,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.26" +CSAR.version="1.0.27" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -963,7 +965,6 @@ end -- @param Core.Point#COORDINATE Point -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. --- @param #boolean addBeacon (optional) yes or no. -- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. -- @param #string Unitname (optional) Name of the lost unit. -- @param #string Typename (optional) Type of plane. @@ -1856,11 +1857,11 @@ function CSAR:_DisplayActiveSAR(_unitName) else distancetext = string.format("%.1fkm", _distance/1000.0) end - if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) - else - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) - end + if _value.frequency == 0 or self.CreateRadioBeacons == false then--shagrat insert CASEVAC without Frequency + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) + else + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + end end end @@ -2231,8 +2232,10 @@ end -- @param #CSAR self -- @param Wrapper.Group#GROUP _group Group #GROUP object. -- @param #number _freq Frequency to use +-- @return #CSAR self function CSAR:_AddBeaconToGroup(_group, _freq) self:T(self.lid .. " _AddBeaconToGroup") + if self.CreateRadioBeacons == false then return end local _group = _group if _group == nil then --return frequency to pool of available @@ -2248,7 +2251,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq) if _group:IsAlive() then local _radioUnit = _group:GetUnit(1) if _radioUnit then - local name = _radioUnit:GetName() + local name = _radioUnit:GetName() local Frequency = _freq -- Freq in Hertz local name = _radioUnit:GetName() local Sound = "l10n/DEFAULT/"..self.radioSound @@ -2261,9 +2264,10 @@ end --- (Internal) Helper function to (re-)add beacon to downed pilot. -- @param #CSAR self --- @param #table _args Arguments +-- @return #CSAR self function CSAR:_RefreshRadioBeacons() self:T(self.lid .. " _RefreshRadioBeacons") + if self.CreateRadioBeacons == false then return end if self:_CountActiveDownedPilots() > 0 then local PilotTable = self.downedPilots for _,_pilot in pairs (PilotTable) do diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 8cbbc45e3..598c872a8 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -773,6 +773,8 @@ do -- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. -- my_ctld.RadioSoundFC3 = "beacon.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. -- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIc types for CTLD for the Chinook +-- my_ctld.TroopUnloadDistGround = 1.5 -- If hovering, spawn dropped troops this far away from the helo +-- my_ctld.TroopUnloadDistHover = 5 -- If grounded, spawn dropped troops this far away from the helo -- -- ## 2.1 CH-47 Chinook support -- @@ -1208,6 +1210,8 @@ CTLD = { pickupZones = {}, DynamicCargo = {}, ChinookTroopCircleRadius = 5, + TroopUnloadDistGround = 1.5, + TroopUnloadDistHover = 5, } ------------------------------ @@ -3432,7 +3436,7 @@ function CTLD:_UnloadTroops(Group, Unit) randomcoord = Group:GetCoordinate() -- slightly left from us local Angle = (heading+270)%360 - local offset = hoverunload and 1.5 or 5 + local offset = hoverunload and self.TroopUnloadDistGround or self.TroopUnloadDistHover randomcoord:Translate(offset,Angle,nil,true) end local tempcount = 0 From 89c6358085e2c3a9a92d7175cd27d97c23f1d311 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 20 Aug 2024 10:55:22 +0200 Subject: [PATCH 032/349] xxx --- Moose Development/Moose/Ops/CTLD.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index ef4d348eb..adfb6436a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -773,8 +773,8 @@ do -- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. -- my_ctld.RadioSoundFC3 = "beacon.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. -- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIc types for CTLD for the Chinook --- my_ctld.TroopUnloadDistGround = 1.5 -- If hovering, spawn dropped troops this far away from the helo --- my_ctld.TroopUnloadDistHover = 5 -- If grounded, spawn dropped troops this far away from the helo +-- my_ctld.TroopUnloadDistGround = 5 -- If hovering, spawn dropped troops this far away in meters from the helo +-- my_ctld.TroopUnloadDistHover = 1.5 -- If grounded, spawn dropped troops this far away in meters from the helo -- -- ## 2.1 CH-47 Chinook support -- @@ -1210,8 +1210,8 @@ CTLD = { pickupZones = {}, DynamicCargo = {}, ChinookTroopCircleRadius = 5, - TroopUnloadDistGround = 1.5, - TroopUnloadDistHover = 5, + TroopUnloadDistGround = 5, + TroopUnloadDistHover = 1.5, } ------------------------------ @@ -1316,7 +1316,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.12" +CTLD.version="1.1.13" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3436,7 +3436,7 @@ function CTLD:_UnloadTroops(Group, Unit) randomcoord = Group:GetCoordinate() -- slightly left from us local Angle = (heading+270)%360 - local offset = hoverunload and self.TroopUnloadDistGround or self.TroopUnloadDistHover + local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround randomcoord:Translate(offset,Angle,nil,true) end local tempcount = 0 From 8f3fc9cb2b317e304f0736ea212e56bb24dbdce0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 20 Aug 2024 17:56:09 +0200 Subject: [PATCH 033/349] xx --- Moose Development/Moose/Ops/Airboss.lua | 4 +-- Moose Development/Moose/Utilities/Enums.lua | 35 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 31116dde9..a61b52551 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -18087,7 +18087,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare ) self:_GetZoneArcIn( case ):FlareZone( FLARECOLOR.White, 45 ) text = text .. "\n* arc turn in with WHITE flares" self:_GetZoneArcOut( case ):FlareZone( FLARECOLOR.White, 45 ) - text = text .. "\n* arc trun out with WHITE flares" + text = text .. "\n* arc turn out with WHITE flares" end end @@ -18139,7 +18139,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare ) self:_GetZoneArcIn( case ):SmokeZone( SMOKECOLOR.Blue, 45 ) text = text .. "\n* arc turn in with BLUE smoke" self:_GetZoneArcOut( case ):SmokeZone( SMOKECOLOR.Blue, 45 ) - text = text .. "\n* arc trun out with BLUE smoke" + text = text .. "\n* arc turn out with BLUE smoke" end end diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index d3b63882f..f47972f7a 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -580,12 +580,22 @@ ENUMS.Link16Power = { --- Enums for the STORAGE class for stores - which need to be in "" -- @type ENUMS.Storage -- @type ENUMS.Storage.weapons +-- @type ENUMS.Storage.weapons.missiles +-- @type ENUMS.Storage.weapons.bombs +-- @type ENUMS.Storage.weapons.nurs +-- @type ENUMS.Storage.weapons.containers +-- @type ENUMS.Storage.weapons.droptanks +-- @type ENUMS.Storage.weapons.adapters +-- @type ENUMS.Storage.weapons.torpedoes ENUMS.Storage = { weapons = { missiles = {}, -- Missiles bombs = {}, -- Bombs nurs = {}, -- Rockets and unguided - containers = {}, -- Containers + containers = { + Gazelle = {}, -- Gazelle specifics + CH47 = {}, -- Chinook specifics + }, -- Containers droptanks = {}, -- Droptanks adapters = {}, -- Adapter torpedoes = {}, -- Torpedoes @@ -1148,4 +1158,25 @@ ENUMS.Storage.weapons.bombs.BDU_50LD = "weapons.bombs.BDU_50LD" ENUMS.Storage.weapons.bombs.AGM_62 = "weapons.bombs.AGM_62" ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE = "weapons.containers.{US_M10_SMOKE_TANK_WHITE}" ENUMS.Storage.weapons.missiles.MICA_T = "weapons.missiles.MICA_T" -ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket" +ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket" +-- Gazelle +ENUMS.Storage.weapons.containers.Gazelle.HMP400_100RDS = {4,15,46,1771} +ENUMS.Storage.weapons.containers.Gazelle.HMP400_200RDS = {4,15,46,1770} +ENUMS.Storage.weapons.containers.Gazelle.HMP400_400RDS = {4,15,46,1769} +ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_AP = {4,15,46,1768} +ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_SAPHEI = {4,15,46,1767} +ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_HE = {4,15,46,1766} +ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_HEAP = {4,15,46,1765} +ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_APHE = {4,15,46,1764} +ENUMS.Storage.weapons.containers.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680} +ENUMS.Storage.weapons.containers.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679} +-- Chinook +ENUMS.Storage.weapons.containers.CH47.CH47_PORT_M60D = {4,15,46,2476} +ENUMS.Storage.weapons.containers.CH47.CH47_STBD_M60D = {4,15,46,2477} +ENUMS.Storage.weapons.containers.CH47.CH47_AFT_M60D = {4,15,46,2478} +ENUMS.Storage.weapons.containers.CH47.CH47_PORT_M134D = {4,15,46,2482} +ENUMS.Storage.weapons.containers.CH47.CH47_STBD_M134D = {4,15,46,2483} +ENUMS.Storage.weapons.containers.CH47.CH47_AFT_M3M = {4,15,46,2484} +ENUMS.Storage.weapons.containers.CH47.CH47_PORT_M240H = {4,15,46,2479} +ENUMS.Storage.weapons.containers.CH47.CH47_STBD_M240H = {4,15,46,2480} +ENUMS.Storage.weapons.containers.CH47.CH47_AFT_M240H = {4,15,46,2481} From 82b4276b338903e2dd92643dfa8df41a4017da64 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 20 Aug 2024 18:03:41 +0200 Subject: [PATCH 034/349] xx --- Moose Development/Moose/Ops/CSAR.lua | 31 +++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 2e4627d79..a0532abc8 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -274,6 +274,7 @@ CSAR = { -- @field #number timestamp Timestamp for approach process. -- @field #boolean alive Group is alive or dead/rescued. -- @field #boolean wetfeet Group is spawned over (deep) water. +-- @field #string BeaconName Name of radio beacon - if any. --- All slot / Limit settings -- @type CSAR.AircraftType @@ -636,7 +637,7 @@ end -- @param #string Playername Name of Player (if applicable) -- @param #boolean Wetfeet Ejected over water -- @return #CSAR self. -function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet) +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet,BeaconName) self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) -- create new entry @@ -644,7 +645,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.desc = Description or "" DownedPilot.frequency = Frequency or 0 DownedPilot.index = self.downedpilotcounter - DownedPilot.name = Groupname or "" + DownedPilot.name = Groupname or Playername or "" DownedPilot.originalUnit = OriginalUnit or "" DownedPilot.player = Playername or "" DownedPilot.side = Side or 0 @@ -653,6 +654,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.timestamp = 0 DownedPilot.alive = true DownedPilot.wetfeet = Wetfeet or false + DownedPilot.BeaconName = BeaconName -- Add Pilot local PilotTable = self.downedPilots @@ -821,8 +823,18 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla end end + local BeaconName + + if _playerName then + BeaconName = _unitName..math.random(1,10000) + elseif _unitName then + BeaconName = _playerName..math.random(1,10000) + else + BeaconName = "Ghost-1-1"..math.random(1,10000) + end + if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 - self:_AddBeaconToGroup(_spawnedGroup, _freq) + self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName) end self:_AddSpecialOptions(_spawnedGroup) @@ -847,7 +859,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _GroupName = _spawnedGroup:GetName() or _alias - self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet) + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet,BeaconName) self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. @@ -2232,11 +2244,13 @@ end -- @param #CSAR self -- @param Wrapper.Group#GROUP _group Group #GROUP object. -- @param #number _freq Frequency to use +-- @param #string _name Beacon Name to use -- @return #CSAR self -function CSAR:_AddBeaconToGroup(_group, _freq) +function CSAR:_AddBeaconToGroup(_group, _freq, _name) self:T(self.lid .. " _AddBeaconToGroup") if self.CreateRadioBeacons == false then return end local _group = _group + if _group == nil then --return frequency to pool of available for _i, _current in ipairs(self.UsedVHFFrequencies) do @@ -2256,9 +2270,10 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local name = _radioUnit:GetName() local Sound = "l10n/DEFAULT/"..self.radioSound local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} - trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight + trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,_name) -- Beacon in MP only runs for exactly 30secs straight end end + return self end @@ -2275,8 +2290,10 @@ function CSAR:_RefreshRadioBeacons() local pilot = _pilot -- #CSAR.DownedPilot local group = pilot.group local frequency = pilot.frequency or 0 -- thanks to @Thrud + local bname = pilot.BeaconName or pilot.name..math.random(1,100000) + trigger.action.stopRadioTransmission(bname) if group and group:IsAlive() and frequency > 0 then - self:_AddBeaconToGroup(group,frequency) + self:_AddBeaconToGroup(group,frequency,bname) end end end From 42069c5bbb1e4fd582a713043441695615c68602 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 22 Aug 2024 17:17:41 +0200 Subject: [PATCH 035/349] xx --- Moose Development/Moose/Utilities/Enums.lua | 74 ++++++++---- Moose Development/Moose/Utilities/Utils.lua | 127 ++++++++++++++++++-- 2 files changed, 169 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index f47972f7a..7570f0c56 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -592,13 +592,12 @@ ENUMS.Storage = { missiles = {}, -- Missiles bombs = {}, -- Bombs nurs = {}, -- Rockets and unguided - containers = { - Gazelle = {}, -- Gazelle specifics - CH47 = {}, -- Chinook specifics - }, -- Containers + containers = {}, -- Containers droptanks = {}, -- Droptanks adapters = {}, -- Adapter torpedoes = {}, -- Torpedoes + Gazelle = {}, -- Gazelle specifics + CH47 = {}, -- Chinook specifics } } @@ -1160,23 +1159,52 @@ ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE = "weapons.containers.{ ENUMS.Storage.weapons.missiles.MICA_T = "weapons.missiles.MICA_T" ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket" -- Gazelle -ENUMS.Storage.weapons.containers.Gazelle.HMP400_100RDS = {4,15,46,1771} -ENUMS.Storage.weapons.containers.Gazelle.HMP400_200RDS = {4,15,46,1770} -ENUMS.Storage.weapons.containers.Gazelle.HMP400_400RDS = {4,15,46,1769} -ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_AP = {4,15,46,1768} -ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_SAPHEI = {4,15,46,1767} -ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_HE = {4,15,46,1766} -ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_HEAP = {4,15,46,1765} -ENUMS.Storage.weapons.containers.Gazelle.GIAT_M261_APHE = {4,15,46,1764} -ENUMS.Storage.weapons.containers.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680} -ENUMS.Storage.weapons.containers.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679} +ENUMS.Storage.weapons.Gazelle.HMP400_100RDS = {4,15,46,1771} +ENUMS.Storage.weapons.Gazelle.HMP400_200RDS = {4,15,46,1770} +ENUMS.Storage.weapons.Gazelle.HMP400_400RDS = {4,15,46,1769} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_AP = {4,15,46,1768} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_SAPHEI = {4,15,46,1767} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_HE = {4,15,46,1766} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_HEAP = {4,15,46,1765} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_APHE = {4,15,46,1764} +ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680} +ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679} -- Chinook -ENUMS.Storage.weapons.containers.CH47.CH47_PORT_M60D = {4,15,46,2476} -ENUMS.Storage.weapons.containers.CH47.CH47_STBD_M60D = {4,15,46,2477} -ENUMS.Storage.weapons.containers.CH47.CH47_AFT_M60D = {4,15,46,2478} -ENUMS.Storage.weapons.containers.CH47.CH47_PORT_M134D = {4,15,46,2482} -ENUMS.Storage.weapons.containers.CH47.CH47_STBD_M134D = {4,15,46,2483} -ENUMS.Storage.weapons.containers.CH47.CH47_AFT_M3M = {4,15,46,2484} -ENUMS.Storage.weapons.containers.CH47.CH47_PORT_M240H = {4,15,46,2479} -ENUMS.Storage.weapons.containers.CH47.CH47_STBD_M240H = {4,15,46,2480} -ENUMS.Storage.weapons.containers.CH47.CH47_AFT_M240H = {4,15,46,2481} +ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2476} +ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2477} +ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2478} +ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2482} +ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2483} +ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2484} +ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2479} +ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2480} +ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2481} + + +--- +-- @type ENUMS.FARPType +-- @field #string FARP +-- @field #string INVISIBLE +-- @field #string HELIPADSINGLE +-- @field #string PADSINGLE +ENUMS.FARPType = { + FARP = "FARP", + INVISIBLE = "INVISIBLE", + HELIPADSINGLE = "HELIPADSINGLE", + PADSINGLE = "PADSINGLE", +} + + +--- +-- @type ENUMS.FARPObjectTypeNamesAndShape +-- @field #string FARP +-- @field #string INVISIBLE +-- @field #string HELIPADSINGLE +-- @field #string PADSINGLE +ENUMS.FARPObjectTypeNamesAndShape ={ + [ENUMS.FARPType.FARP] = { TypeName="FARP", ShapeName="FARPS"}, + [ENUMS.FARPType.INVISIBLE] = { TypeName="Invisible FARP", ShapeName="invisiblefarp"}, + [ENUMS.FARPType.HELIPADSINGLE] = { TypeName="SINGLE_HELIPAD", ShapeName="FARP"}, + [ENUMS.FARPType.PADSINGLE] = { TypeName="FARP_SINGLE_01", ShapeName="FARP_SINGLE_01"}, +} + diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ef92696e6..dc8064569 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2350,17 +2350,19 @@ end --- Function to generate valid VHF frequencies in kHz for radio beacons (FM). -- @return #table VHFrequencies function UTILS.GenerateVHFrequencies() - + -- known and sorted map-wise NDBs in kHz local _skipFrequencies = { - 214,274,291.5,295,297.5, - 300.5,304,305,307,309.5,311,312,312.5,316, - 320,324,328,329,330,332,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 380,381,384,385,389,395,396, - 414,420,430,432,435,440,450,455,462,470,485, - 507,515,520,525,528,540,550,560,570,577,580, + 214,243,264,273,274,288,291.5,295,297.5, + 300.5,304,305,307,309.5,310,311,312,312.5,316,317, + 320,323,324,325,326,328,329,330,332,335,336,337, + 340,342,343,346,348,351,352,353,358, + 360,363,364,365,368,372.5,373,374, + 380,381,384,385,387,389,391,395,396,399, + 403,404,410,412,414,418,420,423, + 430,432,435,440,445, + 450,455,462,470,485,490, + 507,515,520,525,528,540,550,560,563,570,577,580,595, 602,625,641,662,670,680,682,690, 705,720,722,730,735,740,745,750,770,795, 822,830,862,866, @@ -4090,3 +4092,110 @@ function UTILS.LCGRandom() UTILS.lcg.seed = (UTILS.lcg.a * UTILS.lcg.seed + UTILS.lcg.c) % UTILS.lcg.m return UTILS.lcg.seed / UTILS.lcg.m end + +--- Spawns a new FARP of a defined type and coalition and functional statics (fuel depot, ammo storage, tent, windsock) around that FARP to make it operational. +-- Adds vehicles from template if given. Fills the FARP warehouse with liquids and known materiels. +-- References: [DCS Forum Topic](https://forum.dcs.world/topic/282989-farp-equipment-to-run-it) +-- @param #string Name Name of this FARP installation. Must be unique. +-- @param Core.Point#COORDINATE Coordinate Where to spawn the FARP. +-- @param #string FARPType Type of FARP, can be one of the known types ENUMS.FARPType.FARP, ENUMS.FARPType.INVISIBLE, ENUMS.FARPType.HELIPADSINGLE, ENUMS.FARPType.PADSINGLE. Defaults to ENUMS.FARPType.FARP. +-- @param #number Coalition Coalition of this FARP, i.e. coalition.side.BLUE or coalition.side.RED, defaults to coalition.side.BLUE. +-- @param #number Country Country of this FARP, defaults to country.id.USA (blue) or country.id.RUSSIA (red). +-- @param #number CallSign Callsign of the FARP ATC, defaults to CALLSIGN.FARP.Berlin. +-- @param #number Frequency Frequency of the FARP ATC Radio, defaults to 127.5 (MHz). +-- @param #number Modulation Modulation of the FARP ATC Radio, defaults to radio.modulation.AM. +-- @param #number ADF ADF Beacon (FM) Frequency in KHz, e.g. 428. If not nil, creates an VHF/FM ADF Beacon for this FARP. Requires a sound called "beacon.ogg" to be in the mission (trigger "sound to" ...) +-- @param #number SpawnRadius Radius of the FARP, i.e. where the FARP objects will be placed in meters, not more than 150m away. Defaults to 100. +-- @param #string VehicleTemplate, template name for additional vehicles. Can be nil for no additional vehicles. +-- @param #number Liquids Tons of fuel to be added initially to the FARP. Defaults to 10 (tons). Set to 0 for no fill. +-- @param #number Equipment Number of equipment items per known item to be added initially to the FARP. Defaults to 10 (items). Set to 0 for no fill. +-- @return #list Table of spawned objects and vehicle object (if given). +-- @return #string ADFBeaconName Name of the ADF beacon, to be able to remove/stop it later. +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment) + + -- Set Defaults + local farplocation = Coordinate + local farptype = FARPType or ENUMS.FARPType.FARP + local Coalition = Coalition or coalition.side.BLUE + local callsign = CallSign or CALLSIGN.FARP.Berlin + local freq = Frequency or 127.5 + local mod = Modulation or radio.modulation.AM + local radius = SpawnRadius or 100 + if radius < 0 or radius > 150 then radius = 100 end + local liquids = Liquids or 10 + liquids = liquids * 1000 -- tons to kg + local equip = Equipment or 10 + local statictypes = ENUMS.FARPObjectTypeNamesAndShape[farptype] or {TypeName="FARP", ShapeName="FARPS"} + local STypeName = statictypes.TypeName + local SShapeName = statictypes.ShapeName + local Country = Country or (Coalition == coalition.side.BLUE and country.id.USA or country.id.RUSSIA) + local ReturnObjects = {} + + -- Spawn FARP + local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" + newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" + newfarp:InitFARP(callsign,freq,freq) + local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) + table.insert(ReturnObjects,spawnedfarp) + -- Spawn Objects + local FARPStaticObjectsNato = { + ["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"}, + ["AMMO"] = { TypeName = "FARP Ammo Dump Coating", ShapeName = "SetkaKP", Category = "Fortifications"}, + ["TENT"] = { TypeName = "FARP Tent", ShapeName = "PalatkaB", Category = "Fortifications"}, + ["WINDSOCK"] = { TypeName = "Windsock", ShapeName = "H-Windsock_RW", Category = "Fortifications"}, + } + + local farpobcount = 0 + for _name,_object in pairs(FARPStaticObjectsNato) do + local objloc = farplocation:Translate(100,farpobcount*30) + local heading = objloc:HeadingTo(farplocation) + local newobject = SPAWNSTATIC:NewFromType(_object.TypeName,_object.Category,Country) + newobject:InitShape(_object.ShapeName) + newobject:InitHeading(heading) + newobject:SpawnFromCoordinate(objloc,farpobcount*30,_name.." - "..Name) + table.insert(ReturnObjects,newobject) + farpobcount = farpobcount + 1 + end + + -- Vehicle if any + if VehicleTemplate and type(VehicleTemplate) == "string" then + local vcoordinate = farplocation:Translate(100,farpobcount*30) + local heading = vcoordinate:HeadingTo(farplocation) + local vehicles = SPAWN:NewWithAlias(VehicleTemplate,"FARP Vehicles - "..Name) + vehicles:InitGroupHeading(heading) + vehicles:InitCountry(Country) + vehicles:InitCoalition(Coalition) + vehicles:InitDelayOff() + local spawnedvehicle = vehicles:SpawnFromCoordinate(vcoordinate) + table.insert(ReturnObjects,spawnedvehicle) + end + + local newWH = STORAGE:New(Name) + if liquids and liquids > 0 then + -- Storage fill-up + newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons + newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) + newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) + newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) + end + + if equip and equip > 0 then + for cat,nitem in pairs(ENUMS.Storage.weapons) do + for name,item in pairs(nitem) do + newWH:SetItem(item,equip) + end + end + end + + local ADFName + if ADF and type(ADF) == "number" then + local ADFFreq = ADF*1000 -- KHz to Hz + local Sound = "l10n/DEFAULT/beacon.ogg" + local vec3 = farplocation:GetVec3() + ADFName = Name .. " ADF "..tostring(ADF).."KHz" + --BASE:I(string.format("Adding FARP Beacon %d KHz Name %s",ADF,ADFName)) + trigger.action.radioTransmission(Sound, vec3, 0, true, ADFFreq, 250, ADFName) + end + + return ReturnObjects, ADFName +end From 733251bf0666103d409629be42b6b44fedb0cef9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 22 Aug 2024 18:27:15 +0200 Subject: [PATCH 036/349] xx --- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Ops/CTLD.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 918399108..2fc1e19a3 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -2027,7 +2027,7 @@ do local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do --self:I( { "Prefix:", MGroup:GetName(), GroupPrefix } ) - if string.find( string.lower(MGroup:GetName()), string.lower(GroupPrefix), 1 ) or string.find( string.lower(MGroup:GetName()), string.lower(GroupPrefix), 1, true ) then + if string.find(MGroup:GetName(), string.gsub(GroupPrefix,"-","%%-"),1) then MGroupPrefix = true end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index adfb6436a..27b4e0236 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1311,7 +1311,7 @@ CTLD.UnitTypeCapabilities = { ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH-58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, - ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 8000}, + ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 10800}, } --- CTLD class version. From cb4a12c5ba53b649fcf656954992917193795dfd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 23 Aug 2024 12:41:44 +0200 Subject: [PATCH 037/349] xx --- Moose Development/Moose/Core/SpawnStatic.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 965f86f00..5021ac52a 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -472,7 +472,11 @@ end function SPAWNSTATIC:_SpawnStatic(Template, CountryID) Template=Template or {} - + + if not Template.alt then + Template.alt = land.getHeight( {x = Template.x, y = Template.y} ) + end + local CountryID=CountryID or self.CountryID if self.InitStaticType then @@ -486,7 +490,7 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) if self.InitStaticCoordinate then Template.x = self.InitStaticCoordinate.x Template.y = self.InitStaticCoordinate.z - Template.alt = self.InitStaticCoordinate.y + Template.alt = self.InitStaticCoordinate.y or land.getHeight( {x = Template.x, y = Template.z} ) end if self.InitStaticHeading then From 694b7afc71b5641f7c50e2416cbe6a4fc146de6c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 23 Aug 2024 12:42:16 +0200 Subject: [PATCH 038/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 247 ++++++++++++++------------- 1 file changed, 129 insertions(+), 118 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 27b4e0236..720f3d854 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -26,6 +26,11 @@ -- Last Update Aug 2024 + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- TODO CTLD_CARGO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + do ------------------------------------------------------ @@ -47,6 +52,9 @@ do -- @field #boolean DontShowInMenu Show this item in menu or not. -- @field Core.Zone#ZONE Location Location (if set) where to get this cargo item. -- @field #table ResourceMap Resource Map information table if it has been set for static cargo items. +-- @field #string StaticShape Individual shape if set. +-- @field #string StaticType Individual type if set. +-- @field #list<#string> TypeNames Table of unit types able to pick this cargo up. -- @extends Core.Base#BASE --- @@ -126,6 +134,9 @@ CTLD_CARGO = { self.Subcategory = Subcategory or "Other" self.DontShowInMenu = DontShowInMenu or false self.ResourceMap = nil + self.StaticType = nil -- "container_cargo" + self.StaticShape = nil + self.TypeNames = nil if type(Location) == "string" then Location = ZONE:New(Location) end @@ -133,6 +144,30 @@ CTLD_CARGO = { return self end + --- Add specific static type and shape to this CARGO. + -- @param #CTLD_CARGO self + -- @param #string TypeName + -- @param #string ShapeName + -- @return #CTLD_CARGO self + function CTLD_CARGO:SetStaticTypeAndShape(TypeName,ShapeName) + self.StaticType = TypeName or "container_cargo" + self.StaticShape = ShapeName + return self + end + + --- Add specific unit types to this CARGO (restrict what types can pick this up). + -- @param #CTLD_CARGO self + -- @param #string UnitTypes Unit type name, can also be a #list<#string> table of unit type names. + -- @return #CTLD_CARGO self + function CTLD_CARGO:AddUnitTypeName(UnitTypes) + if not self.TypeNames then self.TypeNames = {} end + if type(UnitTypes) ~= "table" then UnitTypes = {UnitTypes} end + for _,_singletype in pairs(UnitTypes or {}) do + self.TypeNames[_singletype]=_singletype + end + return self + end + --- Add Resource Map information table -- @param #CTLD_CARGO self -- @param #table ResourceMap @@ -1087,101 +1122,55 @@ do -- -- my_ctld:AddCratesCargo("FARP",{"FOB"},CTLD_CARGO.Enum.FOB,2) -- --- Also, you need to have **all statics with the fitting names** as per the script in your mission already, as we're going to copy them, and a template --- for FARP vehicles, so -- services are goin to work (e.g. for the blue side: an unarmed humvee, two trucks and a fuel truck. Optionally add a fire fighter). --- --- The following code will build a FARP at the coordinate the FOB was dropped and built: +-- The following code will build a FARP at the coordinate the FOB was dropped and built (the UTILS function used below **does not** need a template for the statics): -- --- -- FARP Radio. First one has 130AM, next 131 and for forth --- local FARPFreq = 130 --- local FARPName = 1 -- numbers 1..10 --- --- local FARPClearnames = { --- [1]="London", --- [2]="Dallas", --- [3]="Paris", --- [4]="Moscow", --- [5]="Berlin", --- [6]="Rome", --- [7]="Madrid", --- [8]="Warsaw", --- [9]="Dublin", --- [10]="Perth", --- } --- --- function BuildAFARP(Coordinate) --- local coord = Coordinate -- Core.Point#COORDINATE --- --- local FarpName = ((FARPName-1)%10)+1 --- local FName = FARPClearnames[FarpName] --- --- FARPFreq = FARPFreq + 1 --- FARPName = FARPName + 1 +-- -- FARP Radio. First one has 130AM name London, next 131 name Dallas, and so forth. +-- local FARPFreq = 129 +-- local FARPName = 1 --numbers 1..10 +-- +-- local FARPClearnames = { +-- [1]="London", +-- [2]="Dallas", +-- [3]="Paris", +-- [4]="Moscow", +-- [5]="Berlin", +-- [6]="Rome", +-- [7]="Madrid", +-- [8]="Warsaw", +-- [9]="Dublin", +-- [10]="Perth", +-- } +-- +-- function BuildAFARP(Coordinate) +-- local coord = Coordinate --Core.Point#COORDINATE -- --- -- Create a SPAWNSTATIC object from a template static FARP object. --- local SpawnStaticFarp=SPAWNSTATIC:NewFromStatic("Static Invisible FARP-1", country.id.USA) --- --- -- Spawning FARPs is special in DCS. Therefore, we need to specify that this is a FARP. We also set the callsign and the frequency. --- SpawnStaticFarp:InitFARP(FARPName, FARPFreq, 0) --- SpawnStaticFarp:InitDead(false) --- --- -- Spawn FARP --- local ZoneSpawn = ZONE_RADIUS:New("FARP "..FName,Coordinate:GetVec2(),160,false) --- local Heading = 0 --- local FarpBerlin=SpawnStaticFarp:SpawnFromZone(ZoneSpawn, Heading, "FARP "..FName) --- --- -- ATC and services - put them 125m from the center of the zone towards North --- local FarpVehicles = SPAWN:NewWithAlias("FARP Vehicles Template","FARP "..FName.." Technicals") --- FarpVehicles:InitHeading(180) --- local FarpVCoord = coord:Translate(125,0) --- FarpVehicles:SpawnFromCoordinate(FarpVCoord) --- --- -- We will put the rest of the statics in a nice circle around the center --- local base = 330 --- local delta = 30 --- --- local windsock = SPAWNSTATIC:NewFromStatic("Static Windsock-1",country.id.USA) --- local sockcoord = coord:Translate(125,base) --- windsock:SpawnFromCoordinate(sockcoord,Heading,"Windsock "..FName) --- base=base-delta --- --- local fueldepot = SPAWNSTATIC:NewFromStatic("Static FARP Fuel Depot-1",country.id.USA) --- local fuelcoord = coord:Translate(125,base) --- fueldepot:SpawnFromCoordinate(fuelcoord,Heading,"Fueldepot "..FName) --- base=base-delta --- --- local ammodepot = SPAWNSTATIC:NewFromStatic("Static FARP Ammo Storage-2-1",country.id.USA) --- local ammocoord = coord:Translate(125,base) --- ammodepot:SpawnFromCoordinate(ammocoord,Heading,"Ammodepot "..FName) --- base=base-delta --- --- local CommandPost = SPAWNSTATIC:NewFromStatic("Static FARP Command Post-1",country.id.USA) --- local CommandCoord = coord:Translate(125,base) --- CommandPost:SpawnFromCoordinate(CommandCoord,Heading,"Command Post "..FName) --- base=base-delta --- --- local Tent1 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA) --- local Tent1Coord = coord:Translate(125,base) --- Tent1:SpawnFromCoordinate(Tent1Coord,Heading,"Command Tent "..FName) --- base=base-delta --- --- local Tent2 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA) --- local Tent2Coord = coord:Translate(125,base) --- Tent2:SpawnFromCoordinate(Tent2Coord,Heading,"Command Tent2 "..FName) --- --- -- add a loadzone to CTLD --- my_ctld:AddCTLDZone("FARP "..FName,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) --- local m = MESSAGE:New(string.format("FARP %s in operation!",FName),15,"CTLD"):ToBlue() --- end +-- local FarpNameNumber = ((FARPName-1)%10)+1 -- make sure 11 becomes 1 etc +-- local FName = FARPClearnames[FarpNameNumber] -- get clear namee -- --- function my_ctld:OnAfterCratesBuild(From,Event,To,Group,Unit,Vehicle) --- local name = Vehicle:GetName() --- if string.match(name,"FOB",1,true) then --- local Coord = Vehicle:GetCoordinate() --- Vehicle:Destroy(false) --- BuildAFARP(Coord) --- end --- end +-- FARPFreq = FARPFreq + 1 +-- FARPName = FARPName + 1 +-- +-- FName = FName .. " FAT COW "..tostring(FARPFreq).."AM" -- make name unique +-- +-- -- Get a Zone for loading +-- local ZoneSpawn = ZONE_RADIUS:New("FARP "..FName,Coordinate:GetVec2(),150,false) +-- +-- -- Spawn a FARP with our little helper and fill it up with resources (10t fuel each type, 10 pieces of each known equipment) +-- UTILS.SpawnFARPAndFunctionalStatics(FName,Coordinate,ENUMS.FARPType.INVISIBLE,my_ctld.coalition,country.id.USA,FarpNameNumber,FARPFreq,radio.modulation.AM,nil,nil,nil,10,10) +-- +-- -- add a loadzone to CTLD +-- my_ctld:AddCTLDZone("FARP "..FName,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) +-- local m = MESSAGE:New(string.format("FARP %s in operation!",FName),15,"CTLD"):ToBlue() +-- end +-- +-- function my_ctld:OnAfterCratesBuild(From,Event,To,Group,Unit,Vehicle) +-- local name = Vehicle:GetName() +-- if string.find(name,"FOB",1,true) then +-- local Coord = Vehicle:GetCoordinate() +-- Vehicle:Destroy(false) +-- BuildAFARP(Coord) +-- end +-- end -- -- -- @field #CTLD @@ -2724,31 +2713,33 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord) local cratename = cargotype:GetName() local cgotype = cargotype:GetType() local cgomass = cargotype:GetMass() - local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000)) - local isstatic = false - if cgotype == CTLD_CARGO.Enum.STATIC then - cratetemplate = cargotype:GetTemplates() - isstatic = true + local cratenumber = cargotype:GetCratesNeeded() or 1 + for i=1,cratenumber do + local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000)) + local isstatic = false + if cgotype == CTLD_CARGO.Enum.STATIC then + cratetemplate = cargotype:GetTemplates() + isstatic = true + end + local basetype = self.basetype or "container_cargo" + if isstatic then + basetype = cratetemplate + end + self.CrateCounter = self.CrateCounter + 1 + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) + :InitCoordinate(cratecoord) + if isstatic then + local map = cargotype:GetStaticResourceMap() + spawnstatic.TemplateStaticUnit.resourcePayload = map + end + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] + table.insert(self.Spawned_Cargo, cargotype) end - local basetype = self.basetype or "container_cargo" - if isstatic then - basetype = cratetemplate - end - self.CrateCounter = self.CrateCounter + 1 - local spawnstatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) - :InitCargoMass(cgomass) - :InitCargo(self.enableslingload) - :InitCoordinate(cratecoord) - if isstatic then - local map = cargotype:GetStaticResourceMap() - spawnstatic.TemplateStaticUnit.resourcePayload = map - end - self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) - local templ = cargotype:GetTemplates() - local sorte = cargotype:GetType() - --self.CargoCounter = self.CargoCounter + 1 - cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] - table.insert(self.Spawned_Cargo, cargotype) return self end @@ -4221,7 +4212,11 @@ end -- @param #string SubCategory Name of sub-category (optional). -- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. -- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) +-- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH-58D"} +-- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape. +-- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape. +-- @return #CTLD self +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,TypeName,ShapeName) self:T(self.lid .. " AddCratesCargo") if not self:_CheckTemplates(Templates) then self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" ) @@ -4230,6 +4225,12 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) + if UnitTypes then + cargo:AddUnitTypeName(UnitTypes) + end + if TypeName then + cargo:SetStaticTypeAndShape(TypeName,ShapeName) + end table.insert(self.Cargo_Crates,cargo) return self end @@ -4293,7 +4294,11 @@ end -- @param #string SubCategory Name of the sub-category (optional). -- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. -- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. -function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) +-- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH-58D"} +-- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape. +-- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape. +-- @return #CTLD self +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,TypeName,ShapeName) self:T(self.lid .. " AddCratesRepair") if not self:_CheckTemplates(Template) then self:E(self.lid .. "Repair Cargo for " .. Name .. " has a missing template!" ) @@ -4302,6 +4307,12 @@ function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,Su self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) + if UnitTypes then + cargo:AddUnitTypeName(UnitTypes) + end + if TypeName then + cargo:SetStaticTypeAndShape(TypeName,ShapeName) + end table.insert(self.Cargo_Crates,cargo) return self end From 540412d11554d51a851f805f3fff999813610ce5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 23 Aug 2024 12:47:08 +0200 Subject: [PATCH 039/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 720f3d854..b27d504ab 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1305,7 +1305,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.13" +CTLD.version="1.1.14" --- Instantiate a new CTLD. -- @param #CTLD self From deb913a95b3db734465c720f87a160839b644dbb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 24 Aug 2024 18:10:46 +0200 Subject: [PATCH 040/349] xx --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index dc8064569..9c0e6b723 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4134,7 +4134,7 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, -- Spawn FARP local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" - newfarp:InitFARP(callsign,freq,freq) + newfarp:InitFARP(callsign,freq,mod) local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) -- Spawn Objects From 0372735a021436975fb3b4f4bd1a948b9ff2d5df Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 26 Aug 2024 15:51:22 +0200 Subject: [PATCH 041/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 203 ++++++++++++-------- Moose Development/Moose/Utilities/Utils.lua | 4 +- 2 files changed, 130 insertions(+), 77 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index b27d504ab..d1332db63 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -54,6 +54,7 @@ do -- @field #table ResourceMap Resource Map information table if it has been set for static cargo items. -- @field #string StaticShape Individual shape if set. -- @field #string StaticType Individual type if set. +-- @field #string StaticCategory Individual static category if set. -- @field #list<#string> TypeNames Table of unit types able to pick this cargo up. -- @extends Core.Base#BASE @@ -134,9 +135,10 @@ CTLD_CARGO = { self.Subcategory = Subcategory or "Other" self.DontShowInMenu = DontShowInMenu or false self.ResourceMap = nil - self.StaticType = nil -- "container_cargo" + self.StaticType = "container_cargo" -- "container_cargo" self.StaticShape = nil self.TypeNames = nil + self.StaticCategory = "Cargos" if type(Location) == "string" then Location = ZONE:New(Location) end @@ -149,12 +151,22 @@ CTLD_CARGO = { -- @param #string TypeName -- @param #string ShapeName -- @return #CTLD_CARGO self - function CTLD_CARGO:SetStaticTypeAndShape(TypeName,ShapeName) + function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) + self.StaticCategory = Category or "Cargos" self.StaticType = TypeName or "container_cargo" self.StaticShape = ShapeName return self end + --- Get the specific static type and shape from this CARGO if set. + -- @param #CTLD_CARGO self + -- @return #string Category + -- @return #string TypeName + -- @return #string ShapeName + function CTLD_CARGO:GetStaticTypeAndShape() + return self.StaticCategory, self.StaticType, self.StaticShape + end + --- Add specific unit types to this CARGO (restrict what types can pick this up). -- @param #CTLD_CARGO self -- @param #string UnitTypes Unit type name, can also be a #list<#string> table of unit type names. @@ -168,6 +180,27 @@ CTLD_CARGO = { return self end + --- Check if a specific unit can carry this CARGO (restrict what types can pick this up). + -- @param #CTLD_CARGO self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD_CARGO:UnitCanCarry(Unit) + local outcome = false + UTILS.PrintTableToLog(self.TypeNames) + if (not self.TypeNames) or (not Unit) or (not Unit:IsAlive()) then + return false + end + local unittype = Unit:GetTypeName() or "none" + --self:I("Checking for type name: "..unittype) + for _,_typeName in pairs(self.TypeNames or {}) do + if _typeName == unittype then + outcome = true + break + end + end + return outcome + end + --- Add Resource Map information table -- @param #CTLD_CARGO self -- @param #table ResourceMap @@ -1305,7 +1338,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.14" +CTLD.version="1.1.15" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2594,40 +2627,63 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) -- spawn behind the Herc addon = 180 end + heading = (heading+addon)%360 + local row = 1 + local column = 1 + local initialdist = IsHerc and 16 or (capabilities.length+2) -- initial spacing of the first crates + local startpos = position:Translate(initialdist,heading) + if self.placeCratesAhead == true then + cratedistance = initialdist + end -- loop crates needed + local cratecoord = nil -- Core.Point#COORDINATE for i=1,number do local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000)) - if not self.placeCratesAhead then + if not self.placeCratesAhead or drop == true then cratedistance = (i-1)*2.5 + capabilities.length if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end -- altered heading logic -- DONE: right standard deviation? rheading = UTILS.RandomGaussian(0,30,-90,90,100) - rheading = math.fmod((heading + rheading + addon), 360) + rheading = math.fmod((heading + rheading), 360) + cratecoord = position:Translate(cratedistance,rheading) else + cratedistance = (row-1)*6 + rheading = 90 + row = row+1 + cratecoord = startpos:Translate(cratedistance,rheading) + if row > 4 then + row = 1 + startpos:Translate(6,heading,nil,true) + end + --[[ local initialSpacing = IsHerc and 16 or (capabilities.length+2) -- initial spacing of the first crates local crateSpacing = 4 -- further spacing of remaining crates local lateralSpacing = 4 -- lateral spacing of crates - local nrSideBySideCrates = 3 -- number of crates that are placed side-by-side + local nrSideBySideCrates = 4 -- number of crates that are placed side-by-side if cratesneeded == 1 then -- single crate needed spawns straight ahead cratedistance = initialSpacing - rheading = heading + rheading = math.fmod((heading + addon), 360) else - if (i - 1) % nrSideBySideCrates == 0 then - cratedistance = i == 1 and initialSpacing or cratedistance + crateSpacing + --if (i - 1) % nrSideBySideCrates == 0 then + cratedistance = i == 1 and initialSpacing or (cratedistance + crateSpacing) angleOffNose = math.ceil(math.deg(math.atan(lateralSpacing / cratedistance))) - rheading = heading - angleOffNose - else - rheading = rheading + angleOffNose - end + self:I("angleOffNose = "..angleOffNose) + rheading = heading + addon - angleOffNose + --else + -- rheading = heading + addon + angleOffNose + --end end + --]] end - local cratecoord = position:Translate(cratedistance,rheading) - local cratevec2 = cratecoord:GetVec2() + + --local cratevec2 = cratecoord:GetVec2() self.CrateCounter = self.CrateCounter + 1 - local basetype = self.basetype or "container_cargo" + local CCat, CType, CShape = Cargo:GetStaticTypeAndShape() + local basetype = CType or self.basetype or "container_cargo" + CCat = CCat or "Cargos" if isstatic then basetype = cratetemplate end @@ -2640,20 +2696,26 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) dist = dist - (20 + math.random(1,10)) local width = width / 2 local Offy = math.random(-width,width) - local spawnstatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) :InitLinkToUnit(Ship,dist,Offy,0) + if CShape then + spawnstatic:InitShape(CShape) + end if isstatic then local map=cargotype:GetStaticResourceMap() spawnstatic.TemplateStaticUnit.resourcePayload = map end self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) else - local spawnstatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCoordinate(cratecoord) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) + if CShape then + spawnstatic:InitShape(CShape) + end if isstatic then local map=cargotype:GetStaticResourceMap() spawnstatic.TemplateStaticUnit.resourcePayload = map @@ -2667,15 +2729,25 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) local realcargo = nil if drop then --CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory) - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) -- #CTLD_CARGO local map=cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) + local CCat, CType, CShape = cargotype:GetStaticTypeAndShape() + realcargo:SetStaticTypeAndShape(CCat,CType,CShape) + if cargotype.TypeNames then + realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames) + end table.insert(droppedcargo,realcargo) else realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) local map=cargotype:GetStaticResourceMap() - realcargo:SetStaticResourceMap(map) + realcargo:SetStaticResourceMap(map) + if cargotype.TypeNames then + realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames) + end end + local CCat, CType, CShape = cargotype:GetStaticTypeAndShape() + realcargo:SetStaticTypeAndShape(CCat,CType,CShape) table.insert(self.Spawned_Cargo, realcargo) end if not (drop or pack) then @@ -2721,15 +2793,20 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord) cratetemplate = cargotype:GetTemplates() isstatic = true end - local basetype = self.basetype or "container_cargo" + local CCat,CType,CShape = cargotype:GetStaticTypeAndShape() + local basetype = CType or self.basetype or "container_cargo" + CCat = CCat or "Cargos" if isstatic then basetype = cratetemplate end self.CrateCounter = self.CrateCounter + 1 - local spawnstatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) :InitCoordinate(cratecoord) + if CShape then + spawnstatic:InitShape(CShape) + end if isstatic then local map = cargotype:GetStaticResourceMap() spawnstatic.TemplateStaticUnit.resourcePayload = map @@ -2891,59 +2968,30 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) if not _ignoreweight then maxloadable = self:_GetMaxLoadableMass(_unit) end - self:T(self.lid .. " Max loadable mass: " .. maxloadable) + self:T2(self.lid .. " Max loadable mass: " .. maxloadable) for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates local weight = cargo:GetMass() -- weight in kgs of this cargo local staticid = cargo:GetID() - self:T(self.lid .. " Found cargo mass: " .. weight) - --local cargoalive = false -- TODO dyn cargo spawn workaround - --local dcsunit = nil - --local dcsunitpos = nil - --[[ - if static and static.DCSCargoObject then - dcsunit = Unit.getByName(static.StaticName) - if dcsunit then - cargoalive = dcsunit:isExist() ~= nil and true or false - end - if cargoalive == true then - local dcsvec3 = dcsunit:getPoint() or dcsunit:getPosition().p or {x=0,y=0,z=0} - self:T({dcsvec3 = dcsunit:getPoint(), dcspos = dcsunit:getPosition().p}) - if dcsvec3 then - dcsunitpos = COORDINATE:New(dcsvec3.x,dcsvec3.z,dcsvec3.y) - end - end - end - --]] + self:T2(self.lid .. " Found cargo mass: " .. weight) if static and static:IsAlive() then --or cargoalive) then local restricthooktononstatics = self.enableChinookGCLoading and IsHook - self:T(self.lid .. " restricthooktononstatics: " .. tostring(restricthooktononstatics)) + --self:I(self.lid .. " restricthooktononstatics: " .. tostring(restricthooktononstatics)) local cargoisstatic = cargo:GetType() == CTLD_CARGO.Enum.STATIC and true or false - self:T(self.lid .. " Cargo is static: " .. tostring(cargoisstatic)) + --self:I(self.lid .. " Cargo is static: " .. tostring(cargoisstatic)) local restricted = cargoisstatic and restricthooktononstatics - self:T(self.lid .. " Loading restricted: " .. tostring(restricted)) + --self:I(self.lid .. " Loading restricted: " .. tostring(restricted)) local staticpos = static:GetCoordinate() --or dcsunitpos - --[[ - --- Testing - local landheight = staticpos:GetLandHeight() - local agl = staticpos.y-landheight - agl = UTILS.Round(agl,2) - local GCloaded = agl > 0 and true or false - if IsNoHook == true then GCloaded = false end - --]] + local cando = cargo:UnitCanCarry(_unit) + --self:I(self.lid .. " Unit can carry: " .. tostring(cando)) --- Testing local distance = self:_GetDistance(location,staticpos) - --self:T({name=static:GetName(),IsHook=IsHook,agl=agl,GCloaded=GCloaded,distance=string.format("%.2f",distance or 0)}) - --if (not restricthooktononstatics) and distance <= finddist and static and (weight <= maxloadable or _ignoreweight) then - self:T(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) - if distance <= finddist and (weight <= maxloadable or _ignoreweight) and restricted == false then + --self:I(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) + if distance <= finddist and (weight <= maxloadable or _ignoreweight) and restricted == false and cando == true then index = index + 1 table.insert(found, staticid, cargo) maxloadable = maxloadable - weight - --elseif restricthooktononstatics and distance < 10 and static then - --indexg = indexg + 1 - --table.insert(LoadedbyGC,staticid, cargo) end end @@ -4212,11 +4260,12 @@ end -- @param #string SubCategory Name of sub-category (optional). -- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. -- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. --- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH-58D"} --- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape. --- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape. +-- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH-58D"}. +-- @param #string Category Static category name (optional). If set, spawn cargo crate with an alternate category type, e.g. "Cargos". +-- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape, e.g. "iso_container". +-- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape, e.g. "iso_container_cargo". -- @return #CTLD self -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,TypeName,ShapeName) +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) self:T(self.lid .. " AddCratesCargo") if not self:_CheckTemplates(Templates) then self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" ) @@ -4229,7 +4278,7 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub cargo:AddUnitTypeName(UnitTypes) end if TypeName then - cargo:SetStaticTypeAndShape(TypeName,ShapeName) + cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end table.insert(self.Cargo_Crates,cargo) return self @@ -4295,10 +4344,11 @@ end -- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. -- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. -- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH-58D"} --- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape. --- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape. +-- @param #string Category Static category name (optional). If set, spawn cargo crate with an alternate category type, e.g. "Cargos". +-- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape, e.g. "iso_container". +-- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape, e.g. "iso_container_cargo". -- @return #CTLD self -function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,TypeName,ShapeName) +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) self:T(self.lid .. " AddCratesRepair") if not self:_CheckTemplates(Template) then self:E(self.lid .. "Repair Cargo for " .. Name .. " has a missing template!" ) @@ -4311,7 +4361,7 @@ function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,Su cargo:AddUnitTypeName(UnitTypes) end if TypeName then - cargo:SetStaticTypeAndShape(TypeName,ShapeName) + cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end table.insert(self.Cargo_Crates,cargo) return self @@ -6618,17 +6668,20 @@ end -- @param Core.Point#POINT_VEC3 Cargo_Drop_Position -- @return #CTLD_HERCULES self function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name, _pos) - local theCargo = self.CTLD:_FindCratesCargoObject(_name) + local theCargo = self.CTLD:_FindCratesCargoObject(_name) -- #CTLD_CARGO if theCargo then self.CTLD.CrateCounter = self.CTLD.CrateCounter + 1 - self.CTLD.CargoCounter = self.CTLD.CargoCounter + 1 - - local basetype = self.CTLD.basetype or "container_cargo" - local theStatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local CCat, CType, CShape = theCargo:GetStaticTypeAndShape() + local basetype = CType or self.CTLD.basetype or "container_cargo" + CCat = CCat or "Cargos" + local theStatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCargoMass(theCargo.PerCrateMass) :InitCargo(self.CTLD.enableslingload) :InitCoordinate(_pos) - :Spawn(270,_name .. "-Container-".. math.random(1,100000)) + if CShape then + theStatic:InitShape(CShape) + end + theStatic:Spawn(270,_name .. "-Container-".. math.random(1,100000)) self.CTLD.Spawned_Crates[self.CTLD.CrateCounter] = theStatic local newCargo = CTLD_CARGO:New(self.CTLD.CargoCounter, theCargo.Name, theCargo.Templates, theCargo.CargoType, true, false, theCargo.CratesNeeded, self.CTLD.Spawned_Crates[self.CTLD.CrateCounter], true, theCargo.PerCrateMass, nil, theCargo.Subcategory) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 9c0e6b723..5e4033223 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4147,7 +4147,7 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, local farpobcount = 0 for _name,_object in pairs(FARPStaticObjectsNato) do - local objloc = farplocation:Translate(100,farpobcount*30) + local objloc = farplocation:Translate(radius,farpobcount*30) local heading = objloc:HeadingTo(farplocation) local newobject = SPAWNSTATIC:NewFromType(_object.TypeName,_object.Category,Country) newobject:InitShape(_object.ShapeName) @@ -4159,7 +4159,7 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, -- Vehicle if any if VehicleTemplate and type(VehicleTemplate) == "string" then - local vcoordinate = farplocation:Translate(100,farpobcount*30) + local vcoordinate = farplocation:Translate(radius,farpobcount*30) local heading = vcoordinate:HeadingTo(farplocation) local vehicles = SPAWN:NewWithAlias(VehicleTemplate,"FARP Vehicles - "..Name) vehicles:InitGroupHeading(heading) From 13bde6f2abc6560353d307132ad275df890ffdd4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 26 Aug 2024 18:21:03 +0200 Subject: [PATCH 042/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index d1332db63..5cfbe085a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2945,11 +2945,12 @@ end -- @param Wrapper.Unit#UNIT _unit Unit -- @param #number _dist Distance -- @param #boolean _ignoreweight Find everything in range, ignore loadable weight +-- @param #boolean ignoretype Find everything in range, ignore loadable type name -- @return #table Crates Table of crates -- @return #number Number Number of crates found -- @return #table CratesGC Table of crates possibly loaded by GC -- @return #number NumberGC Number of crates possibly loaded by GC -function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) +function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype) self:T(self.lid .. " _FindCratesNearby") local finddist = _dist local location = _group:GetCoordinate() @@ -2962,7 +2963,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) local loadedmass = 0 local unittype = "none" local capabilities = {} - local maxmass = 2000 + --local maxmass = 2000 local maxloadable = 2000 local IsHook = self:IsHook(_unit) if not _ignoreweight then @@ -2984,6 +2985,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) --self:I(self.lid .. " Loading restricted: " .. tostring(restricted)) local staticpos = static:GetCoordinate() --or dcsunitpos local cando = cargo:UnitCanCarry(_unit) + if ignoretype == true then cando = true end --self:I(self.lid .. " Unit can carry: " .. tostring(cando)) --- Testing local distance = self:_GetDistance(location,staticpos) From 3813a30deff7614b12f06e389092f0313d9d4678 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 26 Aug 2024 18:30:52 +0200 Subject: [PATCH 043/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 5cfbe085a..d7282531d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -186,19 +186,19 @@ CTLD_CARGO = { -- @return #boolean Outcome function CTLD_CARGO:UnitCanCarry(Unit) local outcome = false - UTILS.PrintTableToLog(self.TypeNames) - if (not self.TypeNames) or (not Unit) or (not Unit:IsAlive()) then - return false - end - local unittype = Unit:GetTypeName() or "none" - --self:I("Checking for type name: "..unittype) - for _,_typeName in pairs(self.TypeNames or {}) do - if _typeName == unittype then - outcome = true - break + if not self.TypeNames then return true end + if Unit and Unit:IsAlive() then + local unittype = Unit:GetTypeName() or "none" + --self:I("Checking for type name: "..unittype) + for _,_typeName in pairs(self.TypeNames or {}) do + if _typeName == unittype then + outcome = true + break + end end + return outcome end - return outcome + return true end --- Add Resource Map information table From add0a0231af39a742c9f8fb286edd1217ae61170 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 27 Aug 2024 10:56:59 +0200 Subject: [PATCH 044/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 33 +++++++++++----------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index d7282531d..e7947addf 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -185,20 +185,13 @@ CTLD_CARGO = { -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD_CARGO:UnitCanCarry(Unit) - local outcome = false - if not self.TypeNames then return true end - if Unit and Unit:IsAlive() then - local unittype = Unit:GetTypeName() or "none" - --self:I("Checking for type name: "..unittype) - for _,_typeName in pairs(self.TypeNames or {}) do - if _typeName == unittype then - outcome = true - break - end - end - return outcome + if self.TypeNames == nil then return true end + local typename = Unit:GetTypeName() or "none" + if self.TypeNames[typename] then + return true + else + return false end - return true end --- Add Resource Map information table @@ -2594,7 +2587,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 35 - local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true) + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true,true) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) return self @@ -2841,7 +2834,7 @@ end function CTLD:_ListCratesNearby( _group, _unit) self:T(self.lid .. " _ListCratesNearby") local finddist = self.CrateDistance or 35 - local crates,number,loadedbygc,indexgc = self:_FindCratesNearby(_group,_unit, finddist,true) -- #table + local crates,number,loadedbygc,indexgc = self:_FindCratesNearby(_group,_unit, finddist,true,true) -- #table if number > 0 or indexgc > 0 then local text = REPORT:New("Crates Found Nearby:") text:Add("------------------------------------------------------------") @@ -2887,7 +2880,7 @@ end function CTLD:_RemoveCratesNearby( _group, _unit) self:T(self.lid .. " _RemoveCratesNearby") local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(_group,_unit, finddist,true) -- #table + local crates,number = self:_FindCratesNearby(_group,_unit, finddist,true,true) -- #table if number > 0 then local text = REPORT:New("Removing Crates Found Nearby:") text:Add("------------------------------------------------------------") @@ -3056,7 +3049,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) end -- get nearby crates local finddist = self.CrateDistance or 35 - local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist,false) -- #table + local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist,false,false) -- #table self:T(self.lid .. " Crates found: " .. number) if number == 0 and self.hoverautoloading then return self -- exit @@ -3662,7 +3655,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) end -- get nearby crates local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(Group,Unit, finddist,true) -- #table + local crates,number = self:_FindCratesNearby(Group,Unit, finddist,true,true) -- #table local buildables = {} local foundbuilds = false local canbuild = false @@ -3797,7 +3790,7 @@ function CTLD:_RepairCrates(Group, Unit, Engineering) self:T(self.lid .. " _RepairCrates") -- get nearby crates local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(Group,Unit,finddist,true) -- #table + local crates,number = self:_FindCratesNearby(Group,Unit,finddist,true,true) -- #table local buildables = {} local foundbuilds = false local canbuild = false @@ -5415,7 +5408,7 @@ end self:T(_engineers.lid .. _engineers:GetStatus()) if wrenches and wrenches:IsAlive() then if engineers:IsStatus("Running") or engineers:IsStatus("Searching") then - local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch,true) -- #table + local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch,true,true) -- #table engineers:Search(crates,number) elseif engineers:IsStatus("Moving") then engineers:Move() From dc358f9cde41aa47dc844f328156ff3814dfe1dd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 27 Aug 2024 13:18:26 +0200 Subject: [PATCH 045/349] xx --- Moose Development/Moose/AI/AI_A2A_Patrol.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 71b392db1..a9bfc8a41 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -10,6 +10,7 @@ -- @image AI_Air_Patrolling.JPG +--- -- @type AI_A2A_PATROL -- @extends AI.AI_A2A#AI_A2A From 6706fc0af9d1cbd166e99c8ef34d07ff471226e7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 27 Aug 2024 18:37:00 +0200 Subject: [PATCH 046/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index e7947addf..9a8669efa 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1331,7 +1331,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.15" +CTLD.version="1.1.16" --- Instantiate a new CTLD. -- @param #CTLD self @@ -4272,6 +4272,7 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub if UnitTypes then cargo:AddUnitTypeName(UnitTypes) end + cargo:SetStaticTypeAndShape("Cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end @@ -4355,6 +4356,7 @@ function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,Su if UnitTypes then cargo:AddUnitTypeName(UnitTypes) end + cargo:SetStaticTypeAndShape("cargos",self.basetype) if TypeName then cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end From 6fb5222358f39dfde9bd9d3786c075964dfb93a3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 29 Aug 2024 10:21:24 +0200 Subject: [PATCH 047/349] CTLD Fix for saved crate reload duplicating crates on load --- Moose Development/Moose/Ops/CTLD.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9a8669efa..666503509 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1331,7 +1331,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.16" +CTLD.version="1.1.17" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2760,8 +2760,9 @@ end -- @param Core.Zone#ZONE Zone Zone to spawn in. -- @param #CTLD_CARGO Cargo The cargo type to spawn. -- @param #boolean RandomCoord Randomize coordinate. +-- @param #boolean FromLoad Create only **one** crate per cargo type, as we are re-creating dropped crates that CTLD has saved prior. -- @return #CTLD self -function CTLD:InjectStatics(Zone, Cargo, RandomCoord) +function CTLD:InjectStatics(Zone, Cargo, RandomCoord, FromLoad) self:T(self.lid .. " InjectStatics") local cratecoord = Zone:GetCoordinate() if RandomCoord then @@ -2779,6 +2780,7 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord) local cgotype = cargotype:GetType() local cgomass = cargotype:GetMass() local cratenumber = cargotype:GetCratesNeeded() or 1 + if FromLoad == true then cratenumber=1 end for i=1,cratenumber do local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000)) local isstatic = false @@ -2822,7 +2824,7 @@ end function CTLD:InjectStaticFromTemplate(Zone, Template, Mass) self:T(self.lid .. " InjectStaticFromTemplate") local cargotype = self:GetStaticsCargoFromTemplate(Template,Mass) -- #CTLD_CARGO - self:InjectStatics(Zone,cargotype,true) + self:InjectStatics(Zone,cargotype,true,true) return self end @@ -6326,7 +6328,7 @@ end injectstatic:SetStaticResourceMap(map) end if injectstatic then - self:InjectStatics(dropzone,injectstatic) + self:InjectStatics(dropzone,injectstatic,false,true) end end end @@ -6654,7 +6656,7 @@ function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Positio local Zone = ZONE_RADIUS:New("Cargo Static " .. math.random(1,10000),position,100) if not dead then local injectstatic = CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) - self.CTLD:InjectStatics(Zone,injectstatic,true) + self.CTLD:InjectStatics(Zone,injectstatic,true,true) end return self end From f11755aad325cf4da4282c7318921beb1b92f765 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 29 Aug 2024 17:31:40 +0200 Subject: [PATCH 048/349] xxx --- Moose Development/Moose/Core/Spawn.lua | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 345f24983..83c0bedf8 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2024,7 +2024,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2( PointVec3 ) + --self:T2( PointVec3 ) -- Set take off type. Default is hot. Takeoff = Takeoff or SPAWN.Takeoff.Hot @@ -2053,7 +2053,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT --self:F( { GroupAlive = GroupAlive } ) -- Debug output - self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) + --self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) -- Template group, unit and its attributes. local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) @@ -2102,7 +2102,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Check if we spawn on ground. local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) - self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + --self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) -- Check where we actually spawn if we spawn on ground. local spawnonship = false @@ -2156,7 +2156,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) --[[ @@ -2164,23 +2164,23 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Parking data explicitly set by user as input parameter. nfree=#Parkingdata spots=Parkingdata - ]] + --]] else if ishelo then if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Not enough helo ports. Let's try also other terminal types. - self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- No terminal type specified. We try all spots except shelters. - self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2189,23 +2189,23 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if termtype == nil then if isbomber or istransport or istanker or isawacs then -- First we fill the potentially bigger spots. - self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) + --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Now we try the smaller ones. - self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) + --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else - self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) + --self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- Terminal type explicitly given. - self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) + --self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2315,7 +2315,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT SpawnTemplate.parked = true for UnitID = 1, nunits do - self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] @@ -2333,7 +2333,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn on ship. We take only the position of the ship. SpawnTemplate.units[UnitID].x = PointVec3.x -- TX @@ -2342,7 +2342,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT else - self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + --self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x @@ -2354,7 +2354,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT else - self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. SpawnTemplate.units[UnitID].x = TX @@ -2371,9 +2371,9 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT end -- Debug output. - self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) - self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) - self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) + --self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end From 1a7117c20dbd136c97d13b7cbf6fc42397d7377e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 1 Sep 2024 15:36:20 +0200 Subject: [PATCH 049/349] #CONTROLLABLE - Added IR Marker Beacons for UNIT and GROUP objects --- .../Moose/Wrapper/Controllable.lua | 130 +++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index e0b63e914..68d16001a 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -174,7 +174,10 @@ -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- -- ## 5.5) Air-2-Air missile attack range: --- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets . +-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. +-- +-- # 6) [GROUND] IR Maker Beacons for GROUPs and UNITs +-- * @{#CONTROLLABLE:NewIRMarker}(): Create a blinking IR Marker on a GROUP or UNIT. -- -- @field #CONTROLLABLE CONTROLLABLE = { @@ -5619,3 +5622,128 @@ function CONTROLLABLE:PatrolRaceTrack(Point1, Point2, Altitude, Speed, Formation return self end + +--- IR Marker courtesy Florian Brinker (fbrinker) + +--- [GROUND] Create and enable a new IR Marker for the given controllable UNIT or GROUP. +-- @param #CONTROLLABLE self +-- @param #boolean EnableImmediately (Optionally) If true start up the IR Marker immediately. Else you need to call `myobject:EnableIRMarker()` later on. +-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. +-- @return #CONTROLLABLE self +function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime) + --sefl:F("NewIRMarker") + if self.ClassName == "GROUP" then + self.IRMarkerGroup = true + self.IRMarkerUnit = false + elseif self.ClassName == "UNIT" then + self.IRMarkerGroup = false + self.IRMarkerUnit = true + end + + self.spot = nil + self.timer = nil + self.stoptimer = nil + + if EnableImmediately and EnableImmediately == true then + self:EnableIRMarker(Runtime) + end + + return self +end + +--- [GROUND] Enable the IR marker. +-- @param #CONTROLLABLE self +-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Else run until you call `myobject:DisableIRMarker()`. +-- @return #CONTROLLABLE self +function CONTROLLABLE:EnableIRMarker(Runtime) + --sefl:F("EnableIRMarker") + if self.IRMarkerGroup == nil then + self:NewIRMarker(true,Runtime) + return + end + + if (self.IRMarkerGroup == true) then + self:EnableIRMarkerForGroup() + return + end + + self.timer = TIMER:New(CONTROLLABLE._MarkerBlink, self) + self.timer:Start(nil, 1 - math.random(1, 5) / 10 / 2, Runtime) -- start randomized + + return self +end + +--- [GROUND] Disable the IR marker. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:DisableIRMarker() + --sefl:F("DisableIRMarker") + if (self.IRMarkerGroup == true) then + self:DisableIRMarkerForGroup() + return + end + + if self.spot then + self.spot:destroy() + self.spot = nil + if self.timer and self.timer:IsRunning() then + self.timer:Stop() + self.timer = nil + end + end + return self +end + +--- [GROUND] Enable the IR markers for a whole group. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:EnableIRMarkerForGroup() + --sefl:F("EnableIRMarkerForGroup") + if self.ClassName == "GROUP" then + local units = self:GetUnits() or {} + for _,_unit in pairs(units) do + _unit:EnableIRMarker() + end + end + return self +end + +--- [GROUND] Disable the IR markers for a whole group. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:DisableIRMarkerForGroup() + --sefl:F("DisableIRMarkerForGroup") + if self.ClassName == "GROUP" then + local units = self:GetUnits() or {} + for _,_unit in pairs(units) do + _unit:DisableIRMarker() + end + end + return self +end + +--- [Internal] This method is called by the scheduler after enabling the IR marker. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:_MarkerBlink() + --sefl:F("_MarkerBlink") + if self:IsAlive() ~= true then + self:DisableIRMarker() + return + end + + self.timer.dT = 1 - (math.random(1, 2) / 10 / 2) -- randomize the blinking by a small amount + + local _, _, unitBBHeight, _ = self:GetObjectSize() + local unitPos = self:GetPositionVec3() + + self.spot = Spot.createInfraRed( + self.DCSUnit, + { x = 0, y = (unitBBHeight + 1), z = 0 }, + { x = unitPos.x, y = (unitPos.y + unitBBHeight), z = unitPos.z } + ) + + local offTimer = TIMER:New(function() if self.spot then self.spot:destroy() end end) + offTimer:Start(0.5) + return self +end From efe3571120484f500cc4f31192c6558cfd2d7add Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:42:10 +0200 Subject: [PATCH 050/349] Update Spawn.lua Remove T2 calls --- Moose Development/Moose/Core/Spawn.lua | 154 ++++++++++++------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 83c0bedf8..5aae7eae8 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1226,7 +1226,7 @@ end -- @param Core.Point#COORDINATE Coordinate The position to spawn from -- @return #SPAWN self function SPAWN:InitPositionCoordinate(Coordinate) - self:T2( { self.SpawnTemplatePrefix, Coordinate:GetVec2()} ) + --self:T2( { self.SpawnTemplatePrefix, Coordinate:GetVec2()} ) self:InitPositionVec2(Coordinate:GetVec2()) return self end @@ -1236,10 +1236,10 @@ end -- @param DCS#Vec2 Vec2 The position to spawn from -- @return #SPAWN self function SPAWN:InitPositionVec2(Vec2) - self:T2( { self.SpawnTemplatePrefix, Vec2} ) + --self:T2( { self.SpawnTemplatePrefix, Vec2} ) self.SpawnInitPosition = Vec2 self.SpawnFromNewPosition = true - self:T2("MaxGroups:"..self.SpawnMaxGroups) + --self:T2("MaxGroups:"..self.SpawnMaxGroups) for SpawnGroupID = 1, self.SpawnMaxGroups do self:_SetInitialPosition( SpawnGroupID ) end @@ -1334,7 +1334,7 @@ function SPAWN:InitCleanUp( SpawnCleanUpInterval ) self.SpawnCleanUpTimeStamps = {} local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T2( { "CleanUp Scheduler:", SpawnGroup } ) + --self:T2( { "CleanUp Scheduler:", SpawnGroup } ) self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) return self @@ -1367,7 +1367,7 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) local SpawnYIndex = 0 for SpawnGroupID = 1, self.SpawnMaxGroups do - self:T2( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) + --self:T2( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) self.SpawnGroups[SpawnGroupID].Visible = true self.SpawnGroups[SpawnGroupID].Spawned = false @@ -1602,12 +1602,12 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if aliveunits ~= self.AliveUnits then self.AliveUnits = aliveunits - self:T2("***** self.AliveUnits accounting failure! Corrected! *****") + --self:T2("***** self.AliveUnits accounting failure! Corrected! *****") end set= nil - self:T2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) + --self:T2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) if self:_GetSpawnIndex( SpawnIndex ) then @@ -1622,12 +1622,12 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate local SpawnZone = self.SpawnGroups[self.SpawnIndex].SpawnZone - self:T2( SpawnTemplate.name ) + --self:T2( SpawnTemplate.name ) if SpawnTemplate then local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) - self:T2( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) + --self:T2( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) -- If RandomizePosition, then Randomize the formation in the zone band, keeping the template. if self.SpawnRandomizePosition then @@ -1639,7 +1639,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) for UnitID = 1, #SpawnTemplate.units do SpawnTemplate.units[UnitID].x = SpawnTemplate.units[UnitID].x + (RandomVec2.x - CurrentX) SpawnTemplate.units[UnitID].y = SpawnTemplate.units[UnitID].y + (RandomVec2.y - CurrentY) - self:T2( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -1660,13 +1660,13 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) end end if (not inZone) then - self:T2("Could not place unit within zone and within radius!") + --self:T2("Could not place unit within zone and within radius!") RandomVec2 = SpawnZone:GetRandomVec2() end end SpawnTemplate.units[UnitID].x = RandomVec2.x SpawnTemplate.units[UnitID].y = RandomVec2.y - self:T2( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -2164,7 +2164,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Parking data explicitly set by user as input parameter. nfree=#Parkingdata spots=Parkingdata - --]] + ]] else if ishelo then if termtype == nil then @@ -2215,12 +2215,12 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Debug: Get parking data. --[[ local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) - self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) + --self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) for _,_spot in pairs(parkingdata) do - self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", + --self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) end - self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) + --self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) ]] -- Set this to true if not enough spots are available for emergency air start. @@ -2451,10 +2451,10 @@ function SPAWN:SpawnAtParkingSpot( Airbase, Spots, Takeoff ) -- Get parking spot data. local spot = Airbase:GetParkingSpotData( TerminalID ) - self:T2( { spot = spot } ) + --self:T2( { spot = spot } ) if spot and spot.Free then - self:T2( string.format( "Adding parking spot ID=%d TermType=%d", spot.TerminalID, spot.TerminalType ) ) + --self:T2( string.format( "Adding parking spot ID=%d TermType=%d", spot.TerminalID, spot.TerminalType ) ) table.insert( Parkingdata, spot ) end @@ -2486,7 +2486,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2( PointVec3 ) + --self:T2( PointVec3 ) -- Set take off type. Default is hot. local Takeoff = SPAWN.Takeoff.Cold @@ -2502,7 +2502,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex local GroupAlive = self:GetGroupFromIndex( SpawnIndex ) -- Debug output - self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) + --self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) -- Template group, unit and its attributes. local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) @@ -2546,7 +2546,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Check if we spawn on ground. local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) - self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + --self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) -- Check where we actually spawn if we spawn on ground. local spawnonship = false @@ -2588,7 +2588,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) --[[ @@ -2601,18 +2601,18 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex if ishelo then if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Not enough helo ports. Let's try also other terminal types. - self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- No terminal type specified. We try all spots except shelters. - self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2624,23 +2624,23 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- TODO: Some attributes are "Helicopters", "Bombers", "Transports", "Battleplanes". Need to check it out. if isbomber or istransport then -- First we fill the potentially bigger spots. - self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) + --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Now we try the smaller ones. - self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) + --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else - self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) + --self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- Terminal type explicitly given. - self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) + --self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2650,12 +2650,12 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Debug: Get parking data. --[[ local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) - self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) + --self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) for _,_spot in pairs(parkingdata) do - self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", + --self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) end - self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) + --self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) ]] -- Set this to true if not enough spots are available for emergency air start. @@ -2733,7 +2733,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn on ship. We take only the position of the ship. SpawnTemplate.units[UnitID].x = PointVec3.x -- TX @@ -2742,7 +2742,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex else - self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + --self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x @@ -2754,7 +2754,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex else - self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. SpawnTemplate.units[UnitID].x = TX @@ -2771,9 +2771,9 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex end -- Debug output. - self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) - self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) - self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) + --self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -2871,7 +2871,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) --self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2( PointVec3 ) + --self:T2( PointVec3 ) if SpawnIndex then else @@ -2884,7 +2884,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) if SpawnTemplate then - self:T2( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) + --self:T2( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) local TemplateHeight = SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil @@ -2909,7 +2909,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) if SpawnTemplate.CategoryID ~= Group.Category.SHIP then SpawnTemplate.units[UnitID].alt = Vec3.y or TemplateHeight end - self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end SpawnTemplate.route.points[1].x = Vec3.x SpawnTemplate.route.points[1].y = Vec3.z @@ -3168,10 +3168,10 @@ function SPAWN:SpawnGroupName( SpawnIndex ) if SpawnIndex then local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex ) - self:T2( SpawnName ) + --self:T2( SpawnName ) return SpawnName else - self:T2( SpawnPrefix ) + --self:T2( SpawnPrefix ) return SpawnPrefix end @@ -3499,7 +3499,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 if SpawnInitKeepUnitIFF == false then UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) - self:T2( { UnitPrefix, Rest } ) + --self:T2( { UnitPrefix, Rest } ) --else --UnitPrefix=SpawnTemplate.units[UnitID].name end @@ -3713,7 +3713,7 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) SpawnTemplate.route.points[t].alt = nil end - self:T2( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) + --self:T2( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) end end @@ -3756,15 +3756,15 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_SetInitialPosition( SpawnIndex ) - self:T2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) + --self:T2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) if self.SpawnFromNewPosition then - self:T2( "Preparing Spawn at Vec2 ", self.SpawnInitPosition ) + --self:T2( "Preparing Spawn at Vec2 ", self.SpawnInitPosition ) local SpawnVec2 = self.SpawnInitPosition - self:T2( { SpawnVec2 = SpawnVec2 } ) + --self:T2( { SpawnVec2 = SpawnVec2 } ) local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate @@ -3774,11 +3774,11 @@ function SPAWN:_SetInitialPosition( SpawnIndex ) SpawnTemplate.route.points[1].x = SpawnTemplate.route.points[1].x or 0 SpawnTemplate.route.points[1].y = SpawnTemplate.route.points[1].y or 0 - self:T2( { Route = SpawnTemplate.route } ) + --self:T2( { Route = SpawnTemplate.route } ) for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + --self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x @@ -3789,7 +3789,7 @@ function SPAWN:_SetInitialPosition( SpawnIndex ) UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... -- SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.route.points[1].x = SpawnVec2.x @@ -3812,26 +3812,26 @@ function SPAWN:_RandomizeZones( SpawnIndex ) if self.SpawnRandomizeZones then local SpawnZone = nil -- Core.Zone#ZONE_BASE while not SpawnZone do - self:T2( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) + --self:T2( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) local ZoneID = math.random( #self.SpawnZoneTable ) - self:T2( ZoneID ) + --self:T2( ZoneID ) SpawnZone = self.SpawnZoneTable[ZoneID]:GetZoneMaybe() end - self:T2( "Preparing Spawn in Zone", SpawnZone:GetName() ) + --self:T2( "Preparing Spawn in Zone", SpawnZone:GetName() ) local SpawnVec2 = SpawnZone:GetRandomVec2() - self:T2( { SpawnVec2 = SpawnVec2 } ) + --self:T2( { SpawnVec2 = SpawnVec2 } ) local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate self.SpawnGroups[SpawnIndex].SpawnZone = SpawnZone - self:T2( { Route = SpawnTemplate.route } ) + --self:T2( { Route = SpawnTemplate.route } ) for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + --self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x @@ -3842,7 +3842,7 @@ function SPAWN:_RandomizeZones( SpawnIndex ) UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... -- SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.x = SpawnVec2.x SpawnTemplate.y = SpawnVec2.y @@ -3897,11 +3897,11 @@ end -- @param #number SpawnIndex Spawn index. -- @return #number self.SpawnIndex function SPAWN:_GetSpawnIndex( SpawnIndex ) - self:T2( { template=self.SpawnTemplatePrefix, SpawnIndex=SpawnIndex, SpawnMaxGroups=self.SpawnMaxGroups, SpawnMaxUnitsAlive=self.SpawnMaxUnitsAlive, AliveUnits=self.AliveUnits, TemplateUnits=#self.SpawnTemplate.units } ) + --self:T2( { template=self.SpawnTemplatePrefix, SpawnIndex=SpawnIndex, SpawnMaxGroups=self.SpawnMaxGroups, SpawnMaxUnitsAlive=self.SpawnMaxUnitsAlive, AliveUnits=self.AliveUnits, TemplateUnits=#self.SpawnTemplate.units } ) if (self.SpawnMaxGroups == 0) or (SpawnIndex <= self.SpawnMaxGroups) then if (self.SpawnMaxUnitsAlive == 0) or (self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive) or self.UnControlled == true then - self:T2( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) + --self:T2( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then self.SpawnCount = self.SpawnCount + 1 SpawnIndex = self.SpawnCount @@ -3932,10 +3932,10 @@ function SPAWN:_OnBirth( EventData ) if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T2( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) + --self:T2( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then self.AliveUnits = self.AliveUnits + 1 - self:T2( "Alive Units: " .. self.AliveUnits ) + --self:T2( "Alive Units: " .. self.AliveUnits ) end end end @@ -3946,8 +3946,8 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnDeadOrCrash( EventData ) - self:T2( "Dead or crash event ID "..tostring(EventData.id or 0)) - self:T2( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") ) + --self:T2( "Dead or crash event ID "..tostring(EventData.id or 0)) + --self:T2( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") ) --if EventData.id == EVENTS.Dead then return end @@ -3959,12 +3959,12 @@ function SPAWN:_OnDeadOrCrash( EventData ) local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T2( { "Dead event: " .. EventPrefix } ) - self:T2(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) + --self:T2( { "Dead event: " .. EventPrefix } ) + --self:T2(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) and self.AliveUnits > 0 then self.AliveUnits = self.AliveUnits - 1 end - self:T2( "New Alive Units: " .. self.AliveUnits ) + --self:T2( "New Alive Units: " .. self.AliveUnits ) end end end @@ -3980,9 +3980,9 @@ function SPAWN:_OnTakeOff( EventData ) if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T2( { "TakeOff event: " .. EventPrefix } ) + --self:T2( { "TakeOff event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then - self:T2( "self.Landed = false" ) + --self:T2( "self.Landed = false" ) SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false ) end end @@ -4000,13 +4000,13 @@ function SPAWN:_OnLand( EventData ) if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T2( { "Land event: " .. EventPrefix } ) + --self:T2( { "Land event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then -- TODO: Check if this is the last unit of the group that lands. SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true ) if self.RepeatOnLanding then local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T2( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + --self:T2( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) -- self:ReSpawn( SpawnGroupIndex ) -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. @@ -4029,13 +4029,13 @@ function SPAWN:_OnEngineShutDown( EventData ) if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T2( { "EngineShutdown event: " .. EventPrefix } ) + --self:T2( { "EngineShutdown event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then -- todo: test if on the runway local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" ) if Landed and self.RepeatOnEngineShutDown then local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T2( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + --self:T2( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) -- self:ReSpawn( SpawnGroupIndex ) -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) @@ -4064,7 +4064,7 @@ function SPAWN:_SpawnCleanUpScheduler() --self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T2( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + --self:T2( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) local IsHelo = false @@ -4081,7 +4081,7 @@ function SPAWN:_SpawnCleanUpScheduler() self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] - self:T2( { SpawnUnitName, Stamp } ) + --self:T2( { SpawnUnitName, Stamp } ) if Stamp.Vec2 then if (SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1) or IsHelo then @@ -4089,7 +4089,7 @@ function SPAWN:_SpawnCleanUpScheduler() if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then - self:T2( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + --self:T2( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) --self:ReSpawn( SpawnCursor ) SCHEDULER:New( nil, self.ReSpawn, { self, SpawnCursor }, 3 ) Stamp.Vec2 = nil @@ -4118,7 +4118,7 @@ function SPAWN:_SpawnCleanUpScheduler() SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - self:T2( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + --self:T2( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) end From 4bafdf940816aa2d5d8d62a78a3cbf4da248c6c1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 3 Sep 2024 12:36:50 +0200 Subject: [PATCH 051/349] #SPAWN - relax checks on SpawnScheduled, ensure count is only done on event --- Moose Development/Moose/Core/Spawn.lua | 51 +++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 5aae7eae8..b0aa78852 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1391,7 +1391,7 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) self.SpawnGroups[SpawnGroupID].Visible = true self:HandleEvent( EVENTS.Birth, self._OnBirth ) - --self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) + self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.UnitLost, self._OnDeadOrCrash ) @@ -1591,7 +1591,7 @@ end -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) - + --[[ local set = SET_GROUP:New():FilterAlive():FilterPrefixes({self.SpawnTemplatePrefix, self.SpawnAliasPrefix}):FilterOnce() local aliveunits = 0 set:ForEachGroupAlive( @@ -1606,7 +1606,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) end set= nil - + --]] --self:T2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) if self:_GetSpawnIndex( SpawnIndex ) then @@ -1887,6 +1887,7 @@ end --- Spawns new groups at varying time intervals. -- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions. +-- **WARNING** - Setting a very low SpawnTime heavily impacts your mission performance and CPU time, it is NOT useful to check the alive state of an object every split second! Be reasonable and stay at 15 seconds and above! -- @param #SPAWN self -- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. -- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. @@ -1909,6 +1910,14 @@ function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation, WithDelay ) local SpawnTime = SpawnTime or 60 local SpawnTimeVariation = SpawnTimeVariation or 0.5 + -- Noob catch + if SpawnTime < 15 then + self:E("****SPAWN SCHEDULED****\nWARNING - Setting a very low SpawnTime heavily impacts your mission performance and CPU time, it is NOT useful to check the alive state of an object every "..tostring(SpawnTime).." seconds.\nSetting to 15 second intervals.\n*****") + SpawnTime = 15 + end + + if SpawnTimeVariation > 1 or SpawnTimeVariation < 0 then SpawnTimeVariation = 0.5 end + if SpawnTime ~= nil and SpawnTimeVariation ~= nil then local InitialDelay = 0 if WithDelay or self.DelayOnOff == true then @@ -3942,6 +3951,35 @@ function SPAWN:_OnBirth( EventData ) end +--- +-- @param #SPAWN self +-- @return #number count +function SPAWN:_CountAliveUnits() + local count = 0 + if self.SpawnAliasPrefix then + if not self.SpawnAliasPrefixEscaped then self.SpawnAliasPrefixEscaped = string.gsub(self.SpawnAliasPrefix,"[%p%s]",".") end + local SpawnAliasPrefix = self.SpawnAliasPrefixEscaped + local agroups = GROUP:FindAllByMatching(SpawnAliasPrefix) + for _,_grp in pairs(agroups) do + local game = self:_GetPrefixFromGroupName(_grp.GroupName) + if game and game == self.SpawnAliasPrefix then + count = count + _grp:CountAliveUnits() + end + end + else + if not self.SpawnTemplatePrefixEscaped then self.SpawnTemplatePrefixEscaped = string.gsub(self.SpawnTemplatePrefix,"[%p%s]",".") end + local SpawnTemplatePrefix = self.SpawnTemplatePrefixEscaped + local groups = GROUP:FindAllByMatching(SpawnTemplatePrefix) + for _,_grp in pairs(groups) do + local game = self:_GetPrefixFromGroupName(_grp.GroupName) + if game and game == self.SpawnTemplatePrefix then + count = count + _grp:CountAliveUnits() + end + end + end + return count +end + --- -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData @@ -3959,12 +3997,13 @@ function SPAWN:_OnDeadOrCrash( EventData ) local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - --self:T2( { "Dead event: " .. EventPrefix } ) --self:T2(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) and self.AliveUnits > 0 then - self.AliveUnits = self.AliveUnits - 1 + --self:I( { "Dead event: " .. EventPrefix } ) + --self.AliveUnits = self.AliveUnits - 1 + self.AliveUnits = self:_CountAliveUnits() + --self:I( "New Alive Units: " .. self.AliveUnits ) end - --self:T2( "New Alive Units: " .. self.AliveUnits ) end end end From 7d3a0568a98282aace4fc32e44cdb6a332f336a5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 6 Sep 2024 13:40:41 +0200 Subject: [PATCH 052/349] Qol --- Moose Development/Moose/AI/AI_A2G_Dispatcher.lua | 4 ++++ Moose Development/Moose/AI/AI_Air_Engage.lua | 14 ++++++++++++-- Moose Development/Moose/Functional/Detection.lua | 11 ++++++----- Moose Development/Moose/Functional/Warehouse.lua | 2 +- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 451414f7f..94d663751 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -3895,10 +3895,14 @@ do -- AI_A2G_DISPATCHER if Squadron then local FirstUnit = AttackSetUnit:GetRandomSurely() + if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE if self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end + else + return + end end self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index 70898d2ba..6a8472ace 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -426,7 +426,12 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac local DefenderCoord = DefenderGroup:GetPointVec3() DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - local TargetCoord = AttackSetUnit:GetRandomSurely():GetPointVec3() + local TargetUnit = AttackSetUnit:GetRandomSurely() + local TargetCoord = nil + + if TargetUnit then + TargetUnit:GetPointVec3() + end if TargetCoord == nil then self:Return() @@ -522,7 +527,12 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU local DefenderCoord = DefenderGroup:GetPointVec3() DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - local TargetCoord = AttackSetUnit:GetRandomSurely():GetPointVec3() + local TargetCoord = nil + + local TargetUnit = AttackSetUnit:GetRandomSurely() + if TargetUnit then + TargetCoord=TargetUnit:GetPointVec3() + end if not TargetCoord then self:Return() return diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 367e460bc..1c1de3d50 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -595,7 +595,8 @@ do -- DETECTION_BASE return self end - + + --- -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. @@ -615,7 +616,7 @@ do -- DETECTION_BASE self:T( { "DetectionGroup is Alive", Detection:GetName() } ) local DetectionGroupName = Detection:GetName() - local DetectionUnit = Detection:GetUnit( 1 ) + local DetectionUnit = Detection:GetFirstUnitAlive() local DetectedUnits = {} @@ -632,20 +633,20 @@ do -- DETECTION_BASE --self:T(UTILS.PrintTableToLog(DetectedTargets)) - for DetectionObjectID, Detection in pairs( DetectedTargets ) do + for DetectionObjectID, Detection in pairs( DetectedTargets or {}) do local DetectedObject = Detection.object -- DCS#Object if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then -- and ( DetectedObject:getCategory() == Object.Category.UNIT or DetectedObject:getCategory() == Object.Category.STATIC ) then local DetectedObjectName = DetectedObject:getName() if not self.DetectedObjects[DetectedObjectName] then self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {} - self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName + self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName self.DetectedObjects[DetectedObjectName].Object = DetectedObject end end end - for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects ) do + for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects or {}) do local DetectedObject = DetectedObjectData.Object diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index cab1d0899..b7a69a44e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -6732,7 +6732,7 @@ end -- @param Wrapper.Group#GROUP deadgroup Group of unit that died. -- @param #WAREHOUSE.Pendingitem request Request that needs to be updated. function WAREHOUSE:_UnitDead(deadunit, deadgroup, request) - self:F(self.lid.."FF unit dead "..deadunit:GetName()) + --self:F(self.lid.."FF unit dead "..deadunit:GetName()) -- Find opsgroup. local opsgroup=_DATABASE:FindOpsGroup(deadgroup) From d6ecde96f862cabb9214b43254e348054de21d2d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 6 Sep 2024 16:40:18 +0200 Subject: [PATCH 053/349] #CTLD #CSAR Function to add own SET_GROUP for pilots --- Moose Development/Moose/Ops/CSAR.lua | 34 ++++++++++++++++++++++++---- Moose Development/Moose/Ops/CTLD.lua | 27 ++++++++++++++++++++-- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index a0532abc8..32d6144a9 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Aug 2024 +-- Last Update Sep 2024 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -41,6 +41,7 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. -- @field Core.Set#SET_GROUP allheligroupset Set of CSAR heli groups. +-- @field Core.Set#SET_GROUP UserSetGroup Set of CSAR heli groups as designed by the mission designer (if any set). -- @extends Core.Fsm#FSM --- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia) @@ -116,8 +117,15 @@ -- mycsar.topmenuname = "CSAR" -- set the menu entry name -- mycsar.ADFRadioPwr = 1000 -- ADF Beacons sending with 1KW as default -- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each +-- +-- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups +-- +-- -- Parameter: Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups. +-- -- Needs to be set before starting the CSAR instance. +-- local myset = SET_GROUP:New():FilterPrefixes("Helikopter"):FilterCoalitions("red"):FilterStart() +-- mycsar:SetOwnSetPilotGroups(myset) -- --- ## 2.1 SRS Features and Other Features +-- ## 2.2 SRS Features and Other Features -- -- mycsar.useSRS = false -- Set true to use FF\'s SRS integration -- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) @@ -258,6 +266,7 @@ CSAR = { ADFRadioPwr = 1000, PilotWeight = 80, CreateRadioBeacons = true, + UserSetGroup = nil, } --- Downed pilots info. @@ -300,7 +309,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.27" +CSAR.version="1.0.28" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -459,6 +468,9 @@ function CSAR:New(Coalition, Template, Alias) -- added 1.0.16 self.PilotWeight = 80 + + -- Own SET_GROUP if any + self.UserSetGroup = nil -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -2330,6 +2342,16 @@ function CSAR:_ReachedPilotLimit() end end + --- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment. + -- Needs to be set before starting the CSAR instance. + -- @param #CSAR self + -- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups. + -- @return #CSAR self + function CSAR:SetOwnSetPilotGroups(Set) + self.UserSetGroup = Set + return self + end + ------------------------------ --- FSM internal Functions --- ------------------------------ @@ -2351,7 +2373,9 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) - if self.allowbronco then + if self.UserSetGroup then + self.PilotGroups = self.UserSetGroup + elseif self.allowbronco then local prefixes = self.csarPrefix or {} self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart() elseif self.useprefix then @@ -2360,7 +2384,9 @@ function CSAR:onafterStart(From, Event, To) else self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end + self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also? + if not self.coordinate then local csarhq = self.mash:GetRandom() if csarhq then diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3343e90e5..464be6780 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -929,6 +929,13 @@ do -- -- Notes: -- Troops dropped back into a LOAD zone will effectively be added to the stock. Crates lost in e.g. a heli crash are just that - lost. +-- +-- ## 2.2.4 Create own SET_GROUP to manage CTLD Pilot groups +-- +-- -- Parameter: Set The SET_GROUP object created by the mission designer/user to represent the CTLD pilot groups. +-- -- Needs to be set before starting the CTLD instance. +-- local myset = SET_GROUP:New():FilterPrefixes("Helikopter"):FilterCoalitions("red"):FilterStart() +-- my_ctld:SetOwnSetPilotGroups(myset) -- -- ## 3. Events -- @@ -1227,6 +1234,7 @@ CTLD = { ChinookTroopCircleRadius = 5, TroopUnloadDistGround = 5, TroopUnloadDistHover = 1.5, + UserSetGroup = nil, } ------------------------------ @@ -1331,7 +1339,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.15" +CTLD.version="1.1.16" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1514,6 +1522,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self.enableChinookGCLoading = true self.ChinookTroopCircleRadius = 5 + -- User SET_GROUP + self.UserSetGroup = nil + local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CTLD_%s_Persist.csv",AliaS) @@ -4973,6 +4984,16 @@ end return self end + --- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment. + -- Needs to be set before starting the CTLD instance. + -- @param #CTLD self + -- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CTLD pilot groups. + -- @return #CTLD self + function CTLD:SetOwnSetPilotGroups(Set) + self.UserSetGroup = Set + return self + end + --- [Deprecated] - Function to add/adjust unittype capabilities. Has been replaced with `SetUnitCapabilities()` - pls use the new one going forward! -- @param #CTLD self -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. @@ -5691,7 +5712,9 @@ end function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) self:I(self.lid .. "Started ("..self.version..")") - if self.useprefix or self.enableHercules then + if self.UserSetGroup then + self.PilotGroups = self.UserSetGroup + elseif self.useprefix or self.enableHercules then local prefix = self.prefixes if self.enableHercules then self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() From 232547a0fb3977e105bc0ba002d6b3e1a36d1dfb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 8 Sep 2024 11:44:59 +0200 Subject: [PATCH 054/349] xxx --- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Wrapper/Group.lua | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 7c18a4222..bc2bb0cd0 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -181,7 +181,7 @@ do -- SET_BASE return false end end - -- No condition was true. + -- No condition was false. return true end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 52f9d7fd7..b213d9ed8 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -367,7 +367,7 @@ function GROUP:GetDCSObject() return DCSGroup end - self:T2(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName))) + --self:T2(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName))) return nil end @@ -1010,6 +1010,20 @@ function GROUP:Activate(delay) return self end +--- Deactivates an activated GROUP. +-- @param #GROUP self +-- @param #number delay Delay in seconds, before the group is activated. +-- @return #GROUP self +function GROUP:Deactivate(delay) + --self:F2( { self.GroupName } ) + if delay and delay>0 then + self:ScheduleOnce(delay, GROUP.Deactivate, self) + else + trigger.action.deactivateGroup( self:GetDCSObject() ) + end + return self +end + --- Gets the type name of the group. -- @param #GROUP self From d58edf7c380c96652d742a79e7647b9c26af84e3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 8 Sep 2024 13:18:32 +0200 Subject: [PATCH 055/349] xx --- Moose Development/Moose/Ops/CSAR.lua | 56 +++++++++++++++++++---- Moose Development/Moose/Wrapper/Group.lua | 23 +++++++--- Moose Development/Moose/Wrapper/Unit.lua | 33 +++++++++++-- 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index c26965a22..d54da54db 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -117,6 +117,8 @@ -- mycsar.topmenuname = "CSAR" -- set the menu entry name -- mycsar.ADFRadioPwr = 1000 -- ADF Beacons sending with 1KW as default -- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each +-- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it). +-- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins). -- -- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups -- @@ -267,6 +269,8 @@ CSAR = { PilotWeight = 80, CreateRadioBeacons = true, UserSetGroup = nil, + AllowIRStrobe = false, + IRStrobeRuntime = 300, } --- Downed pilots info. @@ -309,7 +313,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.28" +CSAR.version="1.0.29" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -753,7 +757,6 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet) :NewWithAlias(template,alias) :InitCoalition(coalition) :InitCountry(country) - --:InitAIOnOff(pilotcacontrol) :InitDelayOff() :SpawnFromCoordinate(point) @@ -1818,9 +1821,6 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid end _text = string.gsub(_text,"km"," kilometer") _text = string.gsub(_text,"nm"," nautical miles") - --self.msrs:SetVoice(self.SRSVoice) - --self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,1) - --self:I("Voice = "..self.SRSVoice) self.SRSQueue:NewTransmission(_text,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,self.SRSVoice,volume,label,coord) end return self @@ -1966,7 +1966,7 @@ function CSAR:_SignalFlare(_unitName) else _distance = string.format("%.1fkm",_closest.distance/1000) end - local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + local _msg = string.format("%s - Firing signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local _coord = _closest.pilot:GetCoordinate() @@ -2000,7 +2000,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen) if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda end - self:F("Voice = "..voice) + --self:F("Voice = "..voice) self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate) end if ToScreen == true or ToScreen == nil then @@ -2014,6 +2014,41 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen) return self end +---(Internal) Request IR Strobe at closest downed pilot. +--@param #CSAR self +--@param #string _unitName Name of the helicopter +function CSAR:_ReqIRStrobe( _unitName ) + self:T(self.lid .. " _ReqIRStrobe") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + local smokedist = 8000 + if smokedist < self.approachdist_far then smokedist = self.approachdist_far end + local _closest = self:_GetClosestDownedPilot(_heli) + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = string.format("%.1fkm",_closest.distance/1000) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.1fkm",_closest.distance/1000) + end + local _msg = string.format("%s - IR Strobe active at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) + _closest.pilot:NewIRMarker(true,self.IRStrobeRuntime or 300) + else + local _distance = string.format("%.1fkm",smokedist/1000) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) + end + return self +end + ---(Internal) Request smoke at closest downed pilot. --@param #CSAR self --@param #string _unitName Name of the helicopter @@ -2165,7 +2200,12 @@ function CSAR:_AddMedevacMenuItem() local _rootMenu1 = MENU_GROUP_COMMAND:New(_group,"List Active CSAR",_rootPath, self._DisplayActiveSAR,self,_unitName) local _rootMenu2 = MENU_GROUP_COMMAND:New(_group,"Check Onboard",_rootPath, self._CheckOnboard,self,_unitName) local _rootMenu3 = MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, self._SignalFlare,self,_unitName) - local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName):Refresh() + local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName) + if self.AllowIRStrobe then + local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh() + else + _rootMenu4:Refresh() + end end end end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index b213d9ed8..a8f27de21 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -360,14 +360,25 @@ end -- @return DCS#Group The DCS Group. function GROUP:GetDCSObject() - -- Get DCS group. - local DCSGroup = Group.getByName( self.GroupName ) + if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) then - if DCSGroup then - return DCSGroup + -- Get DCS group. + local DCSGroup = Group.getByName( self.GroupName ) + + if DCSGroup then + self.LastCallDCSObject = timer.getTime() + self.DCSObject = DCSGroup + return DCSGroup + else + self.DCSObject = nil + self.LastCallDCSObject = nil + end + + else + return self.DCSObject end - - --self:T2(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName))) + + --self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName))) return nil end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 7fe7e06f3..d282ff859 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -219,6 +219,7 @@ function UNIT:Name() return self.UnitName end +--[[ --- Get the DCS unit object. -- @param #UNIT self -- @return DCS#Unit The DCS unit object. @@ -230,10 +231,34 @@ function UNIT:GetDCSObject() return DCSUnit end - --if self.DCSUnit then - --return self.DCSUnit - --end + return nil +end +--]] + +--- Returns the DCS Unit. +-- @param #UNIT self +-- @return DCS#Unit The DCS Group. +function UNIT:GetDCSObject() + + if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) then + + -- Get DCS group. + local DCSUnit = Unit.getByName( self.UnitName ) + + if DCSUnit then + self.LastCallDCSObject = timer.getTime() + self.DCSObject = DCSUnit + return DCSUnit + else + self.DCSObject = nil + self.LastCallDCSObject = nil + end + else + return self.DCSObject + end + + --self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.UnitName))) return nil end @@ -243,7 +268,7 @@ end -- @return #number The height of the group or nil if is not existing or alive. function UNIT:GetAltitude(FromGround) - local DCSUnit = Unit.getByName( self.UnitName ) + local DCSUnit = self:GetDCSObject() if DCSUnit then local altitude = 0 From cadf46ded29a1eaf6c29c12a98a90a0a1442bcd9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 15 Sep 2024 19:24:34 +0200 Subject: [PATCH 056/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 464be6780..f1e8d5b57 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -836,6 +836,8 @@ do -- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIc types for CTLD for the Chinook -- my_ctld.TroopUnloadDistGround = 5 -- If hovering, spawn dropped troops this far away in meters from the helo -- my_ctld.TroopUnloadDistHover = 1.5 -- If grounded, spawn dropped troops this far away in meters from the helo +-- my_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules +-- my_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook -- -- ## 2.1 CH-47 Chinook support -- @@ -1233,6 +1235,8 @@ CTLD = { DynamicCargo = {}, ChinookTroopCircleRadius = 5, TroopUnloadDistGround = 5, + TroopUnloadDistGroundHerc = 25, + TroopUnloadDistGroundHook = 15, TroopUnloadDistHover = 1.5, UserSetGroup = nil, } @@ -1339,7 +1343,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.16" +CTLD.version="1.1.17" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3484,7 +3488,10 @@ function CTLD:_UnloadTroops(Group, Unit) randomcoord = Group:GetCoordinate() -- slightly left from us local Angle = (heading+270)%360 + if IsHerc or IsHook then Angle = (heading+180)%360 end local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround + if IsHerc then offset = self.TroopUnloadDistGroundHerc or 25 end + if IsHook then offset = self.TroopUnloadDistGroundHook or 15 end randomcoord:Translate(offset,Angle,nil,true) end local tempcount = 0 @@ -3494,7 +3501,7 @@ function CTLD:_UnloadTroops(Group, Unit) self.TroopCounter = self.TroopCounter + 1 tempcount = tempcount+1 local alias = string.format("%s-%d", _template, math.random(1,100000)) - local rad = 2.5+tempcount + local rad = 2.5+(tempcount*2) local Positions = self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) --:InitRandomizeUnits(true,20,2) From 68872894664f185be584999f4fd069564053965a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 23 Sep 2024 10:19:58 +0200 Subject: [PATCH 057/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 15dda8cb3..8f130c255 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -50,7 +50,7 @@ -- @field #number capleg -- @field #number maxinterceptsize -- @field #number missionrange --- @field #number noaltert5 +-- @field #number noalert5 -- @field #table ManagedAW -- @field #table ManagedSQ -- @field #table ManagedCP @@ -167,7 +167,7 @@ -- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM. -- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2. -- * @{#EASYGCICAP.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100. --- * @{#EASYGCICAP.SetDefaultNumberAlter5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2. +-- * @{#EASYGCICAP.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2. -- * @{#EASYGCICAP.SetDefaultEngageRange}: Set max engage range for CAP flights if they detect intruders, defaults to 50. -- * @{#EASYGCICAP.SetMaxAliveMissions}: Set max parallel missions can be done (CAP+GCI+Alert5+Tanker+AWACS), defaults to 8. -- * @{#EASYGCICAP.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3. @@ -197,7 +197,7 @@ EASYGCICAP = { capleg = 15, maxinterceptsize = 2, missionrange = 100, - noaltert5 = 4, + noalert5 = 4, ManagedAW = {}, ManagedSQ = {}, ManagedCP = {}, @@ -252,7 +252,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.13" +EASYGCICAP.version="0.1.15" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -278,7 +278,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) -- defaults self.alias = Alias or AirbaseName.." CAP Wing" self.coalitionname = string.lower(Coalition) or "blue" - self.coalition = self.coaltitionname == "blue" and coalition.side.BLUE or coalition.side.RED + self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED self.wings = {} self.EWRName = EWRName or self.coalitionname.." EWR" --self.CapZoneName = CapZoneName @@ -293,7 +293,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.capleg = 15 self.capgrouping = 2 self.missionrange = 100 - self.noaltert5 = 2 + self.noalert5 = 2 self.MaxAliveMissions = 8 self.engagerange = 50 self.repeatsonfailure = 3 @@ -441,9 +441,9 @@ end -- @param #EASYGCICAP self -- @param #number Airframes defaults to 2 -- @return #EASYGCICAP self -function EASYGCICAP:SetDefaultNumberAlter5Standby(Airframes) - self:T(self.lid.."SetDefaultNumberAlter5Standby") - self.noaltert5 = math.abs(Airframes) or 2 +function EASYGCICAP:SetDefaultNumberAlert5Standby(Airframes) + self:T(self.lid.."SetDefaultNumberAlert5Standby") + self.noalert5 = math.abs(Airframes) or 2 return self end @@ -452,7 +452,7 @@ end -- @param #number Range defaults to 50NM -- @return #EASYGCICAP self function EASYGCICAP:SetDefaultEngageRange(Range) - self:T(self.lid.."SetDefaultNumberAlter5Standby") + self:T(self.lid.."SetDefaultEngageRange") self.engagerange = Range or 50 return self end @@ -620,9 +620,9 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) end end - if self.noaltert5 > 0 then + if self.noalert5 > 0 then local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) - alert:SetRequiredAssets(self.noaltert5) + alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) table.insert(self.ListOfAuftrag,alert) From 1f3df107bf9587350d92fc0b06a8abd6ea77b0f3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 23 Sep 2024 12:43:51 +0200 Subject: [PATCH 058/349] xxx --- Moose Development/Moose/Core/Set.lua | 7 +++++-- Moose Development/Moose/Wrapper/DynamicCargo.lua | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index bc2bb0cd0..eeb7baf0e 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -8355,14 +8355,17 @@ do -- SET_SCENERY -- @param #SET_SCENERY self -- @return Core.Point#COORDINATE The center coordinate of all the objects in the set. function SET_SCENERY:GetCoordinate() - + --[[ local Coordinate = COORDINATE:New({0,0,0}) - + local Item = self:GetRandomSurely() if Item then Coordinate:GetCoordinate() end + --]] + + local Coordinate = self:GetFirst():GetCoordinate() local x1 = Coordinate.x local x2 = Coordinate.x diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 63f341009..3ff7ddc53 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -458,7 +458,7 @@ function DYNAMICCARGO:_UpdatePosition() self:T(self.lid.." AGL: "..agl or -1) local isunloaded = true local client - local playername + local playername = self.Owner if count > 0 and (agl > 0 or self.testing) then self:T(self.lid.." Possible alive helos: "..count or -1) if agl ~= 0 or self.testing then @@ -470,6 +470,11 @@ function DYNAMICCARGO:_UpdatePosition() self.Owner = playername _DATABASE:CreateEventDynamicCargoUnloaded(self) end + elseif count > 0 and agl == 0 then + self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) + self.CargoState = DYNAMICCARGO.State.UNLOADED + self.Owner = playername + _DATABASE:CreateEventDynamicCargoUnloaded(self) end end self.LastPosition = pos From e0e4ba7db3ef97d00a4b88ffd45fa792543d86fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 23 Sep 2024 13:43:05 +0200 Subject: [PATCH 059/349] xx --- Moose Development/Moose/Wrapper/DynamicCargo.lua | 2 +- Moose Development/Moose/Wrapper/Storage.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 3ff7ddc53..7a091fc6e 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -183,7 +183,7 @@ end -- @param #DYNAMICCARGO self -- @return DCS static object function DYNAMICCARGO:GetDCSObject() - local DCSStatic = Unit.getByName( self.StaticName ) + local DCSStatic = StaticObject.getByName( self.StaticName ) or Unit.getByName( self.StaticName ) if DCSStatic then return DCSStatic end diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 15cbf995d..4ad6bf6fa 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -226,7 +226,7 @@ function STORAGE:NewFromStaticCargo(StaticCargoName) return self end ---- Create a new STORAGE object from an DCS static cargo object. +--- Create a new STORAGE object from a Wrapper.DynamicCargo#DYNAMICCARGO object. -- @param #STORAGE self -- @param #string DynamicCargoName Unit name of the dynamic cargo. -- @return #STORAGE self @@ -235,7 +235,7 @@ function STORAGE:NewFromDynamicCargo(DynamicCargoName) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #STORAGE - self.airbase=Unit.getByName(DynamicCargoName) + self.airbase=Unit.getByName(DynamicCargoName) or StaticObject.getByName(DynamicCargoName) if Airbase.getWarehouse then self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) From 0c8a4c4cf8ba28b5881c97701764834e537834d9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Sep 2024 09:04:36 +0200 Subject: [PATCH 060/349] xx --- Moose Development/Moose/Functional/Sead.lua | 22 ++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index c50e2d310..e4e6d18f5 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -28,6 +28,16 @@ --- -- @type SEAD +-- @field #string ClassName The Class Name. +-- @field #table TargetSkill Table of target skills. +-- @field #table SEADGroupPrefixes Table of SEAD prefixes. +-- @field #table SuppressedGroups Table of currently suppressed groups. +-- @field #number EngagementRange Engagement Range. +-- @field #number Padding Padding in seconds. +-- @field #function CallBack Callback function for suppression plans. +-- @field #boolean UseCallBack Switch for callback function to be used. +-- @field #boolean debug Debug switch. +-- @field #boolen WeaponTrack Track switch, if true track weapon speed for 15 secs. -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. @@ -60,6 +70,7 @@ SEAD = { CallBack = nil, UseCallBack = false, debug = false, + WeaponTrack = false, } --- Missile enumerators @@ -144,7 +155,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) self:AddTransition("*", "ManageEvasion", "*") self:AddTransition("*", "CalculateHitZone", "*") - self:I("*** SEAD - Started Version 0.4.6") + self:I("*** SEAD - Started Version 0.4.7") return self end @@ -371,7 +382,7 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP reach = wpndata[1] * 1.1 local mach = wpndata[2] wpnspeed = math.floor(mach * 340.29) - if Weapon then + if Weapon and Weapon:GetSpeed() > 0 then wpnspeed = Weapon:GetSpeed() self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed)) end @@ -460,7 +471,7 @@ function SEAD:HandleEventShot( EventData ) local SEADWeapon = EventData.Weapon -- Identify the weapon fired local SEADWeaponName = EventData.WeaponName -- return weapon type - local WeaponWrapper = WEAPON:New(EventData.Weapon) + local WeaponWrapper = WEAPON:New(EventData.Weapon) -- Wrapper.Weapon#WEAPON --local SEADWeaponSpeed = WeaponWrapper:GetSpeed() -- mps self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) @@ -468,6 +479,11 @@ function SEAD:HandleEventShot( EventData ) if self:_CheckHarms(SEADWeaponName) then self:T( '*** SEAD - Weapon Match' ) + if self.WeaponTrack == true then + WeaponWrapper:SetFuncTrack(function(weapon) env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed() or -1)) end) + WeaponWrapper:StartTrack(1) + WeaponWrapper:StopTrack(15) + end local _targetskill = "Random" local _targetgroupname = "none" local _target = EventData.Weapon:getTarget() -- Identify target From e5965f8aec782674095cf1273e8c6e74883886ff Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Sep 2024 11:57:17 +0200 Subject: [PATCH 061/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 2 +- Moose Development/Moose/Functional/Sead.lua | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 019be1eab..747c4bda2 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: July 2024 +-- Last Update: Sep 2024 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index e4e6d18f5..64ee1dc7d 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -66,11 +66,11 @@ SEAD = { SEADGroupPrefixes = {}, SuppressedGroups = {}, EngagementRange = 75, -- default 75% engagement range Feature Request #1355 - Padding = 10, + Padding = 15, CallBack = nil, UseCallBack = false, debug = false, - WeaponTrack = false, + WeaponTrack = true, } --- Missile enumerators @@ -481,8 +481,8 @@ function SEAD:HandleEventShot( EventData ) self:T( '*** SEAD - Weapon Match' ) if self.WeaponTrack == true then WeaponWrapper:SetFuncTrack(function(weapon) env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed() or -1)) end) - WeaponWrapper:StartTrack(1) - WeaponWrapper:StopTrack(15) + WeaponWrapper:StartTrack() + WeaponWrapper:StopTrack(30) end local _targetskill = "Random" local _targetgroupname = "none" @@ -536,7 +536,7 @@ function SEAD:HandleEventShot( EventData ) end if SEADGroupFound == true then -- yes we are being attacked if string.find(SEADWeaponName,"ADM_141",1,true) then - self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) + self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,2,WeaponWrapper) else self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) end From 3d5dfc422d543c85520887c546d7fcca69925d19 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 30 Sep 2024 11:36:19 +0200 Subject: [PATCH 062/349] xx --- Moose Development/Moose/Core/Spawn.lua | 21 ++++++++++++------- Moose Development/Moose/Functional/Sead.lua | 8 +++---- Moose Development/Moose/Wrapper/Group.lua | 3 +++ .../Moose/Wrapper/Positionable.lua | 3 +++ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index b0aa78852..b9450f99d 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3956,12 +3956,17 @@ end -- @return #number count function SPAWN:_CountAliveUnits() local count = 0 + self:I("self.SpawnAliasPrefix="..tostring(self.SpawnAliasPrefix).." | self.SpawnTemplatePrefix="..tostring(self.SpawnTemplatePrefix)) if self.SpawnAliasPrefix then if not self.SpawnAliasPrefixEscaped then self.SpawnAliasPrefixEscaped = string.gsub(self.SpawnAliasPrefix,"[%p%s]",".") end + self:I("self.SpawnAliasPrefixEscaped="..tostring(self.SpawnAliasPrefixEscaped)) local SpawnAliasPrefix = self.SpawnAliasPrefixEscaped local agroups = GROUP:FindAllByMatching(SpawnAliasPrefix) for _,_grp in pairs(agroups) do + self:I("Group Name = " .. _grp:GetName()) local game = self:_GetPrefixFromGroupName(_grp.GroupName) + self:I("Game = "..game) + self:I("Count = ".._grp:CountAliveUnits()) if game and game == self.SpawnAliasPrefix then count = count + _grp:CountAliveUnits() end @@ -3977,15 +3982,16 @@ function SPAWN:_CountAliveUnits() end end end - return count + self.AliveUnits = count + return self end --- -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnDeadOrCrash( EventData ) - --self:T2( "Dead or crash event ID "..tostring(EventData.id or 0)) - --self:T2( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") ) + self:I( "Dead or crash event ID "..tostring(EventData.id or 0)) + self:I( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") ) --if EventData.id == EVENTS.Dead then return end @@ -3997,11 +4003,12 @@ function SPAWN:_OnDeadOrCrash( EventData ) local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - --self:T2(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) + self:I(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) and self.AliveUnits > 0 then - --self:I( { "Dead event: " .. EventPrefix } ) - --self.AliveUnits = self.AliveUnits - 1 - self.AliveUnits = self:_CountAliveUnits() + self:I( { "Dead event: " .. EventPrefix } ) + --self.AliveUnits = self.AliveUnits - 1 + self:ScheduleOnce(1,self._CountAliveUnits,self) + --self.AliveUnits = self:_CountAliveUnits() --self:I( "New Alive Units: " .. self.AliveUnits ) end end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 64ee1dc7d..314c9422f 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -19,7 +19,7 @@ -- -- ### Authors: **applevangelist**, **FlightControl** -- --- Last Update: Dec 2023 +-- Last Update: Oct 2024 -- -- === -- @@ -37,7 +37,7 @@ -- @field #function CallBack Callback function for suppression plans. -- @field #boolean UseCallBack Switch for callback function to be used. -- @field #boolean debug Debug switch. --- @field #boolen WeaponTrack Track switch, if true track weapon speed for 15 secs. +-- @field #boolen WeaponTrack Track switch, if true track weapon speed for 30 secs. -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. @@ -70,7 +70,7 @@ SEAD = { CallBack = nil, UseCallBack = false, debug = false, - WeaponTrack = true, + WeaponTrack = false, } --- Missile enumerators @@ -481,7 +481,7 @@ function SEAD:HandleEventShot( EventData ) self:T( '*** SEAD - Weapon Match' ) if self.WeaponTrack == true then WeaponWrapper:SetFuncTrack(function(weapon) env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed() or -1)) end) - WeaponWrapper:StartTrack() + WeaponWrapper:StartTrack(0.1) WeaponWrapper:StopTrack(30) end local _targetskill = "Random" diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index a8f27de21..ab0992161 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -492,13 +492,16 @@ function GROUP:Destroy( GenerateEvent, delay ) if GenerateEvent and GenerateEvent == true then if self:IsAir() then self:CreateEventCrash( timer.getTime(), UnitData ) + --self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),UnitData) else self:CreateEventDead( timer.getTime(), UnitData ) + --self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),UnitData) end elseif GenerateEvent == false then -- Do nothing! else self:CreateEventRemoveUnit( timer.getTime(), UnitData ) + --self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),UnitData) end end USERFLAG:New( self:GetName() ):Set( 100 ) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 9d8c77873..735760430 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -110,14 +110,17 @@ function POSITIONABLE:Destroy( GenerateEvent ) if GenerateEvent and GenerateEvent == true then if self:IsAir() then + --self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),DCSObject) self:CreateEventCrash( timer.getTime(), DCSObject ) else + --self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),DCSObject) self:CreateEventDead( timer.getTime(), DCSObject ) end elseif GenerateEvent == false then -- Do nothing! else self:CreateEventRemoveUnit( timer.getTime(), DCSObject ) + --self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),DCSObject) end USERFLAG:New( UnitGroupName ):Set( 100 ) From 8ede1fb351ef3b15d4c8c0d2601e3d45651dac1b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 1 Oct 2024 12:03:00 +0200 Subject: [PATCH 063/349] #DCS 2.9.8.1107 airbase updates --- Moose Development/Moose/Core/Database.lua | 21 +++++++++++++++++++-- Moose Development/Moose/Wrapper/Airbase.lua | 19 +++++++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index bb5d65987..6270f1fa6 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1582,12 +1582,29 @@ end -- @param DCS#Airbase airbase Airbase. -- @return #DATABASE self function DATABASE:_RegisterAirbase(airbase) - + + local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false + local countHSyria = 0 + if airbase then -- Get the airbase name. local DCSAirbaseName = airbase:getName() - + + -- DCS 2.9.8.1107 added 143 helipads all named H with the same object ID .. + if IsSyria and DCSAirbaseName == "H" and countHSyria > 0 then + --[[ + local p = airbase:getPosition().p + local mgrs = COORDINATE:New(p.x,p.z,p.y):ToStringMGRS() + self:I("Airbase on Syria map named H @ "..mgrs) + countHSyria = countHSyria + 1 + if countHSyria > 1 then return self end + --]] + return self + elseif IsSyria and DCSAirbaseName == "H" and countHSyria == 0 then + countHSyria = countHSyria + 1 + end + -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. local airbaseID=airbase:getID() diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 4759ebf2f..21cf26938 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -63,6 +63,11 @@ -- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, -- the first letter of the method is also capitalized. So, by example, the DCS Airbase method DCSWrapper.Airbase#Airbase.getName() -- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). +-- +-- ## Note on the "H" heli pads in the Syria map: +-- +-- As of the time of writing (Oct 2024, DCS DCS 2.9.8.1107), these 143 objects have the **same name and object ID**, which makes them unusable in Moose, e.g. you cannot find a specific one for spawning etc. +-- Waiting for Ugra and ED to fix this issue. -- -- @field #AIRBASE AIRBASE AIRBASE = { @@ -450,6 +455,7 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Gaziantep -- * AIRBASE.Syria.Gazipasa -- * AIRBASE.Syria.Gecitkale +-- * AIRBASE.Syria.H -- * AIRBASE.Syria.H3 -- * AIRBASE.Syria.H3_Northwest -- * AIRBASE.Syria.H3_Southwest @@ -518,6 +524,7 @@ AIRBASE.Syria={ ["Gaziantep"] = "Gaziantep", ["Gazipasa"] = "Gazipasa", ["Gecitkale"] = "Gecitkale", + ["H"] = "H", ["H3"] = "H3", ["H3_Northwest"] = "H3 Northwest", ["H3_Southwest"] = "H3 Southwest", @@ -752,12 +759,14 @@ AIRBASE.Sinai = { -- -- * AIRBASE.Kola.Banak -- * AIRBASE.Kola.Bodo +-- * AIRBASE.Kola.Ivalo -- * AIRBASE.Kola.Jokkmokk -- * AIRBASE.Kola.Kalixfors -- * AIRBASE.Kola.Kallax -- * AIRBASE.Kola.Kemi_Tornio -- * AIRBASE.Kola.Kirkenes -- * AIRBASE.Kola.Kiruna +-- * AIRBASE.Kola.Kuusamo -- * AIRBASE.Kola.Monchegorsk -- * AIRBASE.Kola.Murmansk_International -- * AIRBASE.Kola.Olenya @@ -771,20 +780,22 @@ AIRBASE.Sinai = { AIRBASE.Kola = { ["Banak"] = "Banak", ["Bodo"] = "Bodo", + ["Ivalo"] = "Ivalo", ["Jokkmokk"] = "Jokkmokk", ["Kalixfors"] = "Kalixfors", + ["Kallax"] = "Kallax", ["Kemi_Tornio"] = "Kemi Tornio", + ["Kirkenes"] = "Kirkenes", ["Kiruna"] = "Kiruna", + ["Kuusamo"] = "Kuusamo", ["Monchegorsk"] = "Monchegorsk", ["Murmansk_International"] = "Murmansk International", ["Olenya"] = "Olenya", ["Rovaniemi"] = "Rovaniemi", ["Severomorsk_1"] = "Severomorsk-1", ["Severomorsk_3"] = "Severomorsk-3", - ["Vuojarvi"] = "Vuojarvi", - ["Kirkenes"] = "Kirkenes", - ["Kallax"] = "Kallax", ["Vidsel"] = "Vidsel", + ["Vuojarvi"] = "Vuojarvi", } --- Airbases of the Afghanistan map @@ -926,7 +937,7 @@ function AIRBASE:Register(AirbaseName) -- Debug info. --self:I({airbase=AirbaseName, descriptors=self.descriptors}) - + -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME From 2dce709db1604552d85b4b0d50fe5344013d59e1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Oct 2024 14:02:14 +0200 Subject: [PATCH 064/349] Some fixes --- Moose Development/Moose/Functional/Sead.lua | 31 ++++++----- Moose Development/Moose/Ops/Awacs.lua | 22 +++++++- Moose Development/Moose/Sound/SRS.lua | 62 +++++++++++++-------- 3 files changed, 75 insertions(+), 40 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 314c9422f..ac89dddb4 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -155,7 +155,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) self:AddTransition("*", "ManageEvasion", "*") self:AddTransition("*", "CalculateHitZone", "*") - self:I("*** SEAD - Started Version 0.4.7") + self:I("*** SEAD - Started Version 0.4.8") return self end @@ -463,21 +463,24 @@ end -- @return #SEAD self function SEAD:HandleEventShot( EventData ) self:T( { EventData.id } ) - local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT - local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP - local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE - local SEADUnit = EventData.IniDCSUnit - local SEADUnitName = EventData.IniDCSUnitName - local SEADWeapon = EventData.Weapon -- Identify the weapon fired - local SEADWeaponName = EventData.WeaponName -- return weapon type - - local WeaponWrapper = WEAPON:New(EventData.Weapon) -- Wrapper.Weapon#WEAPON - --local SEADWeaponSpeed = WeaponWrapper:GetSpeed() -- mps - self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) - --self:T({ SEADWeapon }) - + local SEADWeapon = EventData.Weapon -- Identify the weapon fired + local SEADWeaponName = EventData.WeaponName or "None" -- return weapon type + if self:_CheckHarms(SEADWeaponName) then + local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT + + if not SEADPlane then return self end -- case IniUnit is empty + + local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP + local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE + local SEADUnit = EventData.IniDCSUnit + local SEADUnitName = EventData.IniDCSUnitName + + local WeaponWrapper = WEAPON:New(EventData.Weapon) -- Wrapper.Weapon#WEAPON + + self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) + self:T( '*** SEAD - Weapon Match' ) if self.WeaponTrack == true then WeaponWrapper:SetFuncTrack(function(weapon) env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed() or -1)) end) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 3b048ada3..e04a1060d 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -122,6 +122,7 @@ do -- @field #number TacticalModulation -- @field #number TacticalInterval -- @field Core.Set#SET_GROUP DetectionSet +-- @field #number MaxMissionRange -- @extends Core.Fsm#FSM @@ -508,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.65", -- #string + version = "0.2.66", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -605,6 +606,7 @@ AWACS = { TacticalModulation = radio.modulation.AM, TacticalInterval = 120, DetectionSet = nil, + MaxMissionRange = 125, } --- @@ -1572,6 +1574,15 @@ function AWACS:SetLocale(Locale) return self end +--- [User] Set the max mission range flights can be away from their home base. +-- @param #AWACS self +-- @param #number NM Distance in nautical miles +-- @return #AWACS self +function AWACS:SetMaxMissionRange(NM) + self.MaxMissionRange = NM or 125 + return self +end + --- [User] Add additional frequency and modulation for AWACS SRS output. -- @param #AWACS self -- @param #number Frequency The frequency to add, e.g. 132.5 @@ -2228,6 +2239,7 @@ function AWACS:_StartEscorts(Shiftchange) local escort = AUFTRAG:NewESCORT(group, {x= -100*((i + (i%2))/2), y=0, z=(100 + 100*((i + (i%2))/2))*(-1)^i},45,{"Air"}) escort:SetRequiredAssets(1) escort:SetTime(nil,timeonstation) + escort:SetMissionRange(self.MaxMissionRange) self.AirWing:AddMission(escort) self.CatchAllMissions[#self.CatchAllMissions+1] = escort @@ -5739,7 +5751,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) local intercept = AUFTRAG:NewINTERCEPT(Target.Target) intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL) intercept:SetWeaponType(ENUMS.WeaponFlag.Auto) - + intercept:SetMissionRange(self.MaxMissionRange) -- TODO -- now this is going to be interesting... -- Check if the target left the "hot" area or is dead already @@ -5787,6 +5799,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) AnchorSpeed = UTILS.KnotsToAltKIAS(AnchorSpeed,Angels) local Anchor = self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -- #AWACS.AnchorData local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{}) + capauftrag:SetMissionRange(self.MaxMissionRange) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) Pilot.FlightGroup:AddMission(capauftrag) @@ -5904,6 +5917,7 @@ function AWACS:onafterStart(From, Event, To) -- set up the AWACS and let it orbit local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) + mission:SetMissionRange(self.MaxMissionRange) local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 mission:SetTime(nil,timeonstation) self.CatchAllMissions[#self.CatchAllMissions+1] = mission @@ -6477,6 +6491,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo if auftragtype == AUFTRAG.Type.ALERT5 then -- all correct local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{}) + capauftrag:SetMissionRange(self.MaxMissionRange) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) capauftrag:AddAsset(managedgroup.FlightGroup) self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag @@ -6840,7 +6855,8 @@ function AWACS:onafterAwacsShiftChange(From,Event,To) self.CatchAllMissions[#self.CatchAllMissions+1] = mission local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 mission:SetTime(nil,timeonstation) - + mission:SetMissionRange(self.MaxMissionRange) + AwacsAW:AddMission(mission) self.AwacsMissionReplacement = mission diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 3f621d594..461c80aae 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -52,6 +52,7 @@ -- @field #table poptions Provider options. Each element is a data structure of type `MSRS.ProvierOptions`. -- @field #string provider Provider of TTS (win, gcloud, azure, amazon). -- @field #string backend Backend used as interface to SRS (MSRS.Backend.SRSEXE or MSRS.Backend.GRPC). +-- @field #boolean UsePowerShell Use PowerShell to execute the command and not cmd.exe -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -256,11 +257,12 @@ MSRS = { ConfigFilePath = "Config\\", ConfigLoaded = false, poptions = {}, + UsePowerShell = false, } --- MSRS class version. -- @field #string version -MSRS.version="0.3.0" +MSRS.version="0.3.2" --- Voices -- @type MSRS.Voices @@ -588,7 +590,7 @@ function MSRS:SetBackendSRSEXE() end --- Set the default backend. --- @param #MSRS self +-- @param #string Backend function MSRS.SetDefaultBackend(Backend) MSRS.backend=Backend or MSRS.Backend.SRSEXE end @@ -1375,20 +1377,25 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp modus=modus:gsub("1", "FM") -- Command. + local pwsh = string.format('Start-Process -WindowStyle Hidden -WorkingDirectory \"%s\" -FilePath \"%s\" -ArgumentList \'-f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume ) + local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume) -- Set voice or gender/culture. - if voice then + if voice and self.UsePowerShell ~= true then -- Use a specific voice (no need for gender and/or culture. command=command..string.format(" --voice=\"%s\"", tostring(voice)) + pwsh=pwsh..string.format(" --voice=\"%s\"", tostring(voice)) else -- Add gender. if gender and gender~="female" then command=command..string.format(" -g %s", tostring(gender)) + pwsh=pwsh..string.format(" -g %s", tostring(gender)) end -- Add culture. if culture and culture~="en-GB" then command=command..string.format(" -l %s", tostring(culture)) + pwsh=pwsh..string.format(" -l %s", tostring(culture)) end end @@ -1396,12 +1403,14 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp if coordinate then local lat,lon,alt=self:_GetLatLongAlt(coordinate) command=command..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt) + pwsh=pwsh..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt) end -- Set provider options if self.provider==MSRS.Provider.GOOGLE then local pops=self:GetProviderOptions() command=command..string.format(' --ssml -G "%s"', pops.credentials) + pwsh=pwsh..string.format(' --ssml -G "%s"', pops.credentials) elseif self.provider==MSRS.Provider.WINDOWS then -- Nothing to do. else @@ -1415,8 +1424,12 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp -- Debug output. self:T("MSRS command from _GetCommand="..command) - - return command + + if self.UsePowerShell == true then + return pwsh + else + return command + end end --- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. @@ -1424,7 +1437,7 @@ end -- @param #string command Command to executer -- @return #number Return value of os.execute() command. function MSRS:_ExecCommand(command) - self:F( {command=command} ) + self:T2( {command=command} ) -- Skip this function if _GetCommand was not able to find the executable if string.find(command, "CommandNotFound") then return 0 end @@ -1432,7 +1445,13 @@ function MSRS:_ExecCommand(command) local batContent = command.." && exit" -- Create a tmp file. local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat" - + + if self.UsePowerShell == true then + filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".ps1" + batContent = command .. "\'" + self:I({batContent=batContent}) + end + local script=io.open(filename, "w+") script:write(batContent) script:close() @@ -1441,7 +1460,7 @@ function MSRS:_ExecCommand(command) self:T("MSRS batch content: "..batContent) local res=nil - if true then + if self.UsePowerShell ~= true then -- Create a tmp file. local filenvbs = os.getenv('TMP') .. "\\MSRS-"..MSRS.uuid()..".vbs" @@ -1469,23 +1488,20 @@ function MSRS:_ExecCommand(command) timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1) self:T("MSRS vbs and batch file removed") - elseif false then - - -- Create a tmp file. - local filenvbs = os.getenv('TMP') .. "\\MSRS-"..MSRS.uuid()..".vbs" - - -- VBS script - local script = io.open(filenvbs, "w+") - script:write(string.format('Set oShell = CreateObject ("Wscript.Shell")\n')) - script:write(string.format('Dim strArgs\n')) - script:write(string.format('strArgs = "cmd /c %s"\n', filename)) - script:write(string.format('oShell.Run strArgs, 0, false')) - script:close() - - local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs) + elseif self.UsePowerShell == true then + local pwsh = string.format('powershell.exe -ExecutionPolicy Unrestricted -WindowStyle Hidden -Command "%s"',filename) + --env.info("[MSRS] TextToSpeech Command :\n" .. pwsh.."\n") + + if string.len(pwsh) > 255 then + self:E("[MSRS] - pwsh string too long") + end + -- Play file in 0.01 seconds - res=os.execute(runvbs) + res=os.execute(pwsh) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) else -- Play command. From d613f59ca8e4590ae1be02e95e34295e39a25a75 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 8 Oct 2024 10:03:45 +0200 Subject: [PATCH 065/349] #AWACS Added an option to set own function to create player callsigns from group or playernames --- Moose Development/Moose/Ops/Awacs.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index e04a1060d..b450bebc1 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -17,7 +17,7 @@ -- === -- -- ### Author: **applevangelist** --- @date Last Update July 2024 +-- @date Last Update Oct 2024 -- @module Ops.AWACS -- @image OPS_AWACS.jpg @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.66", -- #string + version = "0.2.67", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -2446,7 +2446,7 @@ function AWACS:_GetCallSign(Group,GID, IsPlayer) local callsign = "Ghost 1" if Group and Group:IsAlive() then - callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations) + callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations,self.callsignCustomFunc,self.callsignCustomArgs) end return callsign end @@ -2455,10 +2455,12 @@ end -- @param #AWACS self -- @param #boolean ShortCallsign If true, only call out the major flight number -- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name as-is, no amendments or numbers. --- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized +-- @param #table CallsignTranslations (Optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized. -- callsigns from playername or group name. +-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername. -- @return #AWACS self -function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign == false then self.callsignshort = false else @@ -2466,6 +2468,8 @@ function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) end self.keepnumber = Keepnumber or false self.callsignTranslations = CallsignTranslations + self.callsignCustomFunc = CallsignCustomFunc + self.callsignCustomArgs = arg or {} return self end From 98a82c59594bd6931f3bcdba503fa4d741cf1427 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 27 Oct 2024 13:24:45 +0100 Subject: [PATCH 066/349] xx --- Moose Development/Moose/Core/Base.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 94d13a9a0..76e516799 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -1416,7 +1416,7 @@ function BASE:E( Arguments ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) else - env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) ) + env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments) ) ) end end @@ -1443,7 +1443,7 @@ function BASE:I( Arguments ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) else - env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, BASE:_Serialize(Arguments)) ) + env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments)) ) end end From 8a83f69e9ca3a86a4134250156575bb4a61ed417 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Oct 2024 11:38:06 +0100 Subject: [PATCH 067/349] xxx --- .../Moose/Functional/ATC_Ground.lua | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 8953bd13c..0d025d492 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -18,7 +18,7 @@ -- ### Author: FlightControl - Framework Design & Programming -- ### Refactoring to use the Runway auto-detection: Applevangelist -- @date August 2022 --- Last Update Nov 2023 +-- Last Update Oct 2024 -- -- === -- @@ -721,6 +721,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() if NotInRunwayZone then + if IsOnGround then local Taxi = Client:GetState( self, "Taxi" ) self:T( Taxi ) @@ -729,6 +730,8 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " .. Velocity:ToString() , 20, "ATC" ) Client:SetState( self, "Taxi", true ) + Client:SetState( self, "Speeding", false ) + Client:SetState( self, "Warnings", 0 ) end -- TODO: GetVelocityKMH function usage @@ -749,15 +752,15 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() end end if Speeding == true then - MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. - " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() - Client:Destroy() - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) + --MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. + -- " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() + --Client:Destroy() + Client:SetState( self, "Speeding", true ) + local SpeedingWarnings = Client:GetState( self, "Warnings" ) + Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) end end - - + if IsOnGround then local Speeding = false From 9d469681032b8ea483ef27851d05055659229efd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Oct 2024 13:18:00 +0100 Subject: [PATCH 068/349] xxx --- Moose Development/Moose/Ops/CTLD.lua | 39 +++++++++++++++-------- Moose Development/Moose/Wrapper/Group.lua | 3 +- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4318942c2..d582e9b4f 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1343,7 +1343,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.17" +CTLD.version="1.1.18" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2429,6 +2429,7 @@ end local nearestGroup = nil local nearestGroupIndex = -1 local nearestDistance = 10000000 + local maxdistance = 0 local nearestList = {} local distancekeys = {} local extractdistance = self.CrateDistance * self.ExtractFactor @@ -2440,8 +2441,14 @@ end nearestGroup = v nearestGroupIndex = k nearestDistance = distance + if math.floor(distance) > maxdistance then maxdistance = math.floor(distance) end + if nearestList[math.floor(distance)] then + distance = maxdistance+1 + maxdistance = distance + end table.insert(nearestList, math.floor(distance), v) distancekeys[#distancekeys+1] = math.floor(distance) + --self:I(string.format("Adding group %s distance %dm",nearestGroup:GetName(),distance)) end end @@ -2494,7 +2501,7 @@ end nearestGroup.ExtractTime = timer.getTime() local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, Cargotype.CargoType, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) self:T({cargotype=loadcargotype}) - local running = math.floor(nearestDistance / 4)+10 -- time run to helo plus boarding + local running = math.floor(nearestDistance / 4)+20 -- time run to helo plus boarding loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded @@ -2509,26 +2516,32 @@ end local Angle = math.floor((heading+160)%360) Point = coord:Translate(8,Angle):GetVec2() if Point then - nearestGroup:RouteToVec2(Point,4) + nearestGroup:RouteToVec2(Point,5) end end -- clean up: - if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then + local hassecondaries = false + if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then for _,_key in pairs (Cargotype.Templates) do table.insert(secondarygroups,_key) + hassecondaries = true end end - nearestGroup:Destroy(false,running) + local destroytimer = math.random(10,20) + --self:I("Destroying Group "..nearestGroup:GetName().." in "..destroytimer.." seconds!") + nearestGroup:Destroy(false,destroytimer) end end end -- clean up secondary groups - for _,_name in pairs(secondarygroups) do - for _,_group in pairs(nearestList) do - if _group and _group:IsAlive() then - local groupname = string.match(_group:GetName(), "(.+)-(.+)$") - if _name == groupname then - _group:Destroy(false,15) + if hassecondaries == true then + for _,_name in pairs(secondarygroups) do + for _,_group in pairs(nearestList) do + if _group and _group:IsAlive() then + local groupname = string.match(_group:GetName(), "(.+)-(.+)$") + if _name == groupname then + _group:Destroy(false,15) + end end end end @@ -3412,8 +3425,8 @@ function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template) local template = _DATABASE:GetGroupTemplate(Template) --UTILS.PrintTableToLog(template) local numbertroops = #template.units - local slightshift = math.abs(math.random(0,200)/100) - local newcenter = Coordinate:Translate(Radius+slightshift,((Heading+270)%360)) + local slightshift = math.abs(math.random(1,500)/100) + local newcenter = Coordinate:Translate(Radius+slightshift,((Heading+270+math.random(1,10))%360)) for i=1,360,math.floor(360/numbertroops) do local phead = ((Heading+270+i)%360) local post = newcenter:Translate(Radius,phead) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index b3217106d..bd4170a3e 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -485,7 +485,8 @@ function GROUP:Destroy( GenerateEvent, delay ) self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent) else - local DCSGroup = self:GetDCSObject() + --local DCSGroup = self:GetDCSObject() + local DCSGroup = Group.getByName( self.GroupName ) if DCSGroup then for Index, UnitData in pairs( DCSGroup:getUnits() ) do From 20b0902768120d261e0ffe1571da61e4f5a2a3c2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Oct 2024 13:56:28 +0100 Subject: [PATCH 069/349] xxx --- Moose Development/Moose/Core/Event.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index bb9c9941c..9ae9d6d28 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1474,6 +1474,7 @@ function EVENT:onEvent( Event ) --- Event.TgtDCSUnit = Event.target Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + if Event.TgtDCSUnitName==nil then return end Event.TgtUnitName = Event.TgtDCSUnitName Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) Event.TgtCategory = Event.TgtDCSUnit:getDesc().category From e45d22ad35eaf517fc39e1f0cebd78cf6eff517e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 31 Oct 2024 16:05:03 +0100 Subject: [PATCH 070/349] xx --- .../Moose/Wrapper/Controllable.lua | 21 ++++++++++--------- Moose Development/Moose/Wrapper/Group.lua | 14 ++++++------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 9ee8844be..d298cece5 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -899,13 +899,13 @@ function CONTROLLABLE:CommandEPLRS( SwitchOnOff, Delay ) id = 'EPLRS', params = { value = SwitchOnOff, - groupId = nil, + groupId = self:GetID(), }, } - if self:IsGround() then - CommandEPLRS.params.groupId = self:GetID() - end + --if self:IsGround() then + --CommandEPLRS.params.groupId = self:GetID() + --end if Delay and Delay > 0 then SCHEDULER:New( nil, self.CommandEPLRS, { self, SwitchOnOff }, Delay ) @@ -941,7 +941,7 @@ function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff, Delay) end ---- Set radio frequency. See [DCS command EPLRS](https://wiki.hoggitworld.com/view/DCS_command_setFrequency) +--- Set radio frequency. See [DCS command SetFrequency](https://wiki.hoggitworld.com/view/DCS_command_setFrequency) -- @param #CONTROLLABLE self -- @param #number Frequency Radio frequency in MHz. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. @@ -968,7 +968,7 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay ) return self end ---- [AIR] Set radio frequency. See [DCS command EPLRS](https://wiki.hoggitworld.com/view/DCS_command_setFrequencyForUnit) +--- [AIR] Set radio frequency. See [DCS command SetFrequencyForUnit](https://wiki.hoggitworld.com/view/DCS_command_setFrequencyForUnit) -- @param #CONTROLLABLE self -- @param #number Frequency Radio frequency in MHz. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. @@ -1010,12 +1010,13 @@ function CONTROLLABLE:TaskEPLRS( SwitchOnOff, idx ) id = 'EPLRS', params = { value = SwitchOnOff, - groupId = nil, + groupId = self:GetID(), }, } - if self:IsGround() then - CommandEPLRS.params.groupId = self:GetID() - end + + --if self:IsGround() then + --CommandEPLRS.params.groupId = self:GetID() + --end return self:TaskWrappedAction( CommandEPLRS, idx or 1 ) end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index bd4170a3e..bb0bf80f1 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -360,7 +360,7 @@ end -- @return DCS#Group The DCS Group. function GROUP:GetDCSObject() - if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) then + --if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) then -- Get DCS group. local DCSGroup = Group.getByName( self.GroupName ) @@ -369,14 +369,14 @@ function GROUP:GetDCSObject() self.LastCallDCSObject = timer.getTime() self.DCSObject = DCSGroup return DCSGroup - else - self.DCSObject = nil - self.LastCallDCSObject = nil + -- else + -- self.DCSObject = nil + -- self.LastCallDCSObject = nil end - else - return self.DCSObject - end + --else + --return self.DCSObject + --end --self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName))) return nil From 1307e53833ffaedb8b242e58a6e98ff2b1507787 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 31 Oct 2024 18:15:23 +0100 Subject: [PATCH 071/349] xx --- Moose Development/Moose/Wrapper/Controllable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index d298cece5..908631317 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -960,7 +960,7 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay ) } if Delay and Delay > 0 then - SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation, Power } ) + SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation, Power },Delay ) else self:SetCommand( CommandSetFrequency ) end @@ -987,7 +987,7 @@ function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,Unit }, } if Delay and Delay>0 then - SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID}) + SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID},Delay) else self:SetCommand(CommandSetFrequencyForUnit) end From 954190f6dfffdc3d1a326963f87ce96b79f3b92e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 7 Nov 2024 09:05:53 +0100 Subject: [PATCH 072/349] xx --- Moose Development/Moose/Core/Point.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index fd990d7fb..169140b4d 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2410,7 +2410,7 @@ do -- COORDINATE for i,coord in ipairs(Coordinates) do vecs[i+1]=coord:GetVec3() end - + if #vecs<3 then self:E("ERROR: A free form polygon needs at least three points!") elseif #vecs==3 then From cea3ab291e8f44d03c2222e49d8caf7c92876961 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 17 Nov 2024 16:22:32 +0100 Subject: [PATCH 073/349] xxx --- Moose Development/Moose/Wrapper/Net.lua | 32 +++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua index a2b553019..087448a9f 100644 --- a/Moose Development/Moose/Wrapper/Net.lua +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -43,7 +43,7 @@ do -- @field #NET NET = { ClassName = "NET", - Version = "0.1.3", + Version = "0.1.4", BlockTime = 600, BlockedPilots = {}, BlockedUCIDs = {}, @@ -67,6 +67,9 @@ function NET:New() self.KnownPilots = {} self:SetBlockMessage() self:SetUnblockMessage() + self.BlockedSides = {} + self.BlockedSides[1] = false + self.BlockedSides[2] = false -- Start State. self:SetStartState("Stopped") @@ -160,11 +163,12 @@ end -- @param #string PlayerSlot -- @return #boolean IsBlocked function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) + self:T({UCID,Name,PlayerID,PlayerSide,PlayerSlot}) local blocked = false local TNow = timer.getTime() -- UCID if UCID and self.BlockedUCIDs[UCID] and TNow < self.BlockedUCIDs[UCID] then - return true + blocked = true end -- ID/Name if PlayerID and not Name then @@ -172,16 +176,18 @@ function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) end -- Name if Name and self.BlockedPilots[Name] and TNow < self.BlockedPilots[Name] then - return true + blocked = true end -- Side - if PlayerSide and self.BlockedSides[PlayerSide] and TNow < self.BlockedSides[PlayerSide] then - return true + self:T({time = self.BlockedSides[PlayerSide]}) + if PlayerSide and type(self.BlockedSides[PlayerSide]) == "number" and TNow < self.BlockedSides[PlayerSide] then + blocked = true end -- Slot if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then - return true + blocked = true end + self:T("IsAnyBlocke: "..tostring(blocked)) return blocked end @@ -200,6 +206,7 @@ function NET:_EventHandler(EventData) local ucid = self:GetPlayerUCID(nil,name) or "none" local PlayerID = self:GetPlayerIDByName(name) or "none" local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit) + if not PlayerSide then PlayerSide = EventData.IniCoalition end local TNow = timer.getTime() self:T(self.lid.."Event for: "..name.." | UCID: "..ucid) @@ -225,6 +232,7 @@ function NET:_EventHandler(EventData) slot = PlayerSlot, timestamp = TNow, } + UTILS.PrintTableToLog(self.KnownPilots[name]) end return self end @@ -354,7 +362,6 @@ end -- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining. -- @return #NET self function NET:BlockSide(Side,Seconds) - self:T({Side,Seconds}) local addon = Seconds or self.BlockTime if Side == 1 or Side == 2 then self.BlockedSides[Side] = timer.getTime()+addon @@ -367,10 +374,9 @@ end -- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining. -- @return #NET self function NET:UnblockSide(Side,Seconds) - self:T({Side,Seconds}) local addon = Seconds or self.BlockTime if Side == 1 or Side == 2 then - self.BlockedSides[Side] = nil + self.BlockedSides[Side] = false end return self end @@ -485,8 +491,11 @@ end -- @param Wrapper.Client#CLIENT Client The client -- @return #number PlayerID or nil function NET:GetPlayerIDFromClient(Client) + self:T("GetPlayerIDFromClient") + self:T({Client=Client}) if Client then local name = Client:GetPlayerName() + self:T({name=name}) local id = self:GetPlayerIDByName(name) return id else @@ -682,9 +691,12 @@ end -- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue -- @return #number SlotID function NET:GetSlot(Client) + self:T("NET.GetSlot") local PlayerID = self:GetPlayerIDFromClient(Client) + self:T("NET.GetSlot PlayerID = "..tostring(PlayerID)) if PlayerID then local side,slot = net.get_slot(tonumber(PlayerID)) + self:T("NET.GetSlot side, slot = "..tostring(side)..","..tostring(slot)) return side,slot else return nil,nil @@ -781,7 +793,7 @@ function NET:onafterStatus(From,Event,To) local function HouseHold(tavolo) local TNow = timer.getTime() for _,entry in pairs (tavolo) do - if entry >= TNow then entry = nil end + if type(entry) == "number" and entry >= TNow then entry = false end end end From 7ba360a005b4ac5e2bc39917d1df88f519783d33 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 18 Nov 2024 08:54:10 +0100 Subject: [PATCH 074/349] Documentation --- Moose Development/Moose/Ops/PlayerTask.lua | 29 ++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 25d53d788..6e5f33ce0 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update May 2024 +-- @date Last Update Nov 2024 do @@ -274,7 +274,9 @@ function PLAYERTASK:NewFromTarget(Target, Repeat, Times, TTSType) end --- [Internal] Determines AUFTRAG type based on the target characteristics. --- @return #AUFTRAG.Type self +-- @param #PLAYERTASK self +-- @param Ops.Target#TARGET Target Target for this task +-- @return #string AUFTRAG.Type function PLAYERTASK:_GetTaskTypeForTarget(Target) local group = nil --Wrapper.Group#GROUP @@ -1310,11 +1312,34 @@ do -- * Anti-Ship - Any ship targets, if the controller is of type "A2S" -- * CTLD - Combat transport and logistics deployment -- * CSAR - Combat search and rescue +-- * RECON - Identify targets +-- * CAPTUREZONE - Capture an Ops.OpsZone#OPSZONE +-- * Any #string name can be passed as Auftrag type, but then you need to make sure to define a success condition, and possibly also add the task type to the standard scoring list: `PLAYERTASKCONTROLLER.Scores["yournamehere"]=100` -- -- ## 3 Task repetition -- -- On failure, tasks will be replanned by default for a maximum of 5 times. -- +-- ## 3.1 Pre-configured success conditions +-- +-- Pre-configured success conditions for #PLAYERTASK tasks are available as follows: +-- +-- `mytask:AddStaticObjectSuccessCondition()` -- success if static object is at least 80% dead +-- +-- `mytask:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix,Coalition)` -- success if a squad of the given (partial) name and coalition captures the OpsZone +-- +-- `mytask:AddReconSuccessCondition(MinDistance)` -- success if object is in line-of-sight with the given min distance in NM +-- +-- `mytask:AddTimeLimitSuccessCondition(TimeLimit)` -- failure if the task is not completed within the time limit in seconds given +-- +-- ## 3.2 Task chaining +-- +-- You can create chains of tasks, which will depend on success or failure of the previous task with the following commands: +-- +-- `mytask:AddNextTaskAfterSuccess(FollowUpTask)` and +-- +-- `mytask:AddNextTaskAfterFailure(FollowUpTask)` +-- -- ## 4 SETTINGS, SRS and language options (localization) -- -- The system can optionally communicate to players via SRS. Also localization is available, both "en" and "de" has been build in already. From 633f990b361cfc28b93c73f4e4a2e5124561868a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 26 Nov 2024 08:05:40 +0100 Subject: [PATCH 075/349] xxx --- Moose Development/Moose/Wrapper/Net.lua | 32 ++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua index 087448a9f..7316075fd 100644 --- a/Moose Development/Moose/Wrapper/Net.lua +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -187,7 +187,7 @@ function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then blocked = true end - self:T("IsAnyBlocke: "..tostring(blocked)) + self:T("IsAnyBlocked: "..tostring(blocked)) return blocked end @@ -207,19 +207,26 @@ function NET:_EventHandler(EventData) local PlayerID = self:GetPlayerIDByName(name) or "none" local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit) if not PlayerSide then PlayerSide = EventData.IniCoalition end + if not PlayerSlot then PlayerSlot = EventData.IniUnit:GetID() end local TNow = timer.getTime() - self:T(self.lid.."Event for: "..name.." | UCID: "..ucid) + self:T(self.lid.."Event for: "..name.." | UCID: "..ucid .. " | ID/SIDE/SLOT "..PlayerID.."/"..PlayerSide.."/"..PlayerSlot) -- Joining if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id) -- Check for blockages local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot) - - if blocked and PlayerID and tonumber(PlayerID) ~= 1 then + if blocked and PlayerID then -- and tonumber(PlayerID) ~= 1 then + self:T("Player blocked") -- block pilot - local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' ) + local outcome = net.force_player_slot(tonumber(PlayerID), PlayerSide, data.IniUnit:GetID() ) + self:T({Blocked_worked=outcome}) + if outcome == false then + local unit = data.IniUnit + local sched = TIMER:New(unit.Destroy,unit,3):Start(3) + self:__PlayerBlocked(5,unit,name,1) + end else local client = CLIENT:FindByPlayerName(name) or data.IniUnit if not self.KnownPilots[name] or (self.KnownPilots[name] and TNow-self.KnownPilots[name].timestamp > 3) then @@ -232,7 +239,7 @@ function NET:_EventHandler(EventData) slot = PlayerSlot, timestamp = TNow, } - UTILS.PrintTableToLog(self.KnownPilots[name]) + --UTILS.PrintTableToLog(self.KnownPilots[name]) end return self end @@ -358,7 +365,7 @@ end --- Block a specific coalition side, does NOT automatically kick all players of that side or kick out joined players -- @param #NET self --- @param #number side The side to block - 1 : Red, 2 : Blue +-- @param #number Side The side to block - 1 : Red, 2 : Blue -- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining. -- @return #NET self function NET:BlockSide(Side,Seconds) @@ -703,7 +710,7 @@ function NET:GetSlot(Client) end end ---- Force the slot for a specific client. +--- Force the slot for a specific client. If this returns false, it didn't work via `net` (which is ALWAYS the case as of Nov 2024)! -- @param #NET self -- @param Wrapper.Client#CLIENT Client The client -- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue @@ -711,19 +718,22 @@ end -- @return #boolean Success function NET:ForceSlot(Client,SideID,SlotID) local PlayerID = self:GetPlayerIDFromClient(Client) - if PlayerID and tonumber(PlayerID) ~= 1 then - return net.force_player_slot(tonumber(PlayerID), SideID, SlotID or '' ) + local SlotID = SlotID or Client:GetID() + if PlayerID then -- and tonumber(PlayerID) ~= 1 then + return net.force_player_slot(tonumber(PlayerID), SideID, SlotID ) else return false end end ---- Force a client back to spectators. +--- Force a client back to spectators. If this returns false, it didn't work via `net` (which is ALWAYS the case as of Nov 2024)! -- @param #NET self -- @param Wrapper.Client#CLIENT Client The client -- @return #boolean Succes function NET:ReturnToSpectators(Client) local outcome = self:ForceSlot(Client,0) + -- workaround + local sched = TIMER:New(Client.Destroy,Client,1):Start(1) return outcome end From 25740f7e6103f4c736a40030df1a192982128963 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 26 Nov 2024 10:40:46 +0100 Subject: [PATCH 076/349] xx --- Moose Development/Moose/Wrapper/Group.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index bb0bf80f1..7be201af0 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2337,8 +2337,11 @@ end -- @return #table The mission route defined by points. function GROUP:GetTaskRoute() --self:F2( self.GroupName ) - - return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) + if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then + return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) + else + return {} + end end --- Return the route of a group by using the global _DATABASE object (an instance of @{Core.Database#DATABASE}). From c55732a02f0f1df051662de70e7049ad80207a0c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 30 Nov 2024 12:49:36 +0100 Subject: [PATCH 077/349] Leaner new version --- Moose Development/Moose/AI/AI_A2A_Cap.lua | 218 - .../Moose/AI/AI_A2A_Dispatcher.lua | 4518 ---------------- Moose Development/Moose/AI/AI_A2A_Gci.lua | 147 - Moose Development/Moose/AI/AI_A2A_Patrol.lua | 410 -- Moose Development/Moose/AI/AI_A2G_BAI.lua | 96 - Moose Development/Moose/AI/AI_A2G_CAS.lua | 96 - .../Moose/AI/AI_A2G_Dispatcher.lua | 4792 ----------------- Moose Development/Moose/AI/AI_A2G_SEAD.lua | 131 - Moose Development/Moose/AI/AI_Air.lua | 840 --- .../Moose/AI/AI_Air_Dispatcher.lua | 3239 ----------- Moose Development/Moose/AI/AI_Air_Engage.lua | 603 --- Moose Development/Moose/AI/AI_Air_Patrol.lua | 391 -- .../Moose/AI/AI_Air_Squadron.lua | 294 - Moose Development/Moose/AI/AI_BAI.lua | 652 --- Moose Development/Moose/AI/AI_Balancer.lua | 314 -- Moose Development/Moose/AI/AI_CAP.lua | 541 -- Moose Development/Moose/AI/AI_CAS.lua | 570 -- Moose Development/Moose/AI/AI_Cargo.lua | 589 -- Moose Development/Moose/AI/AI_Cargo_APC.lua | 607 --- .../Moose/AI/AI_Cargo_Airplane.lua | 510 -- .../Moose/AI/AI_Cargo_Dispatcher.lua | 1238 ----- .../Moose/AI/AI_Cargo_Dispatcher_APC.lua | 263 - .../Moose/AI/AI_Cargo_Dispatcher_Airplane.lua | 169 - .../AI/AI_Cargo_Dispatcher_Helicopter.lua | 199 - .../Moose/AI/AI_Cargo_Dispatcher_Ship.lua | 198 - .../Moose/AI/AI_Cargo_Helicopter.lua | 661 --- Moose Development/Moose/AI/AI_Cargo_Ship.lua | 402 -- Moose Development/Moose/AI/AI_Escort.lua | 2191 -------- .../Moose/AI/AI_Escort_Dispatcher.lua | 190 - .../Moose/AI/AI_Escort_Dispatcher_Request.lua | 151 - .../Moose/AI/AI_Escort_Request.lua | 321 -- Moose Development/Moose/AI/AI_Formation.lua | 1279 ----- Moose Development/Moose/AI/AI_Patrol.lua | 939 ---- .../Moose/Actions/Act_Account.lua | 310 -- .../Moose/Actions/Act_Assign.lua | 292 - .../Moose/Actions/Act_Assist.lua | 219 - Moose Development/Moose/Actions/Act_Route.lua | 483 -- Moose Development/Moose/Cargo/Cargo.lua | 1399 ----- Moose Development/Moose/Cargo/CargoCrate.lua | 337 -- Moose Development/Moose/Cargo/CargoGroup.lua | 773 --- .../Moose/Cargo/CargoSlingload.lua | 275 - Moose Development/Moose/Cargo/CargoUnit.lua | 395 -- Moose Development/Moose/Modules.lua | 55 - Moose Development/Moose/Modules_local.lua | 53 - .../Moose/Tasking/CommandCenter.lua | 821 --- .../Moose/Tasking/DetectionManager.lua | 402 -- Moose Development/Moose/Tasking/Mission.lua | 1211 ----- Moose Development/Moose/Tasking/Task.lua | 2058 ------- Moose Development/Moose/Tasking/TaskInfo.lua | 377 -- Moose Development/Moose/Tasking/Task_A2A.lua | 654 --- .../Moose/Tasking/Task_A2A_Dispatcher.lua | 620 --- Moose Development/Moose/Tasking/Task_A2G.lua | 635 --- .../Moose/Tasking/Task_A2G_Dispatcher.lua | 828 --- .../Moose/Tasking/Task_CARGO.lua | 1410 ----- .../Moose/Tasking/Task_Capture_Dispatcher.lua | 402 -- .../Moose/Tasking/Task_Capture_Zone.lua | 334 -- .../Moose/Tasking/Task_Cargo_CSAR.lua | 398 -- .../Moose/Tasking/Task_Cargo_Dispatcher.lua | 919 ---- .../Moose/Tasking/Task_Cargo_Transport.lua | 363 -- .../Moose/Tasking/Task_Manager.lua | 192 - Moose Development/Moose/Utilities/STTS.lua | 259 - Moose Setup/Moose.files | 1 - 62 files changed, 44234 deletions(-) delete mode 100644 Moose Development/Moose/AI/AI_A2A_Cap.lua delete mode 100644 Moose Development/Moose/AI/AI_A2A_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_A2A_Gci.lua delete mode 100644 Moose Development/Moose/AI/AI_A2A_Patrol.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G_BAI.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G_CAS.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G_SEAD.lua delete mode 100644 Moose Development/Moose/AI/AI_Air.lua delete mode 100644 Moose Development/Moose/AI/AI_Air_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_Air_Engage.lua delete mode 100644 Moose Development/Moose/AI/AI_Air_Patrol.lua delete mode 100644 Moose Development/Moose/AI/AI_Air_Squadron.lua delete mode 100644 Moose Development/Moose/AI/AI_BAI.lua delete mode 100644 Moose Development/Moose/AI/AI_Balancer.lua delete mode 100644 Moose Development/Moose/AI/AI_CAP.lua delete mode 100644 Moose Development/Moose/AI/AI_CAS.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_APC.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Airplane.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Helicopter.lua delete mode 100644 Moose Development/Moose/AI/AI_Cargo_Ship.lua delete mode 100644 Moose Development/Moose/AI/AI_Escort.lua delete mode 100644 Moose Development/Moose/AI/AI_Escort_Dispatcher.lua delete mode 100644 Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua delete mode 100644 Moose Development/Moose/AI/AI_Escort_Request.lua delete mode 100644 Moose Development/Moose/AI/AI_Formation.lua delete mode 100644 Moose Development/Moose/AI/AI_Patrol.lua delete mode 100644 Moose Development/Moose/Actions/Act_Account.lua delete mode 100644 Moose Development/Moose/Actions/Act_Assign.lua delete mode 100644 Moose Development/Moose/Actions/Act_Assist.lua delete mode 100644 Moose Development/Moose/Actions/Act_Route.lua delete mode 100644 Moose Development/Moose/Cargo/Cargo.lua delete mode 100644 Moose Development/Moose/Cargo/CargoCrate.lua delete mode 100644 Moose Development/Moose/Cargo/CargoGroup.lua delete mode 100644 Moose Development/Moose/Cargo/CargoSlingload.lua delete mode 100644 Moose Development/Moose/Cargo/CargoUnit.lua delete mode 100644 Moose Development/Moose/Tasking/CommandCenter.lua delete mode 100644 Moose Development/Moose/Tasking/DetectionManager.lua delete mode 100644 Moose Development/Moose/Tasking/Mission.lua delete mode 100644 Moose Development/Moose/Tasking/Task.lua delete mode 100644 Moose Development/Moose/Tasking/TaskInfo.lua delete mode 100644 Moose Development/Moose/Tasking/Task_A2A.lua delete mode 100644 Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_A2G.lua delete mode 100644 Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_CARGO.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Capture_Zone.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Cargo_Transport.lua delete mode 100644 Moose Development/Moose/Tasking/Task_Manager.lua delete mode 100644 Moose Development/Moose/Utilities/STTS.lua diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua deleted file mode 100644 index d86c20bbe..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ /dev/null @@ -1,218 +0,0 @@ ---- **AI** - Models the process of Combat Air Patrol (CAP) for airplanes. --- --- This is a class used in the @{AI.AI_A2A_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2A_Cap --- @image AI_Combat_Air_Patrol.JPG - --- @type AI_A2A_CAP --- @extends AI.AI_Air_Patrol#AI_AIR_PATROL --- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE - ---- The AI_A2A_CAP class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group} --- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- --- ![Process](..\Presentations\AI_CAP\Dia3.JPG) --- --- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP 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_A2A_CAP constructor --- --- * @{#AI_A2A_CAP.New}(): Creates a new AI_A2A_CAP object. --- --- ## 2. AI_A2A_CAP is a FSM --- --- ![Process](..\Presentations\AI_CAP\Dia2.JPG) --- --- ### 2.1 AI_A2A_CAP 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_A2A_CAP 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_A2A_CAP.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_A2A_CAP.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_A2A_CAP.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_A2A_CAP.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_A2A_CAP.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_A2A_CAP.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_A2A_CAP -AI_A2A_CAP = { - ClassName = "AI_A2A_CAP", -} - ---- Creates a new AI_A2A_CAP object --- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap --- @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#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#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#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @return #AI_A2A_CAP -function AI_A2A_CAP:New2( AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) - - -- Multiple inheritance ... :-) - local AI_Air = AI_AIR:New( AICap ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - local self = BASE:Inherit( self, AI_Air_Engage ) --#AI_A2A_CAP - - self:SetFuelThreshold( .2, 60 ) - self:SetDamageThreshold( 0.4 ) - self:SetDisengageRadius( 70000 ) - - - return self -end - ---- Creates a new AI_A2A_CAP object --- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap --- @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#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#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_A2A_CAP -function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType ) - - return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) - -end - ---- onafter State Transition for Event Patrol. --- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The AI Group 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_A2A_CAP:onafterStart( AICap, From, Event, To ) - - self:GetParent( self, AI_A2A_CAP ).onafterStart( self, AICap, From, Event, To ) - AICap:HandleEvent( EVENTS.Takeoff, nil, self ) - -end - ---- Set the Engage Zone which defines where the AI will engage bogies. --- @param #AI_A2A_CAP self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP. --- @return #AI_A2A_CAP self -function AI_A2A_CAP: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_A2A_CAP self --- @param #number EngageRange The Engage Range. --- @return #AI_A2A_CAP self -function AI_A2A_CAP:SetEngageRange( EngageRange ) - self:F2() - - if EngageRange then - self.EngageRange = EngageRange - else - self.EngageRange = nil - end -end - ---- Evaluate the attack and create an AttackUnitTask list. --- @param #AI_A2A_CAP 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_A2A_CAP self -function AI_A2A_CAP:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT - if AttackUnit and AttackUnit:IsAlive() and AttackUnit:IsAir() then - -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() - -- Maybe the detected set also contains - self:T( { "Attacking Task:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) - end - end - - return AttackUnitTasks -end diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua deleted file mode 100644 index bbfaa868b..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ /dev/null @@ -1,4518 +0,0 @@ ---- **AI** - Manages the process of an automatic A2A defense system based on an EWR network targets and coordinating CAP and GCI. --- --- === --- --- Features: --- --- * Setup quickly an A2A defense system for a coalition. --- * Setup (CAP) Control Air Patrols at defined zones to enhance your A2A defenses. --- * Setup (GCI) Ground Control Intercept at defined airbases to enhance your A2A defenses. --- * Define and use an EWR (Early Warning Radar) network. --- * Define squadrons at airbases. --- * Enable airbases for A2A defenses. --- * Add different plane types to different squadrons. --- * Add multiple squadrons to different airbases. --- * Define different ranges to engage upon intruders. --- * Establish an automatic in air refuel process for CAP using refuel tankers. --- * Setup default settings for all squadrons and A2A defenses. --- * Setup specific settings for specific squadrons. --- * Quickly setup an A2A defense system using @{#AI_A2A_GCICAP}. --- * Setup a more advanced defense system using @{#AI_A2A_DISPATCHER}. --- --- === --- --- ## Missions: --- --- [AID-A2A - AI A2A Dispatching](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher) --- --- === --- --- ## YouTube Channel: --- --- [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) --- --- === --- --- # QUICK START GUIDE --- --- There are basically two classes available to model an A2A defense system. --- --- AI\_A2A\_DISPATCHER is the main A2A defense class that models the A2A defense system. --- AI\_A2A\_GCICAP derives or inherits from AI\_A2A\_DISPATCHER and is a more **noob** user friendly class, but is less flexible. --- --- Before you start using the AI\_A2A\_DISPATCHER or AI\_A2A\_GCICAP ask yourself the following questions. --- --- ## 0. Do I need AI\_A2A\_DISPATCHER or do I need AI\_A2A\_GCICAP? --- --- AI\_A2A\_GCICAP, automates a lot of the below questions using the mission editor and requires minimal lua scripting. --- But the AI\_A2A\_GCICAP provides less flexibility and a lot of options are defaulted. --- With AI\_A2A\_DISPATCHER you can setup a much more **fine grained** A2A defense mechanism, but some more (easy) lua scripting is required. --- --- ## 1. Which Coalition am I modeling an A2A defense system for? blue or red? --- --- One AI\_A2A\_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. --- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI\_A2A\_DISPATCHER **objects**, --- each governing their defense system. --- --- --- ## 2. Which type of EWR will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). --- --- The MOOSE framework leverages the @{Functional.Detection} classes to perform the EWR detection. --- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: --- --- * Perform detections from multiple FACs as one co-operating entity. --- * Communicate with a Head Quarters, which consolidates each detection. --- * Groups detections based on a method (per area, per type or per unit). --- * Communicates detections. --- --- ## 3. Which EWR units will be used as part of the detection system? Only Ground or also Airborne? --- --- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. --- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). --- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. --- The position of these units is very important as they need to provide enough coverage --- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. --- --- ## 4. Is a border required? --- --- Is this a cold war or a hot war situation? In case of a cold war situation, a border can be set that will only trigger defenses --- if the border is crossed by enemy units. --- --- ## 5. What maximum range needs to be checked to allow defenses to engage any attacker? --- --- A good functioning defense will have a "maximum range" evaluated to the enemy when CAP will be engaged or GCI will be spawned. --- --- ## 6. Which Airbases, Carrier Ships, FARPs will take part in the defense system for the Coalition? --- --- Carefully plan which airbases will take part in the coalition. Color each airbase in the color of the coalition. --- --- ## 7. Which Squadrons will I create and which name will I give each Squadron? --- --- The defense system works with Squadrons. Each Squadron must be given a unique name, that forms the **key** to the defense system. --- Several options and activities can be set per Squadron. --- --- ## 8. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs? --- --- Squadrons are placed as the "home base" on an airfield, carrier or farp. --- Carefully plan where each Squadron will be located as part of the defense system. --- --- ## 9. Which plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron? --- --- Per Squadron, one or multiple plane models can be allocated as **Templates**. --- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. --- The A2A defense system will select from the given templates a random template to spawn a new plane (group). --- --- ## 10. Which payloads, skills and skins will these plane models have? --- --- Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, --- each having different payloads, skills and skins. --- The A2A defense system will select from the given templates a random template to spawn a new plane (group). --- --- ## 11. For each Squadron, which will perform CAP? --- --- Per Squadron, evaluate which Squadrons will perform CAP. --- Not all Squadrons need to perform CAP. --- --- ## 12. For each Squadron doing CAP, in which ZONE(s) will the CAP be performed? --- --- Per CAP, evaluate **where** the CAP will be performed, in other words, define the **zone**. --- Near the border or a bit further away? --- --- ## 13. For each Squadron doing CAP, which zone types will I create? --- --- Per CAP zone, evaluate whether you want: --- --- * simple trigger zones --- * polygon zones --- * moving zones --- --- Depending on the type of zone selected, a different @{Core.Zone} object needs to be created from a ZONE_ class. --- --- ## 14. For each Squadron doing CAP, what are the time intervals and CAP amounts to be performed? --- --- For each CAP: --- --- * **How many** CAP you want to have airborne at the same time? --- * **How frequent** you want the defense mechanism to check whether to start a new CAP? --- --- ## 15. For each Squadron, which will perform GCI? --- --- For each Squadron, evaluate which Squadrons will perform GCI? --- Not all Squadrons need to perform GCI. --- --- ## 16. For each Squadron, which takeoff method will I use? --- --- For each Squadron, evaluate which takeoff method will be used: --- --- * Straight from the air --- * From the runway --- * From a parking spot with running engines --- * From a parking spot with cold engines --- --- **The default takeoff method is straight in the air.** --- --- ## 17. For each Squadron, which landing method will I use? --- --- For each Squadron, evaluate which landing method will be used: --- --- * Despawn near the airbase when returning --- * Despawn after landing on the runway --- * Despawn after engine shutdown after landing --- --- **The default landing method is despawn when near the airbase when returning.** --- --- ## 18. For each Squadron, which overhead will I use? --- --- For each Squadron, depending on the airplane type (modern, old) and payload, which overhead is required to provide any defense? --- In other words, if **X** attacker airplanes are detected, how many **Y** defense airplanes need to be spawned per squadron? --- The **Y** is dependent on the type of airplane (era), payload, fuel levels, skills etc. --- The overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. --- --- **The default overhead is 1. A value greater than 1, like 1.5 will increase the overhead with 50%, a value smaller than 1, like 0.5 will decrease the overhead with 50%.** --- --- ## 19. For each Squadron, which grouping will I use? --- --- When multiple targets are detected, how will defense airplanes be grouped when multiple defense airplanes are spawned for multiple attackers? --- Per one, two, three, four? --- --- **The default grouping is 1. That means, that each spawned defender will act individually.** --- --- # 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 --- --- === --- --- ### Authors: **FlightControl** rework of GCICAP + introduction of new concepts (squadrons). --- ### Authors: **Stonehouse**, **SNAFU** in terms of the advice, documentation, and the original GCICAP script. --- --- @module AI.AI_A2A_Dispatcher --- @image AI_Air_To_Air_Dispatching.JPG - -do -- AI_A2A_DISPATCHER - - --- AI_A2A_DISPATCHER class. - -- @type AI_A2A_DISPATCHER - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Create an automatic air defence system for a coalition. - -- - -- === - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia3.JPG) - -- - -- It includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy air movements that are detected by a ground based radar network. - -- CAP flights will take off and proceed to designated CAP zones where they will remain on station until the ground radars direct them to intercept detected enemy aircraft or they run short of fuel and must return to base (RTB). When a CAP flight leaves their zone to perform an interception or return to base a new CAP flight will spawn to take their place. - -- If all CAP flights are engaged or RTB then additional GCI interceptors will scramble to intercept unengaged enemy aircraft under ground radar control. - -- With a little time and with a little work it provides the mission designer with a convincing and completely automatic air defence system. - -- In short it is a plug in very flexible and configurable air defence module for DCS World. - -- - -- Note that in order to create a two way A2A defense system, two AI\_A2A\_DISPATCHER defense system may need to be created, for each coalition one. - -- This is a good implementation, because maybe in the future, more coalitions may become available in DCS world. - -- - -- === - -- - -- # USAGE GUIDE - -- - -- ## 1. AI\_A2A\_DISPATCHER constructor: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_1.JPG) - -- - -- - -- The @{#AI_A2A_DISPATCHER.New}() method creates a new AI\_A2A\_DISPATCHER instance. - -- - -- ### 1.1. Define the **EWR network**: - -- - -- As part of the AI\_A2A\_DISPATCHER :New() constructor, an EWR network must be given as the first parameter. - -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia5.JPG) - -- - -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. - -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). - -- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. - -- The position of these units is very important as they need to provide enough coverage - -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia7.JPG) - -- - -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. - -- For example if they are a long way forward and can detect enemy planes on the ground and taking off - -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. - -- Having the radars further back will mean a slower escalation because fewer targets will be detected and - -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. - -- It all depends on what the desired effect is. - -- - -- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the AI\_A2A\_DISPATCHER class. - -- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, - -- increasing or decreasing the radar coverage of the Early Warning System. - -- - -- See the following example to setup an EWR network containing EWR stations and AWACS. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_2.JPG) - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_3.JPG) - -- - -- -- Define a SET_GROUP object that builds a collection of groups that define the EWR network. - -- -- Here we build the network with all the groups that have a name starting with DF CCCP AWACS and DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Setup the detection and group targets to a 30km range! - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. - -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with **DF CCCP AWACS** or **DF CCCP EWR** to be included in the Set. - -- **DetectionSetGroup** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. - -- - -- Then a new Detection object is created from the class DETECTION_AREAS. A grouping radius of 30000 is chosen, which is 30km. - -- The **Detection** object is then passed to the @{#AI_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A defense detection mechanism. - -- - -- You could build a **mutual defense system** like this: - -- - -- A2ADispatcher_Red = AI_A2A_DISPATCHER:New( EWR_Red ) - -- A2ADispatcher_Blue = AI_A2A_DISPATCHER:New( EWR_Blue ) - -- - -- ### 1.2. Define the detected **target grouping radius**: - -- - -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. - -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. - -- Fast planes like in the 80s, need a larger radius than WWII planes. - -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. - -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate - -- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small! - -- - -- ## 3. Set the **Engage Radius**: - -- - -- Define the **Engage Radius** to **engage any target by airborne friendlies**, - -- which are executing **cap** or **returning** from an intercept mission. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia10.JPG) - -- - -- If there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, - -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- - -- For example, if **50000** or **50km** is given as a value, then any friendly that is airborne within **50km** from the detected target, - -- will be considered to receive the command to engage that target area. - -- - -- You need to evaluate the value of this parameter carefully: - -- - -- * If too small, more intercept missions may be triggered upon detected target areas. - -- * If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. - -- - -- The **default** Engage Radius is defined as **100000** or **100km**. - -- Use the method @{#AI_A2A_DISPATCHER.SetEngageRadius}() to set a specific Engage Radius. - -- **The Engage Radius is defined for ALL squadrons which are operational.** - -- - -- Demonstration Mission: [AID-019 - AI_A2A - Engage Range Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-019%20-%20Engage%20Range%20Test) - -- - -- In this example an Engage Radius is set to various values. - -- - -- -- Set 50km as the radius to engage any target by airborne friendlies. - -- A2ADispatcher:SetEngageRadius( 50000 ) - -- - -- -- Set 100km as the radius to engage any target by airborne friendlies. - -- A2ADispatcher:SetEngageRadius() -- 100000 is the default value. - -- - -- - -- ## 4. Set the **Ground Controlled Intercept Radius** or **Gci radius**: - -- - -- When targets are detected that are still really far off, you don't want the AI_A2A_DISPATCHER to launch intercepts just yet. - -- You want it to wait until a certain Gci range is reached, which is the **distance of the closest airbase to target** - -- being **smaller** than the **Ground Controlled Intercept radius** or **Gci radius**. - -- - -- The **default** Gci radius is defined as **200000** or **200km**. Override the default Gci radius when the era of the warfare is early, or, - -- when you don't want to let the AI_A2A_DISPATCHER react immediately when a certain border or area is not being crossed. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetGciRadius}() to set a specific controlled ground intercept radius. - -- **The Ground Controlled Intercept radius is defined for ALL squadrons which are operational.** - -- - -- Demonstration Mission: [AID-013 - AI_A2A - Intercept Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-013%20-%20Intercept%20Test) - -- - -- In these examples, the Gci Radius is set to various values: - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Set 100km as the radius to ground control intercept detected targets from the nearest airbase. - -- A2ADispatcher:SetGciRadius( 100000 ) - -- - -- -- Set 200km as the radius to ground control intercept. - -- A2ADispatcher:SetGciRadius() -- 200000 is the default value. - -- - -- ## 5. Set the **borders**: - -- - -- According to the tactical and strategic design of the mission broadly decide the shape and extent of red and blue territories. - -- They should be laid out such that a border area is created between the two coalitions. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia4.JPG) - -- - -- **Define a border area to simulate a cold war scenario.** - -- Use the method @{#AI_A2A_DISPATCHER.SetBorderZone}() to create a border zone for the dispatcher. - -- - -- A **cold war** is one where CAP aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. - -- A **hot war** is one where CAP aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send CAP and GCI aircraft to attack it. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia9.JPG) - -- - -- If it's a cold war then the **borders of red and blue territory** need to be defined using a @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE}. - -- If a hot war is chosen then **no borders** actually need to be defined using the helicopter units other than - -- it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. - -- In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. - -- - -- Demonstration Mission: [AID-009 - AI_A2A - Border Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-009%20-%20Border%20Test) - -- - -- In this example a border is set for the CCCP A2A dispatcher: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_4.JPG) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Setup the border. - -- -- Initialize the dispatcher, setting up a border zone. This is a polygon, - -- -- which takes the waypoints of a late activated group with the name CCCP Border as the boundaries of the border area. - -- -- Any enemy crossing this border will be engaged. - -- - -- CCCPBorderZone = ZONE_POLYGON:New( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) - -- A2ADispatcher:SetBorderZone( CCCPBorderZone ) - -- - -- ## 6. Squadrons: - -- - -- The AI\_A2A\_DISPATCHER works with **Squadrons**, that need to be defined using the different methods available. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, - -- while defining which plane types are being used by the squadron and how many resources are available. - -- - -- Squadrons: - -- - -- * Have name (string) that is the identifier or key of the squadron. - -- * Have specific plane types. - -- * Are located at one airbase. - -- * Optionally have a limited set of resources. The default is that squadrons have **unlimited resources**. - -- - -- The name of the squadron given acts as the **squadron key** in the AI\_A2A\_DISPATCHER:Squadron...() methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new aircraft are taking off from the airfield (in the air, cold, hot, at the runway). - -- * Control how returning aircraft are landing at the airfield (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft. - -- - -- This example defines a couple of squadrons. Note the templates defined within the Mission Editor. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_5.JPG) - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_6.JPG) - -- - -- -- Setup the squadrons. - -- A2ADispatcher:SetSquadron( "Mineralnye", AIRBASE.Caucasus.Mineralnye_Vody, { "SQ CCCP SU-27" }, 20 ) - -- A2ADispatcher:SetSquadron( "Maykop", AIRBASE.Caucasus.Maykop_Khanskaya, { "SQ CCCP MIG-31" }, 20 ) - -- A2ADispatcher:SetSquadron( "Mozdok", AIRBASE.Caucasus.Mozdok, { "SQ CCCP MIG-31" }, 20 ) - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-27" }, 20 ) - -- A2ADispatcher:SetSquadron( "Novo", AIRBASE.Caucasus.Novorossiysk, { "SQ CCCP SU-27" }, 20 ) - -- - -- ### 6.1. Set squadron take-off methods - -- - -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the airfield: - -- - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. - -- - -- **The default take-off method is to spawn new aircraft directly in the air.** - -- - -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. - -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: - -- - -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. - -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. - -- * aircraft may collide at the airbase. - -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- - -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! - -- - -- This example sets the default takeoff method to be from the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Takeoff methods - -- - -- -- The default takeoff - -- A2ADispatcher:SetDefaultTakeOffFromRunway() - -- - -- -- The individual takeoff per squadron - -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2A_DISPATCHER.Takeoff.Air ) - -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) - -- - -- - -- ### 6.1. Set Squadron takeoff altitude when spawning new aircraft in the air. - -- - -- In the case of the @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. - -- That is modifying or setting the **altitude** from where planes spawn in the air. - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. - -- The default takeoff altitude can be modified or set using the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). - -- As part of the method @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. - -- If this parameter is not specified, then the default altitude will be used for the squadron. - -- - -- ### 6.2. Set squadron landing methods - -- - -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: - -- - -- * @{#AI_A2A_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. - -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2A defense system, as no new CAP or GCI planes can takeoff. - -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. - -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. - -- - -- This example defines the default landing method to be at the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Landing methods - -- - -- -- The default landing method - -- A2ADispatcher:SetDefaultLandingAtRunway() - -- - -- -- The individual landing per squadron - -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2ADispatcher:SetSquadronLanding( "Novo", AI_A2A_DISPATCHER.Landing.AtRunway ) - -- - -- - -- ### 6.3. Set squadron grouping - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronGrouping}() to set the grouping of CAP or GCI flights that will take-off when spawned. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia12.JPG) - -- - -- In the case of GCI, the @{#AI_A2A_DISPATCHER.SetSquadronGrouping}() method has additional behavior. When there aren't enough CAP flights airborne, a GCI will be initiated for the remaining - -- targets to be engaged. Depending on the grouping parameter, the spawned flights for GCI are grouped into this setting. - -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by CAP or any airborne flight, - -- a GCI needs to be started, the GCI flights will be grouped as follows: Group 1 of 2 flights and Group 2 of one flight! - -- - -- Even more ... If one target has been detected, and the overhead is 1.5, grouping is 1, then two groups of planes will be spawned, with one unit each! - -- - -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. - -- - -- ### 6.4. Overhead and Balance the effectiveness of the air defenses in case of GCI. - -- - -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. - -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia11.JPG) - -- - -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. - -- - -- The @{#AI_A2A_DISPATCHER.SetSquadronOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. - -- - -- For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the @{#AI_A2A_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: - -- - -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 planes detected, 6 planes will be spawned. - -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 planes detected, only 3 planes will be spawned. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- For example ... If one target has been detected, and the overhead is 1.5, grouping is 1, then two groups of planes will be spawned, with one unit each! - -- - -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. - -- - -- ## 6.5. Squadron fuel threshold. - -- - -- When an airplane gets **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken: - -- - The defender will go RTB, and will be replaced with a new defender if possible. - -- - The defender will refuel at a tanker, if a tanker has been specified for the squadron. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of spawned airplanes for all squadrons. - -- - -- ## 7. Setup a squadron for CAP - -- - -- ### 7.1. Set the CAP zones - -- - -- CAP zones are patrol areas where Combat Air Patrol (CAP) flights loiter until they either return to base due to low fuel or are assigned an interception task by ground control. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia6.JPG) - -- - -- * As the CAP flights wander around within the zone waiting to be tasked, these zones need to be large enough that the aircraft are not constantly turning - -- but do not have to be big and numerous enough to completely cover a border. - -- - -- * CAP zones can be of any type, and are derived from the @{Core.Zone#ZONE_BASE} class. Zones can be @{Core.Zone#ZONE}, @{Core.Zone#ZONE_POLYGON}, @{Core.Zone#ZONE_UNIT}, @{Core.Zone#ZONE_GROUP}, etc. - -- This allows to setup **static, moving and/or complex zones** wherein aircraft will perform the CAP. - -- - -- * Typically 20000-50000 metres width is used and they are spaced so that aircraft in the zone waiting for tasks don't have to far to travel to protect their coalitions important targets. - -- These targets are chosen as part of the mission design and might be an important airfield or town etc. - -- Zone size is also determined somewhat by territory size, plane types - -- (eg WW2 aircraft might mean smaller zones or more zones because they are slower and take longer to intercept enemy aircraft). - -- - -- * In a **cold war** it is important to make sure a CAP zone doesn't intrude into enemy territory as otherwise CAP flights will likely cross borders - -- and spark a full scale conflict which will escalate rapidly. - -- - -- * CAP flights do not need to be in the CAP zone before they are "on station" and ready for tasking. - -- - -- * Typically if a CAP flight is tasked and therefore leaves their zone empty while they go off and intercept their target another CAP flight will spawn to take their place. - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia7.JPG) - -- - -- The following example illustrates how CAP zones are coded: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_8.JPG) - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_7.JPG) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_9.JPG) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- Note the different @{Core.Zone} MOOSE classes being used to create zones of different types. Please click the @{Core.Zone} link for more information about the different zone types. - -- Zones can be circles, can be setup in the mission editor using trigger zones, but can also be setup in the mission editor as polygons and in this case GROUP objects are being used! - -- - -- ## 7.2. Set the squadron to execute CAP: - -- - -- The method @{#AI_A2A_DISPATCHER.SetSquadronCap}() defines a CAP execution for a squadron. - -- - -- Setting-up a CAP zone also requires specific parameters: - -- - -- * The minimum and maximum altitude - -- * The minimum speed and maximum patrol speed - -- * The minimum and maximum engage speed - -- * The type of altitude measurement - -- - -- These define how the squadron will perform the CAP while patrolling. Different terrain types requires different types of CAP. - -- - -- The @{#AI_A2A_DISPATCHER.SetSquadronCapInterval}() method specifies **how much** and **when** CAP flights will takeoff. - -- - -- It is recommended not to overload the air defense with CAP flights, as these will decrease the performance of the overall system. - -- - -- For example, the following setup will create a CAP for squadron "Sochi": - -- - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- ## 7.3. Squadron tanker to refuel when executing CAP and defender is out of fuel. - -- - -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. - -- This greatly increases the efficiency of your CAP operations. - -- - -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. - -- Then, use the method @{#AI_A2A_DISPATCHER.SetDefaultTanker}() to set the default tanker for the refuelling. - -- You can also specify a specific tanker for refuelling for a squadron by using the method @{#AI_A2A_DISPATCHER.SetSquadronTanker}(). - -- - -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. - -- - -- For example, the following setup will create a CAP for squadron "Gelend" with a refuel task for the squadron: - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_10.JPG) - -- - -- -- Define the CAP - -- A2ADispatcher:SetSquadron( "Gelend", AIRBASE.Caucasus.Gelendzhik, { "SQ CCCP SU-30" }, 20 ) - -- A2ADispatcher:SetSquadronCap( "Gelend", ZONE:New( "PatrolZoneGelend" ), 4000, 8000, 600, 800, 1000, 1300 ) - -- A2ADispatcher:SetSquadronCapInterval( "Gelend", 2, 30, 600, 1 ) - -- A2ADispatcher:SetSquadronGci( "Gelend", 900, 1200 ) - -- - -- -- Setup the Refuelling for squadron "Gelend", at tanker (group) "TankerGelend" when the fuel in the tank of the CAP defenders is less than 80%. - -- A2ADispatcher:SetSquadronFuelThreshold( "Gelend", 0.8 ) - -- A2ADispatcher:SetSquadronTanker( "Gelend", "TankerGelend" ) - -- - -- ## 7.4 Set up race track pattern - -- - -- By default, flights patrol randomly within the CAP zone. It is also possible to let them fly a race track pattern using the - -- @{#AI_A2A_DISPATCHER.SetDefaultCapRacetrack}(*LeglengthMin*, *LeglengthMax*, *HeadingMin*, *HeadingMax*, *DurationMin*, *DurationMax*) or - -- @{#AI_A2A_DISPATCHER.SetSquadronCapRacetrack}(*SquadronName*, *LeglengthMin*, *LeglengthMax*, *HeadingMin*, *HeadingMax*, *DurationMin*, *DurationMax*) functions. - -- The first function enables this for all squadrons, the latter only for specific squadrons. For example, - -- - -- -- Enable race track pattern for CAP squadron "Mineralnye". - -- A2ADispatcher:SetSquadronCapRacetrack("Mineralnye", 10000, 20000, 90, 180, 10*60, 20*60) - -- - -- In this case the squadron "Mineralnye" will a race track pattern at a random point in the CAP zone. The leg length will be randomly selected between 10,000 and 20,000 meters. The heading - -- of the race track will randomly selected between 90 (West to East) and 180 (North to South) degrees. - -- After a random duration between 10 and 20 minutes, the flight will get a new random orbit location. - -- - -- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the CAP command used earlier on, e.g. - -- - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- - -- Also note that the center of the race track pattern is chosen randomly within the patrol zone and can be close the the boarder of the zone. Hence, it cannot be guaranteed that the - -- whole pattern lies within the patrol zone. - -- - -- ## 8. Setup a squadron for GCI: - -- - -- The method @{#AI_A2A_DISPATCHER.SetSquadronGci}() defines a GCI execution for a squadron. - -- - -- Setting-up a GCI readiness also requires specific parameters: - -- - -- * The minimum speed and maximum patrol speed - -- - -- Essentially this controls how many flights of GCI aircraft can be active at any time. - -- Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. - -- GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, - -- too short will mean that the intruders may have already passed the ideal interception point! - -- - -- For example, the following setup will create a GCI for squadron "Sochi": - -- - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- - -- ## 9. Other configuration options - -- - -- ### 9.1. Set a tactical display panel: - -- - -- Every 30 seconds, a tactical display panel can be shown that illustrates what the status is of the different groups controlled by AI\_A2A\_DISPATCHER. - -- Use the method @{#AI_A2A_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. - -- Note that there may be some performance impact if this panel is shown. - -- - -- ## 10. Defaults settings. - -- - -- This provides a good overview of the different parameters that are setup or hardcoded by default. - -- For some default settings, a method is available that allows you to tweak the defaults. - -- - -- ## 10.1. Default takeoff method. - -- - -- The default **takeoff method** is set to **in the air**, which means that new spawned airplanes will be spawned directly in the air above the airbase by default. - -- - -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** - -- - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. - -- - -- ## 10.2. Default landing method. - -- - -- The default **landing method** is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. - -- - -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. - -- - -- * @{#AI_A2A_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. - -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. - -- * @{#AI_A2A_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- ## 10.3. Default overhead. - -- - -- The default **overhead** is set to **1**. That essentially means that there isn't any overhead set by default. - -- - -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. - -- - -- Use the @{#AI_A2A_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. - -- - -- ## 10.4. Default grouping. - -- - -- The default **grouping** is set to **one airplane**. That essentially means that there won't be any grouping applied by default. - -- - -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. - -- - -- ## 10.5. Default RTB fuel threshold. - -- - -- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons. - -- - -- ## 10.6. Default RTB damage threshold. - -- - -- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons. - -- - -- ## 10.7. Default settings for CAP. - -- - -- ### 10.7.1. Default CAP Time Interval. - -- - -- CAP is time driven, and will evaluate in random time intervals if a new CAP needs to be spawned. - -- The **default CAP time interval** is between **180** and **600** seconds. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultCapTimeInterval}() to set the **default CAP time interval** of spawned airplanes for all squadrons. - -- Note that you can still change the CAP limit and CAP time intervals for each CAP individually using the @{#AI_A2A_DISPATCHER.SetSquadronCapTimeInterval}() method. - -- - -- ### 10.7.2. Default CAP limit. - -- - -- Multiple CAP can be airborne at the same time for one squadron, which is controlled by the **CAP limit**. - -- The **default CAP limit** is 1 CAP per squadron to be airborne at the same time. - -- Note that the default CAP limit is used when a Squadron CAP is defined, and cannot be changed afterwards. - -- So, ensure that you set the default CAP limit **before** you spawn the Squadron CAP. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultCapTimeInterval}() to set the **default CAP time interval** of spawned airplanes for all squadrons. - -- Note that you can still change the CAP limit and CAP time intervals for each CAP individually using the @{#AI_A2A_DISPATCHER.SetSquadronCapTimeInterval}() method. - -- - -- ## 10.7.3. Default tanker for refuelling when executing CAP. - -- - -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. - -- This greatly increases the efficiency of your CAP operations. - -- - -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. - -- Then, use the method @{#AI_A2A_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_A2A_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed. - -- - -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. - -- - -- For example, the following setup will set the default refuel tanker to "Tanker": - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_DISPATCHER-ME_11.JPG) - -- - -- -- Define the CAP - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) - -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) - -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) - -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) - -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) - -- A2ADispatcher:SetDefaultTanker( "Tanker" ) - -- - -- ## 10.8. Default settings for GCI. - -- - -- ## 10.8.1. Optimal intercept point calculation. - -- - -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. - -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. - -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. - -- - -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: - -- - -- * The average bearing of the intruders for an amount of seconds. - -- * The average speed of the intruders for an amount of seconds. - -- * An assumed time it takes to get planes operational at the airbase. - -- - -- The **intercept point** will determine: - -- - -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. - -- * The optimal airbase from where defenders will takeoff for GCI. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. - -- - -- ## 10.8.2. Default Disengage Radius. - -- - -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. - -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. - -- - -- ## 11. Airbase capture: - -- - -- Different squadrons can be located at one airbase. - -- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, the GCI and CAP will stop! - -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes - -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. - -- - -- ## 12. Q & A: - -- - -- ### 12.1. Which countries will be selected for each coalition? - -- - -- Which countries are assigned to a coalition influences which units are available to the coalition. - -- For example because the mission calls for a EWR radar on the blue side the Ukraine might be chosen as a blue country - -- so that the 55G6 EWR radar unit is available to blue. - -- Some countries assign different tasking to aircraft, for example Germany assigns the CAP task to F-4E Phantoms but the USA does not. - -- Therefore if F4s are wanted as a coalition's CAP or GCI aircraft Germany will need to be assigned to that coalition. - -- - -- ### 12.2. Country, type, load out, skill and skins for CAP and GCI aircraft? - -- - -- * Note these can be from any countries within the coalition but must be an aircraft with one of the main tasks being "CAP". - -- * Obviously skins which are selected must be available to all players that join the mission otherwise they will see a default skin. - -- * Load outs should be appropriate to a CAP mission eg perhaps drop tanks for CAP flights and extra missiles for GCI flights. - -- * These decisions will eventually lead to template aircraft units being placed as late activation units that the script will use as templates for spawning CAP and GCI flights. Up to 4 different aircraft configurations can be chosen for each coalition. The spawned aircraft will inherit the characteristics of the template aircraft. - -- * The selected aircraft type must be able to perform the CAP tasking for the chosen country. - -- - -- - -- @field #AI_A2A_DISPATCHER - AI_A2A_DISPATCHER = { - ClassName = "AI_A2A_DISPATCHER", - Detection = nil, - } - - --- Squadron data structure. - -- @type AI_A2A_DISPATCHER.Squadron - -- @field #string Name Name of the squadron. - -- @field #number ResourceCount Number of resources. - -- @field #string AirbaseName Name of the home airbase. - -- @field Wrapper.Airbase#AIRBASE Airbase The home airbase of the squadron. - -- @field #boolean Captured If true, airbase of the squadron was captured. - -- @field #table Resources Flight group resources Resources[TemplateID][GroupName] = SpawnGroup. - -- @field #boolean Uncontrolled If true, flight groups are spawned uncontrolled and later activated. - -- @field #table Gci GCI. - -- @field #number Overhead Squadron overhead. - -- @field #number Grouping Squadron flight group size. - -- @field #number Takeoff Takeoff type. - -- @field #number TakeoffAltitude Altitude in meters for spawn in air. - -- @field #number Landing Landing type. - -- @field #number FuelThreshold Fuel threshold [0,1] for RTB. - -- @field #string TankerName Name of the refuelling tanker. - -- @field #table Table of template group names of the squadron. - -- @field #table Spawn Table of spawns Core.Spawn#SPAWN. - -- @field #table TemplatePrefixes - -- @field #boolean Racetrack If true, CAP flights will perform a racetrack pattern rather than randomly patrolling the zone. - -- @field #number RacetrackLengthMin Min Length of race track in meters. Default 10,000 m. - -- @field #number RacetrackLengthMax Max Length of race track in meters. Default 15,000 m. - -- @field #number RacetrackHeadingMin Min heading of race track in degrees. Default 0 deg, i.e. from South to North. - -- @field #number RacetrackHeadingMax Max heading of race track in degrees. Default 180 deg, i.e. from North to South. - -- @field #number RacetrackDurationMin Min duration in seconds before the CAP flight changes its orbit position. Default never. - -- @field #number RacetrackDurationMax Max duration in seconds before the CAP flight changes its orbit position. Default never. - - --- Enumerator for spawns at airbases - -- @type AI_A2A_DISPATCHER.Takeoff - -- @extends Wrapper.Group#GROUP.Takeoff - - --- - -- @field #AI_A2A_DISPATCHER.Takeoff Takeoff - AI_A2A_DISPATCHER.Takeoff = GROUP.Takeoff - - --- Defines Landing type/location. - -- @field Landing - AI_A2A_DISPATCHER.Landing = { - NearAirbase = 1, - AtRunway = 2, - AtEngineShutdown = 3, - } - - --- AI_A2A_DISPATCHER constructor. - -- This is defining the A2A DISPATCHER for one coalition. - -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - function AI_A2A_DISPATCHER:New( Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( nil, Detection ) ) -- #AI_A2A_DISPATCHER - - self.Detection = Detection -- Functional.Detection#DETECTION_AREAS - - -- This table models the DefenderSquadron templates. - self.DefenderSquadrons = {} -- The Defender Squadrons. - self.DefenderSpawns = {} - self.DefenderTasks = {} -- The Defenders Tasks. - self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons. - - self.SetSendPlayerMessages = false --#boolean Flash messages to player - - -- TODO: Check detection through radar. - self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - -- self.Detection:InitDetectRadar( true ) - self.Detection:SetRefreshTimeInterval( 30 ) - - self:SetEngageRadius() - self:SetGciRadius() - self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. - self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. - - self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) - self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above the ground. - self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.NearAirbase ) - self:SetDefaultOverhead( 1 ) - self:SetDefaultGrouping( 1 ) - self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the airplane to return to base or refuel. - self:SetDefaultDamageThreshold( 0.4 ) -- When 40% of damage, go RTB. - self:SetDefaultCapTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. - self:SetDefaultCapLimit( 1 ) -- Maximum one CAP per squadron. - - self:AddTransition( "Started", "Assign", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#AI_A2A_DISPATCHER] OnAfterAssign - -- @param #AI_A2A_DISPATCHER self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Tasking.Task_A2A#AI_A2A Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:AddTransition( "*", "CAP", "*" ) - - --- CAP Handler OnBefore for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnBeforeCAP - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- CAP Handler OnAfter for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnAfterCAP - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- CAP Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] CAP - -- @param #AI_A2A_DISPATCHER self - - --- CAP Asynchronous Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] __CAP - -- @param #AI_A2A_DISPATCHER self - -- @param #number Delay - - self:AddTransition( "*", "GCI", "*" ) - - --- GCI Handler OnBefore for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnBeforeGCI - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- GCI Handler OnAfter for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnAfterGCI - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefendersMissing Number of missing defenders. - -- @param #table DefenderFriendlies Friendly defenders. - - --- GCI Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] GCI - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefendersMissing Number of missing defenders. - -- @param #table DefenderFriendlies Friendly defenders. - - --- GCI Asynchronous Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] __GCI - -- @param #AI_A2A_DISPATCHER self - -- @param #number Delay - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefendersMissing Number of missing defenders. - -- @param #table DefenderFriendlies Friendly defenders. - - self:AddTransition( "*", "ENGAGE", "*" ) - - --- ENGAGE Handler OnBefore for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnBeforeENGAGE - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - -- @return #boolean - - --- ENGAGE Handler OnAfter for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] OnAfterENGAGE - -- @param #AI_A2A_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - - --- ENGAGE Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] ENGAGE - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - - --- ENGAGE Asynchronous Trigger for AI_A2A_DISPATCHER - -- @function [parent=#AI_A2A_DISPATCHER] __ENGAGE - -- @param #AI_A2A_DISPATCHER self - -- @param #number Delay - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - - -- Subscribe to the CRASH event so that when planes are shot - -- by a Unit from the dispatcher, they will be removed from the detection... - -- This will avoid the detection to still "know" the shot unit until the next detection. - -- Otherwise, a new intercept or engage may happen for an already shot plane! - - self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) - self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) - -- self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) - - self:HandleEvent( EVENTS.Land ) - self:HandleEvent( EVENTS.EngineShutdown ) - - -- Handle the situation where the airbases are captured. - self:HandleEvent( EVENTS.BaseCaptured ) - - self:SetTacticalDisplay( false ) - - self.DefenderCAPIndex = 0 - - self:__Start( 5 ) - - return self - end - - --- On after "Start" event. - -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:onafterStart( From, Event, To ) - - self:GetParent( self, AI_A2A_DISPATCHER ).onafterStart( self, From, Event, To ) - - -- Spawn the resources. - for SquadronName, _DefenderSquadron in pairs( self.DefenderSquadrons ) do - local DefenderSquadron = _DefenderSquadron -- #AI_A2A_DISPATCHER.Squadron - DefenderSquadron.Resources = {} - if DefenderSquadron.ResourceCount then - for Resource = 1, DefenderSquadron.ResourceCount do - self:ParkDefender( DefenderSquadron ) - end - end - end - end - - --- Park defender. - -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The squadron. - function AI_A2A_DISPATCHER:ParkDefender( DefenderSquadron ) - - local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) - - local Spawn = DefenderSquadron.Spawn[TemplateID] -- Core.Spawn#SPAWN - - Spawn:InitGrouping( 1 ) - - local SpawnGroup - - if self:IsSquadronVisible( DefenderSquadron.Name ) then - - local Grouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - - Grouping = 1 - - Spawn:InitGrouping( Grouping ) - - SpawnGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, SPAWN.Takeoff.Cold ) - - local GroupName = SpawnGroup:GetName() - - DefenderSquadron.Resources = DefenderSquadron.Resources or {} - - DefenderSquadron.Resources[TemplateID] = DefenderSquadron.Resources[TemplateID] or {} - DefenderSquadron.Resources[TemplateID][GroupName] = {} - DefenderSquadron.Resources[TemplateID][GroupName] = SpawnGroup - - self.uncontrolled = self.uncontrolled or {} - self.uncontrolled[DefenderSquadron.Name] = self.uncontrolled[DefenderSquadron.Name] or {} - - table.insert( self.uncontrolled[DefenderSquadron.Name], { group = SpawnGroup, name = GroupName, grouping = Grouping } ) - end - - end - - --- Event base captured. - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2A_DISPATCHER:OnEventBaseCaptured( EventData ) - - local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured. - - self:T( "Captured " .. AirbaseName ) - - -- Now search for all squadrons located at the airbase, and sanitize them. - for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do - if Squadron.AirbaseName == AirbaseName then - Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning. - Squadron.Captured = true - self:T( "Squadron " .. SquadronName .. " captured." ) - end - end - end - - --- Event dead or crash. - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2A_DISPATCHER:OnEventCrashOrDead( EventData ) - self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) - end - - --- Event land. - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2A_DISPATCHER:OnEventLand( EventData ) - self:F( "Landed" ) - local DefenderUnit = EventData.IniUnit - local Defender = EventData.IniGroup - local Squadron = self:GetSquadronFromDefender( Defender ) - if Squadron then - self:F( { SquadronName = Squadron.Name } ) - local LandingMethod = self:GetSquadronLanding( Squadron.Name ) - if LandingMethod == AI_A2A_DISPATCHER.Landing.AtRunway then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ParkDefender( Squadron ) - return - end - if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then - -- Damaged units cannot be repaired anymore. - DefenderUnit:Destroy() - return - end - end - end - - --- Event engine shutdown. - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2A_DISPATCHER:OnEventEngineShutdown( EventData ) - local DefenderUnit = EventData.IniUnit - local Defender = EventData.IniGroup - local Squadron = self:GetSquadronFromDefender( Defender ) - if Squadron then - self:F( { SquadronName = Squadron.Name } ) - local LandingMethod = self:GetSquadronLanding( Squadron.Name ) - if LandingMethod == AI_A2A_DISPATCHER.Landing.AtEngineShutdown and not DefenderUnit:InAir() then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ParkDefender( Squadron ) - end - end - end - - --- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. - -- If there is a target area detected and reported, then any friendlies that are airborne near this target area, - -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- - -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, - -- will be considered to receive the command to engage that target area. - -- - -- You need to evaluate the value of this parameter carefully: - -- - -- * If too small, more intercept missions may be triggered upon detected target areas. - -- * If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. - -- - -- **Use the method @{#AI_A2A_DISPATCHER.SetEngageRadius}() to modify the default Engage Radius for ALL squadrons.** - -- - -- Demonstration Mission: [AID-019 - AI_A2A - Engage Range Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-019%20-%20Engage%20Range%20Test) - -- - -- @param #AI_A2A_DISPATCHER self - -- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- Set 50km as the radius to engage any target by airborne friendlies. - -- A2ADispatcher:SetEngageRadius( 50000 ) - -- - -- -- Set 100km as the radius to engage any target by airborne friendlies. - -- A2ADispatcher:SetEngageRadius() -- 100000 is the default value. - -- - function AI_A2A_DISPATCHER:SetEngageRadius( EngageRadius ) - - self.Detection:SetFriendliesRange( EngageRadius or 100000 ) - - return self - end - - --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. - -- @param #AI_A2A_DISPATCHER self - -- @param #number DisengageRadius (Optional, Default = 300000) The radius in meters to disengage a target when too far from the home base. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- Set 50km as the Disengage Radius. - -- A2ADispatcher:SetDisengageRadius( 50000 ) - -- - -- -- Set 100km as the Disengage Radius. - -- A2ADispatcher:SetDisengageRadius() -- 300000 is the default value. - -- - function AI_A2A_DISPATCHER:SetDisengageRadius( DisengageRadius ) - - self.DisengageRadius = DisengageRadius or 300000 - - return self - end - - --- Define the radius to check if a target can be engaged by an ground controlled intercept. - -- When targets are detected that are still really far off, you don't want the AI_A2A_DISPATCHER to launch intercepts just yet. - -- You want it to wait until a certain Gci range is reached, which is the **distance of the closest airbase to target** - -- being **smaller** than the **Ground Controlled Intercept radius** or **Gci radius**. - -- - -- The **default** Gci radius is defined as **200000** or **200km**. Override the default Gci radius when the era of the warfare is early, or, - -- when you don't want to let the AI_A2A_DISPATCHER react immediately when a certain border or area is not being crossed. - -- - -- Use the method @{#AI_A2A_DISPATCHER.SetGciRadius}() to set a specific controlled ground intercept radius. - -- **The Ground Controlled Intercept radius is defined for ALL squadrons which are operational.** - -- - -- Demonstration Mission: [AID-013 - AI_A2A - Intercept Test](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher/AID-A2A-013%20-%20Intercept%20Test) - -- - -- @param #AI_A2A_DISPATCHER self - -- @param #number GciRadius (Optional, Default = 200000) The radius to ground control intercept detected targets from the nearest airbase. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Set 100km as the radius to ground control intercept detected targets from the nearest airbase. - -- A2ADispatcher:SetGciRadius( 100000 ) - -- - -- -- Set 200km as the radius to ground control intercept. - -- A2ADispatcher:SetGciRadius() -- 200000 is the default value. - -- - function AI_A2A_DISPATCHER:SetGciRadius( GciRadius ) - - self.GciRadius = GciRadius or 200000 - - return self - end - - --- Define a border area to simulate a **cold war** scenario. - -- A **cold war** is one where CAP aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. - -- A **hot war** is one where CAP aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send CAP and GCI aircraft to attack it. - -- If it's a cold war then the **borders of red and blue territory** need to be defined using a @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE}. This method needs to be used for this. - -- If a hot war is chosen then **no borders** actually need to be defined using the helicopter units other than it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. Set the noborders parameter to 1 - -- @param #AI_A2A_DISPATCHER self - -- @param Core.Zone#ZONE_BASE BorderZone An object derived from ZONE_BASE, or a list of objects derived from ZONE_BASE. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Set one ZONE_POLYGON object as the border for the A2A dispatcher. - -- local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit. - -- A2ADispatcher:SetBorderZone( BorderZone ) - -- - -- or - -- - -- -- Set two ZONE_POLYGON objects as the border for the A2A dispatcher. - -- local BorderZone1 = ZONE_POLYGON( "CCCP Border1", GROUP:FindByName( "CCCP Border1" ) ) -- The GROUP object is a late activate helicopter unit. - -- local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit. - -- A2ADispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) - -- - -- - function AI_A2A_DISPATCHER:SetBorderZone( BorderZone ) - - self.Detection:SetAcceptZones( BorderZone ) - - return self - end - - --- Display a tactical report every 30 seconds about which aircraft are: - -- * Patrolling - -- * Engaging - -- * Returning - -- * Damaged - -- * Out of Fuel - -- * ... - -- @param #AI_A2A_DISPATCHER self - -- @param #boolean TacticalDisplay Provide a value of **true** to display every 30 seconds a tactical overview. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the Tactical Display for debug mode. - -- A2ADispatcher:SetTacticalDisplay( true ) - -- - function AI_A2A_DISPATCHER:SetTacticalDisplay( TacticalDisplay ) - - self.TacticalDisplay = TacticalDisplay - - return self - end - - --- Set the default damage threshold when defenders will RTB. - -- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. - -- @param #AI_A2A_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default damage threshold. - -- A2ADispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. - -- - function AI_A2A_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) - - self.DefenderDefault.DamageThreshold = DamageThreshold - - return self - end - - --- Set the default CAP time interval for squadrons, which will be used to determine a random CAP timing. - -- The default CAP time interval is between 180 and 600 seconds. - -- @param #AI_A2A_DISPATCHER self - -- @param #number CapMinSeconds The minimum amount of seconds for the random time interval. - -- @param #number CapMaxSeconds The maximum amount of seconds for the random time interval. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default CAP time interval. - -- A2ADispatcher:SetDefaultCapTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds. - -- - function AI_A2A_DISPATCHER:SetDefaultCapTimeInterval( CapMinSeconds, CapMaxSeconds ) - - self.DefenderDefault.CapMinSeconds = CapMinSeconds - self.DefenderDefault.CapMaxSeconds = CapMaxSeconds - - return self - end - - --- Set the default CAP limit for squadrons, which will be used to determine how many CAP can be airborne at the same time for the squadron. - -- The default CAP limit is 1 CAP, which means one CAP group being spawned. - -- @param #AI_A2A_DISPATCHER self - -- @param #number CapLimit The maximum amount of CAP that can be airborne at the same time for the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default CAP limit. - -- A2ADispatcher:SetDefaultCapLimit( 2 ) -- Maximum 2 CAP per squadron. - -- - function AI_A2A_DISPATCHER:SetDefaultCapLimit( CapLimit ) - - self.DefenderDefault.CapLimit = CapLimit - - return self - end - - --- Set intercept. - -- @param #AI_A2A_DISPATCHER self - -- @param #number InterceptDelay Delay in seconds before intercept. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetIntercept( InterceptDelay ) - - self.DefenderDefault.InterceptDelay = InterceptDelay - - local Detection = self.Detection -- Functional.Detection#DETECTION_AREAS - Detection:SetIntercept( true, InterceptDelay ) - - return self - end - - --- Calculates which AI friendlies are nearby the area - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return #table A list of the friendlies nearby. - function AI_A2A_DISPATCHER:GetAIFriendliesNearBy( DetectedItem ) - - local FriendliesNearBy = self.Detection:GetFriendliesDistance( DetectedItem ) - - return FriendliesNearBy - end - - --- Return the defender tasks table. - -- @param #AI_A2A_DISPATCHER self - -- @return #table Defender tasks as table. - function AI_A2A_DISPATCHER:GetDefenderTasks() - return self.DefenderTasks or {} - end - - --- Get defender task. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return #table Defender task. - function AI_A2A_DISPATCHER:GetDefenderTask( Defender ) - return self.DefenderTasks[Defender] - end - - --- Get defender task FSM. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return Core.Fsm#FSM The FSM. - function AI_A2A_DISPATCHER:GetDefenderTaskFsm( Defender ) - return self:GetDefenderTask( Defender ).Fsm - end - - --- Get target of defender. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return Target - function AI_A2A_DISPATCHER:GetDefenderTaskTarget( Defender ) - return self:GetDefenderTask( Defender ).Target - end - - --- - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return #string Squadron name of the defender task. - function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName( Defender ) - return self:GetDefenderTask( Defender ).SquadronName - end - - --- - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - function AI_A2A_DISPATCHER:ClearDefenderTask( Defender ) - if Defender and Defender:IsAlive() and self.DefenderTasks[Defender] then - local Target = self.DefenderTasks[Defender].Target - local Message = "Clearing (" .. self.DefenderTasks[Defender].Type .. ") " - Message = Message .. Defender:GetName() - if Target then - Message = Message .. (Target and (" from " .. Target.Index .. " [" .. Target.Set:Count() .. "]")) or "" - end - self:F( { Target = Message } ) - end - self.DefenderTasks[Defender] = nil - return self - end - - --- - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - function AI_A2A_DISPATCHER:ClearDefenderTaskTarget( Defender ) - - local DefenderTask = self:GetDefenderTask( Defender ) - - if Defender and Defender:IsAlive() and DefenderTask then - local Target = DefenderTask.Target - local Message = "Clearing (" .. DefenderTask.Type .. ") " - Message = Message .. Defender:GetName() - if Target then - Message = Message .. ((Target and (" from " .. Target.Index .. " [" .. Target.Set:Count() .. "]")) or "") - end - self:F( { Target = Message } ) - end - if Defender and DefenderTask and DefenderTask.Target then - DefenderTask.Target = nil - end - -- if Defender and DefenderTask then - -- if DefenderTask.Fsm:Is( "Fuel" ) - -- or DefenderTask.Fsm:Is( "LostControl") - -- or DefenderTask.Fsm:Is( "Damaged" ) then - -- self:ClearDefenderTask( Defender ) - -- end - -- end - return self - end - - --- Set defender task. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName Name of the squadron. - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @param #table Type Type of the defender task - -- @param Core.Fsm#FSM Fsm The defender task FSM. - -- @param Functional.Detection#DETECTION_BASE.DetectedItem Target The defender detected item. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target ) - - self:F( { SquadronName = SquadronName, Defender = Defender:GetName(), Type = Type, Target = Target } ) - - self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {} - self.DefenderTasks[Defender].Type = Type - self.DefenderTasks[Defender].Fsm = Fsm - self.DefenderTasks[Defender].SquadronName = SquadronName - - if Target then - self:SetDefenderTaskTarget( Defender, Target ) - end - return self - end - - --- Set defender task target. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection The detection object. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefenderTaskTarget( Defender, AttackerDetection ) - - local Message = "(" .. self.DefenderTasks[Defender].Type .. ") " - Message = Message .. Defender:GetName() - Message = Message .. ((AttackerDetection and (" target " .. AttackerDetection.Index .. " [" .. AttackerDetection.Set:Count() .. "]")) or "") - self:F( { AttackerDetection = Message } ) - if AttackerDetection then - self.DefenderTasks[Defender].Target = AttackerDetection - end - return self - end - - --- This is the main method to define Squadrons programmatically. - -- Squadrons: - -- - -- * Have a **name or key** that is the identifier or key of the squadron. - -- * Have **specific plane types** defined by **templates**. - -- * Are **located at one specific airbase**. Multiple squadrons can be located at one airbase through. - -- * Optionally have a limited set of **resources**. The default is that squadrons have unlimited resources. - -- - -- The name of the squadron given acts as the **squadron key** in the AI\_A2A\_DISPATCHER:Squadron...() methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new aircraft are **taking off** from the airfield (in the air, cold, hot, at the runway). - -- * Control how returning aircraft are **landing** at the airfield (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft. - -- - -- @param #AI_A2A_DISPATCHER self - -- - -- @param #string SquadronName A string (text) that defines the squadron identifier or the key of the Squadron. - -- It can be any name, for example `"104th Squadron"` or `"SQ SQUADRON1"`, whatever. - -- As long as you remember that this name becomes the identifier of your squadron you have defined. - -- You need to use this name in other methods too! - -- - -- @param #string AirbaseName The airbase name where you want to have the squadron located. - -- You need to specify here EXACTLY the name of the airbase as you see it in the mission editor. - -- Examples are `"Batumi"` or `"Tbilisi-Lochini"`. - -- EXACTLY the airbase name, between quotes `""`. - -- To ease the airbase naming when using the LDT editor and IntelliSense, the @{Wrapper.Airbase#AIRBASE} class contains enumerations of the airbases of each map. - -- - -- * Caucasus: @{Wrapper.Airbase#AIRBASE.Caucaus} - -- * Nevada or NTTR: @{Wrapper.Airbase#AIRBASE.Nevada} - -- * Normandy: @{Wrapper.Airbase#AIRBASE.Normandy} - -- - -- @param #string TemplatePrefixes A string or an array of strings specifying the **prefix names of the templates** (not going to explain what is templates here again). - -- Examples are `{ "104th", "105th" }` or `"104th"` or `"Template 1"` or `"BLUE PLANES"`. - -- Just remember that your template (groups late activated) need to start with the prefix you have specified in your code. - -- If you have only one prefix name for a squadron, you don't need to use the `{ }`, otherwise you need to use the brackets. - -- - -- @param #number ResourceCount (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available. - -- @return #AI_A2A_DISPATCHER self - -- - -- @usage - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- @usage - -- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock... - -- A2ADispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 ) - -- - -- @usage - -- -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock... - -- -- Note that in this implementation, the A2A dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses. - -- -- Note the usage of the {} for the airplane templates list. - -- A2ADispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 ) - -- - -- @usage - -- -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock... - -- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) - -- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 ) - -- - -- @usage - -- -- This is an example like the previous, but now with infinite resources. - -- -- The ResourceCount parameter is not given in the SetSquadron method. - -- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) - -- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) - -- - function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] -- #AI_A2A_DISPATCHER.Squadron - - DefenderSquadron.Name = SquadronName - DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName ) - DefenderSquadron.AirbaseName = DefenderSquadron.Airbase:GetName() - if not DefenderSquadron.Airbase then - error( "Cannot find airbase with name:" .. AirbaseName ) - end - - DefenderSquadron.Spawn = {} - if type( TemplatePrefixes ) == "string" then - local SpawnTemplate = TemplatePrefixes - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - DefenderSquadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate] - else - for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - DefenderSquadron.Spawn[#DefenderSquadron.Spawn + 1] = self.DefenderSpawns[SpawnTemplate] - end - end - DefenderSquadron.ResourceCount = ResourceCount - DefenderSquadron.TemplatePrefixes = TemplatePrefixes - DefenderSquadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. - - self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default. - - self:F( { Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) - - return self - end - - --- Get an item from the Squadron table. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName Name of the squadron. - -- @return #AI_A2A_DISPATCHER.Squadron Defender squadron table. - function AI_A2A_DISPATCHER:GetSquadron( SquadronName ) - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - if not DefenderSquadron then - error( "Unknown Squadron:" .. SquadronName ) - end - - return DefenderSquadron - end - - --- Get a resource count from a specific squadron - -- @param #AI_A2A_DISPATCHER self - -- @param #string Squadron Name of the squadron. - -- @return #number Number of airframes available or nil if the squadron does not exist - function AI_A2A_DISPATCHER:QuerySquadron(Squadron) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - self:T2(string.format("%s = %s",Squadron.Name,Squadron.ResourceCount)) - return Squadron.ResourceCount - end - self:F({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - return nil - end - - --- [DEPRECATED - Might create problems launching planes] Set the Squadron visible before startup of the dispatcher. - -- All planes will be spawned as uncontrolled on the parking spot. - -- They will lock the parking spot. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- A2ADispatcher:SetSquadronVisible( "Mineralnye" ) - -- - function AI_A2A_DISPATCHER:SetSquadronVisible( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) -- #AI_A2A_DISPATCHER.Squadron - - DefenderSquadron.Uncontrolled = true - - -- For now, grouping is forced to 1 due to other parts of the class which would not work well with grouping>1. - DefenderSquadron.Grouping = 1 - - -- Get free parking for fighter aircraft. - local nfreeparking = DefenderSquadron.Airbase:GetFreeParkingSpotsNumber( AIRBASE.TerminalType.FighterAircraft, true ) - - -- Take number of free parking spots if no resource count was specified. - DefenderSquadron.ResourceCount = DefenderSquadron.ResourceCount or nfreeparking - - -- Check that resource count is not larger than free parking spots. - DefenderSquadron.ResourceCount = math.min( DefenderSquadron.ResourceCount, nfreeparking ) - - -- Set uncontrolled spawning option. - for SpawnTemplate, _DefenderSpawn in pairs( self.DefenderSpawns ) do - local DefenderSpawn = _DefenderSpawn -- Core.Spawn#SPAWN - DefenderSpawn:InitUnControlled( true ) - end - - end - - --- Check if the Squadron is visible before startup of the dispatcher. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #boolean true if visible. - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = A2ADispatcher:IsSquadronVisible( "Mineralnye" ) - -- - function AI_A2A_DISPATCHER:IsSquadronVisible( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) -- #AI_A2A_DISPATCHER.Squadron - - if DefenderSquadron then - return DefenderSquadron.Uncontrolled == true - end - - return nil - - end - - --- Set a CAP for a Squadron. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed The maximum speed at which the engage can be executed. - -- @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 #number EngageAltType The altitude type to engage, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. - -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed The maximum speed at which the cap can be executed. - -- @param #number PatrolFloorAltitude The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude the maximum altitude at which the cap can be executed. - -- @param #number PatrolAltType The altitude type to patrol, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- -- Setup a CAP, engaging between 800 and 900 km/h, altitude 30 (above the sea), radio altitude measurement, - -- -- patrolling speed between 500 and 600 km/h, altitude between 4000 and 10000 meters, barometric altitude measurement. - -- A2ADispatcher:SetSquadronCapV2( "Mineralnye", 800, 900, 30, 30, "RADIO", CAPZoneEast, 500, 600, 4000, 10000, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 4000 and 10000 meters, radio altitude measurement, - -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, barometric altitude measurement. - -- A2ADispatcher:SetSquadronCapV2( "Sochi", 800, 1200, 2000, 3000, "RADIO", CAPZoneWest, 600, 800, 4000, 8000, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 5000 and 8000 meters, barometric altitude measurement, - -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, radio altitude. - -- A2ADispatcher:SetSquadronCapV2( "Maykop", 800, 1200, 5000, 8000, "BARO", CAPZoneMiddle, 600, 800, 4000, 8000, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Maykop", 2, 30, 120, 1 ) - -- - function AI_A2A_DISPATCHER:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Cap = self.DefenderSquadrons[SquadronName].Cap - Cap.Name = SquadronName - Cap.EngageMinSpeed = EngageMinSpeed - Cap.EngageMaxSpeed = EngageMaxSpeed - Cap.EngageFloorAltitude = EngageFloorAltitude - Cap.EngageCeilingAltitude = EngageCeilingAltitude - Cap.Zone = Zone - Cap.PatrolMinSpeed = PatrolMinSpeed - Cap.PatrolMaxSpeed = PatrolMaxSpeed - Cap.PatrolFloorAltitude = PatrolFloorAltitude - Cap.PatrolCeilingAltitude = PatrolCeilingAltitude - Cap.PatrolAltType = PatrolAltType - Cap.EngageAltType = EngageAltType - - self:SetSquadronCapInterval( SquadronName, self.DefenderDefault.CapLimit, self.DefenderDefault.CapMinSeconds, self.DefenderDefault.CapMaxSeconds, 1 ) - - self:T( { CAP = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageAltType } } ) - - -- Add the CAP to the EWR network. - - local RecceSet = self.Detection:GetDetectionSet() - RecceSet:FilterPrefixes( DefenderSquadron.TemplatePrefixes ) - RecceSet:FilterStart() - - self.Detection:SetFriendlyPrefixes( DefenderSquadron.TemplatePrefixes ) - - return self - end - - --- Set a CAP for a Squadron. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. - -- @param #number PatrolFloorAltitude The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude the maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed The maximum speed at which the cap can be executed. - -- @param #number EngageMinSpeed The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed The maximum speed at which the engage can be executed. - -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - function AI_A2A_DISPATCHER:SetSquadronCap( SquadronName, Zone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - return self:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, AltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, AltType ) - end - - --- Set the squadron CAP parameters. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number CapLimit (optional) The maximum amount of CAP groups to be spawned. Note that a CAP is a group, so can consist out of 1 to 4 airplanes. The default is 1 CAP group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new CAP will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new CAP will be spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- CAP Squadron execution. - -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) - -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") - -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - function AI_A2A_DISPATCHER:SetSquadronCapInterval( SquadronName, CapLimit, LowInterval, HighInterval, Probability ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Cap = self.DefenderSquadrons[SquadronName].Cap - if Cap then - Cap.LowInterval = LowInterval or 180 - Cap.HighInterval = HighInterval or 600 - Cap.Probability = Probability or 1 - Cap.CapLimit = CapLimit or 1 - Cap.Scheduler = Cap.Scheduler or SCHEDULER:New( self ) - local Scheduler = Cap.Scheduler -- Core.Scheduler#SCHEDULER - local ScheduleID = Cap.ScheduleID - local Variance = (Cap.HighInterval - Cap.LowInterval) / 2 - local Repeat = Cap.LowInterval + Variance - local Randomization = Variance / Repeat - local Start = math.random( 1, Cap.HighInterval ) - - if ScheduleID then - Scheduler:Stop( ScheduleID ) - end - - Cap.ScheduleID = Scheduler:Schedule( self, self.SchedulerCAP, { SquadronName }, Start, Repeat, Randomization ) - else - error( "This squadron does not exist:" .. SquadronName ) - end - - end - - --- - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:GetCAPDelay( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Cap = self.DefenderSquadrons[SquadronName].Cap - if Cap then - return math.random( Cap.LowInterval, Cap.HighInterval ) - else - error( "This squadron does not exist:" .. SquadronName ) - end - end - - --- Check if squadron can do CAP. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2A_DISPATCHER.Squadron DefenderSquadron - function AI_A2A_DISPATCHER:CanCAP( SquadronName ) - self:F( { SquadronName = SquadronName } ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured. - - if (not DefenderSquadron.ResourceCount) or (DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0) then -- And, if there are sufficient resources. - - local Cap = DefenderSquadron.Cap - if Cap then - local CapCount = self:CountCapAirborne( SquadronName ) - self:F( { CapCount = CapCount } ) - if CapCount < Cap.CapLimit then - local Probability = math.random() - if Probability <= Cap.Probability then - return DefenderSquadron - end - end - end - end - end - return nil - end - - --- Set race track pattern as default when any squadron is performing CAP. - -- @param #AI_A2A_DISPATCHER self - -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. - -- @param #number LeglengthMax Max length of the race track leg in meters. Default 15,000 m. - -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. counter clockwise from South to North. - -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. counter clockwise from North to South. - -- @param #number DurationMin (Optional) Min duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. - -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. - -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefaultCapRacetrack( LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates ) - - self.DefenderDefault.Racetrack = true - self.DefenderDefault.RacetrackLengthMin = LeglengthMin - self.DefenderDefault.RacetrackLengthMax = LeglengthMax - self.DefenderDefault.RacetrackHeadingMin = HeadingMin - self.DefenderDefault.RacetrackHeadingMax = HeadingMax - self.DefenderDefault.RacetrackDurationMin = DurationMin - self.DefenderDefault.RacetrackDurationMax = DurationMax - self.DefenderDefault.RacetrackCoordinates = CapCoordinates - - return self - end - - --- Set race track pattern when squadron is performing CAP. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName Name of the squadron. - -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. - -- @param #number LeglengthMax Max length of the race track leg in meters. Default 15,000 m. - -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. - -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from North to South. - -- @param #number DurationMin (Optional) Min duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. - -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. - -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. - -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetSquadronCapRacetrack( SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron then - DefenderSquadron.Racetrack = true - DefenderSquadron.RacetrackLengthMin = LeglengthMin - DefenderSquadron.RacetrackLengthMax = LeglengthMax - DefenderSquadron.RacetrackHeadingMin = HeadingMin - DefenderSquadron.RacetrackHeadingMax = HeadingMax - DefenderSquadron.RacetrackDurationMin = DurationMin - DefenderSquadron.RacetrackDurationMax = DurationMax - DefenderSquadron.RacetrackCoordinates = CapCoordinates - end - - return self - end - - --- Check if squadron can do GCI. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron - function AI_A2A_DISPATCHER:CanGCI( SquadronName ) - self:F( { SquadronName = SquadronName } ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured. - - if (not DefenderSquadron.ResourceCount) or (DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0) then -- And, if there are sufficient resources. - local Gci = DefenderSquadron.Gci - if Gci then - return DefenderSquadron - end - end - end - return nil - end - - --- Set squadron GCI. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed The minimum speed [km/h] at which the GCI can be executed. - -- @param #number EngageMaxSpeed The maximum speed [km/h] at which the GCI can be executed. - -- @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". - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci2( "Mozdok", 900, 1200, 5000, 5000, "BARO" ) - -- A2ADispatcher:SetSquadronGci2( "Novo", 900, 2100, 30, 30, "RADIO" ) - -- A2ADispatcher:SetSquadronGci2( "Maykop", 900, 1200, 100, 300, "RADIO" ) - -- - function AI_A2A_DISPATCHER:SetSquadronGci2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} - - local Intercept = self.DefenderSquadrons[SquadronName].Gci - Intercept.Name = SquadronName - Intercept.EngageMinSpeed = EngageMinSpeed - Intercept.EngageMaxSpeed = EngageMaxSpeed - Intercept.EngageFloorAltitude = EngageFloorAltitude - Intercept.EngageCeilingAltitude = EngageCeilingAltitude - Intercept.EngageAltType = EngageAltType - - self:T( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - end - - --- Set squadron GCI. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed The minimum speed [km/h] at which the GCI can be executed. - -- @param #number EngageMaxSpeed The maximum speed [km/h] at which the GCI can be executed. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- GCI Squadron execution. - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- A2ADispatcher:SetSquadronGci( "Novo", 900, 2100 ) - -- A2ADispatcher:SetSquadronGci( "Maykop", 900, 1200 ) - -- - function AI_A2A_DISPATCHER:SetSquadronGci( SquadronName, EngageMinSpeed, EngageMaxSpeed ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} - - local Intercept = self.DefenderSquadrons[SquadronName].Gci - Intercept.Name = SquadronName - Intercept.EngageMinSpeed = EngageMinSpeed - Intercept.EngageMaxSpeed = EngageMaxSpeed - - self:F( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed } } ) - end - - --- Defines the default amount of extra planes that will take-off as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- @return #AI_A2A_DISPATCHER - -- The default overhead is 1, so equal balance. The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: - -- - -- * Higher than 1, will increase the defense unit amounts. - -- * Lower than 1, will decrease the defense unit amounts. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. - -- - -- See example below. - -- - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. - -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. - -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. - -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. - -- - -- A2ADispatcher:SetDefaultOverhead( 1.5 ) - -- - function AI_A2A_DISPATCHER:SetDefaultOverhead( Overhead ) - - self.DefenderDefault.Overhead = Overhead - - return self - end - - --- Defines the amount of extra planes that will take-off as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- @return #AI_A2A_DISPATCHER self - -- The default overhead is 1, so equal balance. The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: - -- - -- * Higher than 1, will increase the defense unit amounts. - -- * Lower than 1, will decrease the defense unit amounts. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. - -- - -- See example below. - -- - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. - -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. - -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. - -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. - -- - -- A2ADispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) - -- - function AI_A2A_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Overhead = Overhead - - return self - end - - --- Sets the default grouping of new airplanes spawned. - -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. - -- @param #AI_A2A_DISPATCHER self - -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Set a grouping by default per 2 airplanes. - -- A2ADispatcher:SetDefaultGrouping( 2 ) - -- - function AI_A2A_DISPATCHER:SetDefaultGrouping( Grouping ) - - self.DefenderDefault.Grouping = Grouping - - return self - end - - --- Sets the grouping of new airplanes spawned. - -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Grouping The level of grouping that will be applied of the CAP or GCI defenders. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Set a grouping per 2 airplanes. - -- A2ADispatcher:SetSquadronGrouping( "SquadronName", 2 ) - -- - function AI_A2A_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Grouping = Grouping - - return self - end - - --- Defines the default method at which new flights will spawn and take-off as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights by default take-off from the runway. - -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights by default take-off from the airbase hot. - -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights by default take-off from the airbase cold. - -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Cold ) - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoff( Takeoff ) - - self.DefenderDefault.Takeoff = Takeoff - - return self - end - - --- Defines the method at which new flights will spawn and take-off as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights take-off from the runway. - -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights take-off from the airbase hot. - -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights take-off from the airbase cold. - -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Takeoff = Takeoff - - return self - end - - --- Gets the default method at which new flights will spawn and take-off as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- local TakeoffMethod = A2ADispatcher:GetDefaultTakeoff() - -- if TakeOffMethod == , AI_A2A_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_A2A_DISPATCHER:GetDefaultTakeoff() - - return self.DefenderDefault.Takeoff - end - - --- Gets the method at which new flights will spawn and take-off as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- local TakeoffMethod = A2ADispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeOffMethod == , AI_A2A_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_A2A_DISPATCHER:GetSquadronTakeoff( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff - end - - --- Sets flights to default take-off in the air, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- A2ADispatcher:SetDefaultTakeoffInAir() - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() - - self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Air ) - - return self - end - - --- Set flashing player messages on or off - -- @param #AI_A2A_DISPATCHER self - -- @param #boolean onoff Set messages on (true) or off (false) - function AI_A2A_DISPATCHER:SetSendMessages( onoff ) - self.SetSendPlayerMessages = onoff - end - - --- Sets flights to take-off in the air, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- A2ADispatcher:SetSquadronTakeoffInAir( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) - - self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Air ) - - if TakeoffAltitude then - self:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) - end - - return self - end - - --- Sets flights by default to take-off from the runway, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off from the runway. - -- A2ADispatcher:SetDefaultTakeoffFromRunway() - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() - - self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Runway ) - - return self - end - - --- Sets flights to take-off from the runway, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from the runway. - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Runway ) - - return self - end - - --- Sets flights by default to take-off from the airbase at a hot location, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off at a hot parking spot. - -- A2ADispatcher:SetDefaultTakeoffFromParkingHot() - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() - - self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Hot ) - - return self - end - - --- Sets flights to take-off from the airbase at a hot location, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Hot ) - - return self - end - - --- Sets flights to by default take-off from the airbase at a cold location, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from a cold parking spot. - -- A2ADispatcher:SetDefaultTakeoffFromParkingCold() - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() - - self:SetDefaultTakeoff( AI_A2A_DISPATCHER.Takeoff.Cold ) - - return self - end - - --- Sets flights to take-off from the airbase at a cold location, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from a cold parking spot. - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2A_DISPATCHER.Takeoff.Cold ) - - return self - end - - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. - -- @param #AI_A2A_DISPATCHER self - -- @param #number TakeoffAltitude The altitude in meters above the ground. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- A2ADispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. - -- - function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude ) - - self.DefenderDefault.TakeoffAltitude = TakeoffAltitude - - return self - end - - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude The altitude in meters above the ground. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- A2ADispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. - -- - function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.TakeoffAltitude = TakeoffAltitude - - return self - end - - --- Defines the default method at which flights will land and despawn as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights by default despawn after landing land at the runway. - -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. - -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtEngineShutdown ) - -- - function AI_A2A_DISPATCHER:SetDefaultLanding( Landing ) - - self.DefenderDefault.Landing = Landing - - return self - end - - --- Defines the method at which flights will land and despawn as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights despawn after landing land at the runway. - -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights despawn after landing and parking, and after engine shutdown. - -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown ) - -- - function AI_A2A_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Landing = Landing - - return self - end - - --- Gets the default method at which flights will land and despawn as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = A2ADispatcher:GetDefaultLanding( AI_A2A_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_A2A_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_A2A_DISPATCHER:GetDefaultLanding() - - return self.DefenderDefault.Landing - end - - --- Gets the method at which flights will land and despawn as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- local LandingMethod = A2ADispatcher:GetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_A2A_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_A2A_DISPATCHER:GetSquadronLanding( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Landing or self.DefenderDefault.Landing - end - - --- Sets flights by default to land and despawn near the airbase in the air, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights by default to land near the airbase and despawn. - -- A2ADispatcher:SetDefaultLandingNearAirbase() - -- - function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() - - self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.NearAirbase ) - - return self - end - - --- Sets flights to land and despawn near the airbase in the air, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights to land near the airbase and despawn. - -- A2ADispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.NearAirbase ) - - return self - end - - --- Sets flights by default to land and despawn at the runway, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land at the runway and despawn. - -- A2ADispatcher:SetDefaultLandingAtRunway() - -- - function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() - - self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtRunway ) - - return self - end - - --- Sets flights to land and despawn at the runway, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights land at the runway and despawn. - -- A2ADispatcher:SetSquadronLandingAtRunway( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtRunway ) - - return self - end - - --- Sets flights by default to land and despawn at engine shutdown, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land and despawn at engine shutdown. - -- A2ADispatcher:SetDefaultLandingAtEngineShutdown() - -- - function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() - - self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - --- Sets flights to land and despawn at engine shutdown, as part of the defense system. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #AI_A2A_DISPATCHER self - -- @usage: - -- - -- local A2ADispatcher = AI_A2A_DISPATCHER:New( ... ) - -- - -- -- Let flights land and despawn at engine shutdown. - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) - -- - function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - --- Set the default fuel threshold when defenders will RTB or Refuel in the air. - -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. - -- @param #AI_A2A_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_A2A_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) - - self.DefenderDefault.FuelThreshold = FuelThreshold - - return self - end - - --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2ADispatcher:SetSquadronFuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_A2A_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.FuelThreshold = FuelThreshold - - return self - end - - --- Set the default tanker where defenders will Refuel in the air. - -- @param #AI_A2A_DISPATCHER self - -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_A2A_DISPATCHER self - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the default tanker. - -- A2ADispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. - -- - function AI_A2A_DISPATCHER:SetDefaultTanker( TankerName ) - - self.DefenderDefault.TankerName = TankerName - - return self - end - - --- Set the squadron tanker where defenders will Refuel in the air. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the squadron fuel threshold. - -- A2ADispatcher:SetSquadronFuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the squadron tanker. - -- A2ADispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. - -- - function AI_A2A_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.TankerName = TankerName - - return self - end - - --- Set the squadron language. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #string Language A string defining the language to be embedded within the miz file. - -- @return #AI_A2A_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. - -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) - -- - -- -- Set for English. - -- A2ADispatcher:SetSquadronLanguage( "SquadronName", "EN" ) -- This squadron speaks English. - -- - -- -- Set for Russian. - -- A2ADispatcher:SetSquadronLanguage( "SquadronName", "RU" ) -- This squadron speaks Russian. - function AI_A2A_DISPATCHER:SetSquadronLanguage( SquadronName, Language ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Language = Language - - if DefenderSquadron.RadioQueue then - DefenderSquadron.RadioQueue:SetLanguage( Language ) - end - - return self - end - - --- Set the frequency of communication and the mode of communication for voice overs. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number RadioFrequency The frequency of communication. - -- @param #number RadioModulation The modulation of communication. - -- @param #number RadioPower The power in Watts of communication. - function AI_A2A_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.RadioFrequency = RadioFrequency - DefenderSquadron.RadioModulation = RadioModulation or radio.modulation.AM - DefenderSquadron.RadioPower = RadioPower or 100 - - if DefenderSquadron.RadioQueue then - DefenderSquadron.RadioQueue:Stop() - end - - DefenderSquadron.RadioQueue = nil - - DefenderSquadron.RadioQueue = RADIOSPEECH:New( DefenderSquadron.RadioFrequency, DefenderSquadron.RadioModulation ) - DefenderSquadron.RadioQueue.power = DefenderSquadron.RadioPower - DefenderSquadron.RadioQueue:Start( 0.5 ) - - DefenderSquadron.RadioQueue:SetLanguage( DefenderSquadron.Language ) - end - - --- Add defender to squadron. Resource count will get smaller. - -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_DISPATCHER.Squadron Squadron The squadron. - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @param #number Size Size of the group. - function AI_A2A_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - self.Defenders[DefenderName] = Squadron - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount - Size - end - self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) - end - - --- Remove defender from squadron. Resource count will increase. - -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_DISPATCHER.Squadron Squadron The squadron. - -- @param Wrapper.Group#GROUP Defender The defender group. - function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount + Defender:GetSize() - end - self.Defenders[DefenderName] = nil - self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) - end - - --- Get squadron from defender. - -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return #AI_A2A_DISPATCHER.Squadron Squadron The squadron. - function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender ) - self.Defenders = self.Defenders or {} - if Defender ~= nil then - local DefenderName = Defender:GetName() - self:F( { DefenderName = DefenderName } ) - return self.Defenders[DefenderName] - else - return nil - end - end - - --- Creates an SWEEP task when there are targets for it. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - function AI_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - if DetectedItem.IsDetected == false then - - -- Here we're doing something advanced... We're copying the DetectedSet. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Count number of airborne CAP flights. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName Name of the squadron. - -- @return #number Number of defender CAP groups. - function AI_A2A_DISPATCHER:CountCapAirborne( SquadronName ) - - local CapCount = 0 - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - if DefenderSquadron then - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - if DefenderTask.SquadronName == SquadronName then - if DefenderTask.Type == "CAP" then - if AIGroup and AIGroup:IsAlive() then - -- Check if the CAP is patrolling or engaging. If not, this is not a valid CAP, even if it is alive! - -- The CAP could be damaged, lost control, or out of fuel! - -- env.info("FF fsm state "..tostring(DefenderTask.Fsm:GetState())) - if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" ) or DefenderTask.Fsm:Is( "Started" ) then - -- env.info("FF capcount "..CapCount) - CapCount = CapCount + 1 - end - end - end - end - end - end - - return CapCount - end - - --- Count number of engaging defender groups. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detection object. - -- @return #number Number of defender groups engaging. - function AI_A2A_DISPATCHER:CountDefendersEngaged( AttackerDetection ) - - -- First, count the active AIGroups Units, targeting the DetectedSet - local DefenderCount = 0 - - local DetectedSet = AttackerDetection.Set - -- DetectedSet:Flush() - - local DefenderTasks = self:GetDefenderTasks() - - for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do - local Defender = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskTarget = DefenderTask.Target -- Functional.Detection#DETECTION_BASE.DetectedItem - local DefenderSquadronName = DefenderTask.SquadronName - - if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then - local Squadron = self:GetSquadron( DefenderSquadronName ) - local SquadronOverhead = Squadron.Overhead or self.DefenderDefault.Overhead - - local DefenderSize = Defender:GetInitialSize() - if DefenderSize then - DefenderCount = DefenderCount + DefenderSize / SquadronOverhead - self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize ) - else - DefenderCount = 0 - end - end - end - - self:F( { DefenderCount = DefenderCount } ) - - return DefenderCount - end - - --- Count defenders to be engaged if number of attackers larger than number of defenders. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefenderCount Number of defenders. - -- @return #table Table of friendly groups. - function AI_A2A_DISPATCHER:CountDefendersToBeEngaged( AttackerDetection, DefenderCount ) - - local Friendlies = nil - - local AttackerSet = AttackerDetection.Set - local AttackerCount = AttackerSet:Count() - - local DefenderFriendlies = self:GetAIFriendliesNearBy( AttackerDetection ) - - for FriendlyDistance, AIFriendly in UTILS.spairs( DefenderFriendlies or {} ) do - -- We only allow to ENGAGE targets as long as the Units on both sides are balanced. - if AttackerCount > DefenderCount then - --self:T("***** AI_A2A_DISPATCHER:CountDefendersToBeEngaged() *****\nThis is supposed to be a UNIT:") - if AIFriendly then - local classname = AIFriendly.ClassName or "No Class Name" - local unitname = AIFriendly.IdentifiableName or "No Unit Name" - --self:T("Class Name: " .. classname) - --self:T("Unit Name: " .. unitname) - --self:T({AIFriendly}) - end - local Friendly = nil - if AIFriendly and AIFriendly:IsAlive() then - --self:T("AIFriendly alive, getting GROUP") - Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP - end - - if Friendly and Friendly:IsAlive() then - -- Ok, so we have a friendly near the potential target. - -- Now we need to check if the AIGroup has a Task. - local DefenderTask = self:GetDefenderTask( Friendly ) - if DefenderTask then - -- The Task should be CAP or GCI - if DefenderTask.Type == "CAP" or DefenderTask.Type == "GCI" then - -- If there is no target, then add the AIGroup to the ResultAIGroups for Engagement to the AttackerSet - if DefenderTask.Target == nil then - if DefenderTask.Fsm:Is( "Returning" ) or DefenderTask.Fsm:Is( "Patrolling" ) then - Friendlies = Friendlies or {} - Friendlies[Friendly] = Friendly - DefenderCount = DefenderCount + Friendly:GetSize() - self:F( { Friendly = Friendly:GetName(), FriendlyDistance = FriendlyDistance } ) - end - end - end - end - end - else - break - end - end - - return Friendlies - end - - --- Activate resource. - -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The defender squadron. - -- @param #number DefendersNeeded Number of defenders needed. Default 4. - -- @return Wrapper.Group#GROUP The defender group. - -- @return #boolean Grouping. - function AI_A2A_DISPATCHER:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - local SquadronName = DefenderSquadron.Name - - DefendersNeeded = DefendersNeeded or 4 - - local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - - DefenderGrouping = (DefenderGrouping < DefendersNeeded) and DefenderGrouping or DefendersNeeded - - -- env.info(string.format("FF resource activate: Squadron=%s grouping=%d needed=%d visible=%s", SquadronName, DefenderGrouping, DefendersNeeded, tostring(self:IsSquadronVisible( SquadronName )))) - - if self:IsSquadronVisible( SquadronName ) then - - local n = #self.uncontrolled[SquadronName] - - if n > 0 then - -- Random number 1,...n - local id = math.random( n ) - - -- Pick a random defender group. - local Defender = self.uncontrolled[SquadronName][id].group -- Wrapper.Group#GROUP - - -- Start uncontrolled group. - Defender:StartUncontrolled() - - -- Get grouping. - DefenderGrouping = self.uncontrolled[SquadronName][id].grouping - - -- Add defender to squadron. - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - - -- Remove defender from uncontrolled table. - table.remove( self.uncontrolled[SquadronName], id ) - - return Defender, DefenderGrouping - else - return nil, 0 - end - - -- Here we CAP the new planes. - -- The Resources table is filled in advance. - local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) -- Choose the template. - - --[[ - -- We determine the grouping based on the parameters set. - self:F( { DefenderGrouping = DefenderGrouping } ) - - -- New we will form the group to spawn in. - -- We search for the first free resource matching the template. - local DefenderUnitIndex = 1 - local DefenderCAPTemplate = nil - local DefenderName = nil - for GroupName, DefenderGroup in pairs( DefenderSquadron.Resources[TemplateID] or {} ) do - self:F( { GroupName = GroupName } ) - local DefenderTemplate = _DATABASE:GetGroupTemplate( GroupName ) - if DefenderUnitIndex == 1 then - DefenderCAPTemplate = UTILS.DeepCopy( DefenderTemplate ) - self.DefenderCAPIndex = self.DefenderCAPIndex + 1 - DefenderCAPTemplate.name = SquadronName .. "#" .. self.DefenderCAPIndex .. "#" .. GroupName - DefenderName = DefenderCAPTemplate.name - else - -- Add the unit in the template to the DefenderCAPTemplate. - local DefenderUnitTemplate = DefenderTemplate.units[1] - DefenderCAPTemplate.units[DefenderUnitIndex] = DefenderUnitTemplate - end - DefenderUnitIndex = DefenderUnitIndex + 1 - DefenderSquadron.Resources[TemplateID][GroupName] = nil - if DefenderUnitIndex > DefenderGrouping then - break - end - - end - - if DefenderCAPTemplate then - local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) - local SpawnGroup = GROUP:Register( DefenderName ) - DefenderCAPTemplate.lateActivation = nil - DefenderCAPTemplate.uncontrolled = nil - local Takeoff = self:GetSquadronTakeoff( SquadronName ) - DefenderCAPTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type - DefenderCAPTemplate.route.points[1].action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - local Defender = _DATABASE:Spawn( DefenderCAPTemplate ) - - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - return Defender, DefenderGrouping - end - ]] - - else - - ---------------------------- - --- Squadron not visible --- - ---------------------------- - - local Spawn = DefenderSquadron.Spawn[math.random( 1, #DefenderSquadron.Spawn )] -- Core.Spawn#SPAWN - - if DefenderGrouping then - Spawn:InitGrouping( DefenderGrouping ) - else - Spawn:InitGrouping() - end - - local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) - - local Defender = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP - - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - - return Defender, DefenderGrouping - end - - return nil, nil - end - - --- On after "CAP" event. - -- @param #AI_A2A_DISPATCHER self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param #string SquadronName Name of the squadron. - function AI_A2A_DISPATCHER:onafterCAP( From, Event, To, SquadronName ) - - self:F( { SquadronName = SquadronName } ) - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:CanCAP( SquadronName ) - - if DefenderSquadron then - - local Cap = DefenderSquadron.Cap - - if Cap then - - local DefenderCAP, DefenderGrouping = self:ResourceActivate( DefenderSquadron ) - - if DefenderCAP then - - local AI_A2A_Fsm = AI_A2A_CAP:New2( DefenderCAP, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.EngageFloorAltitude, Cap.EngageCeilingAltitude, Cap.EngageAltType, Cap.Zone, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.PatrolFloorAltitude, Cap.PatrolCeilingAltitude, Cap.PatrolAltType ) - AI_A2A_Fsm:SetDispatcher( self ) - AI_A2A_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - AI_A2A_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - AI_A2A_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - AI_A2A_Fsm:SetDisengageRadius( self.DisengageRadius ) - AI_A2A_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) - if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then - AI_A2A_Fsm:SetRaceTrackPattern( DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, - DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, - DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, - DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, - DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, - DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, - DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates ) - end - AI_A2A_Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", AI_A2A_Fsm ) - - function AI_A2A_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - -- Issue GetCallsign() returns nil, see https://github.com/FlightControl-Master/MOOSE/issues/1228 - if DefenderGroup and DefenderGroup:IsAlive() then - self:F( { "CAP Takeoff", DefenderGroup:GetName() } ) - -- self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2A_Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron then - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup ) - end - AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling - end - end - end - - function AI_A2A_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) - if DefenderGroup and DefenderGroup:IsAlive() then - self:F( { "CAP PatrolRoute", DefenderGroup:GetName() } ) - self:GetParent( self ).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) - end - - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - end - - function AI_A2A_Fsm:onafterRTB( DefenderGroup, From, Event, To ) - if DefenderGroup and DefenderGroup:IsAlive() then - self:F( { "CAP RTB", DefenderGroup:GetName() } ) - - self:GetParent( self ).onafterRTB( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) - end - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - end - - --- AI_A2A_Fsm:onafterHome - -- @param #AI_A2A_DISPATCHER self - function AI_A2A_Fsm:onafterHome( Defender, From, Event, To, Action ) - if Defender and Defender:IsAlive() then - self:F( { "CAP Home", Defender:GetName() } ) - self:GetParent( self ).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ParkDefender( Squadron ) - end - end - end - end - end - end - - end - - --- On after "ENGAGE" event. - -- @param #AI_A2A_DISPATCHER self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #table Defenders Defenders table. - function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, AttackerDetection, Defenders ) - self:F( "ENGAGING Detection ID=" .. tostring( AttackerDetection.ID ) ) - - if Defenders then - - for DefenderID, Defender in pairs( Defenders ) do - - local Fsm = self:GetDefenderTaskFsm( Defender ) - - Fsm:EngageRoute( AttackerDetection.Set ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( Defender, AttackerDetection ) - - end - - end - end - - --- On after "GCI" event. - -- @param #AI_A2A_DISPATCHER self - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. - -- @param #number DefendersMissing Number of missing defenders. - -- @param #table DefenderFriendlies Friendly defenders. - function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies ) - - self:F( "GCI Detection ID=" .. tostring( AttackerDetection.ID ) ) - - self:F( { From, Event, To, AttackerDetection.Index, DefendersMissing, DefenderFriendlies } ) - - local AttackerSet = AttackerDetection.Set - local AttackerUnit = AttackerSet:GetFirst() - - if AttackerUnit and AttackerUnit:IsAlive() then - local AttackerCount = AttackerSet:Count() - local DefenderCount = 0 - - for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do - - local Fsm = self:GetDefenderTaskFsm( DefenderGroup ) - Fsm:__EngageRoute( 0.1, AttackerSet ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( DefenderGroup, AttackerDetection ) - - DefenderCount = DefenderCount + DefenderGroup:GetSize() - end - - self:F( { DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) - DefenderCount = DefendersMissing - - local ClosestDistance = 0 - local ClosestDefenderSquadronName = nil - - local BreakLoop = false - - while (DefenderCount > 0 and not BreakLoop) do - - self:F( { DefenderSquadrons = self.DefenderSquadrons } ) - - for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons or {} ) do - - self:F( { GCI = DefenderSquadron.Gci } ) - - for InterceptID, Intercept in pairs( DefenderSquadron.Gci or {} ) do - - self:F( { DefenderSquadron } ) - local SpawnCoord = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE - local AttackerCoord = AttackerUnit:GetCoordinate() - local InterceptCoord = AttackerDetection.InterceptCoord - self:F( { InterceptCoord = InterceptCoord } ) - if InterceptCoord then - local InterceptDistance = SpawnCoord:Get2DDistance( InterceptCoord ) - local AirbaseDistance = SpawnCoord:Get2DDistance( AttackerCoord ) - self:F( { InterceptDistance = InterceptDistance, AirbaseDistance = AirbaseDistance, InterceptCoord = InterceptCoord } ) - - if ClosestDistance == 0 or InterceptDistance < ClosestDistance then - - -- Only intercept if the distance to target is smaller or equal to the GciRadius limit. - if AirbaseDistance <= self.GciRadius then - ClosestDistance = InterceptDistance - ClosestDefenderSquadronName = SquadronName - end - end - end - end - end - - if ClosestDefenderSquadronName then - - local DefenderSquadron = self:CanGCI( ClosestDefenderSquadronName ) - - if DefenderSquadron then - - local Gci = self.DefenderSquadrons[ClosestDefenderSquadronName].Gci - - if Gci then - - local DefenderOverhead = DefenderSquadron.Overhead or self.DefenderDefault.Overhead - local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - local DefendersNeeded = math.ceil( DefenderCount * DefenderOverhead ) - - self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead, DefaultOverhead = self.DefenderDefault.Overhead } ) - self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } ) - self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } ) - - -- DefenderSquadron.ResourceCount can have the value nil, which expresses unlimited resources. - -- DefendersNeeded cannot exceed DefenderSquadron.ResourceCount! - if DefenderSquadron.ResourceCount and DefendersNeeded > DefenderSquadron.ResourceCount then - DefendersNeeded = DefenderSquadron.ResourceCount - BreakLoop = true - end - - while (DefendersNeeded > 0) do - - local DefenderGCI, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - DefendersNeeded = DefendersNeeded - DefenderGrouping - - if DefenderGCI then - - DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead - - local Fsm = AI_A2A_GCI:New2( DefenderGCI, Gci.EngageMinSpeed, Gci.EngageMaxSpeed, Gci.EngageFloorAltitude, Gci.EngageCeilingAltitude, Gci.EngageAltType ) - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:Start() - - self:SetDefenderTask( ClosestDefenderSquadronName, DefenderGCI, "GCI", Fsm, AttackerDetection ) - - function Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F( { "GCI Birth", DefenderGroup:GetName() } ) - -- self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) - - if DefenderTarget then - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колёса вверх.", DefenderGroup ) - end - -- Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit - Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit - end - end - - function Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F( { "GCI Route", DefenderGroup:GetName() } ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron and AttackSetUnit:Count() > 0 then - local FirstUnit = AttackSetUnit:GetFirst() - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехватывая боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "DE" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - end - end - self:GetParent( Fsm ).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) - end - - function Fsm:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F( { "GCI Engage", DefenderGroup:GetName() } ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron and AttackSetUnit:Count() > 0 then - local FirstUnit = AttackSetUnit:GetFirst() - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", задействуя боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - end - end - self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit ) - end - - function Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F( { "GCI RTB", DefenderGroup:GetName() } ) - self:GetParent( self ).onafterRTB( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron then - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращение на базу.", DefenderGroup ) - end - end - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - - --- function Fsm:onafterLostControl - -- @param #AI_A2A_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F( { "GCI LostControl", Defender:GetName() } ) - self:GetParent( self ).onafterHome( self, Defender, From, Event, To ) - - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - if Defender:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - end - - --- function Fsm:onafterHome - -- @param #AI_A2A_DISPATCHER self - function Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F( { "GCI Home", DefenderGroup:GetName() } ) - self:GetParent( self ).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron.Language == "EN" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup ) - elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", посадка на базу.", DefenderGroup ) - end - - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - Dispatcher:ParkDefender( Squadron ) - end - end - - end -- if DefenderGCI then - end -- while ( DefendersNeeded > 0 ) do - end - else - -- No more resources, try something else. - -- Subject for a later enhancement to try to depart from another squadron and disable this one. - BreakLoop = true - break - end - else - -- There isn't any closest airbase anymore, break the loop. - break - end - end -- if DefenderSquadron then - end -- if AttackerUnit - end - - --- Creates an ENGAGE task when there are human friendlies airborne near the targets. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units or nil. - function AI_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - -- First, count the active AIGroups Units, targeting the DetectedSet - local DefenderCount = self:CountDefendersEngaged( DetectedItem ) - local DefenderGroups = self:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) - - self:F( { DefenderCount = DefenderCount } ) - - -- Only allow ENGAGE when: - -- 1. There are friendly units near the detected attackers. - -- 2. There is sufficient fuel - -- 3. There is sufficient ammo - -- 4. The plane is not damaged - if DefenderGroups and DetectedItem.IsDetected == true then - return DefenderGroups - end - - return nil - end - - --- Creates an GCI task when there are targets for it. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units or nil if there are no targets to be set. - -- @return #table Table of friendly groups. - function AI_A2A_DISPATCHER:EvaluateGCI( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local AttackerSet = DetectedItem.Set - local AttackerCount = AttackerSet:Count() - - -- First, count the active AIGroups Units, targeting the DetectedSet - local DefenderCount = self:CountDefendersEngaged( DetectedItem ) - local DefendersMissing = AttackerCount - DefenderCount - self:F( { AttackerCount = AttackerCount, DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) - - local Friendlies = self:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) - - if DetectedItem.IsDetected == true then - - return DefendersMissing, Friendlies - end - - return nil, nil - end - - --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:Order( DetectedItem ) - - local detection = self.Detection -- Functional.Detection#DETECTION_AREAS - - local ShortestDistance = 999999999 - - -- Get coordinate (or nil). - local AttackCoordinate = detection:GetDetectedItemCoordinate( DetectedItem ) - - -- Issue https://github.com/FlightControl-Master/MOOSE/issues/1232 - if AttackCoordinate then - - for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - - self:T( { DefenderSquadron = DefenderSquadron.Name } ) - - local Airbase = DefenderSquadron.Airbase - local AirbaseCoordinate = Airbase:GetCoordinate() - - local EvaluateDistance = AttackCoordinate:Get2DDistance( AirbaseCoordinate ) - - if EvaluateDistance <= ShortestDistance then - ShortestDistance = EvaluateDistance - end - end - - end - - return ShortestDistance - end - - --- Shows the tactical display. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - function AI_A2A_DISPATCHER:ShowTacticalDisplay( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - local Report = REPORT:New( "Tactical Overview:" ) - - local DefenderGroupCount = 0 - - -- Now that all obsolete tasks are removed, loop through the detected targets. - -- for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) - return self:Order( t[a] ) < self:Order( t[b] ) - end ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - - self:F( { "Target ID", DetectedItem.ItemID } ) - DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local DetectionIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - -- Show tactical situation - Report:Add( string.format( "\n- Target %s (%s): (#%d) %s", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender and Defender:IsAlive() then - DefenderGroupCount = DefenderGroupCount + 1 - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", - Defender:GetName(), - Defender:GetSize(), - Defender:GetInitialSize(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - end - - Report:Add( "\n- No Targets:" ) - local TaskCount = 0 - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - TaskCount = TaskCount + 1 - local Defender = Defender -- Wrapper.Group#GROUP - if not DefenderTask.Target then - if Defender:IsAlive() then - local DefenderHasTask = Defender:HasTask() - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", - Defender:GetName(), - Defender:GetSize(), - Defender:GetInitialSize(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - Report:Add( string.format( "\n- %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) - - self:F( Report:Text( "\n" ) ) - trigger.action.outText( Report:Text( "\n" ), 25 ) - - return true - - end - - --- Assigns A2A AI Tasks in relation to the detected items. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function AI_A2A_DISPATCHER:ProcessDetected( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - local AIGroup = AIGroup -- Wrapper.Group#GROUP - if not AIGroup:IsAlive() then - local DefenderTaskFsm = self:GetDefenderTaskFsm( AIGroup ) - self:F( { Defender = AIGroup:GetName(), DefenderState = DefenderTaskFsm:GetState() } ) - if not DefenderTaskFsm:Is( "Started" ) then - self:ClearDefenderTask( AIGroup ) - end - else - if DefenderTask.Target then - local AttackerItem = Detection:GetDetectedItemByIndex( DefenderTask.Target.Index ) - if not AttackerItem then - self:F( { "Removing obsolete Target:", DefenderTask.Target.Index } ) - self:ClearDefenderTaskTarget( AIGroup ) - else - if DefenderTask.Target.Set then - local AttackerCount = DefenderTask.Target.Set:Count() - if AttackerCount == 0 then - self:F( { "All Targets destroyed in Target, removing:", DefenderTask.Target.Index } ) - self:ClearDefenderTaskTarget( AIGroup ) - end - end - end - end - end - end - - local Report = REPORT:New( "Tactical Overviews" ) - - local DefenderGroupCount = 0 - - -- Now that all obsolete tasks are removed, loop through the detected targets. - -- Closest detected targets to be considered first! - -- for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) - return self:Order( t[a] ) < self:Order( t[b] ) - end ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - - self:F( { "Target ID", DetectedItem.ItemID } ) - DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local DetectionIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - do - local Friendlies = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be GCIed... - if Friendlies then - self:F( { AIGroups = Friendlies } ) - self:ENGAGE( DetectedItem, Friendlies ) - end - end - - do - local DefendersMissing, Friendlies = self:EvaluateGCI( DetectedItem ) - if DefendersMissing and DefendersMissing > 0 then - self:F( { DefendersMissing = DefendersMissing } ) - self:GCI( DetectedItem, DefendersMissing, Friendlies ) - end - end - end - - if self.TacticalDisplay then - self:ShowTacticalDisplay( Detection ) - end - - return true - end - -end - -do - - --- Calculates which HUMAN friendlies are nearby the area. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type. - function AI_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem ) - - local PlayerTypes = {} - local PlayersCount = 0 - - if PlayersNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() - for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do - local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT - local PlayerName = PlayerUnit:GetPlayerName() - -- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) - if PlayerUnit:IsAirPlane() and PlayerName ~= nil then - local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() - PlayersCount = PlayersCount + 1 - local PlayerType = PlayerUnit:GetTypeName() - PlayerTypes[PlayerName] = PlayerType - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then - end - end - end - - end - - -- self:F( { PlayersCount = PlayersCount } ) - - local PlayerTypesReport = REPORT:New() - - if PlayersCount > 0 then - for PlayerName, PlayerType in pairs( PlayerTypes ) do - PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) ) - end - else - PlayerTypesReport:Add( "-" ) - end - - return PlayersCount, PlayerTypesReport - end - - --- Calculates which friendlies are nearby the area. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type. - function AI_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem ) - - local FriendlyTypes = {} - local FriendliesCount = 0 - - if FriendlyUnitsNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() - for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do - local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT - if FriendlyUnit:IsAirPlane() then - local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() - FriendliesCount = FriendliesCount + 1 - local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1 - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then - end - end - end - - end - - -- self:F( { FriendliesCount = FriendliesCount } ) - - local FriendlyTypesReport = REPORT:New() - - if FriendliesCount > 0 then - for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) ) - end - else - FriendlyTypesReport:Add( "-" ) - end - - return FriendliesCount, FriendlyTypesReport - end - - --- Schedules a new CAP for the given SquadronName. - -- @param #AI_A2A_DISPATCHER self - -- @param #string SquadronName The squadron name. - function AI_A2A_DISPATCHER:SchedulerCAP( SquadronName ) - self:CAP( SquadronName ) - end - - --- Add resources to a Squadron - -- @param #AI_A2A_DISPATCHER self - -- @param #string Squadron The squadron name. - -- @param #number Amount Number of resources to add. - function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount + Amount - end - self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end - - --- Remove resources from a Squadron - -- @param #AI_A2A_DISPATCHER self - -- @param #string Squadron The squadron name. - -- @param #number Amount Number of resources to remove. - function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount - Amount - end - self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end - -end - -do - - -- @type AI_A2A_GCICAP - -- @extends #AI_A2A_DISPATCHER - - --- Create an automatic air defence system for a coalition setting up GCI and CAP air defenses. - -- The class derives from @{#AI_A2A_DISPATCHER} and thus, all the methods that are defined in the @{#AI_A2A_DISPATCHER} class, can be used also in AI\_A2A\_GCICAP. - -- - -- === - -- - -- # Demo Missions - -- - -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher) - -- - -- === - -- - -- # YouTube Channel - -- - -- ### [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) - -- - -- === - -- - -- ![Banner Image](..\Presentations\AI_A2A_DISPATCHER\Dia3.JPG) - -- - -- AI\_A2A\_GCICAP includes automatic spawning of Combat Air Patrol aircraft (CAP) and Ground Controlled Intercept aircraft (GCI) in response to enemy - -- air movements that are detected by an airborne or ground based radar network. - -- - -- With a little time and with a little work it provides the mission designer with a convincing and completely automatic air defence system. - -- - -- The AI_A2A_GCICAP provides a lightweight configuration method using the mission editor. Within a very short time, and with very little coding, - -- the mission designer is able to configure a complete A2A defense system for a coalition using the DCS Mission Editor available functions. - -- Using the DCS Mission Editor, you define borders of the coalition which are guarded by GCICAP, - -- configure airbases to belong to the coalition, define squadrons flying certain types of planes or payloads per airbase, and define CAP zones. - -- **Very little lua needs to be applied, a one liner**, which is fully explained below, which can be embedded - -- right in a DO SCRIPT trigger action or in a larger DO SCRIPT FILE trigger action. - -- - -- CAP flights will take off and proceed to designated CAP zones where they will remain on station until the ground radars direct them to intercept - -- detected enemy aircraft or they run short of fuel and must return to base (RTB). - -- - -- When a CAP flight leaves their zone to perform a GCI or return to base a new CAP flight will spawn to take its place. - -- If all CAP flights are engaged or RTB then additional GCI interceptors will scramble to intercept unengaged enemy aircraft under ground radar control. - -- - -- In short it is a plug in very flexible and configurable air defence module for DCS World. - -- - -- === - -- - -- # The following actions need to be followed when using AI\_A2A\_GCICAP in your mission: - -- - -- ## 1) Configure a working AI\_A2A\_GCICAP defense system for ONE coalition. - -- - -- ### 1.1) Define which airbases are for which coalition. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_1.JPG) - -- - -- Color the airbases red or blue. You can do this by selecting the airbase on the map, and select the coalition blue or red. - -- - -- ### 1.2) Place groups of units given a name starting with a **EWR prefix** of your choice to build your EWR network. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_2.JPG) - -- - -- **All EWR groups starting with the EWR prefix (text) will be included in the detection system.** - -- - -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. - -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. - -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). - -- Additionally, ANY other radar capable unit can be part of the EWR network! - -- Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. - -- The position of these units is very important as they need to provide enough coverage - -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. - -- - -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. - -- For example if they are a long way forward and can detect enemy planes on the ground and taking off - -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. - -- Having the radars further back will mean a slower escalation because fewer targets will be detected and - -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. - -- It all depends on what the desired effect is. - -- - -- EWR networks are **dynamically maintained**. By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, - -- increasing or decreasing the radar coverage of the Early Warning System. - -- - -- ### 1.3) Place Airplane or Helicopter Groups with late activation switched on above the airbases to define Squadrons. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_3.JPG) - -- - -- These are **templates**, with a given name starting with a **Template prefix** above each airbase that you wanna have a squadron. - -- These **templates** need to be within 1.5km from the airbase center. They don't need to have a slot at the airplane, they can just be positioned above the airbase, - -- without a route, and should only have ONE unit. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_4.JPG) - -- - -- **All airplane or helicopter groups that are starting with any of the chosen Template Prefixes will result in a squadron created at the airbase.** - -- - -- ### 1.4) Place floating helicopters to create the CAP zones defined by its route points. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_5.JPG) - -- - -- **All airplane or helicopter groups that are starting with any of the chosen Template Prefixes will result in a squadron created at the airbase.** - -- - -- The helicopter indicates the start of the CAP zone. - -- The route points define the form of the CAP zone polygon. - -- - -- ![Mission Editor Action](..\Presentations\AI_A2A_DISPATCHER\AI_A2A_GCICAP-ME_6.JPG) - -- - -- **The place of the helicopter is important, as the airbase closest to the helicopter will be the airbase from where the CAP planes will take off for CAP.** - -- - -- ## 2) There are a lot of defaults set, which can be further modified using the methods in @{#AI_A2A_DISPATCHER}: - -- - -- ### 2.1) Planes are taking off in the air from the airbases. - -- - -- This prevents airbases to get cluttered with airplanes taking off, it also reduces the risk of human players colliding with taxiing airplanes, - -- resulting in the airbase to halt operations. - -- - -- You can change the way how planes take off by using the inherited methods from AI\_A2A\_DISPATCHER: - -- - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. - -- - -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. - -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: - -- - -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. - -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. - -- * aircraft may collide at the airbase. - -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- - -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! - -- - -- ### 2.2) Planes return near the airbase or will land if damaged. - -- - -- When damaged airplanes return to the airbase, they will be routed and will disappear in the air when they are near the airbase. - -- There are exceptions to this rule, airplanes that aren't "listening" anymore due to damage or out of fuel, will return to the airbase and land. - -- - -- You can change the way how planes land by using the inherited methods from AI\_A2A\_DISPATCHER: - -- - -- * @{#AI_A2A_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. - -- * @{#AI_A2A_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. - -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2A defense system, as no new CAP or GCI planes can takeoff. - -- Note that the method @{#AI_A2A_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. - -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. - -- - -- ### 2.3) CAP operations setup for specific airbases, will be executed with the following parameters: - -- - -- * The altitude will range between 6000 and 10000 meters. - -- * The CAP speed will vary between 500 and 800 km/h. - -- * The engage speed between 800 and 1200 km/h. - -- - -- You can change or add a CAP zone by using the inherited methods from AI\_A2A\_DISPATCHER: - -- - -- The method @{#AI_A2A_DISPATCHER.SetSquadronCap}() defines a CAP execution for a squadron. - -- - -- Setting-up a CAP zone also requires specific parameters: - -- - -- * The minimum and maximum altitude - -- * The minimum speed and maximum patrol speed - -- * The minimum and maximum engage speed - -- * The type of altitude measurement - -- - -- These define how the squadron will perform the CAP while patrolling. Different terrain types requires different types of CAP. - -- - -- The @{#AI_A2A_DISPATCHER.SetSquadronCapInterval}() method specifies **how much** and **when** CAP flights will takeoff. - -- - -- It is recommended not to overload the air defense with CAP flights, as these will decrease the performance of the overall system. - -- - -- For example, the following setup will create a CAP for squadron "Sochi": - -- - -- A2ADispatcher:SetSquadronCap( "Sochi", CAPZoneWest, 4000, 8000, 600, 800, 800, 1200, "BARO" ) - -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) - -- - -- ### 2.4) Each airbase will perform GCI when required, with the following parameters: - -- - -- * The engage speed is between 800 and 1200 km/h. - -- - -- You can change or add a GCI parameters by using the inherited methods from AI\_A2A\_DISPATCHER: - -- - -- The method @{#AI_A2A_DISPATCHER.SetSquadronGci}() defines a GCI execution for a squadron. - -- - -- Setting-up a GCI readiness also requires specific parameters: - -- - -- * The minimum speed and maximum patrol speed - -- - -- Essentially this controls how many flights of GCI aircraft can be active at any time. - -- Note allowing large numbers of active GCI flights can adversely impact mission performance on low or medium specification hosts/servers. - -- GCI needs to be setup at strategic airbases. Too far will mean that the aircraft need to fly a long way to reach the intruders, - -- too short will mean that the intruders may have already passed the ideal interception point! - -- - -- For example, the following setup will create a GCI for squadron "Sochi": - -- - -- A2ADispatcher:SetSquadronGci( "Mozdok", 900, 1200 ) - -- - -- ### 2.5) Grouping or detected targets. - -- - -- Detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate - -- group being detected. - -- - -- Targets will be grouped within a radius of 30km by default. - -- - -- The radius indicates that detected targets need to be grouped within a radius of 30km. - -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. - -- Fast planes like in the 80s, need a larger radius than WWII planes. - -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. - -- - -- ## 3) Additional notes: - -- - -- In order to create a two way A2A defense system, **two AI\_A2A\_GCICAP defense systems must need to be created**, for each coalition one. - -- Each defense system needs its own EWR network setup, airplane templates and CAP configurations. - -- - -- This is a good implementation, because maybe in the future, more coalitions may become available in DCS world. - -- - -- ## 4) Coding examples how to use the AI\_A2A\_GCICAP class: - -- - -- ### 4.1) An easy setup: - -- - -- -- Setup the AI_A2A_GCICAP dispatcher for one coalition, and initialize it. - -- GCI_Red = AI_A2A_GCICAP:New( "EWR CCCP", "SQUADRON CCCP", "CAP CCCP", 2 ) - -- -- - -- The following parameters were given to the :New method of AI_A2A_GCICAP, and mean the following: - -- - -- * `"EWR CCCP"`: Groups of the blue coalition are placed that define the EWR network. These groups start with the name `EWR CCCP`. - -- * `"SQUADRON CCCP"`: Late activated Groups objects of the red coalition are placed above the relevant airbases that will contain these templates in the squadron. - -- These late activated Groups start with the name `SQUADRON CCCP`. Each Group object contains only one Unit, and defines the weapon payload, skin and skill level. - -- * `"CAP CCCP"`: CAP Zones are defined using floating, late activated Helicopter Group objects, where the route points define the route of the polygon of the CAP Zone. - -- These Helicopter Group objects start with the name `CAP CCCP`, and will be the locations wherein CAP will be performed. - -- * `2` Defines how many CAP airplanes are patrolling in each CAP zone defined simultaneously. - -- - -- ### 4.2) A more advanced setup: - -- - -- -- Setup the AI_A2A_GCICAP dispatcher for the blue coalition. - -- - -- A2A_GCICAP_Blue = AI_A2A_GCICAP:New( { "BLUE EWR" }, { "104th", "105th", "106th" }, { "104th CAP" }, 4 ) - -- - -- The following parameters for the :New method have the following meaning: - -- - -- * `{ "BLUE EWR" }`: An array of the group name prefixes of the groups of the blue coalition are placed that define the EWR network. These groups start with the name `BLUE EWR`. - -- * `{ "104th", "105th", "106th" } `: An array of the group name prefixes of the Late activated Groups objects of the blue coalition are - -- placed above the relevant airbases that will contain these templates in the squadron. - -- These late activated Groups start with the name `104th` or `105th` or `106th`. - -- * `{ "104th CAP" }`: An array of the names of the CAP zones are defined using floating, late activated helicopter group objects, - -- where the route points define the route of the polygon of the CAP Zone. - -- These Helicopter Group objects start with the name `104th CAP`, and will be the locations wherein CAP will be performed. - -- * `4` Defines how many CAP airplanes are patrolling in each CAP zone defined simultaneously. - -- - -- @field #AI_A2A_GCICAP - AI_A2A_GCICAP = { - ClassName = "AI_A2A_GCICAP", - Detection = nil, - } - - --- AI_A2A_GCICAP constructor. - -- @param #AI_A2A_GCICAP self - -- @param #string EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. - -- @param #string TemplatePrefixes A list of template prefixes. - -- @param #string CapPrefixes A list of CAP zone prefixes (polygon zones). - -- @param #number CapLimit A number of how many CAP maximum will be spawned. - -- @param #number GroupingRadius The radius in meters wherein detected planes are being grouped as one target area. - -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. - -- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task. - -- @param #number GciRadius The radius in meters wherein detected airplanes will GCI. - -- @param #number ResourceCount The amount of resources that will be allocated to each squadron. - -- @return #AI_A2A_GCICAP - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has unlimited resources. - -- -- The EWR network group prefix is DF CCCP. All groups starting with DF CCCP will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000, 150000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has 30 resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. - -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. - -- - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, { "CAP Zone" }, 2, 20000, 60000, 150000, 30 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object. Each squadron has 30 resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is nil. No CAP is created. - -- -- The CAP Limit is nil. - -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. - -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defender being assigned to a task. - -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. - -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. - -- - -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, nil, nil, nil, nil, nil, 30 ) - -- - function AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount ) - - local EWRSetGroup = SET_GROUP:New() - EWRSetGroup:FilterPrefixes( EWRPrefixes ) - EWRSetGroup:FilterStart() - - local Detection = DETECTION_AREAS:New( EWRSetGroup, GroupingRadius or 30000 ) - - local self = BASE:Inherit( self, AI_A2A_DISPATCHER:New( Detection ) ) -- #AI_A2A_GCICAP - - self:SetEngageRadius( EngageRadius ) - self:SetGciRadius( GciRadius ) - - -- Determine the coalition of the EWRNetwork, this will be the coalition of the GCICAP. - local EWRFirst = EWRSetGroup:GetFirst() -- Wrapper.Group#GROUP - local EWRCoalition = EWRFirst:GetCoalition() - - -- Determine the airbases belonging to the coalition. - local AirbaseNames = {} -- #list<#string> - for AirbaseID, AirbaseData in pairs( _DATABASE.AIRBASES ) do - local Airbase = AirbaseData -- Wrapper.Airbase#AIRBASE - local AirbaseName = Airbase:GetName() - if Airbase:GetCoalition() == EWRCoalition then - table.insert( AirbaseNames, AirbaseName ) - end - end - - self.Templates = SET_GROUP:New():FilterPrefixes( TemplatePrefixes ):FilterOnce() - - -- Setup squadrons - - self:T( { Airbases = AirbaseNames } ) - - self:T( "Defining Templates for Airbases ..." ) - for AirbaseID, AirbaseName in pairs( AirbaseNames ) do - local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE - local AirbaseName = Airbase:GetName() - local AirbaseCoord = Airbase:GetCoordinate() - local AirbaseZone = ZONE_RADIUS:New( "Airbase", AirbaseCoord:GetVec2(), 3000 ) - local Templates = nil - self:T( { Airbase = AirbaseName } ) - for TemplateID, Template in pairs( self.Templates:GetSet() ) do - local Template = Template -- Wrapper.Group#GROUP - local TemplateCoord = Template:GetCoordinate() - if AirbaseZone:IsVec2InZone( TemplateCoord:GetVec2() ) then - Templates = Templates or {} - table.insert( Templates, Template:GetName() ) - self:T( { Template = Template:GetName() } ) - end - end - if Templates then - self:SetSquadron( AirbaseName, AirbaseName, Templates, ResourceCount ) - end - end - - -- Setup CAP. - -- Find for each CAP the nearest airbase to the (start or center) of the zone. - -- CAP will be launched from there. - - self.CAPTemplates = SET_GROUP:New() - self.CAPTemplates:FilterPrefixes( CapPrefixes ) - self.CAPTemplates:FilterOnce() - - self:T( "Setting up CAP ..." ) - for CAPID, CAPTemplate in pairs( self.CAPTemplates:GetSet() ) do - local CAPZone = ZONE_POLYGON:New( CAPTemplate:GetName(), CAPTemplate ) - -- Now find the closest airbase from the ZONE (start or center) - local AirbaseDistance = 99999999 - local AirbaseClosest = nil -- Wrapper.Airbase#AIRBASE - self:T( { CAPZoneGroup = CAPID } ) - for AirbaseID, AirbaseName in pairs( AirbaseNames ) do - local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE - local AirbaseName = Airbase:GetName() - local AirbaseCoord = Airbase:GetCoordinate() - local Squadron = self.DefenderSquadrons[AirbaseName] - if Squadron then - local Distance = AirbaseCoord:Get2DDistance( CAPZone:GetCoordinate() ) - self:T( { AirbaseDistance = Distance } ) - if Distance < AirbaseDistance then - AirbaseDistance = Distance - AirbaseClosest = Airbase - end - end - end - if AirbaseClosest then - self:T( { CAPAirbase = AirbaseClosest:GetName() } ) - self:SetSquadronCap( AirbaseClosest:GetName(), CAPZone, 6000, 10000, 500, 800, 800, 1200, "RADIO" ) - self:SetSquadronCapInterval( AirbaseClosest:GetName(), CapLimit, 300, 600, 1 ) - end - end - - -- Setup GCI. - -- GCI is setup for all Squadrons. - self:T( "Setting up GCI ..." ) - for AirbaseID, AirbaseName in pairs( AirbaseNames ) do - local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE - local AirbaseName = Airbase:GetName() - local Squadron = self.DefenderSquadrons[AirbaseName] - self:F( { Airbase = AirbaseName } ) - if Squadron then - self:T( { GCIAirbase = AirbaseName } ) - self:SetSquadronGci( AirbaseName, 800, 1200 ) - end - end - - self:__Start( 5 ) - - self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) - self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) - -- self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) - - self:HandleEvent( EVENTS.Land ) - self:HandleEvent( EVENTS.EngineShutdown ) - - return self - end - - --- AI_A2A_GCICAP constructor with border. - -- @param #AI_A2A_GCICAP self - -- @param #string EWRPrefixes A list of prefixes that of groups that setup the Early Warning Radar network. - -- @param #string TemplatePrefixes A list of template prefixes. - -- @param #string BorderPrefix A Border Zone Prefix. - -- @param #string CapPrefixes A list of CAP zone prefixes (polygon zones). - -- @param #number CapLimit A number of how many CAP maximum will be spawned. - -- @param #number GroupingRadius The radius in meters wherein detected planes are being grouped as one target area. - -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. - -- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task. - -- @param #number GciRadius The radius in meters wherein detected airplanes will GCI. - -- @param #number ResourceCount The amount of resources that will be allocated to each squadron. - -- @return #AI_A2A_GCICAP - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has unlimited resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000, 150000 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has 30 resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is "CAP Zone". - -- -- The CAP Limit is 2. - -- -- The Grouping Radius is set to 20000. Thus all planes within a 20km radius will be grouped as a group of targets. - -- -- The Engage Radius is set to 60000. Any defender without a task, and in healthy condition, - -- -- will be considered a defense task if the target is within 60km from the defender. - -- -- The GCI Radius is set to 150000. Any target detected within 150km will be considered for GCI engagement. - -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", { "CAP Zone" }, 2, 20000, 60000, 150000, 30 ) - -- - -- @usage - -- - -- -- Setup a new GCICAP dispatcher object with a border. Each squadron has 30 resources. - -- -- The EWR network group prefix is "DF CCCP". All groups starting with "DF CCCP" will be part of the EWR network. - -- -- The Squadron Templates prefix is "SQ CCCP". All groups starting with "SQ CCCP" will be considered as airplane templates. - -- -- The Border prefix is "Border". This will setup a border using the group defined within the mission editor with the name Border. - -- -- The CAP Zone prefix is nil. No CAP is created. - -- -- The CAP Limit is nil. - -- -- The Grouping Radius is nil. The default range of 6km radius will be grouped as a group of targets. - -- -- The Engage Radius is set nil. The default Engage Radius will be used to consider a defender being assigned to a task. - -- -- The GCI Radius is nil. Any target detected within the default GCI Radius will be considered for GCI engagement. - -- -- The amount of resources for each squadron is set to 30. Thus about 30 resources are allocated to each squadron created. - -- - -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", nil, nil, nil, nil, nil, 30 ) - -- - function AI_A2A_GCICAP:NewWithBorder( EWRPrefixes, TemplatePrefixes, BorderPrefix, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount ) - - local self = AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount ) - - if BorderPrefix then - self:SetBorderZone( ZONE_POLYGON:New( BorderPrefix, GROUP:FindByName( BorderPrefix ) ) ) - end - - return self - - end - -end diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua deleted file mode 100644 index 8f85f3cd2..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ /dev/null @@ -1,147 +0,0 @@ ---- **AI** - Models the process of Ground Controlled Interception (GCI) for airplanes. --- --- This is a class used in the @{AI.AI_A2A_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2A_Gci --- @image AI_Ground_Control_Intercept.JPG - - - --- @type AI_A2A_GCI --- @extends AI.AI_A2A#AI_A2A - - ---- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- --- The AI_A2A_GCI is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event. --- --- 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. --- --- This cycle will continue. --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- 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. --- --- ## 1. AI_A2A_GCI constructor --- --- * @{#AI_A2A_GCI.New}(): Creates a new AI_A2A_GCI object. --- --- ## 2. AI_A2A_GCI is a FSM --- --- ![Process](..\Presentations\AI_GCI\Dia2.JPG) --- --- ### 2.1 AI_A2A_GCI 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_A2A_GCI 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_A2A_GCI.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_A2A_GCI.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_A2A_GCI.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_A2A_GCI.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. --- --- # 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_A2A_GCI -AI_A2A_GCI = { - ClassName = "AI_A2A_GCI", -} - - - ---- Creates a new AI_A2A_GCI object --- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept --- @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". --- @return #AI_A2A_GCI -function AI_A2A_GCI:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local AI_Air = AI_AIR:New( AIIntercept ) - local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air, AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - local self = BASE:Inherit( self, AI_Air_Engage ) -- #AI_A2A_GCI - - self:SetFuelThreshold( .2, 60 ) - self:SetDamageThreshold( 0.4 ) - self:SetDisengageRadius( 70000 ) - - return self -end - ---- Creates a new AI_A2A_GCI object --- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept --- @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". --- @return #AI_A2A_GCI -function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - return self:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) -end - ---- onafter State Transition for Event Patrol. --- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The AI Group 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_A2A_GCI:onafterStart( AIIntercept, From, Event, To ) - - self:GetParent( self, AI_A2A_GCI ).onafterStart( self, AIIntercept, From, Event, To ) -end - - ---- Evaluate the attack and create an AttackUnitTask list. --- @param #AI_A2A_GCI 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_A2A_GCI self -function AI_A2A_GCI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT - self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) - if AttackUnit:IsAlive() and AttackUnit:IsAir() then - -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() - -- Maybe the detected set also contains - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) - end - end - - return AttackUnitTasks -end diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua deleted file mode 100644 index a9bfc8a41..000000000 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ /dev/null @@ -1,410 +0,0 @@ ---- **AI** - Models the process of air patrol of airplanes. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2A_Patrol --- @image AI_Air_Patrolling.JPG - - ---- --- @type AI_A2A_PATROL --- @extends AI.AI_A2A#AI_A2A - ---- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}. --- --- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) --- --- The AI_A2A_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_PATROL\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_PATROL\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_PATROL\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia9.JPG) --- ----- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or --- use derived AI_ classes to model AI offensive or defensive behaviour. --- --- ![Process](..\Presentations\AI_PATROL\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_PATROL\Dia11.JPG) --- --- ## 1. AI_A2A_PATROL constructor --- --- * @{#AI_A2A_PATROL.New}(): Creates a new AI_A2A_PATROL object. --- --- ## 2. AI_A2A_PATROL is a FSM --- --- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) --- --- ### 2.1. AI_A2A_PATROL States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Returning** ( Group ): The AI is returning to Base. --- * **Stopped** ( Group ): The process is stopped. --- * **Crashed** ( Group ): The AI has crashed or is dead. --- --- ### 2.2. AI_A2A_PATROL Events --- --- * **Start** ( Group ): Start the process. --- * **Stop** ( Group ): Stop the process. --- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone. --- * **RTB** ( Group ): Route the AI to the home base. --- * **Detect** ( Group ): The AI is detecting targets. --- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Set or Get the AI controllable --- --- * @{#AI_A2A_PATROL.SetControllable}(): Set the AIControllable. --- * @{#AI_A2A_PATROL.GetControllable}(): Get the AIControllable. --- --- ## 4. Set the Speed and Altitude boundaries of the AI controllable --- --- * @{#AI_A2A_PATROL.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol. --- * @{#AI_A2A_PATROL.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol. --- --- ## 5. Manage the detection process of the AI controllable --- --- The detection process of the AI controllable can be manipulated. --- Detection requires an amount of CPU power, which has an impact on your mission performance. --- Only put detection on when absolutely necessary, and the frequency of the detection can also be set. --- --- * @{#AI_A2A_PATROL.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_A2A_PATROL.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. --- --- The detection frequency can be set with @{#AI_A2A_PATROL.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection. --- Use the method @{#AI_A2A_PATROL.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI. --- --- The detection can be filtered to potential targets in a specific zone. --- Use the method @{#AI_A2A_PATROL.SetDetectionZone}() to set the zone where targets need to be detected. --- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected --- according the weather conditions. --- --- ## 6. Manage the "out of fuel" in the AI_A2A_PATROL --- --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. --- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, --- while a new AI is targeted to the AI_A2A_PATROL. --- Once the time is finished, the old AI will return to the base. --- Use the method @{#AI_A2A_PATROL.ManageFuel}() to have this proces in place. --- --- ## 7. Manage "damage" behaviour of the AI in the AI_A2A_PATROL --- --- When the AI is damaged, it is required that a new Patrol is started. However, damage cannon be foreseen early on. --- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB). --- Use the method @{#AI_A2A_PATROL.ManageDamage}() to have this proces in place. --- --- # 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_A2A_PATROL -AI_A2A_PATROL = { - ClassName = "AI_A2A_PATROL", -} - ---- Creates a new AI_A2A_PATROL object --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The patrol group object. --- @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 BARO --- @return #AI_A2A_PATROL self --- @usage --- -- Define a new AI_A2A_PATROL Object. This PatrolArea will patrol a Group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolSpawn = SPAWN:New( 'Patrol Group' ) --- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 ) -function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - local AI_Air = AI_AIR:New( AIPatrol ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - local self = BASE:Inherit( self, AI_Air_Patrol ) -- #AI_A2A_PATROL - - self:SetFuelThreshold( .2, 60 ) - self:SetDamageThreshold( 0.4 ) - self:SetDisengageRadius( 70000 ) - - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - -- defafult PatrolAltType to "BARO" if not specified - self.PatrolAltType = PatrolAltType or "BARO" - - self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" ) - ---- OnBefore Transition Handler for Event Patrol. --- @function [parent=#AI_A2A_PATROL] OnBeforePatrol --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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 Patrol. --- @function [parent=#AI_A2A_PATROL] OnAfterPatrol --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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 Patrol. --- @function [parent=#AI_A2A_PATROL] Patrol --- @param #AI_A2A_PATROL self - ---- Asynchronous Event Trigger for Event Patrol. --- @function [parent=#AI_A2A_PATROL] __Patrol --- @param #AI_A2A_PATROL self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Patrolling. --- @function [parent=#AI_A2A_PATROL] OnLeavePatrolling --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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 Patrolling. --- @function [parent=#AI_A2A_PATROL] OnEnterPatrolling --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL. - ---- OnBefore Transition Handler for Event Route. --- @function [parent=#AI_A2A_PATROL] OnBeforeRoute --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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 Route. --- @function [parent=#AI_A2A_PATROL] OnAfterRoute --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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 Route. --- @function [parent=#AI_A2A_PATROL] Route --- @param #AI_A2A_PATROL self - ---- Asynchronous Event Trigger for Event Route. --- @function [parent=#AI_A2A_PATROL] __Route --- @param #AI_A2A_PATROL self --- @param #number Delay The delay in seconds. - - - - self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_PATROL. - - return self -end - - - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_A2A_PATROL self --- @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. --- @return #AI_A2A_PATROL self -function AI_A2A_PATROL:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_A2A_PATROL self --- @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. --- @return #AI_A2A_PATROL self -function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_A2A_PATROL self --- @return #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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_A2A_PATROL:onafterPatrol( AIPatrol, From, Event, To ) - self:F2() - - self:ClearTargetDistance() - - self:__Route( 1 ) - - AIPatrol:OnReSpawn( - function( PatrolGroup ) - self:__Reset( 1 ) - self:__Route( 5 ) - end - ) -end - - ---- This static method is called from the route path within the last task at the last waypoint of the AIPatrol. --- Note that this method is required, as triggers the next route when patrolling for the AIPatrol. --- @param Wrapper.Group#GROUP AIPatrol The AI group. --- @param #AI_A2A_PATROL Fsm The FSM. -function AI_A2A_PATROL.PatrolRoute( AIPatrol, Fsm ) - - AIPatrol:F( { "AI_A2A_PATROL.PatrolRoute:", AIPatrol:GetName() } ) - - if AIPatrol and AIPatrol:IsAlive() then - Fsm:Route() - end - -end - - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_A2A_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) - self:F2() - - -- When RTB, don't allow anymore the routing. - if From == "RTB" then - return - end - - - if AIPatrol and AIPatrol:IsAlive() then - - local PatrolRoute = {} - - --- Calculate the target route point. - - local CurrentCoord = AIPatrol:GetCoordinate() - - -- Random altitude. - local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude) - - -- Random speed in km/h. - local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed) - - -- First waypoint is current position. - PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current") - - if self.racetrack then - - -- Random heading. - local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax) - - -- Random leg length. - local leg=math.random(self.racetracklegmin, self.racetracklegmax) - - -- Random duration if any. - local duration = self.racetrackdurationmin - if self.racetrackdurationmax then - duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax) - end - - -- CAP coordinate. - local c0=self.PatrolZone:GetRandomCoordinate() - if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then - c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] - end - - -- Race track points. - local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE - local c2=c1:Translate(leg, heading):SetAltitude(altitude) - - self:SetTargetDistance(c0) -- For RTB status check - - -- Debug: - self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration))) - --c1:MarkToAll("Race track c1") - --c2:MarkToAll("Race track c2") - - -- Task to orbit. - local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2) - - -- Task function to redo the patrol at other random position. - local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self) - - -- Controlled task with task condition. - local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil) - local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond) - - -- Second waypoint - PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit") - - else - - -- Target coordinate. - local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() --Core.Point#COORDINATE - ToTargetCoord:SetAltitude(altitude) - - self:SetTargetDistance( ToTargetCoord ) -- For RTB status check - - local taskReRoute=AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self ) - - PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskReRoute}, "Patrol Point") - - end - - -- ROE - AIPatrol:OptionROEReturnFire() - AIPatrol:OptionROTEvadeFire() - - -- Patrol. - AIPatrol:Route( PatrolRoute, 0.5) - end - -end - 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 7a6a80fca..000000000 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ /dev/null @@ -1,96 +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_A2A_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. --- --- # 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_A2G_CAS.lua b/Moose Development/Moose/AI/AI_A2G_CAS.lua deleted file mode 100644 index 16c9cb976..000000000 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ /dev/null @@ -1,96 +0,0 @@ ---- **AI** - Models the process of air to ground engagement for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_A2G_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2G_CAS --- @image AI_Air_To_Ground_Engage.JPG - --- @type AI_A2G_CAS --- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL 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. --- --- # 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_CAS -AI_A2G_CAS = { - ClassName = "AI_A2G_CAS", -} - ---- Creates a new AI_A2G_CAS object --- @param #AI_A2G_CAS 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_CAS -function AI_A2G_CAS: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_CAS object --- @param #AI_A2G_CAS 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_CAS -function AI_A2G_CAS: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_CAS 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_CAS self -function AI_A2G_CAS: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( { "CAS 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_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua deleted file mode 100644 index 0396e4e9f..000000000 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ /dev/null @@ -1,4792 +0,0 @@ ---- **AI** - Create an automated A2G defense system with reconnaissance units, coordinating SEAD, BAI and CAS operations. --- --- === --- --- Features: --- --- * Setup quickly an A2G defense system for a coalition. --- * Setup multiple defense zones to defend specific coordinates in your battlefield. --- * Setup (SEAD) Suppression of Air Defense squadrons, to gain control in the air of enemy grounds. --- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. --- * Setup (CAS) Controlled Air Support squadrons, to attack close by enemy ground units near friendly installations. --- * Define and use a detection network controlled by recce. --- * Define A2G defense squadrons at airbases, FARPs and carriers. --- * Enable airbases for A2G defenses. --- * Add different planes and helicopter templates to squadrons. --- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. --- * Add multiple squadrons to different airbases, FARPs or carriers. --- * Define different ranges to engage upon. --- * Establish an automatic in air refuel process for planes using refuel tankers. --- * Setup default settings for all squadrons and A2G defenses. --- * Setup specific settings for specific squadrons. --- --- === --- --- ## Missions: --- --- [AID-A2G - AI A2G Dispatching](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2G_Dispatcher) --- --- === --- --- ## YouTube Channel: --- --- [DCS WORLD - MOOSE - A2G DISPATCHER - Build an automatic A2G Defense System - Introduction](https://www.youtube.com/watch?v=zwSxWRAGVH8) --- --- === --- --- # QUICK START GUIDE --- --- The following class is available to model an A2G defense system. --- --- AI_A2G_DISPATCHER is the main A2G defense class that models the A2G defense system. --- --- Before you start using the AI_A2G_DISPATCHER, ask yourself the following questions: --- --- --- ## 1. Which coalition am I modeling an A2G defense system for? Blue or red? --- --- One AI_A2G_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. --- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI_A2G_DISPATCHER **objects**, --- each governing their defense system for one coalition. --- --- --- ## 2. Which type of detection will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). --- --- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units --- and reporting them to the head quarters. --- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: --- --- * Perform detections from multiple recce as one co-operating entity. --- * Communicate with a @{Tasking.CommandCenter}, which consolidates each detection. --- * Groups detections based on a method (per area, per type or per unit). --- * Communicates detections. --- --- --- ## 3. Which recce units can be used as part of the detection system? Only ground based, or also airborne? --- --- Depending on the type of mission you want to achieve, different types of units can be engaged to perform ground enemy targets reconnaissance. --- Ground recce (FAC) are very useful units to determine the position of enemy ground targets when they spread out over the battlefield at strategic positions. --- Using their varying detection technology, and especially those ground units which have spotting technology, can be extremely effective at --- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. --- Unfortunately, they lack sometimes the visibility to detect targets at greater range, or when scenery is preventing line of sight. --- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then --- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! --- --- Airborne recce (AFAC) are also very effective. The are capable of patrolling at a functional detection altitude, --- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, --- so you need air superiority to make them effective. --- Airborne recce will also have varying ground detection technology, which plays a big role in the effectiveness of the reconnaissance. --- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective --- compared to air units having only visual detection capabilities. --- For example, for the red coalition, the Mi-28N and the Su-34; and for the blue side, the reaper, are such effective airborne recce units. --- --- Typically, don't want these recce units to engage with the enemy, you want to keep them at position. Therefore, it is a good practice --- to set the ROE for these recce to hold weapons, and make them invisible from the enemy. --- --- It is not possible to perform a recce function as a player (unit). --- --- --- ## 4. How do the defenses decide **when and where to engage** on approaching enemy units? --- --- The A2G dispatcher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. --- Any ground based enemy approaching within the proximity of such a defense point, may trigger for a defensive action by friendly air units. --- --- There are 2 important parameters that play a role in the defensive decision making: defensiveness and reactivity. --- --- The A2G dispatcher provides various parameters to setup the **defensiveness**, --- which models the decision **when** a defender will engage with the approaching enemy. --- Defensiveness is calculated by a probability distribution model when to trigger a defense action, --- depending on the distance of the enemy unit from the defense coordinates, and a **defensiveness factor**. --- --- The other parameter considered for defensive action is **where the enemy is located**, thus the distance from a defense coordinate, --- which we call the **reactive distance**. By default, the reactive distance is set to 60km, but can be changed by the mission designer --- using the available method explained further below. --- The combination of the defensiveness and reactivity results in a model that, the closer the attacker is to the defense point, --- the higher the probability will be that a defense action will be launched! --- --- --- ## 5. Are defense coordinates and defense reactivity the only parameters? --- --- No, depending on the target type, and the threat level of the target, the probability of defense will be higher. --- In other words, when a SAM-10 radar emitter is detected, its probability for defense will be much higher than when a BMP-1 vehicle is --- detected, even when both enemies are at the same distance from a defense coordinate. --- This will ensure optimal defenses, SEAD tasks will be launched much more quicker against engaging radar emitters, to ensure air superiority. --- Approaching main battle tanks will be engaged much faster, than a group of approaching trucks. --- --- --- ## 6. Which Squadrons will I create and which name will I give each Squadron? --- --- The A2G defense system works with **Squadrons**. Each Squadron must be given a unique name, that forms the **key** to the squadron. --- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningful --- for your mission, and remember that squadron names are used for communication to the players of your mission. --- --- There are mainly 3 types of defenses: **SEAD**, **BAI**, and **CAS**. --- --- Suppression of Air Defenses (SEAD) are effective against radar emitters. --- Battleground Air Interdiction (BAI) tasks are launched when there are no friendlies around. --- Close Air Support (CAS) is launched when the enemy is close near friendly units. --- --- Depending on the defense type, different payloads will be needed. See further points on squadron definition. --- --- --- ## 7. Where will the Squadrons be located? On Airbases? On Carriers? On FARPs? --- --- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **FARP**. --- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. --- If the home base of the squadron is too far from assumed enemy positions, then the defenses will be too late. --- The home bases must be **behind** enemy lines, you want to prevent your home bases to be engaged by enemies! --- Depending on the units applied for defenses, the home base can be further or closer to the enemies. --- Any airbase, FARP, or carrier can act as the launching platform for A2G defenses. --- Carefully plan which airbases will take part in the coalition. Color each airbase **in the color of the coalition**, using the mission editor, --- or your air units will not return for landing at the airbase! --- --- --- ## 8. Which helicopter or plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron? --- --- Per Squadron, one or multiple helicopter or plane models can be allocated as **Templates**. --- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. --- The A2G defense system will select from the given templates a random template to spawn a new plane (group). --- --- A squadron will perform specific task types (SEAD, BAI or CAS). So, squadrons will require specific templates for the --- task types it will perform. A squadron executing SEAD defenses, will require a payload with long range anti-radar seeking missiles. --- --- --- ## 9. Which payloads, skills and skins will these plane models have? --- --- Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, --- each having different payloads, skills and skins. --- The A2G defense system will select from the given templates a random template to spawn a new plane (group). --- --- --- ## 10. How do squadrons engage in a defensive action? --- --- There are two ways how squadrons engage and execute your A2G defenses. --- Squadrons can start the defense directly from the airbase, FARP or carrier. When a squadron launches a defensive group, that group --- will start directly from the airbase. The other way is to launch early on in the mission a patrolling mechanism. --- Squadrons will launch air units to patrol in specific zone(s), so that when ground enemy targets are detected, that the airborne --- A2G defenses can come immediately into action. --- --- --- ## 11. For each Squadron doing a patrol, which zone types will I create? --- --- Per zone, evaluate whether you want: --- --- * simple trigger zones --- * polygon zones --- * moving zones --- --- Depending on the type of zone selected, a different @{Core.Zone} object needs to be created from a ZONE_ class. --- --- --- ## 12. Are moving defense coordinates possible? --- --- Yes, different COORDINATE types are possible to be used. --- The COORDINATE_UNIT will help you to specify a defense coordinate that is attached to a moving unit. --- --- --- ## 13. How many defense coordinates do I need to create? --- --- It depends, but the idea is to define only the necessary defense points that drive your mission. --- If you define too many defense coordinates, the performance of your mission may decrease. For each defined defense coordinate, --- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the associated defense radius. --- The default defense radius is about 60km. Depending on the defense reactivity, defenses will be launched when the enemy is at a --- closer distance from the defense coordinate than the defense radius. --- --- --- ## 14. For each Squadron doing patrols, what are the time intervals and patrol amounts to be performed? --- --- For each patrol: --- --- * **How many** patrols you want to have airborne at the same time? --- * **How frequent** you want the defense mechanism to check whether to start a new patrol? --- --- Other considerations: --- --- * **How far** is the patrol area from the engagement "hot zone". You want to ensure that the enemy is reached on time! --- * **How safe** is the patrol area taking into account air superiority. Is it well defended, are there nearby A2A bases? --- --- --- ## 15. For each Squadron, which takeoff method will I use? --- --- For each Squadron, evaluate which takeoff method will be used: --- --- * Straight from the air --- * From the runway --- * From a parking spot with running engines --- * From a parking spot with cold engines --- --- **The default takeoff method is straight in the air.** --- This takeoff method is the most useful if you want to avoid airplane clutter at airbases, but it is the least realistic one. --- --- --- ## 16. For each Squadron, which landing method will I use? --- --- For each Squadron, evaluate which landing method will be used: --- --- * Despawn near the airbase when returning --- * Despawn after landing on the runway --- * Despawn after engine shutdown after landing --- --- **The default landing method is to despawn when near the airbase when returning.** --- This landing method is the most useful if you want to avoid aircraft clutter at airbases, but it is the least realistic one. --- --- --- ## 19. For each Squadron, which **defense overhead** will I use? --- --- For each Squadron, depending on the helicopter or airplane type (modern, old) and payload, which overhead is required to provide any defense? --- --- In other words, if **X** enemy ground units are detected, how many **Y** defense helicopters or airplanes need to engage (per squadron)? --- The **Y** is dependent on the type of aircraft (era), payload, fuel levels, skills etc. --- But the most important factor is the payload, which is the amount of A2G weapons the defense can carry to attack the enemy ground units. --- For example, a Ka-50 can carry 16 Vikhrs, this means that it potentially can destroy at least 8 ground units without a reload of ammunition. --- That means, that one defender can destroy more enemy ground units. --- Thus, the overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. --- --- **The default overhead is 1. A smaller value than 1, like 0.25 will decrease the overhead to a 1 / 4 ratio, meaning, --- one defender for each 4 detected ground enemy units. ** --- --- --- ## 19. For each Squadron, which grouping will I use? --- --- When multiple targets are detected, how will defenses be grouped when multiple defense air units are spawned for multiple enemy ground units? --- Per one, two, three, four? --- --- **The default grouping is 1. That means, that each spawned defender will act individually.** --- But you can specify a number between 1 and 4, so that the defenders will act as a group. --- --- # 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 --- --- === --- --- ### Author: **FlightControl** rework of GCICAP + introduction of new concepts (squadrons). --- --- @module AI.AI_A2G_Dispatcher --- @image AI_Air_To_Ground_Dispatching.JPG - - -do -- AI_A2G_DISPATCHER - - --- AI_A2G_DISPATCHER class. - -- @type AI_A2G_DISPATCHER - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAS operations. - -- - -- === - -- - -- When your mission is in the need to take control of the AI to automate and setup a process of air to ground defenses, this is the module you need. - -- The defense system work through the definition of defense coordinates, which are points in your friendly area within the battle field, that your mission need to have defended. - -- Multiple defense coordinates can be setup. Defense coordinates can be strategic or tactical positions or references to strategic units or scenery. - -- The A2G dispatcher will evaluate every x seconds the tactical situation around each defense coordinate. When a defense coordinate - -- is under threat, it will communicate through the command center that defensive actions need to be taken and will launch groups of air units for defense. - -- The level of threat to the defense coordinate varies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. - -- Defensive actions are taken through probability, but the closer and the more threat the enemy poses to the defense coordinate, the faster it will be attacked by friendly A2G units. - -- - -- Please study carefully the underlying explanations how to setup and use this module, as it has many features. - -- It also requires a little study to ensure that you get a good understanding of the defense mechanisms, to ensure a strong - -- defense for your missions. - -- - -- === - -- - -- # USAGE GUIDE - -- - -- - -- ## 1. AI\_A2G\_DISPATCHER constructor: - -- - -- - -- The @{#AI_A2G_DISPATCHER.New}() method creates a new AI_A2G_DISPATCHER instance. - -- - -- - -- ### 1.1. Define the **reconnaissance network**: - -- - -- As part of the AI_A2G_DISPATCHER :New() constructor, a reconnaissance network must be given as the first parameter. - -- A reconnaissance network is provided by passing a @{Functional.Detection} object. - -- The most effective reconnaissance for the A2G dispatcher would be to use the @{Functional.Detection#DETECTION_AREAS} object. - -- - -- A reconnaissance network, is used to detect enemy ground targets, - -- potentially group them into areas, and to understand the position, level of threat of the enemy. - -- - -- As explained in the introduction, depending on the type of mission you want to achieve, different types of units can be applied to detect ground enemy targets. - -- Ground based units are very useful to act as a reconnaissance, but they lack sometimes the visibility to detect targets at greater range. - -- Recce are very useful to acquire the position of enemy ground targets when spread out over the battlefield at strategic positions. - -- Ground units also have varying detectors, and especially the ground units which have laser guiding missiles can be extremely effective at - -- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. - -- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then - -- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! - -- - -- Beside ground level units to use for reconnaissance, air units are also very effective. The are capable of patrolling at great speed - -- covering a large terrain. However, airborne recce can be vulnerable to air to ground attacks, and you need air superiority to make then - -- effective. Also the instruments available at the air units play a big role in the effectiveness of the reconnaissance. - -- Air units which have ground detection capabilities will be much more effective than air units with only visual detection capabilities. - -- For the red coalition, the Mi-28N and for the blue side, the reaper are such effective reconnaissance airborne units. - -- - -- Reconnaissance networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection} instance that is given as the first parameter to the A2G dispatcher. - -- By defining in a **smart way the names or name prefixes of the reconnaissance groups**, these groups will be **automatically added or removed** to or from the reconnaissance network, - -- when these groups are spawned in or destroyed during the ongoing battle. - -- By spawning in dynamically additional recce, you can ensure that there is sufficient reconnaissance coverage so the defense mechanism is continuously - -- alerted of new enemy ground targets. - -- - -- The following is an example defense of a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. - -- - -- -- Define a SET_GROUP object that builds a collection of groups that define the recce network. - -- -- Here we build the network with all the groups that have a name starting with CCCP Recce. - -- DetectionSetGroup = SET_GROUP:New() -- Define a set of group objects, called DetectionSetGroup. - -- - -- DetectionSetGroup:FilterPrefixes( { "CCCP Recce" } ) -- The DetectionSetGroup will search for groups that start with the name "CCCP Recce". - -- - -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, - -- -- which have a group name starting with "CCCP Recce", then these will be automatically added or removed from the set. - -- DetectionSetGroup:FilterStart() - -- - -- -- This command defines the reconnaissance network. - -- -- It will group any detected ground enemy targets within a radius of 1km. - -- -- It uses the DetectionSetGroup, which defines the set of reconnaissance groups to detect for enemy ground targets. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 1000 ) - -- - -- -- Setup the A2G dispatcher, and initialize it. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- - -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. - -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with `"CCCP Recce"` to be included in the set. - -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. - -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. - -- - -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen. - -- - -- The `Detection` object is then passed to the @{#AI_A2G_DISPATCHER.New}() method to indicate the reconnaissance network - -- configuration and setup the A2G defense detection mechanism. - -- - -- - -- ### 1.2. Setup the A2G dispatcher for both a red and blue coalition. - -- - -- Following the above described procedure, you'll need to create for each coalition an separate detection network, and a separate A2G dispatcher. - -- Ensure that while doing so, that you name the objects differently both for red and blue coalition. - -- - -- For example like this for the red coalition: - -- - -- DetectionRed = DETECTION_AREAS:New( DetectionSetGroupRed, 1000 ) - -- A2GDispatcherRed = AI_A2G_DISPATCHER:New( DetectionRed ) - -- - -- And for the blue coalition: - -- - -- DetectionBlue = DETECTION_AREAS:New( DetectionSetGroupBlue, 1000 ) - -- A2GDispatcherBlue = AI_A2G_DISPATCHER:New( DetectionBlue ) - -- - -- Note: Also the SET_GROUP objects should be created for each coalition separately, containing each red and blue recce respectively! - -- - -- - -- ### 1.3. Define the enemy ground target **grouping radius**, in case you use DETECTION_AREAS: - -- - -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_A2G_DISPATCHER:New() method - -- but can be changed. The grouping radius should not be too small, but also depends on the types of ground forces and the way you want your mission to evolve. - -- A large radius will mean large groups of enemy ground targets, while making smaller groups will result in a more fragmented defense system. - -- Typically I suggest a grouping radius of 1km. This is the right balance to create efficient defenses. - -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius - -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher, - -- so don't make this value too small! Again, about 1km, or 1000 meters, is recommended. - -- - -- - -- ## 2. Setup (a) **Defense Coordinate(s)**. - -- - -- As explained above, defense coordinates are the center of your defense operations. - -- The more threat to the defense coordinate, the higher it is likely a defensive action will be launched. - -- - -- Find below an example how to add defense coordinates: - -- - -- -- Add defense coordinates. - -- A2GDispatcher:AddDefenseCoordinate( "HQ", GROUP:FindByName( "HQ" ):GetCoordinate() ) - -- - -- In this example, the coordinate of a group called `"HQ"` is retrieved, using `:GetCoordinate()` - -- This returns a COORDINATE object, pointing to the first unit within the GROUP object. - -- - -- The method @{#AI_A2G_DISPATCHER.AddDefenseCoordinate}() adds a new defense coordinate to the `A2GDispatcher` object. - -- The first parameter is the key of the defense coordinate, the second the coordinate itself. - -- - -- Later, a COORDINATE_UNIT will be added to the framework, which can be used to assign "moving" coordinates to an A2G dispatcher. - -- - -- **REMEMBER!** - -- - -- - **Defense coordinates are the center of the A2G dispatcher defense system!** - -- - **You can define more defense coordinates to defend a larger area.** - -- - **Detected enemy ground targets are not immediately engaged, but are engaged with a reactivity or probability calculation!** - -- - -- But, there is more to it ... - -- - -- - -- ### 2.1. The **Defense Radius**. - -- - -- The defense radius defines the maximum radius that a defense will be initiated around each defense coordinate. - -- So even when there are targets further away than the defense radius, then these targets won't be engaged upon. - -- By default, the defense radius is set to 100km (100.000 meters), but can be changed using the @{#AI_A2G_DISPATCHER.SetDefenseRadius}() method. - -- Note that the defense radius influences the defense reactivity also! The larger the defense radius, the more reactive the defenses will be. - -- - -- For example: - -- - -- A2GDispatcher:SetDefenseRadius( 30000 ) - -- - -- This defines an A2G dispatcher which will engage on enemy ground targets within 30km radius around the defense coordinate. - -- Note that the defense radius **applies to all defense coordinates** defined within the A2G dispatcher. - -- - -- - -- ### 2.2. The **Defense Reactivity**. - -- - -- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is - -- also determined by the distance of the enemy ground target to the defense coordinate. - -- If you want to have a **low** defense reactivity, that is, the probability that an A2G defense will engage to the enemy ground target, then - -- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods - -- @{#AI_A2G_DISPATCHER.SetDefenseReactivityMedium}() and @{#AI_A2G_DISPATCHER.SetDefenseReactivityHigh}() respectively. - -- - -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, - -- the less reactive the defenses will be in terms of distance to enemy ground targets! - -- - -- For example: - -- - -- A2GDispatcher:SetDefenseReactivityHigh() - -- - -- This defines an A2G dispatcher with high defense reactivity. - -- - -- - -- ## 3. **Squadrons**. - -- - -- The A2G dispatcher works with **Squadrons**, that need to be defined using the different methods available. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, FARP or carrier, - -- while defining which helicopter or plane **templates** are being used by the squadron and how many **resources** are available. - -- - -- **Multiple squadrons** can be defined within one A2G dispatcher, each having specific defense tasks and defense parameter settings! - -- - -- Squadrons: - -- - -- * Have name (string) that is the identifier or **key** of the squadron. - -- * Have specific helicopter or plane **templates**. - -- * Are located at **one** airbase, farp or carrier. - -- * Optionally have a **limited set of resources**. The default is that squadrons have **unlimited resources**. - -- - -- The name of the squadron given acts as the **squadron key** in all `A2GDispatcher:SetSquadron...()` or `A2GDispatcher:GetSquadron...()` methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new helicopters or aircraft are taking off from the airfield, farp or carrier (in the air, cold, hot, at the runway). - -- * Control how returning helicopters or aircraft are landing at the airfield, farp or carrier (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new helicopters or aircraft spawned at the airfield, farp or carrier. If there is more than one helicopter or aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, - -- the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- The method @{#AI_A2G_DISPATCHER.SetSquadron}() defines for you a new squadron. - -- The provided parameters are the squadron name, airbase name and a list of template prefixes, and a number that indicates the amount of resources. - -- - -- For example, this defines 3 new squadrons: - -- - -- A2GDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50" }, 10 ) - -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50" }, 10 ) - -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50" }, 10 ) - -- - -- The latter 2 will depart from FARPs, which bare the name `"CAS"` and `"BAI"`. - -- - -- - -- ### 3.1. Squadrons **Tasking**. - -- - -- Squadrons can be commanded to execute 3 types of tasks, as explained above: - -- - -- - SEAD: Suppression of Air Defenses, which are ground targets that have medium or long range radar emitters. - -- - BAI : Battlefield Air Interdiction, which are targets further away from the front-line. - -- - CAS : Close Air Support, when there are enemy ground targets close to friendly units. - -- - -- You need to configure each squadron which task types you want it to perform. Read on ... - -- - -- - -- ### 3.2. Squadrons enemy ground target **engagement types**. - -- - -- There are two ways how targets can be engaged: directly **on call** from the airfield, FARP or carrier, or through a **patrol**. - -- - -- Patrols are extremely handy, as these will get your helicopters or airplanes airborne in advance. They will patrol in defined zones outlined, - -- and will engage with the targets once commanded. If the patrol zone is close enough to the enemy ground targets, then the time required - -- to engage is heavily minimized! - -- - -- However; patrols come with a side effect: since your resources are airborne, they will be vulnerable to incoming air attacks from the enemy. - -- - -- The mission designer needs to carefully balance the need for patrols or the need for engagement on call from the airfields. - -- - -- - -- ### 3.3. Squadron **on call** engagement. - -- - -- So to make squadrons engage targets from the airfields, use the following methods: - -- - -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSead}() method. - -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBai}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCas}() method. - -- - -- Note that for the tasks, specific helicopter or airplane templates are required to be used, which you can configure using your mission editor. - -- Especially the payload (weapons configuration) is important to get right. - -- - -- For example, the following will define for the squadrons different tasks: - -- - -- A2GDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) - -- A2GDispatcher:SetSquadronSead( "Maykop SEAD", 120, 250 ) - -- - -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- A2GDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) - -- - -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- A2GDispatcher:SetSquadronCas( "Maykop CAS", 120, 250 ) - -- - -- - -- ### 3.4. Squadron **on patrol engagement**. - -- - -- Squadrons can be setup to patrol in the air near the engagement hot zone. - -- When needed, the A2G defense units will be close to the battle area, and can engage quickly. - -- - -- So to make squadrons engage targets from a patrol zone, use the following methods: - -- - -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSeadPatrol}() method. - -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBaiPatrol}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrol}() method. - -- - -- Because a patrol requires more parameters, the following methods must be used to fine-tune the patrols for each squadron. - -- - -- - For SEAD, use the @{#AI_A2G_DISPATCHER.SetSquadronSeadPatrolInterval}() method. - -- - For BAI, use the @{#AI_A2G_DISPATCHER.SetSquadronBaiPatrolInterval}() method. - -- - For CAS, use the @{#AI_A2G_DISPATCHER.SetSquadronCasPatrolInterval}() method. - -- - -- Here an example to setup patrols of various task types: - -- - -- A2GDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) - -- A2GDispatcher:SetSquadronSeadPatrol( "Maykop SEAD", PatrolZone, 300, 500, 50, 80, 250, 300 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop SEAD", 2, 30, 60, 1, "SEAD" ) - -- - -- A2GDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- A2GDispatcher:SetSquadronBaiPatrol( "Maykop BAI", PatrolZone, 800, 900, 50, 80, 250, 300 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop BAI", 2, 30, 60, 1, "BAI" ) - -- - -- A2GDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- A2GDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) - -- - -- - -- ### 3.5. Set squadron takeoff methods - -- - -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the home airfield, FARP or ship. - -- - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2G_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. - -- - -- **The default landing method is to spawn new aircraft directly in the air.** - -- - -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. - -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: - -- - -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. - -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. - -- * aircraft may collide at the airbase. - -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- - -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft takeoff or landing, please use one of the above methods as a solution to workaround these issues! - -- - -- This example sets the default takeoff method to be from the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the takeoff methods - -- - -- -- Set the default takeoff method - -- A2GDispatcher:SetDefaultTakeoffFromRunway() - -- - -- -- Set the individual squadrons takeoff method - -- A2GDispatcher:SetSquadronTakeoff( "Mineralnye", AI_A2G_DISPATCHER.Takeoff.Air ) - -- A2GDispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2GDispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) - -- - -- - -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. - -- - -- In the case of the @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. - -- That is modifying or setting the **altitude** from where planes spawn in the air. - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. - -- The default takeoff altitude can be modified or set using the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). - -- As part of the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. - -- If this parameter is not specified, then the default altitude will be used for the squadron. - -- - -- - -- ### 3.5.2. Set Squadron takeoff interval. - -- - -- The different types of available airfields have different amounts of available launching platforms: - -- - -- - Airbases typically have a lot of platforms. - -- - FARPs have 4 platforms. - -- - Ships have 2 to 4 platforms. - -- - -- Depending on the demand of requested takeoffs by the A2G dispatcher, an airfield can become overloaded. Too many aircraft need to be taken - -- off at the same time, which will result in clutter as described above. In order to better control this behaviour, a takeoff scheduler is implemented, - -- which can be used to control how many aircraft are ordered for takeoff between specific time intervals. - -- The takeoff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. - -- - -- For this purpose, the method @{#AI_A2G_DISPATCHER.SetSquadronTakeoffInterval}() can be used to specify the takeoff intervals of - -- aircraft groups per squadron to avoid cluttering of aircraft at airbases. - -- This is especially useful for FARPs and ships. Each takeoff dispatch is queued by the dispatcher and when the interval time - -- has been reached, a new group will be spawned or activated for takeoff. - -- - -- The interval needs to be estimated, and depends on the time needed for the aircraft group to actually depart from the launch platform, and - -- the way how the aircraft are starting up. Cold starts take the longest duration, hot starts a few seconds, and runway takeoff also a few seconds for FARPs and ships. - -- - -- See the underlying example: - -- - -- -- Imagine a squadron launched from a FARP, with a grouping of 4. - -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. - -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. - -- -- It takes about 3 to 4 minutes for helicopters to takeoff from FARPs in cold start. - -- A2GDispatcher:SetSquadronTakeoffInterval( "Mineralnye", 60 * 4 ) - -- - -- - -- ### 3.6. Set squadron landing methods - -- - -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: - -- - -- * @{#AI_A2G_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. - -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. - -- * @{#AI_A2G_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. - -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2G defense system, as no new SEAD, BAI or CAS planes can takeoff. - -- Note that the method @{#AI_A2G_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. - -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. - -- - -- This example defines the default landing method to be at the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Landing methods - -- - -- -- The default landing method - -- A2GDispatcher:SetDefaultLandingAtRunway() - -- - -- -- The individual landing per squadron - -- A2GDispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2GDispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2GDispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2GDispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2GDispatcher:SetSquadronLanding( "Novo", AI_A2G_DISPATCHER.Landing.AtRunway ) - -- - -- - -- ### 3.7. Set squadron **grouping**. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() to set the grouping of aircraft when spawned in. - -- - -- In the case of **on call** engagement, the @{#AI_A2G_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. - -- When there aren't enough patrol flights airborne, a on call will be initiated for the remaining - -- targets to be engaged. Depending on the grouping parameter, the spawned flights for on call aircraft are grouped into this setting. - -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by the available patrols or any airborne flight, - -- an additional on call flight needs to be started. - -- - -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. - -- - -- ### 3.8. Set the squadron **overhead** to balance the effectiveness of the A2G defenses. - -- - -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. - -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. - -- - -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. - -- - -- The @{#AI_A2G_DISPATCHER.SetSquadronOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. - -- - -- For example, a A-10C with full long-distance A2G missiles payload, may still be less effective than a Su-23 with short range A2G missiles... - -- So in this case, one may want to use the @{#AI_A2G_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking ground units. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: - -- - -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 attacking ground units detected, 6 aircraft will be spawned. - -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 attacking ground units detected, only 3 aircraft will be spawned. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group - -- multiplied by the overhead parameter, and rounded up to the smallest integer. - -- - -- Typically, for A2G defenses, values small than 1 will be used. Here are some good values for a couple of aircraft to support CAS operations: - -- - -- - A-10C: 0.15 - -- - Su-34: 0.15 - -- - A-10A: 0.25 - -- - SU-25T: 0.10 - -- - -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, - -- the less risk that the defender may be destroyed by the enemy, thus, the less aircraft needs to be activated in a defense. - -- - -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. - -- - -- ### 3.8. Set the squadron **engage limit**. - -- - -- To limit the amount of aircraft to defend against a large group of intruders, an **engage limit** can be defined per squadron. - -- This limit will avoid an extensive amount of aircraft to engage with the enemy if the attacking ground forces are enormous. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. - -- - -- ## 4. Set the **fuel threshold**. - -- - -- When an aircraft gets **out of fuel** with only a certain % of fuel left, which is **15% (0.15)** by default, there are two possible actions that can be taken: - -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. - -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons. - -- - -- ## 6. Other configuration options - -- - -- ### 6.1. Set a tactical display panel. - -- - -- Every 30 seconds, a tactical display panel can be shown that illustrates what the status is of the different groups controlled by AI_A2G_DISPATCHER. - -- Use the method @{#AI_A2G_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. - -- Note that there may be some performance impact if this panel is shown. - -- - -- ## 10. Default settings. - -- - -- Default settings configure the standard behaviour of the squadrons. - -- This section a good overview of the different parameters that setup the behaviour of **ALL** the squadrons by default. - -- Note that default behaviour can be tweaked, and thus, this will change the behaviour of all the squadrons. - -- Unless there is a specific behaviour set for a specific squadron, the default configured behaviour will be followed. - -- - -- ## 10.1. Default **takeoff** behaviour. - -- - -- The default takeoff behaviour is set to **in the air**, which means that new spawned aircraft will be spawned directly in the air above the airbase by default. - -- - -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** - -- - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_A2G_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. - -- - -- ## 10.2. Default landing behaviour. - -- - -- The default landing behaviour is set to **near the airbase**, which means that returning aircraft will be despawned directly in the air by default. - -- - -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. - -- - -- * @{#AI_A2G_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. - -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. - -- * @{#AI_A2G_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- ## 10.3. Default **overhead**. - -- - -- The default overhead is set to **0.25**. That essentially means that for each 4 ground enemies there will be 1 aircraft dispatched. - -- - -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. - -- - -- Use the @{#AI_A2G_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. - -- - -- ## 10.4. Default **grouping**. - -- - -- The default grouping is set to **one aircraft**. That essentially means that there won't be any grouping applied by default. - -- - -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned aircraft for all squadrons. - -- - -- ## 10.5. Default RTB fuel threshold. - -- - -- When an aircraft gets **out of fuel** with only a certain % of fuel left, which is **15% (0.15)** by default, it will go RTB, and will be replaced with a new aircraft when applicable. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned aircraft for all squadrons. - -- - -- ## 10.6. Default RTB damage threshold. - -- - -- When an aircraft is **damaged** to a certain %, which is **40% (0.40)** by default, it will go RTB, and will be replaced with a new aircraft when applicable. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned aircraft for all squadrons. - -- - -- ## 10.7. Default settings for **patrol**. - -- - -- ### 10.7.1. Default **patrol time Interval**. - -- - -- Patrol dispatching is time event driven, and will evaluate in random time intervals if a new patrol needs to be dispatched. - -- - -- The default patrol time interval is between **180** and **600** seconds. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft for ALL squadrons. - -- - -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using - -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. - -- - -- ### 10.7.2. Default **patrol limit**. - -- - -- Multiple patrol can be airborne at the same time for one squadron, which is controlled by the **patrol limit**. - -- The **default patrol limit** is 1 patrol per squadron to be airborne at the same time. - -- Note that the default patrol limit is used when a squadron patrol is defined, and cannot be changed afterwards. - -- So, ensure that you set the default patrol limit **before** you define or setup the squadron patrol. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft patrols for all squadrons. - -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using - -- the @{#AI_A2G_DISPATCHER.SetSquadronPatrolTimeInterval}() method. - -- - -- ## 10.7.3. Default tanker for refuelling when executing SEAD, BAI and CAS operations. - -- - -- Instead of sending SEAD, BAI and CAS aircraft to RTB when out of fuel, you can let SEAD, BAI and CAS aircraft refuel in mid air using a tanker. - -- This greatly increases the efficiency of your SEAD, BAI and CAS operations. - -- - -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. - -- Then, use the method @{#AI_A2G_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_A2G_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender aircraft tanks when a refuel action is needed. - -- - -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. - -- - -- For example, the following setup will set the default refuel tanker to "Tanker": - -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. - -- A2GDispatcher:SetDefaultFuelThreshold( 0.9 ) - -- A2GDispatcher:SetDefaultTanker( "Tanker" ) - -- - -- ## 10.8. Default settings for GCI. - -- - -- ## 10.8.1. Optimal intercept point calculation. - -- - -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. - -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. - -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. - -- - -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: - -- - -- * The average bearing of the intruders for an amount of seconds. - -- * The average speed of the intruders for an amount of seconds. - -- * An assumed time it takes to get planes operational at the airbase. - -- - -- The **intercept point** will determine: - -- - -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. - -- * The optimal airbase from where defenders will takeoff for GCI. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. - -- - -- ## 10.8.2. Default Disengage Radius. - -- - -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. - -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. - -- - -- ## 11. Airbase capture: - -- - -- Different squadrons can be located at one airbase. - -- If the airbase gets captured, that is when there is an enemy unit near the airbase and there are no friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, further SEAD, BAI, and CAS operations from that airbase will stop. - -- However, the squadron will still stay alive. Any aircraft that is airborne will continue its operations until all airborne aircraft - -- of the squadron are destroyed. This is to keep consistency of air operations and avoid confusing players. - -- - -- - -- - -- - -- @field #AI_A2G_DISPATCHER - AI_A2G_DISPATCHER = { - ClassName = "AI_A2G_DISPATCHER", - Detection = nil, - } - - --- Definition of a Squadron. - -- @type AI_A2G_DISPATCHER.Squadron - -- @field #string Name The Squadron name. - -- @field Wrapper.Airbase#AIRBASE Airbase The home airbase. - -- @field #string AirbaseName The name of the home airbase. - -- @field Core.Spawn#SPAWN Spawn The spawning object. - -- @field #number ResourceCount The number of resources available. - -- @field #list<#string> TemplatePrefixes The list of template prefixes. - -- @field #boolean Captured true if the squadron is captured. - -- @field #number Overhead The overhead for the squadron. - - --- List of defense coordinates. - -- @type AI_A2G_DISPATCHER.DefenseCoordinates - -- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name. - - -- @field #AI_A2G_DISPATCHER.DefenseCoordinates DefenseCoordinates - AI_A2G_DISPATCHER.DefenseCoordinates = {} - - --- Enumerator for spawns at airbases. - -- @type AI_A2G_DISPATCHER.Takeoff - -- @extends Wrapper.Group#GROUP.Takeoff - - -- @field #AI_A2G_DISPATCHER.Takeoff Takeoff - AI_A2G_DISPATCHER.Takeoff = GROUP.Takeoff - - --- Defines Landing location. - -- @field #AI_A2G_DISPATCHER.Landing - AI_A2G_DISPATCHER.Landing = { - NearAirbase = 1, - AtRunway = 2, - AtEngineShutdown = 3, - } - - --- A defense queue item description. - -- @type AI_A2G_DISPATCHER.DefenseQueueItem - -- @field Squadron - -- @field #AI_A2G_DISPATCHER.Squadron DefenderSquadron The squadron in the queue. - -- @field DefendersNeeded - -- @field Defense - -- @field DefenseTaskType - -- @field Functional.Detection#DETECTION_BASE AttackerDetection - -- @field DefenderGrouping - -- @field #string SquadronName The name of the squadron. - - --- Queue of planned defenses to be launched. - -- This queue exists because defenses must be launched from FARPs, in the air, from airbases, or from carriers. - -- And some of these platforms have very limited amount of "launching" platforms. - -- Therefore, this queue concept is introduced that queues each defender request. - -- Depending on the location of the launching site, the queued defenders will be launched at varying time intervals. - -- This guarantees that launched defenders are also directly existing ... - -- @type AI_A2G_DISPATCHER.DefenseQueue - -- @list<#AI_A2G_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... - - -- @field #AI_A2G_DISPATCHER.DefenseQueue DefenseQueue - AI_A2G_DISPATCHER.DefenseQueue = {} - - --- Defense approach types. - -- @type AI_A2G_DISPATCHER.DefenseApproach - AI_A2G_DISPATCHER.DefenseApproach = { - Random = 1, - Distance = 2, - } - - --- AI_A2G_DISPATCHER constructor. - -- This is defining the A2G DISPATCHER for one coalition. - -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. - -- @param #AI_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. - -- @return #AI_A2G_DISPATCHER self - -- @usage - -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) -- - -- - function AI_A2G_DISPATCHER:New( Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( nil, Detection ) ) -- #AI_A2G_DISPATCHER - - self.Detection = Detection -- Functional.Detection#DETECTION_AREAS - - self.Detection:FilterCategories( Unit.Category.GROUND_UNIT ) - - -- This table models the DefenderSquadron templates. - self.DefenderSquadrons = {} -- The Defender Squadrons. - self.DefenderSpawns = {} - self.DefenderTasks = {} -- The Defenders Tasks. - self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons. - - -- TODO: Check detection through radar. --- self.Detection:FilterCategories( { Unit.Category.GROUND } ) --- self.Detection:InitDetectRadar( false ) --- self.Detection:InitDetectVisual( true ) --- self.Detection:SetRefreshTimeInterval( 30 ) - - self.SetSendPlayerMessages = false --flash messages to players - - self:SetDefenseRadius() - self:SetDefenseLimit( nil ) - self:SetDefenseApproach( AI_A2G_DISPATCHER.DefenseApproach.Random ) - self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. - self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Air ) - self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above ground level (AGL). - self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.NearAirbase ) - self:SetDefaultOverhead( 1 ) - self:SetDefaultGrouping( 1 ) - self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the aircraft to return to base or refuel. - self:SetDefaultDamageThreshold( 0.4 ) -- When 40% of damage, go RTB. - self:SetDefaultPatrolTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. - self:SetDefaultPatrolLimit( 1 ) -- Maximum one Patrol per squadron. - - - self:AddTransition( "Started", "Assign", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#AI_A2G_DISPATCHER] OnAfterAssign - -- @param #AI_A2G_DISPATCHER self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Tasking.Task_A2G#AI_A2G Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:AddTransition( "*", "Patrol", "*" ) - - --- Patrol Handler OnBefore for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnBeforePatrol - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Patrol Handler OnAfter for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnAfterPatrol - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Patrol Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] Patrol - -- @param #AI_A2G_DISPATCHER self - - --- Patrol Asynchronous Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] __Patrol - -- @param #AI_A2G_DISPATCHER self - -- @param #number Delay - - self:AddTransition( "*", "Defend", "*" ) - - --- Defend Handler OnBefore for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnBeforeDefend - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Defend Handler OnAfter for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnAfterDefend - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Defend Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] Defend - -- @param #AI_A2G_DISPATCHER self - - --- Defend Asynchronous Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] __Defend - -- @param #AI_A2G_DISPATCHER self - -- @param #number Delay - - self:AddTransition( "*", "Engage", "*" ) - - --- Engage Handler OnBefore for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnBeforeEngage - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Engage Handler OnAfter for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] OnAfterEngage - -- @param #AI_A2G_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Engage Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] Engage - -- @param #AI_A2G_DISPATCHER self - - --- Engage Asynchronous Trigger for AI_A2G_DISPATCHER - -- @function [parent=#AI_A2G_DISPATCHER] __Engage - -- @param #AI_A2G_DISPATCHER self - -- @param #number Delay - - - -- Subscribe to the CRASH event so that when planes are shot - -- by a Unit from the dispatcher, they will be removed from the detection... - -- This will avoid the detection to still "know" the shot unit until the next detection. - -- Otherwise, a new defense or engage may happen for an already shot plane! - - - self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) - self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) - - - self:HandleEvent( EVENTS.Land ) - self:HandleEvent( EVENTS.EngineShutdown ) - - -- Handle the situation where the airbases are captured. - self:HandleEvent( EVENTS.BaseCaptured ) - - self:SetTacticalDisplay( false ) - - self.DefenderPatrolIndex = 0 - - self:SetDefenseReactivityMedium() - - self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self ) - - self:__Start( 1 ) - - return self - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:onafterStart( From, Event, To ) - - self:GetParent( self ).onafterStart( self, From, Event, To ) - - -- Spawn the resources. - for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - DefenderSquadron.Resource = {} - for Resource = 1, DefenderSquadron.ResourceCount or 0 do - self:ResourcePark( DefenderSquadron ) - end - self:T( "Parked resources for squadron " .. DefenderSquadron.Name ) - end - - end - - - --- Locks the DefenseItem from being defended. - -- @param #AI_A2G_DISPATCHER self - -- @param #string DetectedItemIndex The index of the detected item. - function AI_A2G_DISPATCHER:Lock( DetectedItemIndex ) - self:F( { DetectedItemIndex = DetectedItemIndex } ) - local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) - if DetectedItem then - self:F( { Locked = DetectedItem } ) - self.Detection:LockDetectedItem( DetectedItem ) - end - end - - - --- Unlocks the DefenseItem from being defended. - -- @param #AI_A2G_DISPATCHER self - -- @param #string DetectedItemIndex The index of the detected item. - function AI_A2G_DISPATCHER:Unlock( DetectedItemIndex ) - self:F( { DetectedItemIndex = DetectedItemIndex } ) - self:F( { Index = self.Detection.DetectedItemsByIndex } ) - local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) - if DetectedItem then - self:F( { Unlocked = DetectedItem } ) - self.Detection:UnlockDetectedItem( DetectedItem ) - end - end - - - --- Sets maximum zones to be engaged at one time by defenders. - -- @param #AI_A2G_DISPATCHER self - -- @param #number DefenseLimit The maximum amount of detected items to be engaged at the same time. - function AI_A2G_DISPATCHER:SetDefenseLimit( DefenseLimit ) - self:F( { DefenseLimit = DefenseLimit } ) - - self.DefenseLimit = DefenseLimit - end - - - --- Sets the method of the tactical approach of the defenses. - -- @param #AI_A2G_DISPATCHER self - -- @param #number DefenseApproach Use the structure AI_A2G_DISPATCHER.DefenseApproach to set the defense approach. - -- The default defense approach is AI_A2G_DISPATCHER.DefenseApproach.Random. - function AI_A2G_DISPATCHER:SetDefenseApproach( DefenseApproach ) - self:F( { DefenseApproach = DefenseApproach } ) - - self._DefenseApproach = DefenseApproach - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourcePark( DefenderSquadron ) - local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) - local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN - Spawn:InitGrouping( 1 ) - local SpawnGroup - if self:IsSquadronVisible( DefenderSquadron.Name ) then - SpawnGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, SPAWN.Takeoff.Cold ) - local GroupName = SpawnGroup:GetName() - DefenderSquadron.Resources = DefenderSquadron.Resources or {} - DefenderSquadron.Resources[TemplateID] = DefenderSquadron.Resources[TemplateID] or {} - DefenderSquadron.Resources[TemplateID][GroupName] = {} - DefenderSquadron.Resources[TemplateID][GroupName] = SpawnGroup - end - end - - - -- @param #AI_A2G_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2G_DISPATCHER:OnEventBaseCaptured( EventData ) - - local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured. - - self:T( "Captured " .. AirbaseName ) - - -- Now search for all squadrons located at the airbase, and sanitize them. - for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do - if Squadron.AirbaseName == AirbaseName then - Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning. - Squadron.Captured = true - self:T( "Squadron " .. SquadronName .. " captured." ) - end - end - end - - - -- @param #AI_A2G_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2G_DISPATCHER:OnEventCrashOrDead( EventData ) - self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) - end - - - -- @param #AI_A2G_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2G_DISPATCHER:OnEventLand( EventData ) - self:F( "Landed" ) - local DefenderUnit = EventData.IniUnit - local Defender = EventData.IniGroup - local Squadron = self:GetSquadronFromDefender( Defender ) - if Squadron then - self:F( { SquadronName = Squadron.Name } ) - local LandingMethod = self:GetSquadronLanding( Squadron.Name ) - - if LandingMethod == AI_A2G_DISPATCHER.Landing.AtRunway then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ResourcePark( Squadron ) - return - end - if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then - -- Damaged units cannot be repaired anymore. - DefenderUnit:Destroy() - return - end - end - end - - - -- @param #AI_A2G_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_A2G_DISPATCHER:OnEventEngineShutdown( EventData ) - local DefenderUnit = EventData.IniUnit - local Defender = EventData.IniGroup - local Squadron = self:GetSquadronFromDefender( Defender ) - if Squadron then - self:F( { SquadronName = Squadron.Name } ) - local LandingMethod = self:GetSquadronLanding( Squadron.Name ) - if LandingMethod == AI_A2G_DISPATCHER.Landing.AtEngineShutdown and - not DefenderUnit:InAir() then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ResourcePark( Squadron ) - end - end - end - - - do -- Manage the defensive behaviour - - -- @param #AI_A2G_DISPATCHER self - -- @param #string DefenseCoordinateName The name of the coordinate to be defended by A2G defenses. - -- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by A2G defenses. - function AI_A2G_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate ) - self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:SetDefenseReactivityLow() - self.DefenseReactivity = 0.05 - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:SetDefenseReactivityMedium() - self.DefenseReactivity = 0.15 - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:SetDefenseReactivityHigh() - self.DefenseReactivity = 0.5 - end - - end - - - --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. - -- @param #AI_A2G_DISPATCHER self - -- @param #number DisengageRadius (Optional, Default = 300000) The radius to disengage a target when too far from the home base. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Set 50km as the Disengage Radius. - -- A2GDispatcher:SetDisengageRadius( 50000 ) - -- - -- -- Set 100km as the Disengage Radius. - -- A2GDispatcher:SetDisengageRadius() -- 300000 is the default value. - -- - function AI_A2G_DISPATCHER:SetDisengageRadius( DisengageRadius ) - - self.DisengageRadius = DisengageRadius or 300000 - - return self - end - - - --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, BAI, or CAS for defense. - -- When targets are detected that are still really far off, you don't want the AI_A2G_DISPATCHER to launch defenders, as they might need to travel too far. - -- You want it to wait until a certain defend radius is reached, which is calculated as: - -- 1. the **distance of the closest airbase to target**, being smaller than the **Defend Radius**. - -- 2. the **distance to any defense reference point**. - -- - -- The **default** defense radius is defined as **40000** or **40km**. Override the default defense radius when the era of the warfare is early, or, - -- when you don't want to let the AI_A2G_DISPATCHER react immediately when a certain border or area is not being crossed. - -- - -- Use the method @{#AI_A2G_DISPATCHER.SetDefendRadius}() to set a specific defend radius for all squadrons, - -- **the Defense Radius is defined for ALL squadrons which are operational.** - -- - -- @param #AI_A2G_DISPATCHER self - -- @param #number DefenseRadius (Optional, Default = 20000) The defense radius to engage detected targets from the nearest capable and available squadron airbase. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Set 100km as the radius to defend from detected targets from the nearest airbase. - -- A2GDispatcher:SetDefendRadius( 100000 ) - -- - -- -- Set 200km as the radius to defend. - -- A2GDispatcher:SetDefendRadius() -- 200000 is the default value. - -- - function AI_A2G_DISPATCHER:SetDefenseRadius( DefenseRadius ) - - self.DefenseRadius = DefenseRadius or 40000 - - self.Detection:SetAcceptRange( self.DefenseRadius ) - - return self - end - - - --- Define a border area to simulate a **cold war** scenario. - -- A **cold war** is one where Patrol aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. - -- A **hot war** is one where Patrol aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send Patrol and GCI aircraft to attack it. - -- If it's a cold war then the **borders of red and blue territory** need to be defined using a @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE}. This method needs to be used for this. - -- If a hot war is chosen then **no borders** actually need to be defined using the helicopter units other than it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. Set the noborders parameter to 1 - -- @param #AI_A2G_DISPATCHER self - -- @param Core.Zone#ZONE_BASE BorderZone An object derived from ZONE_BASE, or a list of objects derived from ZONE_BASE. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Set one ZONE_POLYGON object as the border for the A2G dispatcher. - -- local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit. - -- A2GDispatcher:SetBorderZone( BorderZone ) - -- - -- or - -- - -- -- Set two ZONE_POLYGON objects as the border for the A2G dispatcher. - -- local BorderZone1 = ZONE_POLYGON( "CCCP Border1", GROUP:FindByName( "CCCP Border1" ) ) -- The GROUP object is a late activate helicopter unit. - -- local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit. - -- A2GDispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) - -- - function AI_A2G_DISPATCHER:SetBorderZone( BorderZone ) - - self.Detection:SetAcceptZones( BorderZone ) - - return self - end - - - --- Display a tactical report every 30 seconds about which aircraft are: - -- * Patrolling - -- * Engaging - -- * Returning - -- * Damaged - -- * Out of Fuel - -- * ... - -- @param #AI_A2G_DISPATCHER self - -- @param #boolean TacticalDisplay Provide a value of **true** to display every 30 seconds a tactical overview. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the Tactical Display for debug mode. - -- A2GDispatcher:SetTacticalDisplay( true ) - -- - function AI_A2G_DISPATCHER:SetTacticalDisplay( TacticalDisplay ) - - self.TacticalDisplay = TacticalDisplay - - return self - end - - - --- Set the default damage threshold when defenders will RTB. - -- The default damage threshold is by default set to 40%, which means that when the aircraft is 40% damaged, it will go RTB. - -- @param #AI_A2G_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of damage when the aircraft will go RTB. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default damage threshold. - -- A2GDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the aircraft is 90% damaged. - -- - function AI_A2G_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) - - self.DefenderDefault.DamageThreshold = DamageThreshold - - return self - end - - - --- Set the default Patrol time interval for squadrons, which will be used to determine a random Patrol timing. - -- The default Patrol time interval is between 180 and 600 seconds. - -- @param #AI_A2G_DISPATCHER self - -- @param #number PatrolMinSeconds The minimum amount of seconds for the random time interval. - -- @param #number PatrolMaxSeconds The maximum amount of seconds for the random time interval. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol time interval. - -- A2GDispatcher:SetDefaultPatrolTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds. - -- - function AI_A2G_DISPATCHER:SetDefaultPatrolTimeInterval( PatrolMinSeconds, PatrolMaxSeconds ) - - self.DefenderDefault.PatrolMinSeconds = PatrolMinSeconds - self.DefenderDefault.PatrolMaxSeconds = PatrolMaxSeconds - - return self - end - - - --- Set the default Patrol limit for squadrons, which will be used to determine how many Patrol can be airborne at the same time for the squadron. - -- The default Patrol limit is 1 Patrol, which means one Patrol group being spawned. - -- @param #AI_A2G_DISPATCHER self - -- @param #number PatrolLimit The maximum amount of Patrol that can be airborne at the same time for the squadron. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol limit. - -- A2GDispatcher:SetDefaultPatrolLimit( 2 ) -- Maximum 2 Patrol per squadron. - -- - function AI_A2G_DISPATCHER:SetDefaultPatrolLimit( PatrolLimit ) - - self.DefenderDefault.PatrolLimit = PatrolLimit - - return self - end - - - --- Set the default engage limit for squadrons, which will be used to determine how many air units will engage at the same time with the enemy. - -- The default eatrol limit is 1, which means one eatrol group maximum per squadron. - -- @param #AI_A2G_DISPATCHER self - -- @param #number EngageLimit The maximum engages that can be done at the same time per squadron. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol limit. - -- A2GDispatcher:SetDefaultEngageLimit( 2 ) -- Maximum 2 engagements with the enemy per squadron. - -- - function AI_A2G_DISPATCHER:SetDefaultEngageLimit( EngageLimit ) - - self.DefenderDefault.EngageLimit = EngageLimit - - return self - end - - - function AI_A2G_DISPATCHER:SetIntercept( InterceptDelay ) - - self.DefenderDefault.InterceptDelay = InterceptDelay - - local Detection = self.Detection -- Functional.Detection#DETECTION_AREAS - Detection:SetIntercept( true, InterceptDelay ) - - return self - end - - - --- Calculates which defender friendlies are nearby the area, to help protect the area. - -- @param #AI_A2G_DISPATCHER self - -- @param DetectedItem - -- @return #table A list of the defender friendlies nearby, sorted by distance. - function AI_A2G_DISPATCHER:GetDefenderFriendliesNearBy( DetectedItem ) - --- local DefenderFriendliesNearBy = self.Detection:GetFriendliesDistance( DetectedItem ) - - local DefenderFriendliesNearBy = {} - - local DetectionCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - - local ScanZone = ZONE_RADIUS:New( "ScanZone", DetectionCoordinate:GetVec2(), self.DefenseRadius ) - - ScanZone:Scan( Object.Category.UNIT, { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - - local DefenderUnits = ScanZone:GetScannedUnits() - - for DefenderUnitID, DefenderUnit in pairs( DefenderUnits ) do - local DefenderUnit = UNIT:FindByName( DefenderUnit:getName() ) - - DefenderFriendliesNearBy[#DefenderFriendliesNearBy+1] = DefenderUnit - end - - - return DefenderFriendliesNearBy - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTasks() - return self.DefenderTasks or {} - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTask( Defender ) - return self.DefenderTasks[Defender] - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTaskFsm( Defender ) - return self:GetDefenderTask( Defender ).Fsm - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTaskTarget( Defender ) - return self:GetDefenderTask( Defender ).Target - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:GetDefenderTaskSquadronName( Defender ) - return self:GetDefenderTask( Defender ).SquadronName - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ClearDefenderTask( Defender ) - if Defender:IsAlive() and self.DefenderTasks[Defender] then - local Target = self.DefenderTasks[Defender].Target - local Message = "Clearing (" .. self.DefenderTasks[Defender].Type .. ") " - Message = Message .. Defender:GetName() - if Target then - Message = Message .. ( Target and ( " from " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or "" - end - self:F( { Target = Message } ) - end - self.DefenderTasks[Defender] = nil - return self - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ClearDefenderTaskTarget( Defender ) - - local DefenderTask = self:GetDefenderTask( Defender ) - - if Defender:IsAlive() and DefenderTask then - local Target = DefenderTask.Target - local Message = "Clearing (" .. DefenderTask.Type .. ") " - Message = Message .. Defender:GetName() - if Target then - Message = Message .. ( Target and ( " from " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or "" - end - self:F( { Target = Message } ) - end - if Defender and DefenderTask and DefenderTask.Target then - DefenderTask.Target = nil - end --- if Defender and DefenderTask then --- if DefenderTask.Fsm:Is( "Fuel" ) --- or DefenderTask.Fsm:Is( "LostControl") --- or DefenderTask.Fsm:Is( "Damaged" ) then --- self:ClearDefenderTask( Defender ) --- end --- end - return self - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target, Size ) - - self:F( { SquadronName = SquadronName, Defender = Defender:GetName() } ) - - self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {} - self.DefenderTasks[Defender].Type = Type - self.DefenderTasks[Defender].Fsm = Fsm - self.DefenderTasks[Defender].SquadronName = SquadronName - self.DefenderTasks[Defender].Size = Size - - if Target then - self:SetDefenderTaskTarget( Defender, Target ) - end - return self - end - - - --- - -- @param #AI_A2G_DISPATCHER self - -- @param Wrapper.Group#GROUP AIGroup - function AI_A2G_DISPATCHER:SetDefenderTaskTarget( Defender, AttackerDetection ) - - local Message = "(" .. self.DefenderTasks[Defender].Type .. ") " - Message = Message .. Defender:GetName() - Message = Message .. ( AttackerDetection and ( " target " .. AttackerDetection.Index .. " [" .. AttackerDetection.Set:Count() .. "]" ) ) or "" - self:F( { AttackerDetection = Message } ) - if AttackerDetection then - self.DefenderTasks[Defender].Target = AttackerDetection - end - return self - end - - - --- This is the main method to define Squadrons programmatically. - -- Squadrons: - -- - -- * Have a **name or key** that is the identifier or key of the squadron. - -- * Have **specific plane types** defined by **templates**. - -- * Are **located at one specific airbase**. Multiple squadrons can be located at one airbase through. - -- * Optionally have a limited set of **resources**. The default is that squadrons have unlimited resources. - -- - -- The name of the squadron given acts as the **squadron key** in the AI\_A2G\_DISPATCHER:Squadron...() methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new aircraft are **taking off** from the airfield (in the air, cold, hot, at the runway). - -- * Control how returning aircraft are **landing** at the airfield (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft. - -- - -- @param #AI_A2G_DISPATCHER self - -- - -- @param #string SquadronName A string (text) that defines the squadron identifier or the key of the Squadron. - -- It can be any name, for example `"104th Squadron"` or `"SQ SQUADRON1"`, whatever. - -- As long as you remember that this name becomes the identifier of your squadron you have defined. - -- You need to use this name in other methods too! - -- - -- @param #string AirbaseName The airbase name where you want to have the squadron located. - -- You need to specify here EXACTLY the name of the airbase as you see it in the mission editor. - -- Examples are `"Batumi"` or `"Tbilisi-Lochini"`. - -- EXACTLY the airbase name, between quotes `""`. - -- To ease the airbase naming when using the LDT editor and IntelliSense, the @{Wrapper.Airbase#AIRBASE} class contains enumerations of the airbases of each map. - -- - -- * Caucasus: @{Wrapper.Airbase#AIRBASE.Caucaus} - -- * Nevada or NTTR: @{Wrapper.Airbase#AIRBASE.Nevada} - -- * Normandy: @{Wrapper.Airbase#AIRBASE.Normandy} - -- - -- @param #string TemplatePrefixes A string or an array of strings specifying the **prefix names of the templates** (not going to explain what is templates here again). - -- Examples are `{ "104th", "105th" }` or `"104th"` or `"Template 1"` or `"BLUE PLANES"`. - -- Just remember that your template (groups late activated) need to start with the prefix you have specified in your code. - -- If you have only one prefix name for a squadron, you don't need to use the `{ }`, otherwise you need to use the brackets. - -- - -- @param #number ResourceCount (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available. - -- - -- @usage - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- @usage - -- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock... - -- A2GDispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 ) - -- - -- @usage - -- -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock... - -- -- Note that in this implementation, the A2G dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses. - -- -- Note the usage of the {} for the airplane templates list. - -- A2GDispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 ) - -- - -- @usage - -- -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock... - -- A2GDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) - -- A2GDispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 ) - -- - -- @usage - -- -- This is an example like the previous, but now with infinite resources. - -- -- The ResourceCount parameter is not given in the SetSquadron method. - -- A2GDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) - -- A2GDispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) - -- - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - DefenderSquadron.Name = SquadronName - DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName ) - DefenderSquadron.AirbaseName = DefenderSquadron.Airbase:GetName() - if not DefenderSquadron.Airbase then - error( "Cannot find airbase with name:" .. AirbaseName ) - end - - DefenderSquadron.Spawn = {} - if type( TemplatePrefixes ) == "string" then - local SpawnTemplate = TemplatePrefixes - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - DefenderSquadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate] - else - for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] - end - end - DefenderSquadron.ResourceCount = ResourceCount - DefenderSquadron.TemplatePrefixes = TemplatePrefixes - DefenderSquadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. - - self:SetSquadronTakeoffInterval( SquadronName, 0 ) - - self:F( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) - - return self - end - - - --- Get an item from the Squadron table. - -- @param #AI_A2G_DISPATCHER self - -- @return #table - function AI_A2G_DISPATCHER:GetSquadron( SquadronName ) - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - if not DefenderSquadron then - error( "Unknown Squadron:" .. SquadronName ) - end - - return DefenderSquadron - end - - --- Get a resource count from a specific squadron - -- @param #AI_A2G_DISPATCHER self - -- @param #string Squadron Name of the squadron. - -- @return #number Number of airframes available or nil if the squadron does not exist - function AI_A2G_DISPATCHER:QuerySquadron(Squadron) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - self:T2(string.format("%s = %s",Squadron.Name,Squadron.ResourceCount)) - return Squadron.ResourceCount - end - self:F({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - return nil - end - - --- Set the Squadron visible before startup of the dispatcher. - -- All planes will be spawned as uncontrolled on the parking spot. - -- They will lock the parking spot. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- A2GDispatcher:SetSquadronVisible( "Mineralnye" ) - -- - -- TODO: disabling because of bug in queueing. --- function AI_A2G_DISPATCHER:SetSquadronVisible( SquadronName ) --- --- self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} --- --- local DefenderSquadron = self:GetSquadron( SquadronName ) --- --- DefenderSquadron.Uncontrolled = true --- self:SetSquadronTakeoffFromParkingCold( SquadronName ) --- self:SetSquadronLandingAtEngineShutdown( SquadronName ) --- --- for SpawnTemplate, DefenderSpawn in pairs( self.DefenderSpawns ) do --- DefenderSpawn:InitUnControlled() --- end --- --- end - - - --- Check if the Squadron is visible before startup of the dispatcher. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #boolean true if visible. - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = A2GDispatcher:IsSquadronVisible( "Mineralnye" ) - -- - function AI_A2G_DISPATCHER:IsSquadronVisible( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron then - return DefenderSquadron.Uncontrolled == true - end - - return nil - - end - - - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps. - -- @usage - -- - -- -- Set the Squadron Takeoff interval every 60 seconds for squadron "SQ50", which is good for a FARP cold start. - -- A2GDispatcher:SetSquadronTakeoffInterval( "SQ50", 60 ) - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffInterval( SquadronName, TakeoffInterval ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron then - DefenderSquadron.TakeoffInterval = TakeoffInterval or 0 - DefenderSquadron.TakeoffTime = 0 - end - - end - - - --- Set the squadron patrol parameters for a specific task type. - -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. - -- - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadPatrolInterval} for SEAD tasks. - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadPatrolInterval} for CAS tasks. - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadPatrolInterval} for BAI tasks. - -- - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that each Patrol is a group, and can consist of 1 to 4 aircraft. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @param #string DefenseTaskType Should contain "SEAD", "CAS" or "BAI". - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Mineralnye", 2, 30, 60, 1, "SEAD" ) - -- - function AI_A2G_DISPATCHER:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, DefenseTaskType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Patrol = DefenderSquadron[DefenseTaskType] - if Patrol then - Patrol.LowInterval = LowInterval or 180 - Patrol.HighInterval = HighInterval or 600 - Patrol.Probability = Probability or 1 - Patrol.PatrolLimit = PatrolLimit or 1 - Patrol.Scheduler = Patrol.Scheduler or SCHEDULER:New( self ) - local Scheduler = Patrol.Scheduler -- Core.Scheduler#SCHEDULER - local ScheduleID = Patrol.ScheduleID - local Variance = ( Patrol.HighInterval - Patrol.LowInterval ) / 2 - local Repeat = Patrol.LowInterval + Variance - local Randomization = Variance / Repeat - local Start = math.random( 1, Patrol.HighInterval ) - - if ScheduleID then - Scheduler:Stop( ScheduleID ) - end - - Patrol.ScheduleID = Scheduler:Schedule( self, self.SchedulerPatrol, { SquadronName }, Start, Repeat, Randomization ) - else - error( "This squadron does not exist:" .. SquadronName ) - end - - end - - - --- Set the squadron Patrol parameters for SEAD tasks. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum ttime in seconds between new Patrols being spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2GDispatcher:SetSquadronSeadPatrolInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - function AI_A2G_DISPATCHER:SetSquadronSeadPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability ) - - self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "SEAD" ) - - end - - - --- Set the squadron Patrol parameters for CAS tasks. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time in seconds between new Patrols being spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronCasPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2GDispatcher:SetSquadronCasPatrolInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - function AI_A2G_DISPATCHER:SetSquadronCasPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability ) - - self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "CAS" ) - - end - - - --- Set the squadron Patrol parameters for BAI tasks. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Each Patrol group can consist of 1 to 4 aircraft. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time in seconds between new Patrols being spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time in seconds between new Patrols being spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronBaiPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2GDispatcher:SetSquadronBaiPatrolInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - function AI_A2G_DISPATCHER:SetSquadronBaiPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability ) - - self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "BAI" ) - - end - - - --- - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:GetPatrolDelay( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Patrol = self.DefenderSquadrons[SquadronName].Patrol or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Patrol = self.DefenderSquadrons[SquadronName].Patrol - if Patrol then - return math.random( Patrol.LowInterval, Patrol.HighInterval ) - else - error( "This squadron does not exist:" .. SquadronName ) - end - end - - - --- - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron - function AI_A2G_DISPATCHER:CanPatrol( SquadronName, DefenseTaskType ) - self:F({SquadronName = SquadronName}) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron.Captured == false then -- We can only spawn new Patrol if the base has not been captured. - - if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources. - - local Patrol = DefenderSquadron[DefenseTaskType] - if Patrol and Patrol.Patrol == true then - local PatrolCount = self:CountPatrolAirborne( SquadronName, DefenseTaskType ) - self:F( { PatrolCount = PatrolCount, PatrolLimit = Patrol.PatrolLimit, PatrolProbability = Patrol.Probability } ) - if PatrolCount < Patrol.PatrolLimit then - local Probability = math.random() - if Probability <= Patrol.Probability then - return DefenderSquadron, Patrol - end - end - else - self:F( "No patrol for " .. SquadronName ) - end - end - end - return nil - end - - - --- - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron - function AI_A2G_DISPATCHER:CanDefend( SquadronName, DefenseTaskType ) - self:F({SquadronName = SquadronName, DefenseTaskType}) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron.Captured == false then -- We can only spawn new defense if the home airbase has not been captured. - - if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources. - if DefenderSquadron[DefenseTaskType] and ( DefenderSquadron[DefenseTaskType].Defend == true ) then - return DefenderSquadron, DefenderSquadron[DefenseTaskType] - end - end - end - return nil - end - - - --- Set the squadron engage limit for a specific task type. - -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. - -- - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit} for SEAD tasks. - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit} for CAS tasks. - -- - @{#AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit} for BAI tasks. - -- - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron. - -- @param #string DefenseTaskType Should contain "SEAD", "CAS" or "BAI". - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronEngageLimit( "Mineralnye", 2, "SEAD" ) -- Engage maximum 2 groups with the enemy for SEAD defense. - -- - function AI_A2G_DISPATCHER:SetSquadronEngageLimit( SquadronName, EngageLimit, DefenseTaskType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Defense = DefenderSquadron[DefenseTaskType] - if Defense then - Defense.EngageLimit = EngageLimit or 1 - else - error( "This squadron does not exist:" .. SquadronName ) - end - - end - - - --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the SEAD task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the SEAD task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @usage - -- - -- -- SEAD Squadron execution. - -- A2GDispatcher:SetSquadronSead( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) - -- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100, 6000, 9000, "BARO" ) - -- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200, 30, 100, "RADIO" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronSead2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.SEAD = DefenderSquadron.SEAD or {} - - local Sead = DefenderSquadron.SEAD - Sead.Name = SquadronName - Sead.EngageMinSpeed = EngageMinSpeed - Sead.EngageMaxSpeed = EngageMaxSpeed - Sead.EngageFloorAltitude = EngageFloorAltitude or 500 - Sead.EngageCeilingAltitude = EngageCeilingAltitude or 1000 - Sead.EngageAltType = EngageAltType - Sead.Defend = true - - self:T( { SEAD = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - - return self - end - - - --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the SEAD task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the SEAD task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @usage - -- - -- -- SEAD Squadron execution. - -- A2GDispatcher:SetSquadronSead( "Mozdok", 900, 1200, 4000, 5000 ) - -- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100, 6000, 8000 ) - -- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200, 6000, 10000 ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronSead( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - return self:SetSquadronSead2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) - end - - - --- Set the squadron SEAD engage limit. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for SEAD defense. - -- - function AI_A2G_DISPATCHER:SetSquadronSeadEngageLimit( SquadronName, EngageLimit ) - - self:SetSquadronEngageLimit( SquadronName, EngageLimit, "SEAD" ) - - end - - - --- Set a Sead patrol for a Squadron. - -- The Sead patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. - -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Sead Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) - -- - function AI_A2G_DISPATCHER:SetSquadronSeadPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.SEAD = DefenderSquadron.SEAD or {} - - local SeadPatrol = DefenderSquadron.SEAD - SeadPatrol.Name = SquadronName - SeadPatrol.Zone = Zone - SeadPatrol.PatrolFloorAltitude = PatrolFloorAltitude - SeadPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude - SeadPatrol.EngageFloorAltitude = EngageFloorAltitude - SeadPatrol.EngageCeilingAltitude = EngageCeilingAltitude - SeadPatrol.PatrolMinSpeed = PatrolMinSpeed - SeadPatrol.PatrolMaxSpeed = PatrolMaxSpeed - SeadPatrol.EngageMinSpeed = EngageMinSpeed - SeadPatrol.EngageMaxSpeed = EngageMaxSpeed - SeadPatrol.PatrolAltType = PatrolAltType - SeadPatrol.EngageAltType = EngageAltType - SeadPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "SEAD" ) - - self:T( { SEAD = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - end - - - --- Set a Sead patrol for a Squadron. - -- The Sead patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Sead Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- - function AI_A2G_DISPATCHER:SetSquadronSeadPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - self:SetSquadronSeadPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - - end - - - --- Set a squadron to engage for close air support, when a defense point is under attack. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the CAS task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the CAS task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @usage - -- - -- -- CAS Squadron execution. - -- A2GDispatcher:SetSquadronCas( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) - -- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100, 6000, 9000, "BARO" ) - -- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200, 30, 100, "RADIO" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronCas2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.CAS = DefenderSquadron.CAS or {} - - local Cas = DefenderSquadron.CAS - Cas.Name = SquadronName - Cas.EngageMinSpeed = EngageMinSpeed - Cas.EngageMaxSpeed = EngageMaxSpeed - Cas.EngageFloorAltitude = EngageFloorAltitude or 500 - Cas.EngageCeilingAltitude = EngageCeilingAltitude or 1000 - Cas.EngageAltType = EngageAltType - Cas.Defend = true - - self:T( { CAS = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - - return self - end - - - --- Set a squadron to engage for close air support, when a defense point is under attack. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the CAS task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the CAS task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @usage - -- - -- -- CAS Squadron execution. - -- A2GDispatcher:SetSquadronCas( "Mozdok", 900, 1200, 4000, 5000 ) - -- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100, 6000, 8000 ) - -- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200, 6000, 10000 ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronCas( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - return self:SetSquadronCas2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) - end - - - --- Set the squadron CAS engage limit. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronCasEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for CAS defense. - -- - function AI_A2G_DISPATCHER:SetSquadronCasEngageLimit( SquadronName, EngageLimit ) - - self:SetSquadronEngageLimit( SquadronName, EngageLimit, "CAS" ) - - end - - - --- Set a Cas patrol for a Squadron. - -- The Cas patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. - -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Cas Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronCasPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) - -- - function AI_A2G_DISPATCHER:SetSquadronCasPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.CAS = DefenderSquadron.CAS or {} - - local CasPatrol = DefenderSquadron.CAS - CasPatrol.Name = SquadronName - CasPatrol.Zone = Zone - CasPatrol.PatrolFloorAltitude = PatrolFloorAltitude - CasPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude - CasPatrol.EngageFloorAltitude = EngageFloorAltitude - CasPatrol.EngageCeilingAltitude = EngageCeilingAltitude - CasPatrol.PatrolMinSpeed = PatrolMinSpeed - CasPatrol.PatrolMaxSpeed = PatrolMaxSpeed - CasPatrol.EngageMinSpeed = EngageMinSpeed - CasPatrol.EngageMaxSpeed = EngageMaxSpeed - CasPatrol.PatrolAltType = PatrolAltType - CasPatrol.EngageAltType = EngageAltType - CasPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "CAS" ) - - self:T( { CAS = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - end - - - --- Set a Cas patrol for a Squadron. - -- The Cas patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Cas Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronCasPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- - function AI_A2G_DISPATCHER:SetSquadronCasPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - self:SetSquadronCasPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - - end - - - --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the BAI task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the BAI task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @usage - -- - -- -- BAI Squadron execution. - -- A2GDispatcher:SetSquadronBai( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) - -- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100, 6000, 9000, "BARO" ) - -- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200, 30, 100, "RADIO" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronBai2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.BAI = DefenderSquadron.BAI or {} - - local Bai = DefenderSquadron.BAI - Bai.Name = SquadronName - Bai.EngageMinSpeed = EngageMinSpeed - Bai.EngageMaxSpeed = EngageMaxSpeed - Bai.EngageFloorAltitude = EngageFloorAltitude or 500 - Bai.EngageCeilingAltitude = EngageCeilingAltitude or 1000 - Bai.EngageAltType = EngageAltType - Bai.Defend = true - - self:T( { BAI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - - return self - end - - - --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the BAI task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the BAI task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @usage - -- - -- -- BAI Squadron execution. - -- A2GDispatcher:SetSquadronBai( "Mozdok", 900, 1200, 4000, 5000 ) - -- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100, 6000, 8000 ) - -- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200, 6000, 10000 ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronBai( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - return self:SetSquadronBai2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) - end - - - --- Set the squadron BAI engage limit. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronBaiEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for BAI defense. - -- - function AI_A2G_DISPATCHER:SetSquadronBaiEngageLimit( SquadronName, EngageLimit ) - - self:SetSquadronEngageLimit( SquadronName, EngageLimit, "BAI" ) - - end - - - --- Set a Bai patrol for a Squadron. - -- The Bai patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. - -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. - -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Bai Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronBaiPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) - -- - function AI_A2G_DISPATCHER:SetSquadronBaiPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.BAI = DefenderSquadron.BAI or {} - - local BaiPatrol = DefenderSquadron.BAI - BaiPatrol.Name = SquadronName - BaiPatrol.Zone = Zone - BaiPatrol.PatrolFloorAltitude = PatrolFloorAltitude - BaiPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude - BaiPatrol.EngageFloorAltitude = EngageFloorAltitude - BaiPatrol.EngageCeilingAltitude = EngageCeilingAltitude - BaiPatrol.PatrolMinSpeed = PatrolMinSpeed - BaiPatrol.PatrolMaxSpeed = PatrolMaxSpeed - BaiPatrol.EngageMinSpeed = EngageMinSpeed - BaiPatrol.EngageMaxSpeed = EngageMaxSpeed - BaiPatrol.PatrolAltType = PatrolAltType - BaiPatrol.EngageAltType = EngageAltType - BaiPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "BAI" ) - - self:T( { BAI = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) - end - - - --- Set a Bai patrol for a Squadron. - -- The Bai patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Bai Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronBaiPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- - function AI_A2G_DISPATCHER:SetSquadronBaiPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - self:SetSquadronBaiPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - - end - - - --- Defines the default amount of extra planes that will takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: - -- - -- * Higher than 1, will increase the defense unit amounts. - -- * Lower than 1, will decrease the defense unit amounts. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. - -- - -- See example below. - -- - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. - -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. - -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. - -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. - -- - -- A2GDispatcher:SetDefaultOverhead( 1.5 ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultOverhead( Overhead ) - - self.DefenderDefault.Overhead = Overhead - - return self - end - - - --- Defines the amount of extra planes that will takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: - -- - -- * Higher than 1, will increase the defense unit amounts. - -- * Lower than 1, will decrease the defense unit amounts. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. - -- - -- See example below. - -- - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. - -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. - -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. - -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. - -- - -- A2GDispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Overhead = Overhead - - return self - end - - - --- Gets the overhead of planes as part of the defense system, in comparison with the attackers. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- The default overhead is 1, so equal balance. The @{#AI_A2G_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: - -- - -- * Higher than 1, will increase the defense unit amounts. - -- * Lower than 1, will decrease the defense unit amounts. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. - -- - -- See example below. - -- - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. - -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. - -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. - -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. - -- - -- local SquadronOverhead = A2GDispatcher:GetSquadronOverhead( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:GetSquadronOverhead( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Overhead or self.DefenderDefault.Overhead - end - - - --- Sets the default grouping of new aircraft spawned. - -- Grouping will trigger how new aircraft will be grouped if more than one aircraft is spawned for defense. - -- @param #AI_A2G_DISPATCHER self - -- @param #number Grouping The level of grouping that will be applied for the Patrol. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set a grouping by default per 2 aircraft. - -- A2GDispatcher:SetDefaultGrouping( 2 ) - -- - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultGrouping( Grouping ) - - self.DefenderDefault.Grouping = Grouping - - return self - end - - - --- Sets the Squadron grouping of new aircraft spawned. - -- Grouping will trigger how new aircraft will be grouped if more than one aircraft is spawned for defense. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Grouping The level of grouping that will be applied for a Patrol from the Squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set a Squadron specific grouping per 2 aircraft. - -- A2GDispatcher:SetSquadronGrouping( "SquadronName", 2 ) - -- - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Grouping = Grouping - - return self - end - - - --- Sets the engage probability if the squadron will engage on a detected target. - -- This can be configured per squadron, to ensure that each squadron as a specific defensive probability setting. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number EngageProbability The probability when the squadron will consider to engage the detected target. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set an defense probability for squadron SquadronName of 50%. - -- -- This will result that this squadron has 50% chance to engage on a detected target. - -- A2GDispatcher:SetSquadronEngageProbability( "SquadronName", 0.5 ) - -- - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronEngageProbability( SquadronName, EngageProbability ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.EngageProbability = EngageProbability - - return self - end - - - --- Defines the default method at which new flights will spawn and takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff in the air. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights by default takeoff from the runway. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights by default takeoff from the airbase hot. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights by default takeoff from the airbase cold. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Cold ) - -- - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoff( Takeoff ) - - self.DefenderDefault.Takeoff = Takeoff - - return self - end - - --- Defines the method at which new flights will spawn and takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff in the air. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights takeoff from the runway. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights takeoff from the airbase hot. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights takeoff from the airbase cold. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Cold ) - -- - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Takeoff = Takeoff - - return self - end - - - --- Gets the default method at which new flights will spawn and takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff in the air. - -- local TakeoffMethod = A2GDispatcher:GetDefaultTakeoff() - -- if TakeoffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_A2G_DISPATCHER:GetDefaultTakeoff( ) - - return self.DefenderDefault.Takeoff - end - - --- Gets the method at which new flights will spawn and takeoff as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff in the air. - -- local TakeoffMethod = A2GDispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeoffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_A2G_DISPATCHER:GetSquadronTakeoff( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff - end - - - --- Sets flights to default takeoff in the air, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff in the air. - -- A2GDispatcher:SetDefaultTakeoffInAir() - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffInAir() - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Air ) - - return self - end - - - --- Sets flights to takeoff in the air, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff in the air. - -- A2GDispatcher:SetSquadronTakeoffInAir( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) - - self:SetSquadronTakeoff( SquadronName, AI_A2G_DISPATCHER.Takeoff.Air ) - - if TakeoffAltitude then - self:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) - end - - return self - end - - - --- Sets flights by default to takeoff from the runway, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff from the runway. - -- A2GDispatcher:SetDefaultTakeoffFromRunway() - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffFromRunway() - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Runway ) - - return self - end - - - --- Sets flights to takeoff from the runway, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff from the runway. - -- A2GDispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2G_DISPATCHER.Takeoff.Runway ) - - return self - end - - - --- Sets flights by default to takeoff from the airbase at a hot location, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default takeoff at a hot parking spot. - -- A2GDispatcher:SetDefaultTakeoffFromParkingHot() - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingHot() - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Hot ) - - return self - end - - --- Sets flights to takeoff from the airbase at a hot location, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff in the air. - -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2G_DISPATCHER.Takeoff.Hot ) - - return self - end - - - --- Sets flights to by default takeoff from the airbase at a cold location, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff from a cold parking spot. - -- A2GDispatcher:SetDefaultTakeoffFromParkingCold() - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffFromParkingCold() - - self:SetDefaultTakeoff( AI_A2G_DISPATCHER.Takeoff.Cold ) - - return self - end - - - --- Sets flights to takeoff from the airbase at a cold location, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights takeoff from a cold parking spot. - -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_A2G_DISPATCHER.Takeoff.Cold ) - - return self - end - - - --- Defines the default altitude where aircraft will spawn in the air and takeoff as part of the defense system, when the takeoff in the air method has been selected. - -- @param #AI_A2G_DISPATCHER self - -- @param #number TakeoffAltitude The altitude in meters above ground level (AGL). - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- A2GDispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude ) - - self.DefenderDefault.TakeoffAltitude = TakeoffAltitude - - return self - end - - --- Defines the default altitude where aircraft will spawn in the air and takeoff as part of the defense system, when the takeoff in the air method has been selected. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude The altitude in meters above ground level (AGL). - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- A2GDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes aircraft start at 2000 meters above ground level (AGL). - -- - -- @return #AI_A2G_DISPATCHER - -- - function AI_A2G_DISPATCHER:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.TakeoffAltitude = TakeoffAltitude - - return self - end - - - --- Defines the default method at which flights will land and despawn as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights by default despawn after landing land at the runway. - -- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. - -- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.AtEngineShutdown ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultLanding( Landing ) - - self.DefenderDefault.Landing = Landing - - return self - end - - - --- Defines the method at which flights will land and despawn as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights despawn after landing land at the runway. - -- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights despawn after landing and parking, and after engine shutdown. - -- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.AtEngineShutdown ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Landing = Landing - - return self - end - - - --- Gets the default method at which flights will land and despawn as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = A2GDispatcher:GetDefaultLanding() - -- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_A2G_DISPATCHER:GetDefaultLanding() - - return self.DefenderDefault.Landing - end - - - --- Gets the method at which flights will land and despawn as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- local LandingMethod = A2GDispatcher:GetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_A2G_DISPATCHER:GetSquadronLanding( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Landing or self.DefenderDefault.Landing - end - - - --- Sets flights by default to land and despawn near the airbase in the air, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights by default to land near the airbase and despawn. - -- A2GDispatcher:SetDefaultLandingNearAirbase() - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultLandingNearAirbase() - - self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.NearAirbase ) - - return self - end - - - --- Sets flights to land and despawn near the airbase in the air, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights to land near the airbase and despawn. - -- A2GDispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2G_DISPATCHER.Landing.NearAirbase ) - - return self - end - - - --- Sets flights by default to land and despawn at the runway, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land at the runway and despawn. - -- A2GDispatcher:SetDefaultLandingAtRunway() - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultLandingAtRunway() - - self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.AtRunway ) - - return self - end - - - --- Sets flights to land and despawn at the runway, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights land at the runway and despawn. - -- A2GDispatcher:SetSquadronLandingAtRunway( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2G_DISPATCHER.Landing.AtRunway ) - - return self - end - - - --- Sets flights by default to land and despawn at engine shutdown, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land and despawn at engine shutdown. - -- A2GDispatcher:SetDefaultLandingAtEngineShutdown() - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetDefaultLandingAtEngineShutdown() - - self:SetDefaultLanding( AI_A2G_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - - --- Sets flights to land and despawn at engine shutdown, as part of the defense system. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local A2GDispatcher = AI_A2G_DISPATCHER:New( ... ) - -- - -- -- Let flights land and despawn at engine shutdown. - -- A2GDispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) - -- - -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_A2G_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - --- Set the default fuel threshold when defenders will RTB or Refuel in the air. - -- The fuel threshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. - -- @param #AI_A2G_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_A2G_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) - - self.DefenderDefault.FuelThreshold = FuelThreshold - - return self - end - - - --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel threshold is by default set to 15%, which means that an aircraft will stay in the air until 15% of its fuel is remaining. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_A2G_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.FuelThreshold = FuelThreshold - - return self - end - - --- Set the default tanker where defenders will Refuel in the air. - -- @param #AI_A2G_DISPATCHER self - -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the default tanker. - -- A2GDispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. - function AI_A2G_DISPATCHER:SetDefaultTanker( TankerName ) - - self.DefenderDefault.TankerName = TankerName - - return self - end - - - --- Set the squadron tanker where defenders will Refuel in the air. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_A2G_DISPATCHER - -- @usage - -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_A2G_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the squadron fuel threshold. - -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the squadron tanker. - -- A2GDispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. - function AI_A2G_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.TankerName = TankerName - - return self - end - - - --- Set the frequency of communication and the mode of communication for voice overs. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number RadioFrequency The frequency of communication. - -- @param #number RadioModulation The modulation of communication. - -- @param #number RadioPower The power in Watts of communication. - function AI_A2G_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.RadioFrequency = RadioFrequency - DefenderSquadron.RadioModulation = RadioModulation or radio.modulation.AM - DefenderSquadron.RadioPower = RadioPower or 100 - - if DefenderSquadron.RadioQueue then - DefenderSquadron.RadioQueue:Stop() - end - - DefenderSquadron.RadioQueue = nil - - DefenderSquadron.RadioQueue = RADIOSPEECH:New( DefenderSquadron.RadioFrequency, DefenderSquadron.RadioModulation ) - DefenderSquadron.RadioQueue.power = DefenderSquadron.RadioPower - DefenderSquadron.RadioQueue:Start( 0.5 ) - - DefenderSquadron.RadioQueue:SetLanguage( DefenderSquadron.Language ) - end - - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - self.Defenders[ DefenderName ] = Squadron - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount - Size - end - self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount + Defender:GetSize() - end - self.Defenders[ DefenderName ] = nil - self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) - end - - function AI_A2G_DISPATCHER:GetSquadronFromDefender( Defender ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - self:F( { DefenderName = DefenderName } ) - return self.Defenders[ DefenderName ] - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:CountPatrolAirborne( SquadronName, DefenseTaskType ) - - local PatrolCount = 0 - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - if DefenderSquadron then - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - if DefenderTask.SquadronName == SquadronName then - if DefenderTask.Type == DefenseTaskType then - if AIGroup:IsAlive() then - -- Check if the Patrol is patrolling or engaging. If not, this is not a valid Patrol, even if it is alive! - -- The Patrol could be damaged, lost control, or out of fuel! - if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" ) - or DefenderTask.Fsm:Is( "Started" ) then - PatrolCount = PatrolCount + 1 - end - end - end - end - end - end - - return PatrolCount - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount ) - - -- First, count the active AIGroups Units, targeting the DetectedSet - local DefendersEngaged = 0 - local DefendersTotal = 0 - - local AttackerSet = AttackerDetection.Set - local DefendersMissing = AttackerCount - --DetectedSet:Flush() - - local DefenderTasks = self:GetDefenderTasks() - for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do - local Defender = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskTarget = DefenderTask.Target - local DefenderSquadronName = DefenderTask.SquadronName - local DefenderSize = DefenderTask.Size - - -- Count the total of defenders on the battlefield. - --local DefenderSize = Defender:GetInitialSize() - if DefenderTask.Target then - --if DefenderTask.Fsm:Is( "Engaging" ) then - self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize ) - DefendersTotal = DefendersTotal + DefenderSize - if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then - - local SquadronOverhead = self:GetSquadronOverhead( DefenderSquadronName ) - self:F( { SquadronOverhead = SquadronOverhead } ) - if DefenderSize then - DefendersEngaged = DefendersEngaged + DefenderSize - DefendersMissing = DefendersMissing - DefenderSize / SquadronOverhead - self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize ) - else - DefendersEngaged = 0 - end - end - --end - end - - - end - - for QueueID, QueueItem in pairs( self.DefenseQueue ) do - local QueueItem = QueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem - if QueueItem.AttackerDetection and QueueItem.AttackerDetection.ItemID == AttackerDetection.ItemID then - DefendersMissing = DefendersMissing - QueueItem.DefendersNeeded / QueueItem.DefenderSquadron.Overhead - --DefendersEngaged = DefendersEngaged + QueueItem.DefenderGrouping - self:F( { QueueItemName = QueueItem.Defense, QueueItem_ItemID = QueueItem.AttackerDetection.ItemID, DetectedItem = AttackerDetection.ItemID, DefendersMissing = DefendersMissing } ) - end - end - - self:F( { DefenderCount = DefendersEngaged } ) - - return DefendersTotal, DefendersEngaged, DefendersMissing - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:CountDefenders( AttackerDetection, DefenderCount, DefenderTaskType ) - - local Friendlies = nil - - local AttackerSet = AttackerDetection.Set - local AttackerCount = AttackerSet:Count() - - local DefenderFriendlies = self:GetDefenderFriendliesNearBy( AttackerDetection ) - - for FriendlyDistance, DefenderFriendlyUnit in UTILS.spairs( DefenderFriendlies or {} ) do - -- We only allow to engage targets as long as the units on both sides are balanced. - if AttackerCount > DefenderCount then - local FriendlyGroup = DefenderFriendlyUnit:GetGroup() -- Wrapper.Group#GROUP - if FriendlyGroup and FriendlyGroup:IsAlive() then - -- Ok, so we have a friendly near the potential target. - -- Now we need to check if the AIGroup has a Task. - local DefenderTask = self:GetDefenderTask( FriendlyGroup ) - if DefenderTask then - -- The Task should be of the same type. - if DefenderTaskType == DefenderTask.Type then - -- If there is no target, then add the AIGroup to the ResultAIGroups for Engagement to the AttackerSet - if DefenderTask.Target == nil then - if DefenderTask.Fsm:Is( "Returning" ) - or DefenderTask.Fsm:Is( "Patrolling" ) then - Friendlies = Friendlies or {} - Friendlies[FriendlyGroup] = FriendlyGroup - DefenderCount = DefenderCount + FriendlyGroup:GetSize() - self:F( { Friendly = FriendlyGroup:GetName(), FriendlyDistance = FriendlyDistance } ) - end - end - end - end - end - else - break - end - end - - return Friendlies - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - local SquadronName = DefenderSquadron.Name - DefendersNeeded = DefendersNeeded or 4 - local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded - - if self:IsSquadronVisible( SquadronName ) then - - -- Here we Patrol the new planes. - -- The Resources table is filled in advance. - local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) -- Choose the template. - - -- We determine the grouping based on the parameters set. - self:F( { DefenderGrouping = DefenderGrouping } ) - - -- New we will form the group to spawn in. - -- We search for the first free resource matching the template. - local DefenderUnitIndex = 1 - local DefenderPatrolTemplate = nil - local DefenderName = nil - for GroupName, DefenderGroup in pairs( DefenderSquadron.Resources[TemplateID] or {} ) do - self:F( { GroupName = GroupName } ) - local DefenderTemplate = _DATABASE:GetGroupTemplate( GroupName ) - if DefenderUnitIndex == 1 then - DefenderPatrolTemplate = UTILS.DeepCopy( DefenderTemplate ) - self.DefenderPatrolIndex = self.DefenderPatrolIndex + 1 - --DefenderPatrolTemplate.name = SquadronName .. "#" .. self.DefenderPatrolIndex .. "#" .. GroupName - DefenderPatrolTemplate.name = GroupName - DefenderName = DefenderPatrolTemplate.name - else - -- Add the unit in the template to the DefenderPatrolTemplate. - local DefenderUnitTemplate = DefenderTemplate.units[1] - DefenderPatrolTemplate.units[DefenderUnitIndex] = DefenderUnitTemplate - end - DefenderPatrolTemplate.units[DefenderUnitIndex].name = string.format( DefenderPatrolTemplate.name .. '-%02d', DefenderUnitIndex ) - DefenderPatrolTemplate.units[DefenderUnitIndex].unitId = nil - DefenderUnitIndex = DefenderUnitIndex + 1 - DefenderSquadron.Resources[TemplateID][GroupName] = nil - if DefenderUnitIndex > DefenderGrouping then - break - end - - end - - if DefenderPatrolTemplate then - local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) - local SpawnGroup = GROUP:Register( DefenderName ) - DefenderPatrolTemplate.lateActivation = nil - DefenderPatrolTemplate.uncontrolled = nil - local Takeoff = self:GetSquadronTakeoff( SquadronName ) - DefenderPatrolTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type - DefenderPatrolTemplate.route.points[1].action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - local Defender = _DATABASE:Spawn( DefenderPatrolTemplate ) - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - Defender:Activate() - return Defender, DefenderGrouping - end - else - local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN - if DefenderGrouping then - Spawn:InitGrouping( DefenderGrouping ) - else - Spawn:InitGrouping() - end - - local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) - local Defender = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - return Defender, DefenderGrouping - end - - return nil, nil - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:onafterPatrol( From, Event, To, SquadronName, DefenseTaskType ) - - local DefenderSquadron, Patrol = self:CanPatrol( SquadronName, DefenseTaskType ) - - -- Determine if there are sufficient resources to form a complete group for patrol. - if DefenderSquadron then - local DefendersNeeded - local DefendersGrouping = ( DefenderSquadron.Grouping or self.DefenderDefault.Grouping ) - if DefenderSquadron.ResourceCount == nil then - DefendersNeeded = DefendersGrouping - else - if DefenderSquadron.ResourceCount >= DefendersGrouping then - DefendersNeeded = DefendersGrouping - else - DefendersNeeded = DefenderSquadron.ResourceCount - end - end - - if Patrol then - self:ResourceQueue( true, DefenderSquadron, DefendersNeeded, Patrol, DefenseTaskType, nil, SquadronName ) - end - end - - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourceQueue( Patrol, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName ) - - self:F( { DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName } ) - - local DefenseQueueItem = {} -- #AI_A2G_DISPATCHER.DefenderQueueItem - - - DefenseQueueItem.Patrol = Patrol - DefenseQueueItem.DefenderSquadron = DefenderSquadron - DefenseQueueItem.DefendersNeeded = DefendersNeeded - DefenseQueueItem.Defense = Defense - DefenseQueueItem.DefenseTaskType = DefenseTaskType - DefenseQueueItem.AttackerDetection = AttackerDetection - DefenseQueueItem.SquadronName = SquadronName - - table.insert( self.DefenseQueue, DefenseQueueItem ) - self:F( { QueueItems = #self.DefenseQueue } ) - - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourceTakeoff() - - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - self:F( { DefenseQueueID } ) - end - - for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do - - if #self.DefenseQueue > 0 then - - self:F( { SquadronName, Squadron.Name, Squadron.TakeoffTime, Squadron.TakeoffInterval, timer.getTime() } ) - - local DefenseQueueItem = self.DefenseQueue[1] - self:F( {DefenderSquadron=DefenseQueueItem.DefenderSquadron} ) - - if DefenseQueueItem.SquadronName == SquadronName then - - if Squadron.TakeoffTime + Squadron.TakeoffInterval < timer.getTime() then - Squadron.TakeoffTime = timer.getTime() - - if DefenseQueueItem.Patrol == true then - self:ResourcePatrol( DefenseQueueItem.DefenderSquadron, DefenseQueueItem.DefendersNeeded, DefenseQueueItem.Defense, DefenseQueueItem.DefenseTaskType, DefenseQueueItem.AttackerDetection, DefenseQueueItem.SquadronName ) - else - self:ResourceEngage( DefenseQueueItem.DefenderSquadron, DefenseQueueItem.DefendersNeeded, DefenseQueueItem.Defense, DefenseQueueItem.DefenseTaskType, DefenseQueueItem.AttackerDetection, DefenseQueueItem.SquadronName ) - end - table.remove( self.DefenseQueue, 1 ) - end - end - end - - end - - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourcePatrol( DefenderSquadron, DefendersNeeded, Patrol, DefenseTaskType, AttackerDetection, SquadronName ) - - - self:F({DefenderSquadron=DefenderSquadron}) - self:F({DefendersNeeded=DefendersNeeded}) - self:F({Patrol=Patrol}) - self:F({DefenseTaskType=DefenseTaskType}) - self:F({AttackerDetection=AttackerDetection}) - self:F({SquadronName=SquadronName}) - - local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - if DefenderGroup then - - local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - - local AI_A2G_Fsm = AI_A2G_PATROL[DefenseTaskType]:New2( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.EngageAltType, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.PatrolAltType ) - AI_A2G_Fsm:SetDispatcher( self ) - AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius ) - AI_A2G_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) - AI_A2G_Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, nil, DefenderGrouping ) - - function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F({"Takeoff", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() -- #string - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron then - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) - end - AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit - end - end - - function AI_A2G_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) - self:F({"PatrolRoute", DefenderGroup:GetName()}) - self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron and self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) - end - - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - - function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", DefenderGroup:GetName()}) - - self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron and AttackSetUnit:Count() > 0 then - local FirstUnit = AttackSetUnit:GetFirst() - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) - end - end - end - - function AI_A2G_Fsm:OnAfterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - local FirstUnit = AttackSetUnit:GetFirst() - if FirstUnit then - local Coordinate = FirstUnit:GetCoordinate() - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) - end - end - end - - function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F({"RTB", DefenderGroup:GetName()}) - self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) - end - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) - self:F({"LostControl", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." ) - end - if DefenderGroup:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F({"Home", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) - end - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - Dispatcher:ResourcePark( Squadron, DefenderGroup ) - end - end - end - - end - - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ResourceEngage( DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName ) - - self:F({DefenderSquadron=DefenderSquadron}) - self:F({DefendersNeeded=DefendersNeeded}) - self:F({Defense=Defense}) - self:F({DefenseTaskType=DefenseTaskType}) - self:F({AttackerDetection=AttackerDetection}) - self:F({SquadronName=SquadronName}) - - local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - if DefenderGroup then - - local AI_A2G_ENGAGE = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - - local AI_A2G_Fsm = AI_A2G_ENGAGE[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude, Defense.EngageAltType ) -- AI.AI_AIR_ENGAGE - AI_A2G_Fsm:SetDispatcher( self ) - AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius ) - AI_A2G_Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, AttackerDetection, DefenderGrouping ) - - function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F({"Defender Birth", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) - - self:F( { DefenderTarget = DefenderTarget } ) - - if DefenderTarget then - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) - end - AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit - end - end - - function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", DefenderGroup:GetName()}) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - - if Squadron then - local FirstUnit = AttackSetUnit:GetRandomSurely() - if FirstUnit then - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) - end - else - return - end - end - self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) - end - - function AI_A2G_Fsm:OnAfterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", DefenderGroup:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - local FirstUnit = AttackSetUnit:GetFirst() - if FirstUnit then - local Coordinate = FirstUnit:GetCoordinate() - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) - end - end - end - - function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F({"Defender RTB", DefenderGroup:GetName()}) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) - end - self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) - - Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) - self:F({"Defender LostControl", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - end - if DefenderGroup:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - end - - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F({"Defender Home", DefenderGroup:GetName()}) - self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - - local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if self.SetSendPlayerMessages then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) - end - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) - DefenderGroup:Destroy() - Dispatcher:ResourcePark( Squadron, DefenderGroup ) - end - end - end - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:onafterEngage( From, Event, To, AttackerDetection, Defenders ) - - if Defenders then - - for DefenderID, Defender in pairs( Defenders or {} ) do - - local Fsm = self:GetDefenderTaskFsm( Defender ) - Fsm:Engage( AttackerDetection.Set ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( Defender, AttackerDetection ) - - end - end - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:HasDefenseLine( DefenseCoordinate, DetectedItem ) - - local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) - - -- Now check if this coordinate is not in a danger zone, meaning, that the attack line is not crossing other coordinates. - -- (y1 - y2)x + (x2 - x1)y + (x1y2 - x2y1) = 0 - - local c1 = DefenseCoordinate - local c2 = AttackCoordinate - - local a = c1.z - c2.z -- Calculate a - local b = c2.x - c1.x -- Calculate b - local c = c1.x * c2.z - c2.x * c1.z -- calculate c - - local ok = true - - -- Now we check if each coordinate radius of about 30km of each attack is crossing a defense line. If yes, then this is not a good attack! - for AttackItemID, CheckAttackItem in pairs( self.Detection:GetDetectedItems() ) do - - -- Only compare other detected coordinates. - if AttackItemID ~= DetectedItem.ID then - - local CheckAttackCoordinate = self.Detection:GetDetectedItemCoordinate( CheckAttackItem ) - - local x = CheckAttackCoordinate.x - local y = CheckAttackCoordinate.z - local r = 5000 - - -- now we check if the coordinate is intersecting with the defense line. - - local IntersectDistance = ( math.abs( a * x + b * y + c ) ) / math.sqrt( a * a + b * b ) - self:F( { IntersectDistance = IntersectDistance, x = x, y = y } ) - - local IntersectAttackDistance = CheckAttackCoordinate:Get2DDistance( DefenseCoordinate ) - - self:F( { IntersectAttackDistance=IntersectAttackDistance, EvaluateDistance=EvaluateDistance } ) - - -- If the distance of the attack coordinate is larger than the test radius; then the line intersects, and this is not a good coordinate. - if IntersectDistance < r and IntersectAttackDistance < EvaluateDistance then - ok = false - break - end - end - end - - return ok - end - - --- - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:onafterDefend( From, Event, To, DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, DefenderFriendlies, DefenseTaskType ) - - self:F( { From, Event, To, DetectedItem.Index, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing, DefenderFriendlies = DefenderFriendlies } ) - - DetectedItem.Type = DefenseTaskType -- This is set to report the task type in the status panel. - - local AttackerSet = DetectedItem.Set - local AttackerUnit = AttackerSet:GetFirst() - - if AttackerUnit and AttackerUnit:IsAlive() then - local AttackerCount = AttackerSet:Count() - local DefenderCount = 0 - - for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do - - -- Here we check if the defenders have a defense line to the attackers. - -- If the attackers are behind enemy lines or too close to an other defense line; then don't engage. - local DefenseCoordinate = DefenderGroup:GetCoordinate() - local HasDefenseLine = self:HasDefenseLine( DefenseCoordinate, DetectedItem ) - - if HasDefenseLine == true then - local SquadronName = self:GetDefenderTask( DefenderGroup ).SquadronName - local SquadronOverhead = self:GetSquadronOverhead( SquadronName ) - - local Fsm = self:GetDefenderTaskFsm( DefenderGroup ) - Fsm:EngageRoute( AttackerSet ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( DefenderGroup, DetectedItem ) - - local DefenderGroupSize = DefenderGroup:GetSize() - DefendersMissing = DefendersMissing - DefenderGroupSize / SquadronOverhead - DefendersTotal = DefendersTotal + DefenderGroupSize / SquadronOverhead - end - - if DefendersMissing <= 0 then - break - end - end - - self:F( { DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) - DefenderCount = DefendersMissing - - local ClosestDistance = 0 - local EngageSquadronName = nil - - local BreakLoop = false - - while( DefenderCount > 0 and not BreakLoop ) do - - self:F( { DefenderSquadrons = self.DefenderSquadrons } ) - - for SquadronName, DefenderSquadron in UTILS.rpairs( self.DefenderSquadrons or {} ) do - - if DefenderSquadron[DefenseTaskType] then - - local AirbaseCoordinate = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE - local AttackerCoord = AttackerUnit:GetCoordinate() - local InterceptCoord = DetectedItem.InterceptCoord - self:F( { InterceptCoord = InterceptCoord } ) - if InterceptCoord then - local InterceptDistance = AirbaseCoordinate:Get2DDistance( InterceptCoord ) - local AirbaseDistance = AirbaseCoordinate:Get2DDistance( AttackerCoord ) - self:F( { InterceptDistance = InterceptDistance, AirbaseDistance = AirbaseDistance, InterceptCoord = InterceptCoord } ) - - -- Only intercept if the distance to target is smaller or equal to the GciRadius limit. - if AirbaseDistance <= self.DefenseRadius then - - -- Check if there is a defense line... - local HasDefenseLine = self:HasDefenseLine( AirbaseCoordinate, DetectedItem ) - if HasDefenseLine == true then - local EngageProbability = ( DefenderSquadron.EngageProbability or 1 ) - local Probability = math.random() - if Probability < EngageProbability then - EngageSquadronName = SquadronName - break - end - end - end - end - end - end - - if EngageSquadronName then - - local DefenderSquadron, Defense = self:CanDefend( EngageSquadronName, DefenseTaskType ) - - if Defense then - - local DefenderOverhead = DefenderSquadron.Overhead or self.DefenderDefault.Overhead - local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - local DefendersNeeded = math.ceil( DefenderCount * DefenderOverhead ) - - self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead , DefaultOverhead = self.DefenderDefault.Overhead } ) - self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } ) - self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } ) - - -- Validate that the maximum limit of Defenders has been reached. - -- If yes, then cancel the engaging of more defenders. - local DefendersLimit = DefenderSquadron.EngageLimit or self.DefenderDefault.EngageLimit - if DefendersLimit then - if DefendersTotal >= DefendersLimit then - DefendersNeeded = 0 - BreakLoop = true - else - -- If the total of amount of defenders + the defenders needed, is larger than the limit of defenders, - -- then the defenders needed is the difference between defenders total - defenders limit. - if DefendersTotal + DefendersNeeded > DefendersLimit then - DefendersNeeded = DefendersLimit - DefendersTotal - end - end - end - - -- DefenderSquadron.ResourceCount can have the value nil, which expresses unlimited resources. - -- DefendersNeeded cannot exceed DefenderSquadron.ResourceCount! - if DefenderSquadron.ResourceCount and DefendersNeeded > DefenderSquadron.ResourceCount then - DefendersNeeded = DefenderSquadron.ResourceCount - BreakLoop = true - end - - while ( DefendersNeeded > 0 ) do - self:ResourceQueue( false, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, DetectedItem, EngageSquadronName ) - DefendersNeeded = DefendersNeeded - DefenderGrouping - DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead - end -- while ( DefendersNeeded > 0 ) do - else - -- No more resources, try something else. - -- Subject for a later enhancement to try to depart from another squadron and disable this one. - BreakLoop = true - break - end - else - -- There isn't any closest airbase anymore, break the loop. - break - end - end -- if DefenderSquadron then - end -- if AttackerUnit - end - - - - --- Creates an SEAD task when the targets have radars. - -- @param #AI_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT The set of units of the targets to be engaged. - -- @return #nil If there are no targets to be set. - function AI_A2G_DISPATCHER:Evaluate_SEAD( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT - local AttackerCount = AttackerSet:HasSEAD() -- Is the AttackerSet a SEAD group, then the amount of radar emitters will be returned; that need to be attacked. - - if ( AttackerCount > 0 ) then - - -- First, count the active defenders, engaging the DetectedItem. - local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount ) - - self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - - local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "SEAD" ) - - - - if DetectedItem.IsDetected == true then - - return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups - end - end - - return 0, 0, 0 - end - - - --- Creates an CAS task. - -- @param #AI_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT The set of units of the targets to be engaged. - -- @return #nil If there are no targets to be set. - function AI_A2G_DISPATCHER:Evaluate_CAS( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT - local AttackerCount = AttackerSet:Count() - local AttackerRadarCount = AttackerSet:HasSEAD() - local IsFriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) - local IsCas = ( AttackerRadarCount == 0 ) and ( IsFriendliesNearBy == true ) -- Is the AttackerSet a CAS group? - - if IsCas == true then - - -- First, count the active defenders, engaging the DetectedItem. - local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount ) - - self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - - local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "CAS" ) - - if DetectedItem.IsDetected == true then - - return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups - end - end - - return 0, 0, 0 - end - - - --- Evaluates an BAI task. - -- @param #AI_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT The set of units of the targets to be engaged. - -- @return #nil If there are no targets to be set. - function AI_A2G_DISPATCHER:Evaluate_BAI( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT - local AttackerCount = AttackerSet:Count() - local AttackerRadarCount = AttackerSet:HasSEAD() - local IsFriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) - local IsBai = ( AttackerRadarCount == 0 ) and ( IsFriendliesNearBy == false ) -- Is the AttackerSet a BAI group? - - if IsBai == true then - - -- First, count the active defenders, engaging the DetectedItem. - local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount ) - - self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - - local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "BAI" ) - - if DetectedItem.IsDetected == true then - - return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups - end - end - - return 0, 0, 0 - end - - - --- Determine the distance as the keys of reference of the detected items. - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:Keys( DetectedItem ) - - self:F( { DetectedItem = DetectedItem } ) - - local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - - local ShortestDistance = 999999999 - - for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do - local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE - - local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) - - if EvaluateDistance <= ShortestDistance then - ShortestDistance = EvaluateDistance - end - end - - return ShortestDistance - end - - - --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:Order( DetectedItem ) - local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - - local ShortestDistance = 999999999 - - for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do - local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE - - local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) - - if EvaluateDistance <= ShortestDistance then - ShortestDistance = EvaluateDistance - end - end - - return ShortestDistance - end - - - --- Shows the tactical display. - -- @param #AI_A2G_DISPATCHER self - function AI_A2G_DISPATCHER:ShowTacticalDisplay( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - local DefenseTotal = 0 - - local Report = REPORT:New( "\nTactical Overview" ) - - local DefenderGroupCount = 0 - local DefendersTotal = 0 - - -- Now that all obsolete tasks are removed, loop through the detected targets. - --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do - - if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - - self:F( { "Target ID", DetectedItem.ItemID } ) - - self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) - DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local DetectionIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - -- Show tactical situation - local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G() - Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender:IsAlive() then - DefenderGroupCount = DefenderGroupCount + 1 - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", - Defender:GetName(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - end - end - - Report:Add( "\n - No Targets:") - local TaskCount = 0 - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - TaskCount = TaskCount + 1 - local Defender = Defender -- Wrapper.Group#GROUP - if not DefenderTask.Target then - if Defender:IsAlive() then - local DefenderHasTask = Defender:HasTask() - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", - Defender:GetName(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - Report:Add( string.format( "\n - %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) - - Report:Add( string.format( "\n - %d Queued Aircraft Launches", #self.DefenseQueue ) ) - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - local DefenseQueueItem = DefenseQueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem - Report:Add( string.format( " - %s - %s", DefenseQueueItem.SquadronName, DefenseQueueItem.DefenderSquadron.TakeoffTime, DefenseQueueItem.DefenderSquadron.TakeoffInterval) ) - - end - - Report:Add( string.format( "\n - Squadron Resources: ", #self.DefenseQueue ) ) - for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - Report:Add( string.format( " - %s - %s", DefenderSquadronName, DefenderSquadron.ResourceCount and tostring(DefenderSquadron.ResourceCount) or "n/a" ) ) - end - - self:F( Report:Text( "\n" ) ) - trigger.action.outText( Report:Text( "\n" ), 25 ) - - end - - - --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function AI_A2G_DISPATCHER:ProcessDetected( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - local DefenseTotal = 0 - - for DefenderGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - local DefenderGroup = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskFsm = self:GetDefenderTaskFsm( DefenderGroup ) - --if DefenderTaskFsm:Is( "LostControl" ) then - -- self:ClearDefenderTask( DefenderGroup ) - --end - if not DefenderGroup:IsAlive() then - self:F( { Defender = DefenderGroup:GetName(), DefenderState = DefenderTaskFsm:GetState() } ) - if not DefenderTaskFsm:Is( "Started" ) then - self:ClearDefenderTask( DefenderGroup ) - end - else - -- TODO: prio 1, what is this index stuff again, simplify it. - if DefenderTask.Target then - self:F( { TargetIndex = DefenderTask.Target.Index } ) - local AttackerItem = Detection:GetDetectedItemByIndex( DefenderTask.Target.Index ) - if not AttackerItem then - self:F( { "Removing obsolete Target:", DefenderTask.Target.Index } ) - self:ClearDefenderTaskTarget( DefenderGroup ) - else - if DefenderTask.Target.Set then - local TargetCount = DefenderTask.Target.Set:Count() - if TargetCount == 0 then - self:F( { "All Targets destroyed in Target, removing:", DefenderTask.Target.Index } ) - self:ClearDefenderTask( DefenderGroup ) - end - end - end - end - end - end - --- for DefenderGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do --- DefenseTotal = DefenseTotal + 1 --- end - - local Report = REPORT:New( "\nTactical Overview" ) - - local DefenderGroupCount = 0 - - local DefendersTotal = 0 - - -- Now that all obsolete tasks are removed, loop through the detected targets. - --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedDistance, DetectedItem in UTILS.kpairs( Detection:GetDetectedItems(), function( t ) return self:Keys( t ) end, function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do - - if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - - self:F( { "Target ID", DetectedItem.ItemID } ) - - self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) - DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local DetectionIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - - -- Calculate if for this DetectedItem if a defense needs to be initiated. - -- This calculation is based on the distance between the defense point and the attackers, and the defensiveness parameter. - -- The attackers closest to the defense coordinates will be handled first, or course! - - local EngageDefenses = nil - - self:F( { DetectedDistance = DetectedDistance, DefenseRadius = self.DefenseRadius } ) - if DetectedDistance <= self.DefenseRadius then - - self:F( { DetectedApproach = self._DefenseApproach } ) - if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Distance then - EngageDefenses = true - self:F( { EngageDefenses = EngageDefenses } ) - end - - if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Random then - local DistanceProbability = ( self.DefenseRadius / DetectedDistance * self.DefenseReactivity ) - local DefenseProbability = math.random() - - self:F( { DistanceProbability = DistanceProbability, DefenseProbability = DefenseProbability } ) - - if DefenseProbability <= DistanceProbability / ( 300 / 30 ) then - EngageDefenses = true - end - end - - - end - - self:F( { EngageDefenses = EngageDefenses, DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) - - -- There needs to be an EngageCoordinate. - -- If self.DefenseLimit is set (thus limit the amount of defenses to one zone), then only start a new defense if the maximum has not been reached. - -- If self.DefenseLimit has not been set, there is an unlimited amount of zones to be defended. - if ( EngageDefenses and ( self.DefenseLimit and DefenseTotal < self.DefenseLimit ) or not self.DefenseLimit ) then - do - local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_SEAD( DetectedItem ) -- Returns a SET_UNIT with the SEAD targets to be engaged... - if DefendersMissing > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "SEAD" ) - end - end - - do - local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_CAS( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged... - if DefendersMissing > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "CAS" ) - end - end - - do - local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_BAI( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged... - if DefendersMissing > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "BAI" ) - end - end - end - - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - DefenseTotal = DefenseTotal + 1 - end - end - - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - local DefenseQueueItem = DefenseQueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem - if DefenseQueueItem.AttackerDetection and DefenseQueueItem.AttackerDetection.Index and DefenseQueueItem.AttackerDetection.Index == DetectedItem.Index then - DefenseTotal = DefenseTotal + 1 - end - end - - if self.TacticalDisplay then - -- Show tactical situation - local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G() - Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender:IsAlive() then - DefenderGroupCount = DefenderGroupCount + 1 - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", - Defender:GetName(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - end - end - end - - if self.TacticalDisplay then - Report:Add( "\n - No Targets:") - local TaskCount = 0 - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - TaskCount = TaskCount + 1 - local Defender = Defender -- Wrapper.Group#GROUP - if not DefenderTask.Target then - if Defender:IsAlive() then - local DefenderHasTask = Defender:HasTask() - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", - Defender:GetName(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - Report:Add( string.format( "\n - %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) - - Report:Add( string.format( "\n - %d Queued Aircraft Launches", #self.DefenseQueue ) ) - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - local DefenseQueueItem = DefenseQueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem - Report:Add( string.format( " - %s - %s", DefenseQueueItem.SquadronName, DefenseQueueItem.DefenderSquadron.TakeoffTime, DefenseQueueItem.DefenderSquadron.TakeoffInterval) ) - - end - - Report:Add( string.format( "\n - Squadron Resources: ", #self.DefenseQueue ) ) - for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - Report:Add( string.format( " - %s - %s", DefenderSquadronName, DefenderSquadron.ResourceCount and tostring(DefenderSquadron.ResourceCount) or "n/a" ) ) - end - - self:F( Report:Text( "\n" ) ) - trigger.action.outText( Report:Text( "\n" ), 25 ) - end - - return true - end - -end - -do - - --- Calculates which HUMAN friendlies are nearby the area. - -- @param #AI_A2G_DISPATCHER self - -- @param DetectedItem The detected item. - -- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type. - function AI_A2G_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem ) - - local PlayerTypes = {} - local PlayersCount = 0 - - if PlayersNearBy then - local DetectedThreatLevel = DetectedSet:CalculateThreatLevelA2G() - for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do - local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT - local PlayerName = PlayerUnit:GetPlayerName() - --self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) - if PlayerUnit:IsAirPlane() and PlayerName ~= nil then - local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() - PlayersCount = PlayersCount + 1 - local PlayerType = PlayerUnit:GetTypeName() - PlayerTypes[PlayerName] = PlayerType - if DetectedThreatLevel < FriendlyUnitThreatLevel + 2 then - end - end - end - - end - - --self:F( { PlayersCount = PlayersCount } ) - - local PlayerTypesReport = REPORT:New() - - if PlayersCount > 0 then - for PlayerName, PlayerType in pairs( PlayerTypes ) do - PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) ) - end - else - PlayerTypesReport:Add( "-" ) - end - - - return PlayersCount, PlayerTypesReport - end - - --- Calculates which friendlies are nearby the area. - -- @param #AI_A2G_DISPATCHER self - -- @param DetectedItem The detected item. - -- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type. - function AI_A2G_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem ) - - local FriendlyTypes = {} - local FriendliesCount = 0 - - if FriendlyUnitsNearBy then - local DetectedThreatLevel = DetectedSet:CalculateThreatLevelA2G() - for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do - local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT - if FriendlyUnit:IsAirPlane() then - local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() - FriendliesCount = FriendliesCount + 1 - local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 - if DetectedThreatLevel < FriendlyUnitThreatLevel + 2 then - end - end - end - - end - - --self:F( { FriendliesCount = FriendliesCount } ) - - local FriendlyTypesReport = REPORT:New() - - if FriendliesCount > 0 then - for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) - end - else - FriendlyTypesReport:Add( "-" ) - end - - - return FriendliesCount, FriendlyTypesReport - end - - --- Schedules a new Patrol for the given SquadronName. - -- @param #AI_A2G_DISPATCHER self - -- @param #string SquadronName The squadron name. - function AI_A2G_DISPATCHER:SchedulerPatrol( SquadronName ) - local PatrolTaskTypes = { "SEAD", "CAS", "BAI" } - local PatrolTaskType = PatrolTaskTypes[math.random(1,3)] - self:Patrol( SquadronName, PatrolTaskType ) - end - - --- Set flashing player messages on or off - -- @param #AI_A2G_DISPATCHER self - -- @param #boolean onoff Set messages on (true) or off (false) - function AI_A2G_DISPATCHER:SetSendMessages( onoff ) - self.SetSendPlayerMessages = onoff - end -end - - --- Add resources to a Squadron - -- @param #AI_A2G_DISPATCHER self - -- @param #string Squadron The squadron name. - -- @param #number Amount Number of resources to add. - function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount + Amount - end - self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end - - --- Remove resources from a Squadron - -- @param #AI_A2G_DISPATCHER self - -- @param #string Squadron The squadron name. - -- @param #number Amount Number of resources to remove. - function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount) - local Squadron = self:GetSquadron(Squadron) - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount - Amount - end - self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end - \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua deleted file mode 100644 index 17f9f86f5..000000000 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ /dev/null @@ -1,131 +0,0 @@ ---- **AI** - Models the process of air to ground SEAD engagement for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_A2G_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2G_SEAD --- @image AI_Air_To_Ground_Engage.JPG - - - --- @type AI_A2G_SEAD --- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL - - ---- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders. --- --- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event. --- --- 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. --- --- This cycle will continue. --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- 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. --- --- ## 1. AI_A2G_SEAD constructor --- --- * @{#AI_A2G_SEAD.New}(): Creates a new AI_A2G_SEAD object. --- --- ## 3. Set the Range of Engagement --- --- 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_AIR_PATROL.SetEngageRange}() to define that range. --- --- # 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_SEAD -AI_A2G_SEAD = { - ClassName = "AI_A2G_SEAD", -} - ---- Creates a new AI_A2G_SEAD object --- @param #AI_A2G_SEAD 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_SEAD -function AI_A2G_SEAD: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 ) - 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_SEAD object --- @param #AI_A2G_SEAD 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_SEAD -function AI_A2G_SEAD: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_SEAD 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_SEAD self -function AI_A2G_SEAD:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - - local AttackUnitTasks = {} - - local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do - if AttackUnit then - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - local HasRadar = AttackUnit:HasSEAD() - if HasRadar then - self:F( { "SEAD Unit:", AttackUnit:GetName() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) - end - end - end - end - - return AttackUnitTasks -end - diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua deleted file mode 100644 index 5d2feda5e..000000000 --- a/Moose Development/Moose/AI/AI_Air.lua +++ /dev/null @@ -1,840 +0,0 @@ ---- **AI** - Models the process of AI air operations. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Air --- @image MOOSE.JPG - ---- --- @type AI_AIR --- @extends Core.Fsm#FSM_CONTROLLABLE - ---- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}. --- --- --- # 1) AI_AIR constructor --- --- * @{#AI_AIR.New}(): Creates a new AI_AIR object. --- --- # 2) AI_AIR is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- --- ## 2.1) AI_AIR States. --- --- * **Idle**: The process is idle. --- --- ## 2.2) AI_AIR Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- # 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_AIR -AI_AIR = { - ClassName = "AI_AIR", -} - -AI_AIR.TaskDelay = 0.5 -- The delay of each task given to the AI. - ---- Creates a new AI_AIR process. --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process. --- @return #AI_AIR -function AI_AIR:New( AIGroup ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_AIR - - self:SetControllable( AIGroup ) - - self:SetStartState( "Stopped" ) - - self:AddTransition( "*", "Queue", "Queued" ) - - self:AddTransition( "*", "Start", "Started" ) - - --- Start Handler OnBefore for AI_AIR - -- @function [parent=#AI_AIR] OnBeforeStart - -- @param #AI_AIR self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Start Handler OnAfter for AI_AIR - -- @function [parent=#AI_AIR] OnAfterStart - -- @param #AI_AIR self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Start Trigger for AI_AIR - -- @function [parent=#AI_AIR] Start - -- @param #AI_AIR self - - --- Start Asynchronous Trigger for AI_AIR - -- @function [parent=#AI_AIR] __Start - -- @param #AI_AIR self - -- @param #number Delay - - self:AddTransition( "*", "Stop", "Stopped" ) - ---- OnLeave Transition Handler for State Stopped. --- @function [parent=#AI_AIR] OnLeaveStopped --- @param #AI_AIR 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 Stopped. --- @function [parent=#AI_AIR] OnEnterStopped --- @param #AI_AIR 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. - ---- OnBefore Transition Handler for Event Stop. --- @function [parent=#AI_AIR] OnBeforeStop --- @param #AI_AIR 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 Stop. --- @function [parent=#AI_AIR] OnAfterStop --- @param #AI_AIR 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 Stop. --- @function [parent=#AI_AIR] Stop --- @param #AI_AIR self - ---- Asynchronous Event Trigger for Event Stop. --- @function [parent=#AI_AIR] __Stop --- @param #AI_AIR self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR. - ---- OnBefore Transition Handler for Event Status. --- @function [parent=#AI_AIR] OnBeforeStatus --- @param #AI_AIR 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 Status. --- @function [parent=#AI_AIR] OnAfterStatus --- @param #AI_AIR 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 Status. --- @function [parent=#AI_AIR] Status --- @param #AI_AIR self - ---- Asynchronous Event Trigger for Event Status. --- @function [parent=#AI_AIR] __Status --- @param #AI_AIR self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR. - ---- OnBefore Transition Handler for Event RTB. --- @function [parent=#AI_AIR] OnBeforeRTB --- @param #AI_AIR 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 RTB. --- @function [parent=#AI_AIR] OnAfterRTB --- @param #AI_AIR 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 RTB. --- @function [parent=#AI_AIR] RTB --- @param #AI_AIR self - ---- Asynchronous Event Trigger for Event RTB. --- @function [parent=#AI_AIR] __RTB --- @param #AI_AIR self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Returning. --- @function [parent=#AI_AIR] OnLeaveReturning --- @param #AI_AIR 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 Returning. --- @function [parent=#AI_AIR] OnEnterReturning --- @param #AI_AIR 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( "Patrolling", "Refuel", "Refuelling" ) - - --- Refuel Handler OnBefore for AI_AIR - -- @function [parent=#AI_AIR] OnBeforeRefuel - -- @param #AI_AIR self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Refuel Handler OnAfter for AI_AIR - -- @function [parent=#AI_AIR] OnAfterRefuel - -- @param #AI_AIR self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Refuel Trigger for AI_AIR - -- @function [parent=#AI_AIR] Refuel - -- @param #AI_AIR self - - --- Refuel Asynchronous Trigger for AI_AIR - -- @function [parent=#AI_AIR] __Refuel - -- @param #AI_AIR self - -- @param #number Delay - - self:AddTransition( "*", "Takeoff", "Airborne" ) - self:AddTransition( "*", "Return", "Returning" ) - self:AddTransition( "*", "Hold", "Holding" ) - self:AddTransition( "*", "Home", "Home" ) - self:AddTransition( "*", "LostControl", "LostControl" ) - self:AddTransition( "*", "Fuel", "Fuel" ) - self:AddTransition( "*", "Damaged", "Damaged" ) - self:AddTransition( "*", "Eject", "*" ) - self:AddTransition( "*", "Crash", "Crashed" ) - self:AddTransition( "*", "PilotDead", "*" ) - - self.IdleCount = 0 - - self.RTBSpeedMaxFactor = 0.6 - self.RTBSpeedMinFactor = 0.5 - - return self -end - --- @param Wrapper.Group#GROUP self --- @param Core.Event#EVENTDATA EventData -function GROUP:OnEventTakeoff( EventData, Fsm ) - Fsm:Takeoff() - self:UnHandleEvent( EVENTS.Takeoff ) -end - - - -function AI_AIR:SetDispatcher( Dispatcher ) - self.Dispatcher = Dispatcher -end - -function AI_AIR:GetDispatcher() - return self.Dispatcher -end - -function AI_AIR:SetTargetDistance( Coordinate ) - - local CurrentCoord = self.Controllable:GetCoordinate() - self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate ) - - self.ClosestTargetDistance = ( not self.ClosestTargetDistance or self.ClosestTargetDistance > self.TargetDistance ) and self.TargetDistance or self.ClosestTargetDistance -end - - -function AI_AIR:ClearTargetDistance() - - self.TargetDistance = nil - self.ClosestTargetDistance = nil -end - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_AIR self --- @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. --- @return #AI_AIR self -function AI_AIR:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - ---- Sets (modifies) the minimum and maximum RTB speed of the patrol. --- @param #AI_AIR self --- @param DCS#Speed RTBMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed RTBMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @return #AI_AIR self -function AI_AIR:SetRTBSpeed( RTBMinSpeed, RTBMaxSpeed ) - self:F( { RTBMinSpeed, RTBMaxSpeed } ) - - self.RTBMinSpeed = RTBMinSpeed - self.RTBMaxSpeed = RTBMaxSpeed -end - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_AIR self --- @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. --- @return #AI_AIR self -function AI_AIR:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - ---- Sets the home airbase. --- @param #AI_AIR self --- @param Wrapper.Airbase#AIRBASE HomeAirbase --- @return #AI_AIR self -function AI_AIR:SetHomeAirbase( HomeAirbase ) - self:F2( { HomeAirbase } ) - - self.HomeAirbase = HomeAirbase -end - ---- Sets to refuel at the given tanker. --- @param #AI_AIR self --- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned. --- @return #AI_AIR self -function AI_AIR:SetTanker( TankerName ) - self:F2( { TankerName } ) - - self.TankerName = TankerName -end - - ---- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB. --- @param #AI_AIR self --- @param #number DisengageRadius The disengage range. --- @return #AI_AIR self -function AI_AIR:SetDisengageRadius( DisengageRadius ) - self:F2( { DisengageRadius } ) - - self.DisengageRadius = DisengageRadius -end - ---- Set the status checking off. --- @param #AI_AIR self --- @return #AI_AIR self -function AI_AIR:SetStatusOff() - self:F2() - - self.CheckStatus = false -end - - ---- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. --- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targeted to the AI_AIR. --- Once the time is finished, the old AI will return to the base. --- @param #AI_AIR self --- @param #number FuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. --- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. --- @return #AI_AIR self -function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime ) - - self.FuelThresholdPercentage = FuelThresholdPercentage - self.OutOfFuelOrbitTime = OutOfFuelOrbitTime - - self.Controllable:OptionRTBBingoFuel( false ) - - return self -end - ---- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base. --- However, damage cannot be foreseen early on. --- Therefore, when the damage threshold is reached, --- the AI will return immediately to the home base (RTB). --- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25. --- @param #AI_AIR self --- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged. --- @return #AI_AIR self -function AI_AIR:SetDamageThreshold( PatrolDamageThreshold ) - - self.PatrolManageDamage = true - self.PatrolDamageThreshold = PatrolDamageThreshold - - return self -end - - - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_AIR self --- @return #AI_AIR 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_AIR:onafterStart( Controllable, From, Event, To ) - - self:__Status( 10 ) -- Check status status every 30 seconds. - - self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead ) - self:HandleEvent( EVENTS.Crash, self.OnCrash ) - self:HandleEvent( EVENTS.Ejection, self.OnEjection ) - - Controllable:OptionROEHoldFire() - Controllable:OptionROTVertical() -end - ---- Coordinates the approriate returning action. --- @param #AI_AIR self --- @return #AI_AIR 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_AIR:onafterReturn( Controllable, From, Event, To ) - - self:__RTB( self.TaskDelay ) - -end - --- @param #AI_AIR self -function AI_AIR:onbeforeStatus() - - return self.CheckStatus -end - --- @param #AI_AIR self -function AI_AIR:onafterStatus() - - if self.Controllable and self.Controllable:IsAlive() then - - local RTB = false - - local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) - - if not self:Is( "Holding" ) and not self:Is( "Returning" ) then - local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) - - if DistanceFromHomeBase > self.DisengageRadius then - self:T( self.Controllable:GetName() .. " is too far from home base, RTB!" ) - self:Hold( 300 ) - RTB = false - end - end - --- I think this code is not requirement anymore after release 2.5. --- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then --- if DistanceFromHomeBase < 5000 then --- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" ) --- self:Home( "Destroy" ) --- end --- end - - - if not self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" )then - - local Fuel = self.Controllable:GetFuelMin() - - -- If the fuel in the controllable is below the threshold percentage, - -- then send for refuel in case of a tanker, otherwise RTB. - if Fuel < self.FuelThresholdPercentage then - - if self.TankerName then - self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" ) - self:Refuel() - else - self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" ) - local OldAIControllable = self.Controllable - - local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil ) ) - OldAIControllable:SetTask( TimedOrbitTask, 10 ) - - self:Fuel() - RTB = true - end - else - end - end - - if self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" ) then - RTB = true - end - - -- TODO: Check GROUP damage function. - local Damage = self.Controllable:GetLife() - local InitialLife = self.Controllable:GetLife0() - - -- If the group is damaged, then RTB. - -- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue. - -- The damaged unit will RTB due to DCS logic, and the others will continue to engage. - if ( Damage / InitialLife ) < self.PatrolDamageThreshold then - self:T( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) - self:Damaged() - RTB = true - self:SetStatusOff() - end - - -- Check if planes went RTB and are out of control. - -- We only check if planes are out of control, when they are in duty. - if self.Controllable:HasTask() == false then - if not self:Is( "Started" ) and - not self:Is( "Stopped" ) and - not self:Is( "Fuel" ) and - not self:Is( "Damaged" ) and - not self:Is( "Home" ) then - if self.IdleCount >= 10 then - if Damage ~= InitialLife then - self:Damaged() - else - self:T( self.Controllable:GetName() .. " control lost! " ) - - self:LostControl() - end - else - self.IdleCount = self.IdleCount + 1 - end - end - else - self.IdleCount = 0 - end - - if RTB == true then - self:__RTB( self.TaskDelay ) - end - - if not self:Is("Home") then - self:__Status( 10 ) - end - - end -end - - --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR.RTBRoute( AIGroup, Fsm ) - - AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } ) - - if AIGroup:IsAlive() then - Fsm:RTB() - end - -end - --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR.RTBHold( AIGroup, Fsm ) - - AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } ) - if AIGroup:IsAlive() then - Fsm:__RTB( Fsm.TaskDelay ) - Fsm:Return() - local Task = AIGroup:TaskOrbitCircle( 4000, 400 ) - AIGroup:SetTask( Task ) - end - -end - ---- Set the min and max factors on RTB speed. Use this, if your planes are heading back to base too fast. Default values are 0.5 and 0.6. --- The RTB speed is calculated as the max speed of the unit multiplied by MinFactor (lower bracket) and multiplied by MaxFactor (upper bracket). --- A random value in this bracket is then applied in the waypoint routing generation. --- @param #AI_AIR self --- @param #number MinFactor Lower bracket factor. Defaults to 0.5. --- @param #number MaxFactor Upper bracket factor. Defaults to 0.6. --- @return #AI_AIR self -function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor) - self.RTBSpeedMaxFactor = MaxFactor or 0.6 - self.RTBSpeedMinFactor = MinFactor or 0.5 - return self -end - - --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR:onafterRTB( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - if AIGroup and AIGroup:IsAlive() then - - self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) - - self:ClearTargetDistance() - --AIGroup:ClearTasks() - - AIGroup:OptionProhibitAfterburner(true) - - local EngageRoute = {} - - --- Calculate the target route point. - - local FromCoord = AIGroup:GetCoordinate() - if not FromCoord then return end - - local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!) - - local ToTargetVec3 = ToTargetCoord:GetVec3() - ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground - local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 ) - - if not self.RTBMinSpeed or not self.RTBMaxSpeed then - local RTBSpeedMax = AIGroup:GetSpeedMax() - local RTBSpeedMaxFactor = self.RTBSpeedMaxFactor or 0.6 - local RTBSpeedMinFactor = self.RTBSpeedMinFactor or 0.5 - self:SetRTBSpeed( RTBSpeedMax * RTBSpeedMinFactor, RTBSpeedMax * RTBSpeedMaxFactor) - end - - local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) - --local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) ) - - local Distance = FromCoord:Get2DDistance( ToTargetCoord2 ) - - --local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle ) - local ToAirbaseCoord = ToTargetCoord2 - - if Distance < 5000 then - self:T( "RTB and near the airbase!" ) - self:Home() - return - end - - if not AIGroup:InAir() == true then - self:T( "Not anymore in the air, considered Home." ) - self:Home() - return - end - - - --- Create a route point of type air. - local FromRTBRoutePoint = FromCoord:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - RTBSpeed, - true - ) - - --- Create a route point of type air. - local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - RTBSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = FromRTBRoutePoint - EngageRoute[#EngageRoute+1] = ToRTBRoutePoint - - local Tasks = {} - Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self ) - - EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks ) - - AIGroup:OptionROEHoldFire() - AIGroup:OptionROTEvadeFire() - - --- NOW ROUTE THE GROUP! - AIGroup:Route( EngageRoute, self.TaskDelay ) - - end - -end - --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR:onafterHome( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - self:T( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" ) - - if AIGroup and AIGroup:IsAlive() then - end - -end - - - --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime ) - self:F( { AIGroup, From, Event, To } ) - - self:T( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" ) - - if AIGroup and AIGroup:IsAlive() then - local Coordinate = AIGroup:GetCoordinate() - if Coordinate == nil then return end - local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed, Coordinate ) - local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) ) - - local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self ) - - local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed ) - - --AIGroup:SetState( AIGroup, "AI_AIR", self ) - - AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 ) - end - -end - --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR.Resume( AIGroup, Fsm ) - - AIGroup:T( { "AI_AIR.Resume:", AIGroup:GetName() } ) - if AIGroup:IsAlive() then - Fsm:__RTB( Fsm.TaskDelay ) - end - -end - --- @param #AI_AIR self --- @param Wrapper.Group#GROUP AIGroup -function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - if AIGroup and AIGroup:IsAlive() then - - -- Get tanker group. - local Tanker = GROUP:FindByName( self.TankerName ) - - if Tanker and Tanker:IsAlive() and Tanker:IsAirPlane() then - - self:T( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName ) - - local RefuelRoute = {} - - --- Calculate the target route point. - - local FromRefuelCoord = AIGroup:GetCoordinate() - local ToRefuelCoord = Tanker:GetCoordinate() - local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - - --- Create a route point of type air. - local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true) - - --- Create a route point of type air. NOT used! - local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true) - - self:F( { ToRefuelSpeed = ToRefuelSpeed } ) - - RefuelRoute[#RefuelRoute+1] = FromRefuelRoutePoint - RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint - - AIGroup:OptionROEHoldFire() - AIGroup:OptionROTEvadeFire() - - -- Get Class name for .Resume function - local classname=self:GetClassName() - - -- AI_A2A_CAP can call this function but does not have a .Resume function. Try to fix. - if classname=="AI_A2A_CAP" then - classname="AI_AIR_PATROL" - end - - env.info("FF refueling classname="..classname) - - local Tasks = {} - Tasks[#Tasks+1] = AIGroup:TaskRefueling() - Tasks[#Tasks+1] = AIGroup:TaskFunction( classname .. ".Resume", self ) - RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks ) - - AIGroup:Route( RefuelRoute, self.TaskDelay ) - - else - - -- No tanker defined ==> RTB! - self:RTB() - - end - - end - -end - - - --- @param #AI_AIR self -function AI_AIR:onafterDead() - self:SetStatusOff() -end - - --- @param #AI_AIR self --- @param Core.Event#EVENTDATA EventData -function AI_AIR:OnCrash( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - if #self.Controllable:GetUnits() == 1 then - self:__Crash( self.TaskDelay, EventData ) - end - end -end - --- @param #AI_AIR self --- @param Core.Event#EVENTDATA EventData -function AI_AIR:OnEjection( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__Eject( self.TaskDelay, EventData ) - end -end - --- @param #AI_AIR self --- @param Core.Event#EVENTDATA EventData -function AI_AIR:OnPilotDead( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__PilotDead( self.TaskDelay, EventData ) - end -end diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua deleted file mode 100644 index 9e5939aa0..000000000 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ /dev/null @@ -1,3239 +0,0 @@ ---- **AI** - Create an automated AIR defense system with reconnaissance units, coordinating SEAD, BAI and CAP operations. --- --- === --- --- Features: --- --- * Setup quickly an AIR defense system for a coalition. --- * Setup multiple defense zones to defend specific coordinates in your battlefield. --- * Setup (SEAD) Suppression of Air Defense squadrons, to gain control in the air of enemy grounds. --- * Setup (CAS) Controlled Air Support squadrons, to attack close by enemy ground units near friendly installations. --- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. --- * Define and use a detection network controlled by recce. --- * Define AIR defense squadrons at airbases, FARPs and carriers. --- * Enable airbases for AIR defenses. --- * Add different planes and helicopter templates to squadrons. --- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. --- * Add multiple squadrons to different airbases, FARPs or carriers. --- * Define different ranges to engage upon. --- * Establish an automatic in air refuel process for planes using refuel tankers. --- * Setup default settings for all squadrons and AIR defenses. --- * Setup specific settings for specific squadrons. --- --- === --- --- ## Missions: --- --- [AI_A2A_Dispatcher](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_A2A_Dispatcher) --- --- === --- --- ## YouTube Channel: --- --- [DCS WORLD - MOOSE - AIR GCICAP - Build an automatic AIR Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) --- --- === --- --- # QUICK START GUIDE --- --- The following class is available to model an AIR defense system. --- --- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system. --- --- Before you start using the AI_AIR_DISPATCHER, ask yourself the following questions. --- --- --- ## 1. Which coalition am I modeling an AIR defense system for? blue or red? --- --- One AI_AIR_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. --- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI_AIR_DISPATCHER **objects**, --- each governing their defense system for one coalition. --- --- --- ## 2. Which type of detection will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). --- --- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units --- and reporting them to the head quarters. --- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: --- --- * Perform detections from multiple recce as one co-operating entity. --- * Communicate with a @{Tasking.CommandCenter}, which consolidates each detection. --- * Groups detections based on a method (per area, per type or per unit). --- * Communicates detections. --- --- --- ## 3. Which recce units can be used as part of the detection system? Only ground based, or also airborne? --- --- Depending on the type of mission you want to achieve, different types of units can be engaged to perform ground enemy targets reconnaissance. --- Ground recce (FAC) are very useful units to determine the position of enemy ground targets when they spread out over the battlefield at strategic positions. --- Using their varying detection technology, and especially those ground units which have spotting technology, can be extremely effective at --- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. --- Unfortunately, they lack sometimes the visibility to detect targets at greater range, or when scenery is preventing line of sight. --- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then --- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! --- --- Airborne recce (AFAC) are also very effective. The are capable of patrolling at a functional detection altitude, --- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, --- so you need air superiority to make them effective. --- Airborne recce will also have varying ground detection technology, which plays a big role in the effectiveness of the reconnaissance. --- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective --- compared to air units having only visual detection capabilities. --- For example, for the red coalition, the Mi-28N and the Su-34; and for the blue side, the reaper, are such effective airborne recce units. --- --- Typically, don't want these recce units to engage with the enemy, you want to keep them at position. Therefore, it is a good practice --- to set the ROE for these recce to hold weapons, and make them invisible from the enemy. --- --- It is not possible to perform a recce function as a player (unit). --- --- --- ## 4. How do the defenses decide **when and where to engage** on approaching enemy units? --- --- The AIR dispatcher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. --- Any ground based enemy approaching within the proximity of such a defense point, may trigger for a defensive action by friendly air units. --- --- There are 2 important parameters that play a role in the defensive decision making: defensiveness and reactivity. --- --- The AIR dispatcher provides various parameters to setup the **defensiveness**, --- which models the decision **when** a defender will engage with the approaching enemy. --- Defensiveness is calculated by a probability distribution model when to trigger a defense action, --- depending on the distance of the enemy unit from the defense coordinates, and a **defensiveness factor**. --- --- The other parameter considered for defensive action is **where the enemy is located**, thus the distance from a defense coordinate, --- which we call the **reactive distance**. By default, the reactive distance is set to 60km, but can be changed by the mission designer --- using the available method explained further below. --- The combination of the defensiveness and reactivity results in a model that, the closer the attacker is to the defense point, --- the higher the probability will be that a defense action will be launched! --- --- --- ## 5. Are defense coordinates and defense reactivity the only parameters? --- --- No, depending on the target type, and the threat level of the target, the probability of defense will be higher. --- In other words, when a SAM-10 radar emitter is detected, its probability for defense will be much higher than when a BMP-1 vehicle is --- detected, even when both enemies are at the same distance from a defense coordinate. --- This will ensure optimal defenses, SEAD tasks will be launched much more quicker against engaging radar emitters, to ensure air superiority. --- Approaching main battle tanks will be engaged much faster, than a group of approaching trucks. --- --- --- ## 6. Which Squadrons will I create and which name will I give each Squadron? --- --- The AIR defense system works with **Squadrons**. Each Squadron must be given a unique name, that forms the **key** to the squadron. --- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningful --- for your mission, and remember that squadron names are used for communication to the players of your mission. --- --- There are mainly 3 types of defenses: **SEAD**, **CAS** and **BAI**. --- --- Suppression of Air Defenses (SEAD) are effective against radar emitters. Close Air Support (CAS) is launched when the enemy is close near friendly units. --- Battleground Air Interdiction (BAI) tasks are launched when there are no friendlies around. --- --- Depending on the defense type, different payloads will be needed. See further points on squadron definition. --- --- --- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On FARPs? --- --- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**. --- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. --- If the home base of the squadron is too far from assumed enemy positions, then the defenses will be too late. --- The home bases must be **behind** enemy lines, you want to prevent your home bases to be engaged by enemies! --- Depending on the units applied for defenses, the home base can be further or closer to the enemies. --- Any airbase, farp or carrier can act as the launching platform for AIR defenses. --- Carefully plan which airbases will take part in the coalition. Color each airbase **in the color of the coalition**, using the mission editor, --- or your air units will not return for landing at the airbase! --- --- --- ## 8. Which helicopter or plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron? --- --- Per Squadron, one or multiple helicopter or plane models can be allocated as **Templates**. --- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. --- The AIR defense system will select from the given templates a random template to spawn a new plane (group). --- --- A squadron will perform specific task types (SEAD, CAS or BAI). So, squadrons will require specific templates for the --- task types it will perform. A squadron executing SEAD defenses, will require a payload with long range anti-radar seeking missiles. --- --- --- ## 9. Which payloads, skills and skins will these plane models have? --- --- Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, --- each having different payloads, skills and skins. --- The AIR defense system will select from the given templates a random template to spawn a new plane (group). --- --- --- ## 10. How to squadrons engage in a defensive action? --- --- There are two ways how squadrons engage and execute your AIR defenses. --- Squadrons can start the defense directly from the airbase, farp or carrier. When a squadron launches a defensive group, that group --- will start directly from the airbase. The other way is to launch early on in the mission a patrolling mechanism. --- Squadrons will launch air units to patrol in specific zone(s), so that when ground enemy targets are detected, that the airborne --- AIR defenses can come immediately into action. --- --- --- ## 11. For each Squadron doing a patrol, which zone types will I create? --- --- Per zone, evaluate whether you want: --- --- * simple trigger zones --- * polygon zones --- * moving zones --- --- Depending on the type of zone selected, a different @{Core.Zone} object needs to be created from a ZONE_ class. --- --- --- ## 12. Are moving defense coordinates possible? --- --- Yes, different COORDINATE types are possible to be used. --- The COORDINATE_UNIT will help you to specify a defense coordinate that is attached to a moving unit. --- --- --- ## 13. How much defense coordinates do I need to create? --- --- It depends, but the idea is to define only the necessary defense points that drive your mission. --- If you define too much defense points, the performance of your mission may decrease. Per defense point defined, --- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the defense radius. --- The default defense radius is about 60km, and depending on the defense reactivity, defenses will be launched when the enemy is at --- close or greater distance from the defense coordinate. --- --- --- ## 14. For each Squadron doing patrols, what are the time intervals and patrol amounts to be performed? --- --- For each patrol: --- --- * **How many** patrol you want to have airborne at the same time? --- * **How frequent** you want the defense mechanism to check whether to start a new patrol? --- --- other considerations: --- --- * **How far** is the patrol area from the engagement "hot zone". You want to ensure that the enemy is reached on time! --- * **How safe** is the patrol area taking into account air superiority. Is it well defended, are there nearby A2A bases? --- --- --- ## 15. For each Squadron, which takeoff method will I use? --- --- For each Squadron, evaluate which takeoff method will be used: --- --- * Straight from the air --- * From the runway --- * From a parking spot with running engines --- * From a parking spot with cold engines --- --- **The default takeoff method is straight in the air.** --- This takeoff method is the most useful if you want to avoid airplane clutter at airbases! --- But it is the least realistic one! --- --- --- ## 16. For each Squadron, which landing method will I use? --- --- For each Squadron, evaluate which landing method will be used: --- --- * Despawn near the airbase when returning --- * Despawn after landing on the runway --- * Despawn after engine shutdown after landing --- --- **The default landing method is despawn when near the airbase when returning.** --- This landing method is the most useful if you want to avoid airplane clutter at airbases! --- But it is the least realistic one! --- --- --- ## 19. For each Squadron, which **defense overhead** will I use? --- --- For each Squadron, depending on the helicopter or airplane type (modern, old) and payload, which overhead is required to provide any defense? --- --- In other words, if **X** enemy ground units are detected, how many **Y** defense helicopters or airplanes need to engage (per squadron)? --- The **Y** is dependent on the type of airplane (era), payload, fuel levels, skills etc. --- But the most important factor is the payload, which is the amount of AIR weapons the defense can carry to attack the enemy ground units. --- For example, a Ka-50 can carry 16 vikrs, that means, that it potentially can destroy at least 8 ground units without a reload of ammunition. --- That means, that one defender can destroy more enemy ground units. --- Thus, the overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. --- --- **The default overhead is 1. A smaller value than 1, like 0.25 will decrease the overhead to a 1 / 4 ratio, meaning, --- one defender for each 4 detected ground enemy units. ** --- --- --- ## 19. For each Squadron, which grouping will I use? --- --- When multiple targets are detected, how will defenses be grouped when multiple defense air units are spawned for multiple enemy ground units? --- Per one, two, three, four? --- --- **The default grouping is 1. That means, that each spawned defender will act individually.** --- But you can specify a number between 1 and 4, so that the defenders will act as a group. --- --- === --- --- ### Author: **FlightControl** rework of GCICAP + introduction of new concepts (squadrons). --- --- @module AI.AI_Air_Dispatcher --- @image AI_Air_To_Ground_Dispatching.JPG - - - -do -- AI_AIR_DISPATCHER - - --- AI_AIR_DISPATCHER class. - -- @type AI_AIR_DISPATCHER - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Create an automated AIR defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAP operations. - -- - -- === - -- - -- When your mission is in the need to take control of the AI to automate and setup a process of air to ground defenses, this is the module you need. - -- The defense system work through the definition of defense coordinates, which are points in your friendly area within the battle field, that your mission need to have defended. - -- Multiple defense coordinates can be setup. Defense coordinates can be strategic or tactical positions or references to strategic units or scenery. - -- The AIR dispatcher will evaluate every x seconds the tactical situation around each defense coordinate. When a defense coordinate - -- is under threat, it will communicate through the command center that defensive actions need to be taken and will launch groups of air units for defense. - -- The level of threat to the defense coordinate varies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. - -- Defensive actions are taken through probability, but the closer and the more threat the enemy poses to the defense coordinate, the faster it will be attacked by friendly AIR units. - -- - -- Please study carefully the underlying explanations how to setup and use this module, as it has many features. - -- It also requires a little study to ensure that you get a good understanding of the defense mechanisms, to ensure a strong - -- defense for your missions. - -- - -- === - -- - -- # USAGE GUIDE - -- - -- ## 1. AI\_AIR\_DISPATCHER constructor: - -- - -- - -- The @{#AI_AIR_DISPATCHER.New}() method creates a new AI_AIR_DISPATCHER instance. - -- - -- ### 1.1. Define the **reconnaissance network**: - -- - -- As part of the AI_AIR_DISPATCHER :New() constructor, a reconnaissance network must be given as the first parameter. - -- A reconnaissance network is provided by passing a @{Functional.Detection} object. - -- The most effective reconnaissance for the AIR dispatcher would be to use the @{Functional.Detection#DETECTION_AREAS} object. - -- - -- A reconnaissance network, is used to detect enemy ground targets, - -- potentially group them into areas, and to understand the position, level of threat of the enemy. - -- - -- As explained in the introduction, depending on the type of mission you want to achieve, different types of units can be applied to detect ground enemy targets. - -- Ground based units are very useful to act as a reconnaissance, but they lack sometimes the visibility to detect targets at greater range. - -- Recce are very useful to acquire the position of enemy ground targets when spread out over the battlefield at strategic positions. - -- Ground units also have varying detectors, and especially the ground units which have laser guiding missiles can be extremely effective at - -- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. - -- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then - -- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! - -- - -- Beside ground level units to use for reconnaissance, air units are also very effective. The are capable of patrolling at great speed - -- covering a large terrain. However, airborne recce can be vulnerable to air to ground attacks, and you need air superiority to make then - -- effective. Also the instruments available at the air units play a big role in the effectiveness of the reconnaissance. - -- Air units which have ground detection capabilities will be much more effective than air units with only visual detection capabilities. - -- For the red coalition, the Mi-28N and for the blue side, the reaper are such effective reconnaissance airborne units. - -- - -- Reconnaissance networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection} instance that is given as the first parameter to the AIR dispatcher. - -- By defining in a **smart way the names or name prefixes of the reconnaissance groups**, these groups will be **automatically added or removed** to or from the reconnaissance network, - -- when these groups are spawned in or destroyed during the ongoing battle. - -- By spawning in dynamically additional recce, you can ensure that there is sufficient reconnaissance coverage so the defense mechanism is continuously - -- alerted of new enemy ground targets. - -- - -- The following example defense a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. - -- - -- -- Define a SET_GROUP object that builds a collection of groups that define the recce network. - -- -- Here we build the network with all the groups that have a name starting with CCCP Recce. - -- DetectionSetGroup = SET_GROUP:New() -- Defene a set of group objects, caled DetectionSetGroup. - -- - -- DetectionSetGroup:FilterPrefixes( { "CCCP Recce" } ) -- The DetectionSetGroup will search for groups that start with the name "CCCP Recce". - -- - -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, - -- -- which have a group name starting with "CCCP Recce", then these will be automatically added or removed from the set. - -- DetectionSetGroup:FilterStart() - -- - -- -- This command defines the reconnaissance network. - -- -- It will group any detected ground enemy targets within a radius of 1km. - -- -- It uses the DetectionSetGroup, which defines the set of reconnaissance groups to detect for enemy ground targets. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 1000 ) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- - -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. - -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with `"CCCP Recce"` to be included in the set. - -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. - -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. - -- - -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is chosen. - -- - -- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network - -- configuration and setup the AIR defense detection mechanism. - -- - -- ### 1.2. Setup the AIR dispatcher for both a red and blue coalition. - -- - -- Following the above described procedure, you'll need to create for each coalition an separate detection network, and a separate AIR dispatcher. - -- Ensure that while doing so, that you name the objects differently both for red and blue coalition. - -- - -- For example like this for the red coalition: - -- - -- DetectionRed = DETECTION_AREAS:New( DetectionSetGroupRed, 1000 ) - -- AIRDispatcherRed = AI_AIR_DISPATCHER:New( DetectionRed ) - -- - -- And for the blue coalition: - -- - -- DetectionBlue = DETECTION_AREAS:New( DetectionSetGroupBlue, 1000 ) - -- AIRDispatcherBlue = AI_AIR_DISPATCHER:New( DetectionBlue ) - -- - -- - -- Note: Also the SET_GROUP objects should be created for each coalition separately, containing each red and blue recce respectively! - -- - -- ### 1.3. Define the enemy ground target **grouping radius**, in case you use DETECTION_AREAS: - -- - -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_AIR_DISPATCHER:New() method, - -- but can be changed. The grouping radius should not be too small, but also depends on the types of ground forces and the way you want your mission to evolve. - -- A large radius will mean large groups of enemy ground targets, while making smaller groups will result in a more fragmented defense system. - -- Typically I suggest a grouping radius of 1km. This is the right balance to create efficient defenses. - -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius, - -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher! - -- So don't make this value too small! Again, I advise about 1km or 1000 meters. - -- - -- ## 2. Setup (a) **Defense Coordinate(s)**. - -- - -- As explained above, defense coordinates are the center of your defense operations. - -- The more threat to the defense coordinate, the higher it is likely a defensive action will be launched. - -- - -- Find below an example how to add defense coordinates: - -- - -- -- Add defense coordinates. - -- AIRDispatcher:AddDefenseCoordinate( "HQ", GROUP:FindByName( "HQ" ):GetCoordinate() ) - -- - -- In this example, the coordinate of a group called `"HQ"` is retrieved, using `:GetCoordinate()` - -- This returns a COORDINATE object, pointing to the first unit within the GROUP object. - -- - -- The method @{#AI_AIR_DISPATCHER.AddDefenseCoordinate}() adds a new defense coordinate to the `AIRDispatcher` object. - -- The first parameter is the key of the defense coordinate, the second the coordinate itself. - -- - -- Later, a COORDINATE_UNIT will be added to the framework, which can be used to assign "moving" coordinates to an AIR dispatcher. - -- - -- **REMEMBER!** - -- - -- - **Defense coordinates are the center of the AIR dispatcher defense system!** - -- - **You can define more defense coordinates to defend a larger area.** - -- - **Detected enemy ground targets are not immediately engaged, but are engaged with a reactivity or probability calculation!** - -- - -- But, there is more to it ... - -- - -- - -- ### 2.1. The **Defense Radius**. - -- - -- The defense radius defines the maximum radius that a defense will be initiated around each defense coordinate. - -- So even when there are targets further away than the defense radius, then these targets won't be engaged upon. - -- By default, the defense radius is set to 100km (100.000 meters), but can be changed using the @{#AI_AIR_DISPATCHER.SetDefenseRadius}() method. - -- Note that the defense radius influences the defense reactivity also! The larger the defense radius, the more reactive the defenses will be. - -- - -- For example: - -- - -- AIRDispatcher:SetDefenseRadius( 30000 ) - -- - -- This defines an AIR dispatcher which will engage on enemy ground targets within 30km radius around the defense coordinate. - -- Note that the defense radius **applies to all defense coordinates** defined within the AIR dispatcher. - -- - -- ### 2.2. The **Defense Reactivity**. - -- - -- There are 5 levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is - -- also determined by the distance of the enemy ground target to the defense coordinate. - -- If you want to have a **low** defense reactivity, that is, the probability that an AIR defense will engage to the enemy ground target, then - -- use the @{#AI_AIR_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods - -- @{#AI_AIR_DISPATCHER.SetDefenseReactivityMedium}() and @{#AI_AIR_DISPATCHER.SetDefenseReactivityHigh}() respectively. - -- - -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, - -- the less reactive the defenses will be in terms of distance to enemy ground targets! - -- - -- For example: - -- - -- AIRDispatcher:SetDefenseReactivityHigh() - -- - -- This defines an AIR dispatcher with high defense reactivity. - -- - -- ## 3. **Squadrons**. - -- - -- The AIR dispatcher works with **Squadrons**, that need to be defined using the different methods available. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, farp or carrier, - -- while defining which helicopter or plane **templates** are being used by the squadron and how many **resources** are available. - -- - -- **Multiple squadrons** can be defined within one AIR dispatcher, each having specific defense tasks and defense parameter settings! - -- - -- Squadrons: - -- - -- * Have name (string) that is the identifier or **key** of the squadron. - -- * Have specific helicopter or plane **templates**. - -- * Are located at **one** airbase, farp or carrier. - -- * Optionally have a **limited set of resources**. The default is that squadrons have **unlimited resources**. - -- - -- The name of the squadron given acts as the **squadron key** in all `AIRDispatcher:SetSquadron...()` or `AIRDispatcher:GetSquadron...()` methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new helicopters or aircraft are taking off from the airfield, farp or carrier (in the air, cold, hot, at the runway). - -- * Control how returning helicopters or aircraft are landing at the airfield, farp or carrier (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new helicopters or aircraft spawned at the airfield, farp or carrier. If there is more than one helicopter or aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, - -- the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- The method @{#AI_AIR_DISPATCHER.SetSquadron}() defines for you a new squadron. - -- The provided parameters are the squadron name, airbase name and a list of template prefixes, and a number that indicates the amount of resources. - -- - -- For example, this defines 3 new squadrons: - -- - -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50" }, 10 ) - -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50" }, 10 ) - -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50" }, 10 ) - -- - -- The latter 2 will depart from FARPs, which bare the name `"CAS"` and `"BAI"`. - -- - -- - -- ### 3.1. Squadrons **Tasking**. - -- - -- Squadrons can be commanded to execute 3 types of tasks, as explained above: - -- - -- - SEAD: Suppression of Air Defenses, which are ground targets that have medium or long range radar emitters. - -- - CAS : Close Air Support, when there are enemy ground targets close to friendly units. - -- - BAI : Battlefield Air Interdiction, which are targets further away from the frond-line. - -- - -- You need to configure each squadron which task types you want it to perform. Read on ... - -- - -- ### 3.2. Squadrons enemy ground target **engagement types**. - -- - -- There are two ways how targets can be engaged: directly **on call** from the airfield, farp or carrier, or through a **patrol**. - -- - -- Patrols are extremely handy, as these will airborne your helicopters or airplanes in advance. They will patrol in defined zones outlined, - -- and will engage with the targets once commanded. If the patrol zone is close enough to the enemy ground targets, then the time required - -- to engage is heavily minimized! - -- - -- However; patrols come with a side effect: since your resources are airborne, they will be vulnerable to incoming air attacks from the enemy. - -- - -- The mission designer needs to carefully balance the need for patrols or the need for engagement on call from the airfields. - -- - -- ### 3.3. Squadron **on call** engagement. - -- - -- So to make squadrons engage targets from the airfields, use the following methods: - -- - -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSead}() method. - -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCas}() method. - -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBai}() method. - -- - -- Note that for the tasks, specific helicopter or airplane templates are required to be used, which you can configure using your mission editor. - -- Especially the payload (weapons configuration) is important to get right. - -- - -- For example, the following will define for the squadrons different tasks: - -- - -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) - -- AIRDispatcher:SetSquadronSead( "Maykop SEAD", 120, 250 ) - -- - -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- AIRDispatcher:SetSquadronCas( "Maykop CAS", 120, 250 ) - -- - -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- AIRDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) - -- - -- ### 3.4. Squadron **on patrol engagement**. - -- - -- Squadrons can be setup to patrol in the air near the engagement hot zone. - -- When needed, the AIR defense units will be close to the battle area, and can engage quickly. - -- - -- So to make squadrons engage targets from a patrol zone, use the following methods: - -- - -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSeadPatrol}() method. - -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCasPatrol}() method. - -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBaiPatrol}() method. - -- - -- Because a patrol requires more parameters, the following methods must be used to fine-tune the patrols for each squadron. - -- - -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSeadPatrolInterval}() method. - -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCasPatrolInterval}() method. - -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBaiPatrolInterval}() method. - -- - -- Here an example to setup patrols of various task types: - -- - -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) - -- AIRDispatcher:SetSquadronSeadPatrol( "Maykop SEAD", PatrolZone, 300, 500, 50, 80, 250, 300 ) - -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop SEAD", 2, 30, 60, 1, "SEAD" ) - -- - -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) - -- AIRDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) - -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) - -- - -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) - -- AIRDispatcher:SetSquadronBaiPatrol( "Maykop BAI", PatrolZone, 800, 900, 50, 80, 250, 300 ) - -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop BAI", 2, 30, 60, 1, "BAI" ) - -- - -- - -- ### 3.5. Set squadron take-off methods - -- - -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the home airfield, FARP or ship. - -- - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. - -- - -- **The default landing method is to spawn new aircraft directly in the air.** - -- - -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. - -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: - -- - -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. - -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. - -- * aircraft may collide at the airbase. - -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... - -- - -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. - -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! - -- - -- This example sets the default takeoff method to be from the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Takeoff methods - -- - -- -- The default takeoff - -- A2ADispatcher:SetDefaultTakeOffFromRunway() - -- - -- -- The individual takeoff per squadron - -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_AIR_DISPATCHER.Takeoff.Air ) - -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) - -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) - -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) - -- - -- - -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. - -- - -- In the case of the @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. - -- That is modifying or setting the **altitude** from where planes spawn in the air. - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. - -- The default takeoff altitude can be modified or set using the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). - -- As part of the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. - -- If this parameter is not specified, then the default altitude will be used for the squadron. - -- - -- ### 3.5.2. Set Squadron takeoff interval. - -- - -- The different types of available airfields have different amounts of available launching platforms: - -- - -- - Airbases typically have a lot of platforms. - -- - FARPs have 4 platforms. - -- - Ships have 2 to 4 platforms. - -- - -- Depending on the demand of requested takeoffs by the AIR dispatcher, an airfield can become overloaded. Too many aircraft need to be taken - -- off at the same time, which will result in clutter as described above. In order to better control this behaviour, a takeoff scheduler is implemented, - -- which can be used to control how many aircraft are ordered for takeoff between specific time intervals. - -- The takeoff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. - -- - -- For this purpose, the method @{#AI_AIR_DISPATCHER.SetSquadronTakeOffInterval}() can be used to specify the takeoff intervals of - -- aircraft groups per squadron to avoid cluttering of aircraft at airbases. - -- This is especially useful for FARPs and ships. Each takeoff dispatch is queued by the dispatcher and when the interval time - -- has been reached, a new group will be spawned or activated for takeoff. - -- - -- The interval needs to be estimated, and depends on the time needed for the aircraft group to actually depart from the launch platform, and - -- the way how the aircraft are starting up. Cold starts take the longest duration, hot starts a few seconds, and runway takeoff also a few seconds for FARPs and ships. - -- - -- See the underlying example: - -- - -- -- Imagine a squadron launched from a FARP, with a grouping of 4. - -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. - -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. - -- -- It takes about 3 to 4 minutes to takeoff helicopters from FARPs in cold start. - -- A2ADispatcher:SetSquadronTakeOffInterval( "Mineralnye", 60 * 4 ) - -- - -- - -- ### 3.6. Set squadron landing methods - -- - -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: - -- - -- * @{#AI_AIR_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. - -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. - -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- You can use these methods to minimize the airbase coordination overhead and to increase the airbase efficiency. - -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the - -- A2A defense system, as no new CAP or GCI planes can takeoff. - -- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. - -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. - -- - -- This example defines the default landing method to be at the runway. - -- And for a couple of squadrons overrides this default method. - -- - -- -- Setup the Landing methods - -- - -- -- The default landing method - -- A2ADispatcher:SetDefaultLandingAtRunway() - -- - -- -- The individual landing per squadron - -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) - -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) - -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) - -- A2ADispatcher:SetSquadronLanding( "Novo", AI_AIR_DISPATCHER.Landing.AtRunway ) - -- - -- - -- ### 3.7. Set squadron **grouping**. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronGrouping}() to set the grouping of aircraft when spawned in. - -- - -- In the case of **on call** engagement, the @{#AI_AIR_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. - -- When there aren't enough patrol flights airborne, a on call will be initiated for the remaining - -- targets to be engaged. Depending on the grouping parameter, the spawned flights for on call aircraft are grouped into this setting. - -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by the available patrols or any airborne flight, - -- an additional on call flight needs to be started. - -- - -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. - -- - -- ### 3.8. Set the squadron **overhead** to balance the effectiveness of the AIR defenses. - -- - -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. - -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. - -- - -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. - -- - -- The @{#AI_AIR_DISPATCHER.SetSquadronOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. - -- - -- For example, a A-10C with full long-distance AIR missiles payload, may still be less effective than a Su-23 with short range AIR missiles... - -- So in this case, one may want to use the @{#AI_AIR_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking ground units. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: - -- - -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 attacking ground units detected, 6 aircraft will be spawned. - -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 attacking ground units detected, only 3 aircraft will be spawned. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group - -- multiplied by the overhead parameter, and rounded up to the smallest integer. - -- - -- Typically, for AIR defenses, values small than 1 will be used. Here are some good values for a couple of aircraft to support CAS operations: - -- - -- - A-10C: 0.15 - -- - Su-34: 0.15 - -- - A-10A: 0.25 - -- - SU-25T: 0.10 - -- - -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, - -- the less risk that the defender may be destroyed by the enemy, thus, the less aircraft needs to be activated in a defense. - -- - -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. - -- - -- ### 3.8. Set the squadron **engage limit**. - -- - -- To limit the amount of aircraft to defend against a large group of intruders, an **engage limit** can be defined per squadron. - -- This limit will avoid an extensive amount of aircraft to engage with the enemy if the attacking ground forces are enormous. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. - -- - -- ## 4. Set the **fuel threshold**. - -- - -- When aircraft get **out of fuel** to a certain %, which is by default **15% (0.15)**, there are two possible actions that can be taken: - -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. - -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel threshold** of the aircraft for all squadrons. - -- - -- ## 6. Other configuration options - -- - -- ### 6.1. Set a tactical display panel. - -- - -- Every 30 seconds, a tactical display panel can be shown that illustrates what the status is of the different groups controlled by AI_AIR_DISPATCHER. - -- Use the method @{#AI_AIR_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. - -- Note that there may be some performance impact if this panel is shown. - -- - -- ## 10. Default settings. - -- - -- Default settings configure the standard behaviour of the squadrons. - -- This section a good overview of the different parameters that setup the behaviour of **ALL** the squadrons by default. - -- Note that default behaviour can be tweaked, and thus, this will change the behaviour of all the squadrons. - -- Unless there is a specific behaviour set for a specific squadron, the default configured behaviour will be followed. - -- - -- ## 10.1. Default **takeoff** behaviour. - -- - -- The default takeoff behaviour is set to **in the air**, which means that new spawned aircraft will be spawned directly in the air above the airbase by default. - -- - -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** - -- - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. - -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. - -- - -- ## 10.2. Default landing behaviour. - -- - -- The default landing behaviour is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. - -- - -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. - -- - -- * @{#AI_AIR_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. - -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. - -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. - -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. - -- - -- ## 10.3. Default **overhead**. - -- - -- The default overhead is set to **0.25**. That essentially means that for each 4 ground enemies there will be 1 aircraft dispatched. - -- - -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. - -- - -- Use the @{#AI_AIR_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. - -- - -- ## 10.4. Default **grouping**. - -- - -- The default grouping is set to **one airplane**. That essentially means that there won't be any grouping applied by default. - -- - -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. - -- - -- ## 10.5. Default RTB fuel threshold. - -- - -- When an airplane gets **out of fuel** to a certain %, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel threshold** of spawned airplanes for all squadrons. - -- - -- ## 10.6. Default RTB damage threshold. - -- - -- When an airplane is **damaged** to a certain %, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage threshold** of spawned airplanes for all squadrons. - -- - -- ## 10.7. Default settings for **patrol**. - -- - -- ### 10.7.1. Default **patrol time Interval**. - -- - -- Patrol dispatching is time event driven, and will evaluate in random time intervals if a new patrol needs to be dispatched. - -- - -- The default patrol time interval is between **180** and **600** seconds. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft for ALL squadrons. - -- - -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using - -- the @{#AI_AIR_DISPATCHER.SetSquadronPatrolTimeInterval}() method. - -- - -- ### 10.7.2. Default **patrol limit**. - -- - -- Multiple patrol can be airborne at the same time for one squadron, which is controlled by the **patrol limit**. - -- The **default patrol limit** is 1 patrol per squadron to be airborne at the same time. - -- Note that the default patrol limit is used when a squadron patrol is defined, and cannot be changed afterwards. - -- So, ensure that you set the default patrol limit **before** you define or setup the squadron patrol. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft patrols for all squadrons. - -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using - -- the @{#AI_AIR_DISPATCHER.SetSquadronPatrolTimeInterval}() method. - -- - -- ## 10.7.3. Default tanker for refuelling when executing CAP. - -- - -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. - -- This greatly increases the efficiency of your CAP operations. - -- - -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. - -- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. - -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the % left in the defender airplane tanks when a refuel action is needed. - -- - -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. - -- - -- For example, the following setup will set the default refuel tanker to "Tanker": - -- - -- -- Define the CAP - -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) - -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) - -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) - -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) - -- - -- -- Set the default tanker for refuelling to "Tanker", when the default fuel threshold has reached 90% fuel left. - -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) - -- A2ADispatcher:SetDefaultTanker( "Tanker" ) - -- - -- ## 10.8. Default settings for GCI. - -- - -- ## 10.8.1. Optimal intercept point calculation. - -- - -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. - -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. - -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. - -- - -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: - -- - -- * The average bearing of the intruders for an amount of seconds. - -- * The average speed of the intruders for an amount of seconds. - -- * An assumed time it takes to get planes operational at the airbase. - -- - -- The **intercept point** will determine: - -- - -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. - -- * The optimal airbase from where defenders will takeoff for GCI. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. - -- - -- ## 10.8.2. Default Disengage Radius. - -- - -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. - -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. - -- - -- ## 11. Airbase capture: - -- - -- Different squadrons can be located at one airbase. - -- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership. - -- As a result, the GCI and CAP will stop! - -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes - -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. - -- - -- # 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_AIR_DISPATCHER - AI_AIR_DISPATCHER = { - ClassName = "AI_AIR_DISPATCHER", - Detection = nil, - } - - --- Definition of a Squadron. - -- @type AI_AIR_DISPATCHER.Squadron - -- @field #string Name The Squadron name. - -- @field Wrapper.Airbase#AIRBASE Airbase The home airbase. - -- @field #string AirbaseName The name of the home airbase. - -- @field Core.Spawn#SPAWN Spawn The spawning object. - -- @field #number ResourceCount The number of resources available. - -- @field #list<#string> TemplatePrefixes The list of template prefixes. - -- @field #boolean Captured true if the squadron is captured. - -- @field #number Overhead The overhead for the squadron. - - - --- List of defense coordinates. - -- @type AI_AIR_DISPATCHER.DefenseCoordinates - -- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name. - - -- @field #AI_AIR_DISPATCHER.DefenseCoordinates DefenseCoordinates - AI_AIR_DISPATCHER.DefenseCoordinates = {} - - --- Enumerator for spawns at airbases - -- @type AI_AIR_DISPATCHER.Takeoff - -- @extends Wrapper.Group#GROUP.Takeoff - - -- @field #AI_AIR_DISPATCHER.Takeoff Takeoff - AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff - - --- Defnes Landing location. - -- @field #AI_AIR_DISPATCHER.Landing - AI_AIR_DISPATCHER.Landing = { - NearAirbase = 1, - AtRunway = 2, - AtEngineShutdown = 3, - } - - --- A defense queue item description - -- @type AI_AIR_DISPATCHER.DefenseQueueItem - -- @field Squadron - -- @field #AI_AIR_DISPATCHER.Squadron DefenderSquadron The squadron in the queue. - -- @field DefendersNeeded - -- @field Defense - -- @field DefenseTaskType - -- @field Functional.Detection#DETECTION_BASE AttackerDetection - -- @field DefenderGrouping - -- @field #string SquadronName The name of the squadron. - - --- Queue of planned defenses to be launched. - -- This queue exists because defenses must be launched on FARPS, or in the air, or on an airbase, or on carriers. - -- And some of these platforms have very limited amount of "launching" platforms. - -- Therefore, this queue concept is introduced that queues each defender request. - -- Depending on the location of the launching site, the queued defenders will be launched at varying time intervals. - -- This guarantees that launched defenders are also directly existing ... - -- @type AI_AIR_DISPATCHER.DefenseQueue - -- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... - - -- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue - AI_AIR_DISPATCHER.DefenseQueue = {} - - --- Defense approach types - -- @type AI_AIR_DISPATCHER.DefenseApproach - AI_AIR_DISPATCHER.DefenseApproach = { - Random = 1, - Distance = 2, - } - - --- AI_AIR_DISPATCHER constructor. - -- This is defining the AIR DISPATCHER for one coalition. - -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. - -- The Detection object is polymorphic, depending on the type of detection object chosen, the detection will work differently. - -- @param #AI_AIR_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. - -- @return #AI_AIR_DISPATCHER self - -- @usage - -- - -- -- Setup the Detection, using DETECTION_AREAS. - -- -- First define the SET of GROUPs that are defining the EWR network. - -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. - -- DetectionSetGroup = SET_GROUP:New() - -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) - -- DetectionSetGroup:FilterStart() - -- - -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. - -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - function AI_AIR_DISPATCHER:New( Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( nil, Detection ) ) -- #AI_AIR_DISPATCHER - - self.Detection = Detection -- Functional.Detection#DETECTION_AREAS - - self.Detection:FilterCategories( Unit.Category.GROUND_UNIT ) - - -- This table models the DefenderSquadron templates. - self.DefenderSquadrons = {} -- The Defender Squadrons. - self.DefenderSpawns = {} - self.DefenderTasks = {} -- The Defenders Tasks. - self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons. - - -- TODO: Check detection through radar. --- self.Detection:FilterCategories( { Unit.Category.GROUND } ) --- self.Detection:InitDetectRadar( false ) --- self.Detection:InitDetectVisual( true ) --- self.Detection:SetRefreshTimeInterval( 30 ) - - self:SetDefenseRadius() - self:SetDefenseLimit( nil ) - self:SetDefenseApproach( AI_AIR_DISPATCHER.DefenseApproach.Random ) - self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. - self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. - - self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Air ) - self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above the ground. - self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.NearAirbase ) - self:SetDefaultOverhead( 1 ) - self:SetDefaultGrouping( 1 ) - self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the airplane to return to base or refuel. - self:SetDefaultDamageThreshold( 0.4 ) -- When 40% of damage, go RTB. - self:SetDefaultPatrolTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. - self:SetDefaultPatrolLimit( 1 ) -- Maximum one Patrol per squadron. - - - self:AddTransition( "Started", "Assign", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#AI_AIR_DISPATCHER] OnAfterAssign - -- @param #AI_AIR_DISPATCHER self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param AI.AI_Air#AI_AIR Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:AddTransition( "*", "Patrol", "*" ) - - --- Patrol Handler OnBefore for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] OnBeforePatrol - -- @param #AI_AIR_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Patrol Handler OnAfter for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] OnAfterPatrol - -- @param #AI_AIR_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Patrol Trigger for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] Patrol - -- @param #AI_AIR_DISPATCHER self - - --- Patrol Asynchronous Trigger for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] __Patrol - -- @param #AI_AIR_DISPATCHER self - -- @param #number Delay - - self:AddTransition( "*", "Defend", "*" ) - - --- Defend Handler OnBefore for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] OnBeforeDefend - -- @param #AI_AIR_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Defend Handler OnAfter for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] OnAfterDefend - -- @param #AI_AIR_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Defend Trigger for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] Defend - -- @param #AI_AIR_DISPATCHER self - - --- Defend Asynchronous Trigger for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] __Defend - -- @param #AI_AIR_DISPATCHER self - -- @param #number Delay - - self:AddTransition( "*", "Engage", "*" ) - - --- Engage Handler OnBefore for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] OnBeforeEngage - -- @param #AI_AIR_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Engage Handler OnAfter for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] OnAfterEngage - -- @param #AI_AIR_DISPATCHER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Engage Trigger for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] Engage - -- @param #AI_AIR_DISPATCHER self - - --- Engage Asynchronous Trigger for AI_AIR_DISPATCHER - -- @function [parent=#AI_AIR_DISPATCHER] __Engage - -- @param #AI_AIR_DISPATCHER self - -- @param #number Delay - - - -- Subscribe to the CRASH event so that when planes are shot - -- by a Unit from the dispatcher, they will be removed from the detection... - -- This will avoid the detection to still "know" the shot unit until the next detection. - -- Otherwise, a new defense or engage may happen for an already shot plane! - - - self:HandleEvent( EVENTS.Crash, self.OnEventCrashOrDead ) - self:HandleEvent( EVENTS.Dead, self.OnEventCrashOrDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCrashOrDead ) - - - self:HandleEvent( EVENTS.Land ) - self:HandleEvent( EVENTS.EngineShutdown ) - - -- Handle the situation where the airbases are captured. - self:HandleEvent( EVENTS.BaseCaptured ) - - self:SetTacticalDisplay( false ) - - self.DefenderPatrolIndex = 0 - - self:SetDefenseReactivityMedium() - - self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self ) - - self:__Start( 1 ) - - return self - end - - - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:onafterStart( From, Event, To ) - - self:GetParent( self ).onafterStart( self, From, Event, To ) - - -- Spawn the resources. - for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - DefenderSquadron.Resource = {} - for Resource = 1, DefenderSquadron.ResourceCount or 0 do - self:ResourcePark( DefenderSquadron ) - end - self:T( "Parked resources for squadron " .. DefenderSquadron.Name ) - end - - end - - --- Locks the DefenseItem from being defended. - -- @param #AI_AIR_DISPATCHER self - -- @param #string DetectedItemIndex The index of the detected item. - function AI_AIR_DISPATCHER:Lock( DetectedItemIndex ) - self:F( { DetectedItemIndex = DetectedItemIndex } ) - local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) - if DetectedItem then - self:F( { Locked = DetectedItem } ) - self.Detection:LockDetectedItem( DetectedItem ) - end - end - - - --- Unlocks the DefenseItem from being defended. - -- @param #AI_AIR_DISPATCHER self - -- @param #string DetectedItemIndex The index of the detected item. - function AI_AIR_DISPATCHER:Unlock( DetectedItemIndex ) - self:F( { DetectedItemIndex = DetectedItemIndex } ) - self:F( { Index = self.Detection.DetectedItemsByIndex } ) - local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) - if DetectedItem then - self:F( { Unlocked = DetectedItem } ) - self.Detection:UnlockDetectedItem( DetectedItem ) - end - end - - - --- Sets maximum zones to be engaged at one time by defenders. - -- @param #AI_AIR_DISPATCHER self - -- @param #number DefenseLimit The maximum amount of detected items to be engaged at the same time. - function AI_AIR_DISPATCHER:SetDefenseLimit( DefenseLimit ) - self:F( { DefenseLimit = DefenseLimit } ) - - self.DefenseLimit = DefenseLimit - end - - - --- Sets the method of the tactical approach of the defenses. - -- @param #AI_AIR_DISPATCHER self - -- @param #number DefenseApproach Use the structure AI_AIR_DISPATCHER.DefenseApproach to set the defense approach. - -- The default defense approach is AI_AIR_DISPATCHER.DefenseApproach.Random. - function AI_AIR_DISPATCHER:SetDefenseApproach( DefenseApproach ) - self:F( { DefenseApproach = DefenseApproach } ) - - self._DefenseApproach = DefenseApproach - end - - - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ResourcePark( DefenderSquadron ) - local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) - local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN - Spawn:InitGrouping( 1 ) - local SpawnGroup - if self:IsSquadronVisible( DefenderSquadron.Name ) then - SpawnGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, SPAWN.Takeoff.Cold ) - local GroupName = SpawnGroup:GetName() - DefenderSquadron.Resources = DefenderSquadron.Resources or {} - DefenderSquadron.Resources[TemplateID] = DefenderSquadron.Resources[TemplateID] or {} - DefenderSquadron.Resources[TemplateID][GroupName] = {} - DefenderSquadron.Resources[TemplateID][GroupName] = SpawnGroup - end - end - - - -- @param #AI_AIR_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_AIR_DISPATCHER:OnEventBaseCaptured( EventData ) - - local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured. - - self:T( "Captured " .. AirbaseName ) - - -- Now search for all squadrons located at the airbase, and sanitize them. - for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do - if Squadron.AirbaseName == AirbaseName then - Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning. - Squadron.Captured = true - self:T( "Squadron " .. SquadronName .. " captured." ) - end - end - end - - -- @param #AI_AIR_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_AIR_DISPATCHER:OnEventCrashOrDead( EventData ) - self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) - end - - -- @param #AI_AIR_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_AIR_DISPATCHER:OnEventLand( EventData ) - self:F( "Landed" ) - local DefenderUnit = EventData.IniUnit - local Defender = EventData.IniGroup - local Squadron = self:GetSquadronFromDefender( Defender ) - if Squadron then - self:F( { SquadronName = Squadron.Name } ) - local LandingMethod = self:GetSquadronLanding( Squadron.Name ) - - if LandingMethod == AI_AIR_DISPATCHER.Landing.AtRunway then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ResourcePark( Squadron ) - return - end - if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then - -- Damaged units cannot be repaired anymore. - DefenderUnit:Destroy() - return - end - end - end - - -- @param #AI_AIR_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function AI_AIR_DISPATCHER:OnEventEngineShutdown( EventData ) - local DefenderUnit = EventData.IniUnit - local Defender = EventData.IniGroup - local Squadron = self:GetSquadronFromDefender( Defender ) - if Squadron then - self:F( { SquadronName = Squadron.Name } ) - local LandingMethod = self:GetSquadronLanding( Squadron.Name ) - if LandingMethod == AI_AIR_DISPATCHER.Landing.AtEngineShutdown and - not DefenderUnit:InAir() then - local DefenderSize = Defender:GetSize() - if DefenderSize == 1 then - self:RemoveDefenderFromSquadron( Squadron, Defender ) - end - DefenderUnit:Destroy() - self:ResourcePark( Squadron ) - end - end - end - - do -- Manage the defensive behaviour - - -- @param #AI_AIR_DISPATCHER self - -- @param #string DefenseCoordinateName The name of the coordinate to be defended by AIR defenses. - -- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by AIR defenses. - function AI_AIR_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate ) - self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate - end - - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:SetDefenseReactivityLow() - self.DefenseReactivity = 0.05 - end - - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:SetDefenseReactivityMedium() - self.DefenseReactivity = 0.15 - end - - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:SetDefenseReactivityHigh() - self.DefenseReactivity = 0.5 - end - - end - - - --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. - -- @param #AI_AIR_DISPATCHER self - -- @param #number DisengageRadius (Optional, Default = 300000) The radius to disengage a target when too far from the home base. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Set 50km as the Disengage Radius. - -- AIRDispatcher:SetDisengageRadius( 50000 ) - -- - -- -- Set 100km as the Disengage Radius. - -- AIRDispatcher:SetDisngageRadius() -- 300000 is the default value. - -- - function AI_AIR_DISPATCHER:SetDisengageRadius( DisengageRadius ) - - self.DisengageRadius = DisengageRadius or 300000 - - return self - end - - - --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, CAS or BAI for defense. - -- When targets are detected that are still really far off, you don't want the AI_AIR_DISPATCHER to launch defenders, as they might need to travel too far. - -- You want it to wait until a certain defend radius is reached, which is calculated as: - -- 1. the **distance of the closest airbase to target**, being smaller than the **Defend Radius**. - -- 2. the **distance to any defense reference point**. - -- - -- The **default** defense radius is defined as **400000** or **40km**. Override the default defense radius when the era of the warfare is early, or, - -- when you don't want to let the AI_AIR_DISPATCHER react immediately when a certain border or area is not being crossed. - -- - -- Use the method @{#AI_AIR_DISPATCHER.SetDefendRadius}() to set a specific defend radius for all squadrons, - -- **the Defense Radius is defined for ALL squadrons which are operational.** - -- - -- @param #AI_AIR_DISPATCHER self - -- @param #number DefenseRadius (Optional, Default = 200000) The defense radius to engage detected targets from the nearest capable and available squadron airbase. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Set 100km as the radius to defend from detected targets from the nearest airbase. - -- AIRDispatcher:SetDefendRadius( 100000 ) - -- - -- -- Set 200km as the radius to defend. - -- AIRDispatcher:SetDefendRadius() -- 200000 is the default value. - -- - function AI_AIR_DISPATCHER:SetDefenseRadius( DefenseRadius ) - - self.DefenseRadius = DefenseRadius or 100000 - - self.Detection:SetAcceptRange( self.DefenseRadius ) - - return self - end - - - - --- Define a border area to simulate a **cold war** scenario. - -- A **cold war** is one where Patrol aircraft patrol their territory but will not attack enemy aircraft or launch GCI aircraft unless enemy aircraft enter their territory. In other words the EWR may detect an enemy aircraft but will only send aircraft to attack it if it crosses the border. - -- A **hot war** is one where Patrol aircraft will intercept any detected enemy aircraft and GCI aircraft will launch against detected enemy aircraft without regard for territory. In other words if the ground radar can detect the enemy aircraft then it will send Patrol and GCI aircraft to attack it. - -- If it's a cold war then the **borders of red and blue territory** need to be defined using a @{Core.Zone} object derived from @{Core.Zone#ZONE_BASE}. This method needs to be used for this. - -- If a hot war is chosen then **no borders** actually need to be defined using the helicopter units other than it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. Set the noborders parameter to 1 - -- @param #AI_AIR_DISPATCHER self - -- @param Core.Zone#ZONE_BASE BorderZone An object derived from ZONE_BASE, or a list of objects derived from ZONE_BASE. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Set one ZONE_POLYGON object as the border for the AIR dispatcher. - -- local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit. - -- AIRDispatcher:SetBorderZone( BorderZone ) - -- - -- or - -- - -- -- Set two ZONE_POLYGON objects as the border for the AIR dispatcher. - -- local BorderZone1 = ZONE_POLYGON( "CCCP Border1", GROUP:FindByName( "CCCP Border1" ) ) -- The GROUP object is a late activate helicopter unit. - -- local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit. - -- AIRDispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) - -- - -- - function AI_AIR_DISPATCHER:SetBorderZone( BorderZone ) - - self.Detection:SetAcceptZones( BorderZone ) - - return self - end - - --- Display a tactical report every 30 seconds about which aircraft are: - -- * Patrolling - -- * Engaging - -- * Returning - -- * Damaged - -- * Out of Fuel - -- * ... - -- @param #AI_AIR_DISPATCHER self - -- @param #boolean TacticalDisplay Provide a value of **true** to display every 30 seconds a tactical overview. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the Tactical Display for debug mode. - -- AIRDispatcher:SetTacticalDisplay( true ) - -- - function AI_AIR_DISPATCHER:SetTacticalDisplay( TacticalDisplay ) - - self.TacticalDisplay = TacticalDisplay - - return self - end - - - --- Set the default damage threshold when defenders will RTB. - -- The default damage threshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. - -- @param #AI_AIR_DISPATCHER self - -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the % of the damage threshold before going RTB. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default damage threshold. - -- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. - -- - function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) - - self.DefenderDefault.DamageThreshold = DamageThreshold - - return self - end - - - --- Set the default Patrol time interval for squadrons, which will be used to determine a random Patrol timing. - -- The default Patrol time interval is between 180 and 600 seconds. - -- @param #AI_AIR_DISPATCHER self - -- @param #number PatrolMinSeconds The minimum amount of seconds for the random time interval. - -- @param #number PatrolMaxSeconds The maximum amount of seconds for the random time interval. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol time interval. - -- AIRDispatcher:SetDefaultPatrolTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds. - -- - function AI_AIR_DISPATCHER:SetDefaultPatrolTimeInterval( PatrolMinSeconds, PatrolMaxSeconds ) - - self.DefenderDefault.PatrolMinSeconds = PatrolMinSeconds - self.DefenderDefault.PatrolMaxSeconds = PatrolMaxSeconds - - return self - end - - - --- Set the default Patrol limit for squadrons, which will be used to determine how many Patrol can be airborne at the same time for the squadron. - -- The default Patrol limit is 1 Patrol, which means one Patrol group being spawned. - -- @param #AI_AIR_DISPATCHER self - -- @param #number PatrolLimit The maximum amount of Patrol that can be airborne at the same time for the squadron. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol limit. - -- AIRDispatcher:SetDefaultPatrolLimit( 2 ) -- Maximum 2 Patrol per squadron. - -- - function AI_AIR_DISPATCHER:SetDefaultPatrolLimit( PatrolLimit ) - - self.DefenderDefault.PatrolLimit = PatrolLimit - - return self - end - - - --- Set the default engage limit for squadrons, which will be used to determine how many air units will engage at the same time with the enemy. - -- The default eatrol limit is 1, which means one eatrol group maximum per squadron. - -- @param #AI_AIR_DISPATCHER self - -- @param #number EngageLimit The maximum engages that can be done at the same time per squadron. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default Patrol limit. - -- AIRDispatcher:SetDefaultEngageLimit( 2 ) -- Maximum 2 engagements with the enemy per squadron. - -- - function AI_AIR_DISPATCHER:SetDefaultEngageLimit( EngageLimit ) - - self.DefenderDefault.EngageLimit = EngageLimit - - return self - end - - - function AI_AIR_DISPATCHER:SetIntercept( InterceptDelay ) - - self.DefenderDefault.InterceptDelay = InterceptDelay - - local Detection = self.Detection -- Functional.Detection#DETECTION_AREAS - Detection:SetIntercept( true, InterceptDelay ) - - return self - end - - - --- Calculates which defender friendlies are nearby the area, to help protect the area. - -- @param #AI_AIR_DISPATCHER self - -- @param DetectedItem - -- @return #table A list of the defender friendlies nearby, sorted by distance. - function AI_AIR_DISPATCHER:GetDefenderFriendliesNearBy( DetectedItem ) - --- local DefenderFriendliesNearBy = self.Detection:GetFriendliesDistance( DetectedItem ) - - local DefenderFriendliesNearBy = {} - - local DetectionCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - - local ScanZone = ZONE_RADIUS:New( "ScanZone", DetectionCoordinate:GetVec2(), self.DefenseRadius ) - - ScanZone:Scan( Object.Category.UNIT, { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - - local DefenderUnits = ScanZone:GetScannedUnits() - - for DefenderUnitID, DefenderUnit in pairs( DefenderUnits ) do - local DefenderUnit = UNIT:FindByName( DefenderUnit:getName() ) - - DefenderFriendliesNearBy[#DefenderFriendliesNearBy+1] = DefenderUnit - end - - - return DefenderFriendliesNearBy - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:GetDefenderTasks() - return self.DefenderTasks or {} - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:GetDefenderTask( Defender ) - return self.DefenderTasks[Defender] - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:GetDefenderTaskFsm( Defender ) - return self:GetDefenderTask( Defender ).Fsm - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:GetDefenderTaskTarget( Defender ) - return self:GetDefenderTask( Defender ).Target - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:GetDefenderTaskSquadronName( Defender ) - return self:GetDefenderTask( Defender ).SquadronName - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ClearDefenderTask( Defender ) - if Defender:IsAlive() and self.DefenderTasks[Defender] then - local Target = self.DefenderTasks[Defender].Target - local Message = "Clearing (" .. self.DefenderTasks[Defender].Type .. ") " - Message = Message .. Defender:GetName() - if Target then - Message = Message .. ( Target and ( " from " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or "" - end - self:F( { Target = Message } ) - end - self.DefenderTasks[Defender] = nil - return self - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ClearDefenderTaskTarget( Defender ) - - local DefenderTask = self:GetDefenderTask( Defender ) - - if Defender:IsAlive() and DefenderTask then - local Target = DefenderTask.Target - local Message = "Clearing (" .. DefenderTask.Type .. ") " - Message = Message .. Defender:GetName() - if Target then - Message = Message .. ( Target and ( " from " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or "" - end - self:F( { Target = Message } ) - end - if Defender and DefenderTask and DefenderTask.Target then - DefenderTask.Target = nil - end --- if Defender and DefenderTask then --- if DefenderTask.Fsm:Is( "Fuel" ) --- or DefenderTask.Fsm:Is( "LostControl") --- or DefenderTask.Fsm:Is( "Damaged" ) then --- self:ClearDefenderTask( Defender ) --- end --- end - return self - end - - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target, Size ) - - self:F( { SquadronName = SquadronName, Defender = Defender:GetName() } ) - - self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {} - self.DefenderTasks[Defender].Type = Type - self.DefenderTasks[Defender].Fsm = Fsm - self.DefenderTasks[Defender].SquadronName = SquadronName - self.DefenderTasks[Defender].Size = Size - - if Target then - self:SetDefenderTaskTarget( Defender, Target ) - end - return self - end - - - --- - -- @param #AI_AIR_DISPATCHER self - -- @param Wrapper.Group#GROUP AIGroup - function AI_AIR_DISPATCHER:SetDefenderTaskTarget( Defender, AttackerDetection ) - - local Message = "(" .. self.DefenderTasks[Defender].Type .. ") " - Message = Message .. Defender:GetName() - Message = Message .. ( AttackerDetection and ( " target " .. AttackerDetection.Index .. " [" .. AttackerDetection.Set:Count() .. "]" ) ) or "" - self:F( { AttackerDetection = Message } ) - if AttackerDetection then - self.DefenderTasks[Defender].Target = AttackerDetection - end - return self - end - - - --- This is the main method to define Squadrons programmatically. - -- Squadrons: - -- - -- * Have a **name or key** that is the identifier or key of the squadron. - -- * Have **specific plane types** defined by **templates**. - -- * Are **located at one specific airbase**. Multiple squadrons can be located at one airbase through. - -- * Optionally have a limited set of **resources**. The default is that squadrons have unlimited resources. - -- - -- The name of the squadron given acts as the **squadron key** in the AI\_AIR\_DISPATCHER:Squadron...() methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new aircraft are **taking off** from the airfield (in the air, cold, hot, at the runway). - -- * Control how returning aircraft are **landing** at the airfield (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft. - -- - -- @param #AI_AIR_DISPATCHER self - -- - -- @param #string SquadronName A string (text) that defines the squadron identifier or the key of the Squadron. - -- It can be any name, for example `"104th Squadron"` or `"SQ SQUADRON1"`, whatever. - -- As long as you remember that this name becomes the identifier of your squadron you have defined. - -- You need to use this name in other methods too! - -- - -- @param #string AirbaseName The airbase name where you want to have the squadron located. - -- You need to specify here EXACTLY the name of the airbase as you see it in the mission editor. - -- Examples are `"Batumi"` or `"Tbilisi-Lochini"`. - -- EXACTLY the airbase name, between quotes `""`. - -- To ease the airbase naming when using the LDT editor and IntelliSense, the @{Wrapper.Airbase#AIRBASE} class contains enumerations of the airbases of each map. - -- - -- * Caucasus: @{Wrapper.Airbase#AIRBASE.Caucaus} - -- * Nevada or NTTR: @{Wrapper.Airbase#AIRBASE.Nevada} - -- * Normandy: @{Wrapper.Airbase#AIRBASE.Normandy} - -- - -- @param #string TemplatePrefixes A string or an array of strings specifying the **prefix names of the templates** (not going to explain what is templates here again). - -- Examples are `{ "104th", "105th" }` or `"104th"` or `"Template 1"` or `"BLUE PLANES"`. - -- Just remember that your template (groups late activated) need to start with the prefix you have specified in your code. - -- If you have only one prefix name for a squadron, you don't need to use the `{ }`, otherwise you need to use the brackets. - -- - -- @param #number ResourceCount (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available. - -- - -- @usage - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- @usage - -- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock... - -- AIRDispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 ) - -- - -- @usage - -- -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock... - -- -- Note that in this implementation, the AIR dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses. - -- -- Note the usage of the {} for the airplane templates list. - -- AIRDispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 ) - -- - -- @usage - -- -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock... - -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) - -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 ) - -- - -- @usage - -- -- This is an example like the previous, but now with infinite resources. - -- -- The ResourceCount parameter is not given in the SetSquadron method. - -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) - -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - local Squadron = AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - return self:SetSquadron2( Squadron ) - end - - --- This is the new method to define Squadrons programmatically. - -- - -- Define a squadron using the AI_AIR_SQUADRON class. - -- - -- Squadrons: - -- - -- * Have a **name or key** that is the identifier or key of the squadron. - -- * Have **specific plane types** defined by **templates**. - -- * Are **located at one specific airbase**. Multiple squadrons can be located at one airbase through. - -- * Optionally have a limited set of **resources**. The default is that squadrons have unlimited resources. - -- - -- The name of the squadron given acts as the **squadron key** in the AI\_AIR\_DISPATCHER:Squadron...() methods. - -- - -- Additionally, squadrons have specific configuration options to: - -- - -- * Control how new aircraft are **taking off** from the airfield (in the air, cold, hot, at the runway). - -- * Control how returning aircraft are **landing** at the airfield (in the air near the airbase, after landing, after engine shutdown). - -- * Control the **grouping** of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped. - -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned. - -- - -- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft. - -- - -- @param #AI_AIR_DISPATCHER self - -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron The Air Squadron to be set active for the Air Dispatcher. - -- - -- @usage - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- @usage - -- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock... - -- AIRDispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 ) - -- - -- @usage - -- -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock... - -- -- Note that in this implementation, the AIR dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses. - -- -- Note the usage of the {} for the airplane templates list. - -- AIRDispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 ) - -- - -- @usage - -- -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock... - -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) - -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 ) - -- - -- @usage - -- -- This is an example like the previous, but now with infinite resources. - -- -- The ResourceCount parameter is not given in the SetSquadron method. - -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) - -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadron2( Squadron ) - - local SquadronName = Squadron:GetName() -- Retrieves the Squadron Name. - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - return self - end - - --- Get the @{AI.AI_Air_Squadron} object from the Squadron Name given. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The Squadron Name to search the Squadron object. - -- @return AI.AI_Air_Squadron#AI_AIR_SQUADRON The Squadron object. - function AI_AIR_DISPATCHER:GetSquadron( SquadronName ) - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - - if not DefenderSquadron then - error( "Unknown Squadron for Dispatcher:" .. SquadronName ) - end - - return DefenderSquadron - end - - - --- Set the Squadron visible before startup of the dispatcher. - -- All planes will be spawned as uncontrolled on the parking spot. - -- They will lock the parking spot. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- AIRDispatcher:SetSquadronVisible( "Mineralnye" ) - -- - -- TODO: disabling because of bug in queueing. --- function AI_AIR_DISPATCHER:SetSquadronVisible( SquadronName ) --- --- self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} --- --- local DefenderSquadron = self:GetSquadron( SquadronName ) --- --- DefenderSquadron.Uncontrolled = true --- self:SetSquadronTakeoffFromParkingCold( SquadronName ) --- self:SetSquadronLandingAtEngineShutdown( SquadronName ) --- --- for SpawnTemplate, DefenderSpawn in pairs( self.DefenderSpawns ) do --- DefenderSpawn:InitUnControlled() --- end --- --- end - - --- Check if the Squadron is visible before startup of the dispatcher. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #boolean true if visible. - -- @usage - -- - -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = AIRDispatcher:IsSquadronVisible( "Mineralnye" ) - -- - function AI_AIR_DISPATCHER:IsSquadronVisible( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron then - return DefenderSquadron.Uncontrolled == true - end - - return nil - - end - - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps. - -- @usage - -- - -- -- Set the Squadron Takeoff interval every 60 seconds for squadron "SQ50", which is good for a FARP cold start. - -- AIRDispatcher:SetSquadronTakeoffInterval( "SQ50", 60 ) - -- - function AI_AIR_DISPATCHER:SetSquadronTakeoffInterval( SquadronName, TakeoffInterval ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron then - DefenderSquadron.TakeoffInterval = TakeoffInterval or 0 - DefenderSquadron.TakeoffTime = 0 - end - - end - - - - --- Set the squadron patrol parameters for a specific task type. - -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. - -- - -- - @{#AI_AIR_DISPATCHER:SetSquadronSeadPatrolInterval} for SEAD tasks. - -- - @{#AI_AIR_DISPATCHER:SetSquadronSeadPatrolInterval} for CAS tasks. - -- - @{#AI_AIR_DISPATCHER:SetSquadronSeadPatrolInterval} for BAI tasks. - -- - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @param #string DefenseTaskType Should contain "SEAD", "CAS" or "BAI". - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- AIRDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- AIRDispatcher:SetSquadronPatrolInterval( "Mineralnye", 2, 30, 60, 1, "SEAD" ) - -- - function AI_AIR_DISPATCHER:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, DefenseTaskType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Patrol = DefenderSquadron[DefenseTaskType] - if Patrol then - Patrol.LowInterval = LowInterval or 180 - Patrol.HighInterval = HighInterval or 600 - Patrol.Probability = Probability or 1 - Patrol.PatrolLimit = PatrolLimit or 1 - Patrol.Scheduler = Patrol.Scheduler or SCHEDULER:New( self ) - local Scheduler = Patrol.Scheduler -- Core.Scheduler#SCHEDULER - local ScheduleID = Patrol.ScheduleID - local Variance = ( Patrol.HighInterval - Patrol.LowInterval ) / 2 - local Repeat = Patrol.LowInterval + Variance - local Randomization = Variance / Repeat - local Start = math.random( 1, Patrol.HighInterval ) - - if ScheduleID then - Scheduler:Stop( ScheduleID ) - end - - Patrol.ScheduleID = Scheduler:Schedule( self, self.SchedulerPatrol, { SquadronName }, Start, Repeat, Randomization ) - else - error( "This squadron does not exist:" .. SquadronName ) - end - - end - - - - --- Set the squadron engage limit for a specific task type. - -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. - -- - -- - @{#AI_AIR_DISPATCHER:SetSquadronSeadEngageLimit} for SEAD tasks. - -- - @{#AI_AIR_DISPATCHER:SetSquadronSeadEngageLimit} for CAS tasks. - -- - @{#AI_AIR_DISPATCHER:SetSquadronSeadEngageLimit} for BAI tasks. - -- - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron. - -- @param #string DefenseTaskType Should contain "SEAD", "CAS" or "BAI". - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- AIRDispatcher:SetSquadronEngageLimit( "Mineralnye", 2, "SEAD" ) -- Engage maximum 2 groups with the enemy for SEAD defense. - -- - function AI_AIR_DISPATCHER:SetSquadronEngageLimit( SquadronName, EngageLimit, DefenseTaskType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Defense = DefenderSquadron[DefenseTaskType] - if Defense then - Defense.EngageLimit = EngageLimit or 1 - else - error( "This squadron does not exist:" .. SquadronName ) - end - - end - - - - - - --- Defines the default amount of extra planes that will take-off as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: - -- - -- * Higher than 1, will increase the defense unit amounts. - -- * Lower than 1, will decrease the defense unit amounts. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. - -- - -- See example below. - -- - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. - -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. - -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. - -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. - -- - -- AIRDispatcher:SetDefaultOverhead( 1.5 ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetDefaultOverhead( Overhead ) - - self.DefenderDefault.Overhead = Overhead - - return self - end - - - --- Defines the amount of extra planes that will take-off as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Overhead The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: - -- - -- * Higher than 1, will increase the defense unit amounts. - -- * Lower than 1, will decrease the defense unit amounts. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. - -- - -- See example below. - -- - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. - -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. - -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. - -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. - -- - -- AIRDispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetOverhead( Overhead ) - - return self - end - - - --- Gets the overhead of planes as part of the defense system, in comparison with the attackers. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number The % of Units that dispatching command will allocate to intercept in surplus of detected amount of units. - -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... - -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. - -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: - -- - -- * Higher than 1, will increase the defense unit amounts. - -- * Lower than 1, will decrease the defense unit amounts. - -- - -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group - -- multiplied by the Overhead and rounded up to the smallest integer. - -- - -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. - -- - -- See example below. - -- - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. - -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. - -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. - -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. - -- - -- local SquadronOverhead = AIRDispatcher:GetSquadronOverhead( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:GetSquadronOverhead( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron:GetOverhead() or self.DefenderDefault.Overhead - end - - - --- Sets the default grouping of new airplanes spawned. - -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. - -- @param #AI_AIR_DISPATCHER self - -- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set a grouping by default per 2 airplanes. - -- AIRDispatcher:SetDefaultGrouping( 2 ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetDefaultGrouping( Grouping ) - - self.DefenderDefault.Grouping = Grouping - - return self - end - - - --- Sets the grouping of new airplanes spawned. - -- Grouping will trigger how new airplanes will be grouped if more than one airplane is spawned for defense. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set a grouping per 2 airplanes. - -- AIRDispatcher:SetSquadronGrouping( "SquadronName", 2 ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetGrouping( Grouping ) - - return self - end - - - --- Sets the engage probability if the squadron will engage on a detected target. - -- This can be configured per squadron, to ensure that each squadron as a specific defensive probability setting. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number EngageProbability The probability when the squadron will consider to engage the detected target. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set an defense probability for squadron SquadronName of 50%. - -- -- This will result that this squadron has 50% chance to engage on a detected target. - -- AIRDispatcher:SetSquadronEngageProbability( "SquadronName", 0.5 ) - -- - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronEngageProbability( SquadronName, EngageProbability ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetEngageProbability( EngageProbability ) - - return self - end - - - --- Defines the default method at which new flights will spawn and take-off as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights by default take-off from the runway. - -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights by default take-off from the airbase hot. - -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights by default take-off from the airbase cold. - -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Cold ) - -- - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetDefaultTakeoff( Takeoff ) - - self.DefenderDefault.Takeoff = Takeoff - - return self - end - - --- Defines the method at which new flights will spawn and take-off as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Air ) - -- - -- -- Let new flights take-off from the runway. - -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Runway ) - -- - -- -- Let new flights take-off from the airbase hot. - -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Hot ) - -- - -- -- Let new flights take-off from the airbase cold. - -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Cold ) - -- - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetTakeoff( Takeoff ) - - return self - end - - - --- Gets the default method at which new flights will spawn and take-off as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- local TakeoffMethod = AIRDispatcher:GetDefaultTakeoff() - -- if TakeOffMethod == , AI_AIR_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_AIR_DISPATCHER:GetDefaultTakeoff( ) - - return self.DefenderDefault.Takeoff - end - - --- Gets the method at which new flights will spawn and take-off as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- local TakeoffMethod = AIRDispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeOffMethod == , AI_AIR_Dispatcher.Takeoff.InAir then - -- ... - -- end - -- - function AI_AIR_DISPATCHER:GetSquadronTakeoff( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron:GetTakeoff() or self.DefenderDefault.Takeoff - end - - - --- Sets flights to default take-off in the air, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off in the air. - -- AIRDispatcher:SetDefaultTakeoffInAir() - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetDefaultTakeoffInAir() - - self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Air ) - - return self - end - - - --- Sets flights to take-off in the air, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- AIRDispatcher:SetSquadronTakeoffInAir( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) - - self:SetSquadronTakeoff( SquadronName, AI_AIR_DISPATCHER.Takeoff.Air ) - - if TakeoffAltitude then - self:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) - end - - return self - end - - - --- Sets flights by default to take-off from the runway, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off from the runway. - -- AIRDispatcher:SetDefaultTakeoffFromRunway() - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetDefaultTakeoffFromRunway() - - self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Runway ) - - return self - end - - - --- Sets flights to take-off from the runway, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from the runway. - -- AIRDispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_AIR_DISPATCHER.Takeoff.Runway ) - - return self - end - - - --- Sets flights by default to take-off from the airbase at a hot location, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default take-off at a hot parking spot. - -- AIRDispatcher:SetDefaultTakeoffFromParkingHot() - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetDefaultTakeoffFromParkingHot() - - self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Hot ) - - return self - end - - --- Sets flights to take-off from the airbase at a hot location, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off in the air. - -- AIRDispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_AIR_DISPATCHER.Takeoff.Hot ) - - return self - end - - - --- Sets flights to by default take-off from the airbase at a cold location, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from a cold parking spot. - -- AIRDispatcher:SetDefaultTakeoffFromParkingCold() - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetDefaultTakeoffFromParkingCold() - - self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Cold ) - - return self - end - - - --- Sets flights to take-off from the airbase at a cold location, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights take-off from a cold parking spot. - -- AIRDispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName ) - - self:SetSquadronTakeoff( SquadronName, AI_AIR_DISPATCHER.Takeoff.Cold ) - - return self - end - - - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. - -- @param #AI_AIR_DISPATCHER self - -- @param #number TakeoffAltitude The altitude in meters above the ground. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- AIRDispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude ) - - self.DefenderDefault.TakeoffAltitude = TakeoffAltitude - - return self - end - - --- Defines the default altitude where airplanes will spawn in the air and take-off as part of the defense system, when the take-off in the air method has been selected. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number TakeoffAltitude The altitude in meters above the ground. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Set the default takeoff altitude when taking off in the air. - -- AIRDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. - -- - -- @return #AI_AIR_DISPATCHER - -- - function AI_AIR_DISPATCHER:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.TakeoffAltitude = TakeoffAltitude - - return self - end - - - --- Defines the default method at which flights will land and despawn as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- AIRDispatcher:SetDefaultLanding( AI_AIR_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights by default despawn after landing land at the runway. - -- AIRDispatcher:SetDefaultLanding( AI_AIR_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. - -- AIRDispatcher:SetDefaultLanding( AI_AIR_Dispatcher.Landing.AtEngineShutdown ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetDefaultLanding( Landing ) - - self.DefenderDefault.Landing = Landing - - return self - end - - - --- Defines the method at which flights will land and despawn as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.NearAirbase ) - -- - -- -- Let new flights despawn after landing land at the runway. - -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.AtRunway ) - -- - -- -- Let new flights despawn after landing and parking, and after engine shutdown. - -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.AtEngineShutdown ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetLanding( Landing ) - - return self - end - - - --- Gets the default method at which flights will land and despawn as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = AIRDispatcher:GetDefaultLanding( AI_AIR_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_AIR_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_AIR_DISPATCHER:GetDefaultLanding() - - return self.DefenderDefault.Landing - end - - - --- Gets the method at which flights will land and despawn as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let new flights despawn near the airbase when returning. - -- local LandingMethod = AIRDispatcher:GetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_AIR_Dispatcher.Landing.NearAirbase then - -- ... - -- end - -- - function AI_AIR_DISPATCHER:GetSquadronLanding( SquadronName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron:GetLanding() or self.DefenderDefault.Landing - end - - - --- Sets flights by default to land and despawn near the airbase in the air, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights by default to land near the airbase and despawn. - -- AIRDispatcher:SetDefaultLandingNearAirbase() - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetDefaultLandingNearAirbase() - - self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.NearAirbase ) - - return self - end - - - --- Sets flights to land and despawn near the airbase in the air, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights to land near the airbase and despawn. - -- AIRDispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_AIR_DISPATCHER.Landing.NearAirbase ) - - return self - end - - - --- Sets flights by default to land and despawn at the runway, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land at the runway and despawn. - -- AIRDispatcher:SetDefaultLandingAtRunway() - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetDefaultLandingAtRunway() - - self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.AtRunway ) - - return self - end - - - --- Sets flights to land and despawn at the runway, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights land at the runway and despawn. - -- AIRDispatcher:SetSquadronLandingAtRunway( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_AIR_DISPATCHER.Landing.AtRunway ) - - return self - end - - - --- Sets flights by default to land and despawn at engine shutdown, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights by default land and despawn at engine shutdown. - -- AIRDispatcher:SetDefaultLandingAtEngineShutdown() - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetDefaultLandingAtEngineShutdown() - - self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - - --- Sets flights to land and despawn at engine shutdown, as part of the defense system. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @usage: - -- - -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) - -- - -- -- Let flights land and despawn at engine shutdown. - -- AIRDispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) - - self:SetSquadronLanding( SquadronName, AI_AIR_DISPATCHER.Landing.AtEngineShutdown ) - - return self - end - - --- Set the default fuel threshold when defenders will RTB or Refuel in the air. - -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. - -- @param #AI_AIR_DISPATCHER self - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) - - self.DefenderDefault.FuelThreshold = FuelThreshold - - return self - end - - - --- Set the fuel threshold for the squadron when defenders will RTB or Refuel in the air. - -- The fuel threshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the % of the threshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetFuelThreshold( FuelThreshold ) - - return self - end - - --- Set the default tanker where defenders will Refuel in the air. - -- @param #AI_AIR_DISPATCHER self - -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the default fuel threshold. - -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the default tanker. - -- AIRDispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. - function AI_AIR_DISPATCHER:SetDefaultTanker( TankerName ) - - self.DefenderDefault.TankerName = TankerName - - return self - end - - - --- Set the squadron tanker where defenders will Refuel in the air. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. - -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) - -- - -- -- Now Setup the squadron fuel threshold. - -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. - -- - -- -- Now Setup the squadron tanker. - -- AIRDispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. - function AI_AIR_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetTankerName( TankerName ) - - return self - end - - - --- Set the frequency of communication and the mode of communication for voice overs. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The name of the squadron. - -- @param #number RadioFrequency The frequency of communication. - -- @param #number RadioModulation The modulation of communication. - -- @param #number RadioPower The power in Watts of communication. - function AI_AIR_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower, Language ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron:SetRadio( RadioFrequency, RadioModulation, RadioPower, DefenderSquadron.Language ) - end - - - -- TODO: Need to model the resources in a squadron. - - -- @param #AI_AIR_DISPATCHER self - -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron - function AI_AIR_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - self.Defenders[ DefenderName ] = Squadron - local SquadronResourceCount = Squadron:GetResourceCount() - if SquadronResourceCount then - Squadron:RemoveResources( Size ) - end - self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) - end - - -- @param #AI_AIR_DISPATCHER self - -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron - function AI_AIR_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - local SquadronResourceCount = Squadron:GetResourceCount() - if SquadronResourceCount then - Squadron:AddResources( Defender:GetSize() ) - end - self.Defenders[ DefenderName ] = nil - self:F( { DefenderName = DefenderName, SquadronResourceCount = SquadronResourceCount } ) - end - - -- @param #AI_AIR_DISPATCHER self - -- @param Wrapper.Group#GROUP Defender - -- @return AI.AI_Air_Squadron#AI_AIR_SQUADRON The Squadron. - function AI_AIR_DISPATCHER:GetSquadronFromDefender( Defender ) - self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - self:F( { DefenderName = DefenderName } ) - return self.Defenders[ DefenderName ] - end - - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:CountPatrolAirborne( SquadronName, DefenseTaskType ) - - local PatrolCount = 0 - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - if DefenderSquadron then - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - if DefenderTask.SquadronName == SquadronName then - if DefenderTask.Type == DefenseTaskType then - if AIGroup:IsAlive() then - -- Check if the Patrol is patrolling or engaging. If not, this is not a valid Patrol, even if it is alive! - -- The Patrol could be damaged, lost control, or out of fuel! - if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" ) - or DefenderTask.Fsm:Is( "Started" ) then - PatrolCount = PatrolCount + 1 - end - end - end - end - end - end - - return PatrolCount - end - - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:CountDefendersEngaged( AttackerDetection, AttackerCount ) - - -- First, count the active AIGroups Units, targeting the DetectedSet - local DefendersEngaged = 0 - local DefendersTotal = 0 - - local AttackerSet = AttackerDetection.Set - local DefendersMissing = AttackerCount - --DetectedSet:Flush() - - local DefenderTasks = self:GetDefenderTasks() - for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do - local Defender = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskTarget = DefenderTask.Target - local DefenderSquadronName = DefenderTask.SquadronName - local DefenderSize = DefenderTask.Size - - -- Count the total of defenders on the battlefield. - --local DefenderSize = Defender:GetInitialSize() - if DefenderTask.Target then - --if DefenderTask.Fsm:Is( "Engaging" ) then - self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize ) - DefendersTotal = DefendersTotal + DefenderSize - if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then - - local SquadronOverhead = self:GetSquadronOverhead( DefenderSquadronName ) - self:F( { SquadronOverhead = SquadronOverhead } ) - if DefenderSize then - DefendersEngaged = DefendersEngaged + DefenderSize - DefendersMissing = DefendersMissing - DefenderSize / SquadronOverhead - self:F( "Defender Group Name: " .. Defender:GetName() .. ", Size: " .. DefenderSize ) - else - DefendersEngaged = 0 - end - end - --end - end - - - end - - for QueueID, QueueItem in pairs( self.DefenseQueue ) do - local QueueItem = QueueItem -- #AI_AIR_DISPATCHER.DefenseQueueItem - if QueueItem.AttackerDetection and QueueItem.AttackerDetection.ItemID == AttackerDetection.ItemID then - DefendersMissing = DefendersMissing - QueueItem.DefendersNeeded / QueueItem.DefenderSquadron.Overhead - --DefendersEngaged = DefendersEngaged + QueueItem.DefenderGrouping - self:F( { QueueItemName = QueueItem.Defense, QueueItem_ItemID = QueueItem.AttackerDetection.ItemID, DetectedItem = AttackerDetection.ItemID, DefendersMissing = DefendersMissing } ) - end - end - - self:F( { DefenderCount = DefendersEngaged } ) - - return DefendersTotal, DefendersEngaged, DefendersMissing - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:CountDefenders( AttackerDetection, DefenderCount, DefenderTaskType ) - - local Friendlies = nil - - local AttackerSet = AttackerDetection.Set - local AttackerCount = AttackerSet:Count() - - local DefenderFriendlies = self:GetDefenderFriendliesNearBy( AttackerDetection ) - - for FriendlyDistance, DefenderFriendlyUnit in UTILS.spairs( DefenderFriendlies or {} ) do - -- We only allow to engage targets as long as the units on both sides are balanced. - if AttackerCount > DefenderCount then - local FriendlyGroup = DefenderFriendlyUnit:GetGroup() -- Wrapper.Group#GROUP - if FriendlyGroup and FriendlyGroup:IsAlive() then - -- Ok, so we have a friendly near the potential target. - -- Now we need to check if the AIGroup has a Task. - local DefenderTask = self:GetDefenderTask( FriendlyGroup ) - if DefenderTask then - -- The Task should be of the same type. - if DefenderTaskType == DefenderTask.Type then - -- If there is no target, then add the AIGroup to the ResultAIGroups for Engagement to the AttackerSet - if DefenderTask.Target == nil then - if DefenderTask.Fsm:Is( "Returning" ) - or DefenderTask.Fsm:Is( "Patrolling" ) then - Friendlies = Friendlies or {} - Friendlies[FriendlyGroup] = FriendlyGroup - DefenderCount = DefenderCount + FriendlyGroup:GetSize() - self:F( { Friendly = FriendlyGroup:GetName(), FriendlyDistance = FriendlyDistance } ) - end - end - end - end - end - else - break - end - end - - return Friendlies - end - - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - local SquadronName = DefenderSquadron.Name - DefendersNeeded = DefendersNeeded or 4 - local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded - - if self:IsSquadronVisible( SquadronName ) then - - -- Here we Patrol the new planes. - -- The Resources table is filled in advance. - local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) -- Choose the template. - - -- We determine the grouping based on the parameters set. - self:F( { DefenderGrouping = DefenderGrouping } ) - - -- New we will form the group to spawn in. - -- We search for the first free resource matching the template. - local DefenderUnitIndex = 1 - local DefenderPatrolTemplate = nil - local DefenderName = nil - for GroupName, DefenderGroup in pairs( DefenderSquadron.Resources[TemplateID] or {} ) do - self:F( { GroupName = GroupName } ) - local DefenderTemplate = _DATABASE:GetGroupTemplate( GroupName ) - if DefenderUnitIndex == 1 then - DefenderPatrolTemplate = UTILS.DeepCopy( DefenderTemplate ) - self.DefenderPatrolIndex = self.DefenderPatrolIndex + 1 - --DefenderPatrolTemplate.name = SquadronName .. "#" .. self.DefenderPatrolIndex .. "#" .. GroupName - DefenderPatrolTemplate.name = GroupName - DefenderName = DefenderPatrolTemplate.name - else - -- Add the unit in the template to the DefenderPatrolTemplate. - local DefenderUnitTemplate = DefenderTemplate.units[1] - DefenderPatrolTemplate.units[DefenderUnitIndex] = DefenderUnitTemplate - end - DefenderPatrolTemplate.units[DefenderUnitIndex].name = string.format( DefenderPatrolTemplate.name .. '-%02d', DefenderUnitIndex ) - DefenderPatrolTemplate.units[DefenderUnitIndex].unitId = nil - DefenderUnitIndex = DefenderUnitIndex + 1 - DefenderSquadron.Resources[TemplateID][GroupName] = nil - if DefenderUnitIndex > DefenderGrouping then - break - end - - end - - if DefenderPatrolTemplate then - local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) - local SpawnGroup = GROUP:Register( DefenderName ) - DefenderPatrolTemplate.lateActivation = nil - DefenderPatrolTemplate.uncontrolled = nil - local Takeoff = self:GetSquadronTakeoff( SquadronName ) - DefenderPatrolTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type - DefenderPatrolTemplate.route.points[1].action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - local Defender = _DATABASE:Spawn( DefenderPatrolTemplate ) - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - Defender:Activate() - return Defender, DefenderGrouping - end - else - local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN - if DefenderGrouping then - Spawn:InitGrouping( DefenderGrouping ) - else - Spawn:InitGrouping() - end - - local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) - local Defender = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP - self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) - return Defender, DefenderGrouping - end - - return nil, nil - end - - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ResourceQueue( Patrol, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName ) - - self:F( { DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName } ) - - local DefenseQueueItem = {} -- #AI_AIR_DISPATCHER.DefenderQueueItem - - - DefenseQueueItem.Patrol = Patrol - DefenseQueueItem.DefenderSquadron = DefenderSquadron - DefenseQueueItem.DefendersNeeded = DefendersNeeded - DefenseQueueItem.Defense = Defense - DefenseQueueItem.DefenseTaskType = DefenseTaskType - DefenseQueueItem.AttackerDetection = AttackerDetection - DefenseQueueItem.SquadronName = SquadronName - - table.insert( self.DefenseQueue, DefenseQueueItem ) - self:F( { QueueItems = #self.DefenseQueue } ) - - end - - - - - --- Shows the tactical display. - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ShowTacticalDisplay( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - local DefenseTotal = 0 - - local Report = REPORT:New( "\nTactical Overview" ) - - local DefenderGroupCount = 0 - local DefendersTotal = 0 - - -- Now that all obsolete tasks are removed, loop through the detected targets. - --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do - - if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - - self:F( { "Target ID", DetectedItem.ItemID } ) - - self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) - DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local DetectionIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - -- Show tactical situation - local ThreatLevel = DetectedItem.Set:CalculateThreatLevelAIR() - Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender:IsAlive() then - DefenderGroupCount = DefenderGroupCount + 1 - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", - Defender:GetName(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - end - end - - Report:Add( "\n - No Targets:") - local TaskCount = 0 - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - TaskCount = TaskCount + 1 - local Defender = Defender -- Wrapper.Group#GROUP - if not DefenderTask.Target then - if Defender:IsAlive() then - local DefenderHasTask = Defender:HasTask() - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", - Defender:GetName(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - Report:Add( string.format( "\n - %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) - - Report:Add( string.format( "\n - %d Queued Aircraft Launches", #self.DefenseQueue ) ) - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - local DefenseQueueItem = DefenseQueueItem -- #AI_AIR_DISPATCHER.DefenseQueueItem - Report:Add( string.format( " - %s - %s", DefenseQueueItem.SquadronName, DefenseQueueItem.DefenderSquadron.TakeoffTime, DefenseQueueItem.DefenderSquadron.TakeoffInterval) ) - - end - - Report:Add( string.format( "\n - Squadron Resources: ", #self.DefenseQueue ) ) - for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - Report:Add( string.format( " - %s - %d", DefenderSquadronName, DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount or "n/a" ) ) - end - - self:F( Report:Text( "\n" ) ) - trigger.action.outText( Report:Text( "\n" ), 25 ) - - end - -end - - -do - - --- Calculates which HUMAN friendlies are nearby the area. - -- @param #AI_AIR_DISPATCHER self - -- @param DetectedItem The detected item. - -- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type. - function AI_AIR_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem ) - - local PlayerTypes = {} - local PlayersCount = 0 - - if PlayersNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelAIR() - for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do - local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT - local PlayerName = PlayerUnit:GetPlayerName() - --self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) - if PlayerUnit:IsAirPlane() and PlayerName ~= nil then - local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() - PlayersCount = PlayersCount + 1 - local PlayerType = PlayerUnit:GetTypeName() - PlayerTypes[PlayerName] = PlayerType - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then - end - end - end - - end - - --self:F( { PlayersCount = PlayersCount } ) - - local PlayerTypesReport = REPORT:New() - - if PlayersCount > 0 then - for PlayerName, PlayerType in pairs( PlayerTypes ) do - PlayerTypesReport:Add( string.format('"%s" in %s', PlayerName, PlayerType ) ) - end - else - PlayerTypesReport:Add( "-" ) - end - - - return PlayersCount, PlayerTypesReport - end - - --- Calculates which friendlies are nearby the area. - -- @param #AI_AIR_DISPATCHER self - -- @param DetectedItem The detected item. - -- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type. - function AI_AIR_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem ) - - local FriendlyTypes = {} - local FriendliesCount = 0 - - if FriendlyUnitsNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelAIR() - for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do - local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT - if FriendlyUnit:IsAirPlane() then - local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() - FriendliesCount = FriendliesCount + 1 - local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then - end - end - end - - end - - --self:F( { FriendliesCount = FriendliesCount } ) - - local FriendlyTypesReport = REPORT:New() - - if FriendliesCount > 0 then - for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) - end - else - FriendlyTypesReport:Add( "-" ) - end - - - return FriendliesCount, FriendlyTypesReport - end - - -end - diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua deleted file mode 100644 index 6a8472ace..000000000 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ /dev/null @@ -1,603 +0,0 @@ ---- **AI** - Models the process of air to ground engagement for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_A2G_Dispatcher}. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Air_Engage --- @image AI_Air_To_Ground_Engage.JPG - - - --- @type AI_AIR_ENGAGE --- @extends AI.AI_AIR#AI_AIR - - ---- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- --- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event. --- --- 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. --- --- This cycle will continue. --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- 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. --- --- ## 1. AI_AIR_ENGAGE constructor --- --- * @{#AI_AIR_ENGAGE.New}(): Creates a new AI_AIR_ENGAGE object. --- --- ## 2. Set the Zone of Engagement --- --- 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.AI_CAP#AI_AIR_ENGAGE.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 - -- --- # 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_AIR_ENGAGE -AI_AIR_ENGAGE = { - ClassName = "AI_AIR_ENGAGE", -} - - - ---- Creates a new AI_AIR_ENGAGE object --- @param #AI_AIR_ENGAGE self --- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM. --- @param Wrapper.Group#GROUP AIGroup The AI group. --- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. --- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". --- @return #AI_AIR_ENGAGE -function AI_AIR_ENGAGE:New( AI_Air, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_ENGAGE - - self.Accomplished = false - self.Engaging = false - - local SpeedMax = AIGroup:GetSpeedMax() - - self.EngageMinSpeed = EngageMinSpeed or SpeedMax * 0.5 - self.EngageMaxSpeed = EngageMaxSpeed or SpeedMax * 0.75 - self.EngageFloorAltitude = EngageFloorAltitude or 1000 - self.EngageCeilingAltitude = EngageCeilingAltitude or 1500 - self.EngageAltType = EngageAltType or "RADIO" - - self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "EngageRoute", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event EngageRoute. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngageRoute - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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 EngageRoute. - -- @function [parent=#AI_AIR_ENGAGE] OnAfterEngageRoute - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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 EngageRoute. - -- @function [parent=#AI_AIR_ENGAGE] EngageRoute - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event EngageRoute. - -- @function [parent=#AI_AIR_ENGAGE] __EngageRoute - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] OnEnterEngaging --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group 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( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngage - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] OnAfterEngage - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] Engage - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_AIR_ENGAGE] __Engage - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] OnEnterEngaging --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeFired - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] OnAfterFired - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] Fired - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_AIR_ENGAGE] __Fired - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeDestroy - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] OnAfterDestroy - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] Destroy - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_AIR_ENGAGE] __Destroy - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeAbort - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] OnAfterAbort - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] Abort - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_AIR_ENGAGE] __Abort - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_AIR_ENGAGE] OnBeforeAccomplish - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] OnAfterAccomplish - -- @param #AI_AIR_ENGAGE self - -- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE] Accomplish - -- @param #AI_AIR_ENGAGE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_AIR_ENGAGE] __Accomplish - -- @param #AI_AIR_ENGAGE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" ) - - return self -end - ---- onafter event handler for Start event. --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The AI group 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_AIR_ENGAGE:onafterStart( AIGroup, From, Event, To ) - - self:GetParent( self, AI_AIR_ENGAGE ).onafterStart( self, AIGroup, From, Event, To ) - - AIGroup:HandleEvent( EVENTS.Takeoff, nil, self ) - -end - - - ---- onafter event handler for Engage event. --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The AI Group 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_AIR_ENGAGE:onafterEngage( AIGroup, From, Event, To ) - -- TODO: This function is overwritten below! - self:HandleEvent( EVENTS.Dead ) -end - --- todo: need to fix this global function - - ---- onbefore event handler for Engage event. --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The group 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_AIR_ENGAGE:onbeforeEngage( AIGroup, From, Event, To ) - if self.Accomplished == true then - return false - end - return true -end - ---- onafter event handler for Abort event. --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The AI Group 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_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To ) - AIGroup:ClearTasks() - self:Return() -end - - --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE:onafterAccomplish( AIGroup, From, Event, To ) - self.Accomplished = true - --self:SetDetectionOff() -end - --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP AIGroup The Group 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_AIR_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData ) - - if EventData.IniUnit then - self.AttackUnits[EventData.IniUnit] = nil - end -end - --- @param #AI_AIR_ENGAGE self --- @param Core.Event#EVENTDATA EventData -function AI_AIR_ENGAGE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then - self:__Destroy( self.TaskDelay, EventData ) - end - end -end - - --- @param Wrapper.Group#GROUP AIControllable -function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit ) - Fsm:T(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName()))) - - if AIGroup and AIGroup:IsAlive() then - Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit ) - end -end - - --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup 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.Set#SET_UNIT AttackSetUnit Unit set to be attacked. -function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:T( { DefenderGroup, From, Event, To, AttackSetUnit } ) - - local DefenderGroupName = DefenderGroup:GetName() - - self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - - local AttackCount = AttackSetUnit:CountAlive() - - if AttackCount > 0 then - - if DefenderGroup:IsAlive() then - - local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) - local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - - -- Determine the distance to the target. - -- If it is less than 10km, then attack without a route. - -- Otherwise perform a route attack. - - local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetUnit = AttackSetUnit:GetRandomSurely() - local TargetCoord = nil - - if TargetUnit then - TargetUnit:GetPointVec3() - end - - if TargetCoord == nil then - self:Return() - return - end - - TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) - local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) - - -- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes! - if TargetDistance <= EngageDistance * 9 then - - --self:T(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000)) - self:__Engage( 0.1, AttackSetUnit ) - - else - - --self:T(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000)) - - local EngageRoute = {} - local AttackTasks = {} - - --- Calculate the target route point. - - local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) - - EngageRoute[#EngageRoute+1] = FromWP - - self:SetTargetDistance( TargetCoord ) -- For RTB status check - - local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) - local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ) - - local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) - - EngageRoute[#EngageRoute+1] = ToWP - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___EngageRoute", self, AttackSetUnit ) - EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) - - DefenderGroup:OptionROEReturnFire() - DefenderGroup:OptionROTEvadeFire() - - DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 ) - end - - end - else - -- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling! - self:T( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - end -end - - --- @param Wrapper.Group#GROUP AIControllable -function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit ) - - Fsm:T(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName()))) - - if AIGroup and AIGroup:IsAlive() then - local delay=Fsm.TaskDelay or 0.1 - Fsm:__Engage(delay, AttackSetUnit) - end -end - - --- @param #AI_AIR_ENGAGE self --- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup 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.Set#SET_UNIT AttackSetUnit Set of units to be attacked. -function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) - - local DefenderGroupName = DefenderGroup:GetName() - - self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - - local AttackCount = AttackSetUnit:CountAlive() - self:T({AttackCount = AttackCount}) - - if AttackCount > 0 then - - if DefenderGroup and DefenderGroup:IsAlive() then - - local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 ) - local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - - local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetCoord = nil - - local TargetUnit = AttackSetUnit:GetRandomSurely() - if TargetUnit then - TargetCoord=TargetUnit:GetPointVec3() - end - if not TargetCoord then - self:Return() - return - end - TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) - - local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) - - local EngageRoute = {} - local AttackTasks = {} - - local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) - EngageRoute[#EngageRoute+1] = FromWP - - self:SetTargetDistance( TargetCoord ) -- For RTB status check - - local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) - local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ) - - local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) - EngageRoute[#EngageRoute+1] = ToWP - - -- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task! - if TargetDistance <= EngageDistance * 9 then - - local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic - - if #AttackUnitTasks == 0 then - self:T( DefenderGroupName .. ": No valid targets found -> Going RTB") - self:Return() - return - else - local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance)) - self:T(text) - DefenderGroup:OptionROEOpenFire() - DefenderGroup:OptionROTEvadeFire() - DefenderGroup:OptionKeepWeaponsOnThreat() - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks ) - end - end - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___Engage", self, AttackSetUnit ) - EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) - - DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 ) - - end - else - -- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling! - self:T( DefenderGroupName .. ": No targets found -> returning.") - self:Return() - return - end -end - --- @param Wrapper.Group#GROUP AIEngage -function AI_AIR_ENGAGE.Resume( AIEngage, Fsm ) - - AIEngage:F( { "Resume:", AIEngage:GetName() } ) - if AIEngage and AIEngage:IsAlive() then - Fsm:__Reset( Fsm.TaskDelay or 0.1 ) - Fsm:__EngageRoute( Fsm.TaskDelay or 0.2, Fsm.AttackSetUnit ) - end - -end diff --git a/Moose Development/Moose/AI/AI_Air_Patrol.lua b/Moose Development/Moose/AI/AI_Air_Patrol.lua deleted file mode 100644 index 9950740a8..000000000 --- a/Moose Development/Moose/AI/AI_Air_Patrol.lua +++ /dev/null @@ -1,391 +0,0 @@ ---- **AI** - Models the process of A2G patrolling and engaging ground targets for airplanes and helicopters. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Air_Patrol --- @image AI_Air_To_Ground_Patrol.JPG - --- @type AI_AIR_PATROL --- @extends AI.AI_Air#AI_AIR - ---- The AI_AIR_PATROL class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} --- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- --- ![Process](..\Presentations\AI_CAP\Dia3.JPG) --- --- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL 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_AIR_PATROL constructor --- --- * @{#AI_AIR_PATROL.New}(): Creates a new AI_AIR_PATROL object. --- --- ## 2. AI_AIR_PATROL is a FSM --- --- ![Process](..\Presentations\AI_CAP\Dia2.JPG) --- --- ### 2.1 AI_AIR_PATROL 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_AIR_PATROL Events --- --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI.AI_Patrol#AI_PATROL_ZONE.PatrolRoute}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_AIR_PATROL.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_AIR_PATROL.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_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_AIR_PATROL.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_AIR_PATROL.SetEngageRange}() to define that range. --- --- # 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_AIR_PATROL -AI_AIR_PATROL = { - ClassName = "AI_AIR_PATROL", -} - ---- Creates a new AI_AIR_PATROL object --- @param #AI_AIR_PATROL self --- @param AI.AI_Air#AI_AIR AI_Air The AI_AIR FSM. --- @param Wrapper.Group#GROUP AIGroup The AI group. --- @param Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @param DCS#Altitude PatrolFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the patrol. --- @param DCS#Speed PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h. --- @param DCS#Speed PatrolMaxSpeed (optional, default = 75% of max speed) 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_AIR_PATROL -function AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_PATROL - - local SpeedMax = AIGroup:GetSpeedMax() - - self.PatrolZone = PatrolZone - - self.PatrolFloorAltitude = PatrolFloorAltitude or 1000 - self.PatrolCeilingAltitude = PatrolCeilingAltitude or 1500 - self.PatrolMinSpeed = PatrolMinSpeed or SpeedMax * 0.5 - self.PatrolMaxSpeed = PatrolMaxSpeed or SpeedMax * 0.75 - - -- defafult PatrolAltType to "RADIO" if not specified - self.PatrolAltType = PatrolAltType or "RADIO" - - self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" ) - - --- OnBefore Transition Handler for Event Patrol. - -- @function [parent=#AI_AIR_PATROL] OnBeforePatrol - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group 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 Patrol. - -- @function [parent=#AI_AIR_PATROL] OnAfterPatrol - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group 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 Patrol. - -- @function [parent=#AI_AIR_PATROL] Patrol - -- @param #AI_AIR_PATROL self - - --- Asynchronous Event Trigger for Event Patrol. - -- @function [parent=#AI_AIR_PATROL] __Patrol - -- @param #AI_AIR_PATROL self - -- @param #number Delay The delay in seconds. - - --- OnLeave Transition Handler for State Patrolling. - -- @function [parent=#AI_AIR_PATROL] OnLeavePatrolling - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group 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 Patrolling. - -- @function [parent=#AI_AIR_PATROL] OnEnterPatrolling - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group 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( "Patrolling", "PatrolRoute", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL. - - --- OnBefore Transition Handler for Event PatrolRoute. - -- @function [parent=#AI_AIR_PATROL] OnBeforePatrolRoute - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group 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 PatrolRoute. - -- @function [parent=#AI_AIR_PATROL] OnAfterPatrolRoute - -- @param #AI_AIR_PATROL self - -- @param Wrapper.Group#GROUP AIPatrol The Group 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 PatrolRoute. - -- @function [parent=#AI_AIR_PATROL] PatrolRoute - -- @param #AI_AIR_PATROL self - - --- Asynchronous Event Trigger for Event PatrolRoute. - -- @function [parent=#AI_AIR_PATROL] __PatrolRoute - -- @param #AI_AIR_PATROL self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL. - - return self -end - ---- Set the Engage Range when the AI will engage with airborne enemies. --- @param #AI_AIR_PATROL self --- @param #number EngageRange The Engage Range. --- @return #AI_AIR_PATROL self -function AI_AIR_PATROL:SetEngageRange( EngageRange ) - self:F2() - - if EngageRange then - self.EngageRange = EngageRange - else - self.EngageRange = nil - end -end - ---- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone. --- @param #AI_AIR_PATROL self --- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m. --- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m. --- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. --- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North. --- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage. --- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage. --- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. --- @return #AI_AIR_PATROL self -function AI_AIR_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) - - self.racetrack=true - self.racetracklegmin=LegMin or 10000 - self.racetracklegmax=LegMax or 15000 - self.racetrackheadingmin=HeadingMin or 0 - self.racetrackheadingmax=HeadingMax or 180 - self.racetrackdurationmin=DurationMin - self.racetrackdurationmax=DurationMax - - if self.racetrackdurationmax and not self.racetrackdurationmin then - self.racetrackdurationmin=self.racetrackdurationmax - end - - self.racetrackcapcoordinates=CapCoordinates - -end - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_AIR_PATROL self --- @return #AI_AIR_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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_AIR_PATROL:onafterPatrol( AIPatrol, From, Event, To ) - self:F2() - - self:ClearTargetDistance() - - self:__PatrolRoute( self.TaskDelay ) - - AIPatrol:OnReSpawn( - function( PatrolGroup ) - self:__Reset( self.TaskDelay ) - self:__PatrolRoute( self.TaskDelay ) - end - ) -end - ---- This static method is called from the route path within the last task at the last waypoint of the AIPatrol. --- Note that this method is required, as triggers the next route when patrolling for the AIPatrol. --- @param Wrapper.Group#GROUP AIPatrol The AI group. --- @param #AI_AIR_PATROL Fsm The FSM. -function AI_AIR_PATROL.___PatrolRoute( AIPatrol, Fsm ) - - AIPatrol:F( { "AI_AIR_PATROL.___PatrolRoute:", AIPatrol:GetName() } ) - - if AIPatrol and AIPatrol:IsAlive() then - Fsm:PatrolRoute() - end - -end - ---- Defines a new patrol route using the @{AI.AI_Patrol#AI_PATROL_ZONE} parameters and settings. --- @param #AI_AIR_PATROL self --- @param Wrapper.Group#GROUP AIPatrol The Group 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_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) - - self:F2() - - -- When RTB, don't allow anymore the routing. - if From == "RTB" then - return - end - - if AIPatrol and AIPatrol:IsAlive() then - - local PatrolRoute = {} - - --- Calculate the target route point. - - local CurrentCoord = AIPatrol:GetCoordinate() - - local altitude= math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - - local ToTargetCoord = self.PatrolZone:GetRandomPointVec2() - ToTargetCoord:SetAlt( altitude ) - self:SetTargetDistance( ToTargetCoord ) -- For RTB status check - - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - local speedkmh=ToTargetSpeed - - local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true) - PatrolRoute[#PatrolRoute+1] = FromWP - - if self.racetrack then - - -- Random heading. - local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax) - - -- Random leg length. - local leg=math.random(self.racetracklegmin, self.racetracklegmax) - - -- Random duration if any. - local duration = self.racetrackdurationmin - if self.racetrackdurationmax then - duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax) - end - - -- CAP coordinate. - local c0=self.PatrolZone:GetRandomCoordinate() - if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then - c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] - end - - -- Race track points. - local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE - local c2=c1:Translate(leg, heading):SetAltitude(altitude) - - self:SetTargetDistance(c0) -- For RTB status check - - -- Debug: - self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration))) - --c1:MarkToAll("Race track c1") - --c2:MarkToAll("Race track c2") - - -- Task to orbit. - local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2) - - -- Task function to redo the patrol at other random position. - local taskPatrol=AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self) - - -- Controlled task with task condition. - local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil) - local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond) - - -- Second waypoint - PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit") - - else - - --- Create a route point of type air. - local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true) - PatrolRoute[#PatrolRoute+1] = ToWP - - local Tasks = {} - Tasks[#Tasks+1] = AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self) - PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks ) - - end - - AIPatrol:OptionROEReturnFire() - AIPatrol:OptionROTEvadeFire() - - AIPatrol:Route( PatrolRoute, self.TaskDelay ) - - end - -end - ---- Resumes the AIPatrol --- @param Wrapper.Group#GROUP AIPatrol --- @param Core.Fsm#FSM Fsm -function AI_AIR_PATROL.Resume( AIPatrol, Fsm ) - - AIPatrol:F( { "AI_AIR_PATROL.Resume:", AIPatrol:GetName() } ) - if AIPatrol and AIPatrol:IsAlive() then - Fsm:__Reset( Fsm.TaskDelay ) - Fsm:__PatrolRoute( Fsm.TaskDelay ) - end - -end diff --git a/Moose Development/Moose/AI/AI_Air_Squadron.lua b/Moose Development/Moose/AI/AI_Air_Squadron.lua deleted file mode 100644 index 6651a92a5..000000000 --- a/Moose Development/Moose/AI/AI_Air_Squadron.lua +++ /dev/null @@ -1,294 +0,0 @@ ---- **AI** - Models squadrons for airplanes and helicopters. --- --- This is a class used in the @{AI.AI_Air_Dispatcher} and derived dispatcher classes. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Air_Squadron --- @image MOOSE.JPG - - - --- @type AI_AIR_SQUADRON --- @extends Core.Base#BASE - - ---- Implements the core functions modeling squadrons for airplanes and helicopters. --- --- # 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_AIR_SQUADRON -AI_AIR_SQUADRON = { - ClassName = "AI_AIR_SQUADRON", -} - - - ---- Creates a new AI_AIR_SQUADRON object --- @param #AI_AIR_SQUADRON self --- @return #AI_AIR_SQUADRON -function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) - - self:T( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) - - local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON - - AI_Air_Squadron.Name = SquadronName - AI_Air_Squadron.Airbase = AIRBASE:FindByName( AirbaseName ) - AI_Air_Squadron.AirbaseName = AI_Air_Squadron.Airbase:GetName() - if not AI_Air_Squadron.Airbase then - error( "Cannot find airbase with name:" .. AirbaseName ) - end - - AI_Air_Squadron.Spawn = {} - if type( TemplatePrefixes ) == "string" then - local SpawnTemplate = TemplatePrefixes - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - AI_Air_Squadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate] - else - for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - AI_Air_Squadron.Spawn[#AI_Air_Squadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] - end - end - AI_Air_Squadron.ResourceCount = ResourceCount - AI_Air_Squadron.TemplatePrefixes = TemplatePrefixes - AI_Air_Squadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. - - self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default. - - return AI_Air_Squadron -end - ---- Set the Name of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #string Name The Squadron Name. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetName( Name ) - - self.Name = Name - - return self -end - ---- Get the Name of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #string The Squadron Name. -function AI_AIR_SQUADRON:GetName() - - return self.Name -end - ---- Set the ResourceCount of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number ResourceCount The Squadron ResourceCount. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetResourceCount( ResourceCount ) - - self.ResourceCount = ResourceCount - - return self -end - ---- Get the ResourceCount of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron ResourceCount. -function AI_AIR_SQUADRON:GetResourceCount() - - return self.ResourceCount -end - ---- Add Resources to the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Resources The Resources to be added. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:AddResources( Resources ) - - self.ResourceCount = self.ResourceCount + Resources - - return self -end - ---- Remove Resources to the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Resources The Resources to be removed. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:RemoveResources( Resources ) - - self.ResourceCount = self.ResourceCount - Resources - - return self -end - ---- Set the Overhead of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Overhead The Squadron Overhead. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetOverhead( Overhead ) - - self.Overhead = Overhead - - return self -end - ---- Get the Overhead of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron Overhead. -function AI_AIR_SQUADRON:GetOverhead() - - return self.Overhead -end - ---- Set the Grouping of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Grouping The Squadron Grouping. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetGrouping( Grouping ) - - self.Grouping = Grouping - - return self -end - ---- Get the Grouping of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron Grouping. -function AI_AIR_SQUADRON:GetGrouping() - - return self.Grouping -end - ---- Set the FuelThreshold of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number FuelThreshold The Squadron FuelThreshold. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetFuelThreshold( FuelThreshold ) - - self.FuelThreshold = FuelThreshold - - return self -end - ---- Get the FuelThreshold of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron FuelThreshold. -function AI_AIR_SQUADRON:GetFuelThreshold() - - return self.FuelThreshold -end - ---- Set the EngageProbability of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number EngageProbability The Squadron EngageProbability. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetEngageProbability( EngageProbability ) - - self.EngageProbability = EngageProbability - - return self -end - ---- Get the EngageProbability of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron EngageProbability. -function AI_AIR_SQUADRON:GetEngageProbability() - - return self.EngageProbability -end - ---- Set the Takeoff of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Takeoff The Squadron Takeoff. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetTakeoff( Takeoff ) - - self.Takeoff = Takeoff - - return self -end - ---- Get the Takeoff of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron Takeoff. -function AI_AIR_SQUADRON:GetTakeoff() - - return self.Takeoff -end - ---- Set the Landing of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number Landing The Squadron Landing. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetLanding( Landing ) - - self.Landing = Landing - - return self -end - ---- Get the Landing of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #number The Squadron Landing. -function AI_AIR_SQUADRON:GetLanding() - - return self.Landing -end - ---- Set the TankerName of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #string TankerName The Squadron Tanker Name. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetTankerName( TankerName ) - - self.TankerName = TankerName - - return self -end - ---- Get the Tanker Name of the Squadron. --- @param #AI_AIR_SQUADRON self --- @return #string The Squadron Tanker Name. -function AI_AIR_SQUADRON:GetTankerName() - - return self.TankerName -end - - ---- Set the Radio of the Squadron. --- @param #AI_AIR_SQUADRON self --- @param #number RadioFrequency The frequency of communication. --- @param #number RadioModulation The modulation of communication. --- @param #number RadioPower The power in Watts of communication. --- @param #string Language The language of the radio speech. --- @return #AI_AIR_SQUADRON The Squadron. -function AI_AIR_SQUADRON:SetRadio( RadioFrequency, RadioModulation, RadioPower, Language ) - - self.RadioFrequency = RadioFrequency - self.RadioModulation = RadioModulation or radio.modulation.AM - self.RadioPower = RadioPower or 100 - - if self.RadioSpeech then - self.RadioSpeech:Stop() - end - - self.RadioSpeech = nil - - self.RadioSpeech = RADIOSPEECH:New( RadioFrequency, RadioModulation ) - self.RadioSpeech.power = RadioPower - self.RadioSpeech:Start( 0.5 ) - - self.RadioSpeech:SetLanguage( Language ) - - return self -end - - diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua deleted file mode 100644 index 65fd78645..000000000 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ /dev/null @@ -1,652 +0,0 @@ ---- **AI** - Peform Battlefield Area Interdiction (BAI) within an engagement zone. --- --- **Features:** --- --- * Hold and standby within a patrol zone. --- * Engage upon command the assigned targets within an engagement zone. --- * Loop the zone until all targets are eliminated. --- * Trigger different events upon the results achieved. --- * After combat, return to the patrol zone and hold. --- * RTB when commanded or after out of fuel. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_BAI) --- --- === --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Gunterlund**: Test case revision. --- --- === --- --- @module AI.AI_BAI --- @image AI_Battlefield_Air_Interdiction.JPG - - ---- AI_BAI_ZONE class --- @type AI_BAI_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 provide BattleGround Air Interdiction in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}. --- --- The AI_BAI_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone. --- --- ![HoldAndEngage](..\Presentations\AI_BAI\Dia3.JPG) --- --- The AI_BAI_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_BAI_ZONE process can be started through the **Start** event. --- --- ![Start Event](..\Presentations\AI_BAI\Dia4.JPG) --- --- Upon started, The AI will **Route** itself towards the random 3D point within a 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. --- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- --- ![Route Event](..\Presentations\AI_BAI\Dia5.JPG) --- --- When the AI is commanded to provide BattleGround Air Interdiction (through the event **Engage**), the AI will fly towards the Engage Zone. --- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia6.JPG) --- --- The AI will detect the targets and will only destroy the targets within the Engage Zone. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia7.JPG) --- --- Every target that is destroyed, is reported< by the AI. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia8.JPG) --- --- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia9.JPG) --- --- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party: --- --- * a FAC --- * a timed event --- * a menu option selected by a human --- * a condition --- * others ... --- --- ![Engage Event](..\Presentations\AI_BAI\Dia10.JPG) --- --- When the AI has accomplished the Bombing, it will fly back to the Patrol Zone. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia11.JPG) --- --- It will keep patrolling there, until it is notified to RTB or move to another BOMB Zone. --- It can be notified to go RTB through the **RTB** event. --- --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Engage Event](..\Presentations\AI_BAI\Dia12.JPG) --- --- # 1. AI_BAI_ZONE constructor --- --- * @{#AI_BAI_ZONE.New}(): Creates a new AI_BAI_ZONE object. --- --- ## 2. AI_BAI_ZONE is a FSM --- --- ![Process](..\Presentations\AI_BAI\Dia2.JPG) --- --- ### 2.1. AI_BAI_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 targets in the Engage Zone, executing BOMB. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2. AI_BAI_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_BAI_ZONE.Engage}**: Engage the AI to provide BOMB in the Engage Zone, destroying any target it finds. --- * **@{#AI_BAI_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_BAI_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. --- * **@{#AI_BAI_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the BOMB task. --- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Modify the Engage Zone behaviour to pinpoint a **map object** or **scenery object** --- --- Use the method @{#AI_BAI_ZONE.SearchOff}() to specify that the EngageZone is not to be searched for potential targets (UNITs), but that the center of the zone --- is the point where a map object is to be destroyed (like a bridge). --- --- Example: --- --- -- Tell the BAI not to search for potential targets in the BAIEngagementZone, but rather use the center of the BAIEngagementZone as the bombing location. --- AIBAIZone:SearchOff() --- --- Searching can be switched back on with the method @{#AI_BAI_ZONE.SearchOn}(). Use the method @{#AI_BAI_ZONE.SearchOnOff}() to flexibily switch searching on or off. --- --- # 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_BAI_ZONE -AI_BAI_ZONE = { - ClassName = "AI_BAI_ZONE", -} - - - ---- Creates a new AI_BAI_ZONE object --- @param #AI_BAI_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 Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_BAI_ZONE self -function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_BAI_ZONE - - self.EngageZone = EngageZone - self.Accomplished = false - - self:SetDetectionZone( self.EngageZone ) - self:SearchOn() - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_BAI_ZONE] OnBeforeEngage - -- @param #AI_BAI_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_BAI_ZONE] OnAfterEngage - -- @param #AI_BAI_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_BAI_ZONE] Engage - -- @param #AI_BAI_ZONE self - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. - -- If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_BAI_ZONE] __Engage - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. - -- If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_BAI_ZONE] OnLeaveEngaging --- @param #AI_BAI_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_BAI_ZONE] OnEnterEngaging --- @param #AI_BAI_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", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_BAI_ZONE] OnBeforeFired - -- @param #AI_BAI_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_BAI_ZONE] OnAfterFired - -- @param #AI_BAI_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_BAI_ZONE] Fired - -- @param #AI_BAI_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_BAI_ZONE] __Fired - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_BAI_ZONE] OnBeforeDestroy - -- @param #AI_BAI_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_BAI_ZONE] OnAfterDestroy - -- @param #AI_BAI_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_BAI_ZONE] Destroy - -- @param #AI_BAI_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_BAI_ZONE] __Destroy - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_BAI_ZONE] OnBeforeAbort - -- @param #AI_BAI_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_BAI_ZONE] OnAfterAbort - -- @param #AI_BAI_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_BAI_ZONE] Abort - -- @param #AI_BAI_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_BAI_ZONE] __Abort - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_BAI_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_BAI_ZONE] OnBeforeAccomplish - -- @param #AI_BAI_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_BAI_ZONE] OnAfterAccomplish - -- @param #AI_BAI_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_BAI_ZONE] Accomplish - -- @param #AI_BAI_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_BAI_ZONE] __Accomplish - -- @param #AI_BAI_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - - ---- Set the Engage Zone where the AI is performing BOMB. Note that if the EngageZone is changed, the AI needs to re-detect targets. --- @param #AI_BAI_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing BOMB. --- @return #AI_BAI_ZONE self -function AI_BAI_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - - ---- Specifies whether to search for potential targets in the zone, or let the center of the zone be the bombing coordinate. --- AI_BAI_ZONE will search for potential targets by default. --- @param #AI_BAI_ZONE self --- @return #AI_BAI_ZONE -function AI_BAI_ZONE:SearchOnOff( Search ) - - self.Search = Search - - return self -end - ---- If Search is Off, the current zone coordinate will be the center of the bombing. --- @param #AI_BAI_ZONE self --- @return #AI_BAI_ZONE -function AI_BAI_ZONE:SearchOff() - - self:SearchOnOff( false ) - - return self -end - - ---- If Search is On, BAI will search for potential targets in the zone. --- @param #AI_BAI_ZONE self --- @return #AI_BAI_ZONE -function AI_BAI_ZONE:SearchOn() - - self:SearchOnOff( true ) - - return self -end - - ---- onafter State Transition for Event Start. --- @param #AI_BAI_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_BAI_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 ) - - self:SetDetectionDeactivated() -- When not engaging, set the detection off. -end - --- @param Wrapper.Controllable#CONTROLLABLE AIControllable -function _NewEngageRoute( AIControllable ) - - AIControllable:T( "NewEngageRoute" ) - local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_BAI#AI_BAI_ZONE - EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection ) -end - - --- @param #AI_BAI_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_BAI_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - --- @param #AI_BAI_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_BAI_ZONE:onafterTarget( Controllable, From, Event, To ) - self:F({"onafterTarget",self.Search,Controllable:IsAlive()}) - - - - if Controllable:IsAlive() then - - local AttackTasks = {} - - if self.Search == true then - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - if Detected == true then - self:F( {"Target: ", DetectedUnit } ) - self.DetectedUnits[DetectedUnit] = false - local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) - self.Controllable:PushTask( AttackTask, 1 ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - else - self:F("Attack zone") - local AttackTask = Controllable:TaskAttackMapObject( - self.EngageZone:GetPointVec2():GetVec2(), - true, - self.EngageWeaponExpend, - self.EngageAttackQty, - self.EngageDirection, - self.EngageAltitude - ) - self.Controllable:PushTask( AttackTask, 1 ) - end - - self:__Target( -10 ) - - end -end - - --- @param #AI_BAI_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_BAI_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - --- @param #AI_BAI_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 #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. --- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. --- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, - EngageSpeed, - EngageAltitude, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection ) - - self:F("onafterEngage") - - self.EngageSpeed = EngageSpeed or 400 - self.EngageAltitude = EngageAltitude or 2000 - self.EngageWeaponExpend = EngageWeaponExpend - self.EngageAttackQty = EngageAttackQty - self.EngageDirection = EngageDirection - - if Controllable:IsAlive() then - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - local AttackTasks = {} - - if self.Search == true then - - for DetectedUnitID, DetectedUnitData in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - self:T( DetectedUnit ) - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - self:F( {"Engaging ", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskBombing( - DetectedUnit:GetPointVec2():GetVec2(), - true, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection, - EngageAltitude - ) - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - else - self:F("Attack zone") - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackMapObject( - self.EngageZone:GetPointVec2():GetVec2(), - true, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection, - EngageAltitude - ) - end - - EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks ) - - --- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in EngageZone. - local ToTargetVec2 = self.EngageZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToTargetRoutePoint - - Controllable:OptionROEOpenFire() - Controllable:OptionROTVertical() - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - Controllable:WayPointInitialize( EngageRoute ) - - --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... - Controllable:SetState( Controllable, "EngageZone", self ) - - Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" ) - - --- NOW ROUTE THE GROUP! - Controllable:WayPointExecute( 1 ) - - self:SetRefreshTimeInterval( 2 ) - self:SetDetectionActivated() - self:__Target( -2 ) -- Start targeting - end -end - - --- @param #AI_BAI_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_BAI_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionDeactivated() -end - - --- @param #AI_BAI_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_BAI_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - - --- @param #AI_BAI_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_BAI_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 - - diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua deleted file mode 100644 index 6499a7fd5..000000000 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ /dev/null @@ -1,314 +0,0 @@ ---- **AI** - Balance player slots with AI to create an engaging simulation environment, independent of the amount of players. --- --- **Features:** --- --- * Automatically spawn AI as a replacement of free player slots for a coalition. --- * Make the AI to perform tasks. --- * Define a maximum amount of AI to be active at the same time. --- * Configure the behaviour of AI when a human joins a slot for which an AI is active. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Balancer) --- --- === --- --- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl2CJVIrL1TdAumuVS8n64B7) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Dutch_Baron**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- --- === --- --- @module AI.AI_Balancer --- @image AI_Balancing.JPG - --- @type AI_BALANCER --- @field Core.Set#SET_CLIENT SetClient --- @field Core.Spawn#SPAWN SpawnAI --- @field Wrapper.Group#GROUP Test --- @extends Core.Fsm#FSM_SET - - ---- Monitors and manages as many replacement AI groups as there are --- CLIENTS in a SET\_CLIENT collection, which are not occupied by human players. --- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions. --- --- The parent class @{Core.Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM). --- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods. --- An explanation about state and event transition methods can be found in the @{Core.Fsm} module documentation. --- --- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following: --- --- * @{#AI_BALANCER.OnAfterSpawned}( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned. --- --- ## 1. AI_BALANCER construction --- --- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method: --- --- ## 2. AI_BALANCER is a FSM --- --- ![Process](..\Presentations\AI_BALANCER\Dia13.JPG) --- --- ### 2.1. AI_BALANCER States --- --- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients. --- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference. --- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes. --- * **Destroying** ( Set, AIGroup ): The AI is being destroyed. --- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any. --- --- ### 2.2. AI_BALANCER Events --- --- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set. --- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference. --- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes. --- * **Destroy** ( Set, AIGroup ): The AI is being destroyed. --- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. --- --- ## 3. AI_BALANCER spawn interval for replacement AI --- --- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned. --- --- ## 4. AI_BALANCER returns AI to Airbases --- --- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default. --- However, there are 2 additional options that you can use to customize the destroy behaviour. --- When a human player joins a slot, you can configure to let the AI return to: --- --- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Wrapper.Airbase#AIRBASE}. --- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Wrapper.Airbase#AIRBASE}. --- --- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return, --- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed. --- --- # 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_BALANCER -AI_BALANCER = { - ClassName = "AI_BALANCER", - PatrolZones = {}, - AIGroups = {}, - Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds. - Latest = 60, -- Latest a new AI can be spawned is in 60 seconds. -} - - - ---- Creates a new AI_BALANCER object --- @param #AI_BALANCER self --- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). --- @param Core.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed. --- @return #AI_BALANCER -function AI_BALANCER:New( SetClient, SpawnAI ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER - - -- TODO: Define the OnAfterSpawned event - self:SetStartState( "None" ) - self:AddTransition( "*", "Monitor", "Monitoring" ) - self:AddTransition( "*", "Spawn", "Spawning" ) - self:AddTransition( "Spawning", "Spawned", "Spawned" ) - self:AddTransition( "*", "Destroy", "Destroying" ) - self:AddTransition( "*", "Return", "Returning" ) - - self.SetClient = SetClient - self.SetClient:FilterOnce() - self.SpawnAI = SpawnAI - - self.SpawnQueue = {} - - self.ToNearestAirbase = false - self.ToHomeAirbase = false - - self:__Monitor( 1 ) - - return self -end - ---- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI. --- Provide 2 identical seconds if the interval should be a fixed amount of seconds. --- @param #AI_BALANCER self --- @param #number Earliest The earliest a new AI can be spawned in seconds. --- @param #number Latest The latest a new AI can be spawned in seconds. --- @return self -function AI_BALANCER:InitSpawnInterval( Earliest, Latest ) - - self.Earliest = Earliest - self.Latest = Latest - - return self -end - ---- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. --- @param #AI_BALANCER self --- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. --- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to. -function AI_BALANCER:ReturnToNearestAirbases( ReturnThresholdRange, ReturnAirbaseSet ) - - self.ToNearestAirbase = true - self.ReturnThresholdRange = ReturnThresholdRange - self.ReturnAirbaseSet = ReturnAirbaseSet -end - ---- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. --- @param #AI_BALANCER self --- @param DCS#Distance ReturnThresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnThresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. -function AI_BALANCER:ReturnToHomeAirbase( ReturnThresholdRange ) - - self.ToHomeAirbase = true - self.ReturnThresholdRange = ReturnThresholdRange -end - ---- AI_BALANCER:onenterSpawning --- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param #string ClientName --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName ) - - -- OK, Spawn a new group from the default SpawnAI object provided. - local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP - if AIGroup then - AIGroup:T( { "Spawning new AIGroup", ClientName = ClientName } ) - --TODO: need to rework UnitName thing ... - - SetGroup:Remove( ClientName ) -- Ensure that the previously allocated AIGroup to ClientName is removed in the Set. - SetGroup:Add( ClientName, AIGroup ) - self.SpawnQueue[ClientName] = nil - - -- Fire the Spawned event. The first parameter is the AIGroup just Spawned. - -- Mission designers can catch this event to bind further actions to the AIGroup. - self:Spawned( AIGroup ) - end -end - ---- AI_BALANCER:onenterDestroying --- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup ) - - AIGroup:Destroy() - SetGroup:Flush( self ) - SetGroup:Remove( ClientName ) - SetGroup:Flush( self ) -end - ---- RTB --- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param #string From --- @param #string Event --- @param #string To --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup ) - - local AIGroupTemplate = AIGroup:GetTemplate() - if self.ToHomeAirbase == true then - local WayPointCount = #AIGroupTemplate.route.points - local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) - AIGroup:SetCommand( SwitchWayPointCommand ) - AIGroup:MessageToRed( "Returning to home base ...", 30 ) - else - -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) - local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:T( ClosestAirbase.AirbaseName ) - --[[ - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - ]] - AIGroup:RouteRTB(ClosestAirbase) - end - -end - ---- AI_BALANCER:onenterMonitoring --- @param #AI_BALANCER self -function AI_BALANCER:onenterMonitoring( SetGroup ) - - self:T2( { self.SetClient:Count() } ) - --self.SetClient:Flush() - - self.SetClient:ForEachClient( - --- SetClient:ForEachClient - -- @param Wrapper.Client#CLIENT Client - function( Client ) - self:T3(Client.ClientName) - - local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP - if AIGroup then self:T( { AIGroup = AIGroup:GetName(), IsAlive = AIGroup:IsAlive() } ) end - if Client:IsAlive() == true then - - if AIGroup and AIGroup:IsAlive() == true then - - if self.ToNearestAirbase == false and self.ToHomeAirbase == false then - self:Destroy( Client.UnitName, AIGroup ) - else - -- We test if there is no other CLIENT within the self.ReturnThresholdRange of the first unit of the AI group. - -- If there is a CLIENT, the AI stays engaged and will not return. - -- If there is no CLIENT within the self.ReturnThresholdRange, then the unit will return to the Airbase return method selected. - - local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnThresholdRange ) - - self:T2( RangeZone ) - - _DATABASE:ForEachPlayerUnit( - --- Nameless function - -- @param Wrapper.Unit#UNIT RangeTestUnit - function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) - self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) - if RangeTestUnit:IsInZone( RangeZone ) == true then - self:T2( "in zone" ) - if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then - self:T2( "in range" ) - PlayerInRange.Value = true - end - end - end, - - --- Nameless function - -- @param Core.Zone#ZONE_RADIUS RangeZone - -- @param Wrapper.Group#GROUP AIGroup - function( RangeZone, AIGroup, PlayerInRange ) - if PlayerInRange.Value == false then - self:Return( AIGroup ) - end - end - , RangeZone, AIGroup, PlayerInRange - ) - - end - self.Set:Remove( Client.UnitName ) - end - else - if not AIGroup or not AIGroup:IsAlive() == true then - self:T( "Client " .. Client.UnitName .. " not alive." ) - self:T( { Queue = self.SpawnQueue[Client.UnitName] } ) - if not self.SpawnQueue[Client.UnitName] then - -- Spawn a new AI taking into account the spawn interval Earliest, Latest - self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName ) - self.SpawnQueue[Client.UnitName] = true - self:T( "New AI Spawned for Client " .. Client.UnitName ) - end - end - end - return true - end - ) - - self:__Monitor( 10 ) -end diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua deleted file mode 100644 index f4cfe55bf..000000000 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ /dev/null @@ -1,541 +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. --- --- ![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 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.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 diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua deleted file mode 100644 index 7fb848d42..000000000 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ /dev/null @@ -1,570 +0,0 @@ ---- **AI** - Perform Close Air Support (CAS) near friendlies. --- --- **Features:** --- --- * Hold and standby within a patrol zone. --- * Engage upon command the enemies within an engagement zone. --- * Loop the zone until all enemies are eliminated. --- * Trigger different events upon the results achieved. --- * After combat, return to the patrol zone and hold. --- * RTB when commanded or after fuel. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_CAS) --- --- === --- --- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl3JBO1WDqqpyYRRmIkR2ir2) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Quax**: Concept, Advice & Testing. --- * **Pikey**: Concept, Advice & Testing. --- * **Gunterlund**: Test case revision. --- --- === --- --- @module AI.AI_CAS --- @image AI_Close_Air_Support.JPG - ---- AI_CAS_ZONE class --- @type AI_CAS_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 provide Close Air Support in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}. --- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone. --- --- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG) --- --- The AI_CAS_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event. --- --- ![Start Event](..\Presentations\AI_CAS\Dia4.JPG) --- --- Upon started, The AI will **Route** itself towards the random 3D point within a 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. --- This cycle will continue until a fuel or damage threshold has been reached by the AI, or when the AI is commanded to RTB. --- --- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG) --- --- When the AI is commanded to provide Close Air Support (through the event **Engage**), the AI will fly towards the Engage Zone. --- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia6.JPG) --- --- The AI will detect the targets and will only destroy the targets within the Engage Zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia7.JPG) --- --- Every target that is destroyed, is reported< by the AI. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia8.JPG) --- --- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia9.JPG) --- --- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party: --- --- * a FAC --- * a timed event --- * a menu option selected by a human --- * a condition --- * others ... --- --- ![Engage Event](..\Presentations\AI_CAS\Dia10.JPG) --- --- When the AI has accomplished the CAS, it will fly back to the Patrol Zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia11.JPG) --- --- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone. --- It can be notified to go RTB through the **RTB** event. --- --- When the fuel threshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG) --- --- ## AI_CAS_ZONE constructor --- --- * @{#AI_CAS_ZONE.New}(): Creates a new AI_CAS_ZONE object. --- --- ## AI_CAS_ZONE is a FSM --- --- ![Process](..\Presentations\AI_CAS\Dia2.JPG) --- --- ### 2.1. AI_CAS_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 targets in the Engage Zone, executing CAS. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 2.2. AI_CAS_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_CAS_ZONE.Engage}**: Engage the AI to provide CAS in the Engage Zone, destroying any target it finds. --- * **@{#AI_CAS_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_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Wrapper.Unit}. --- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Wrapper.Unit}s assigned in the CAS task. --- * **Status**: The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- # 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_CAS_ZONE -AI_CAS_ZONE = { - ClassName = "AI_CAS_ZONE", -} - - - ---- Creates a new AI_CAS_ZONE object --- @param #AI_CAS_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 Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen. --- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_CAS_ZONE self -function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAS_ZONE - - self.EngageZone = EngageZone - self.Accomplished = false - - self:SetDetectionZone( self.EngageZone ) - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_CAS_ZONE] OnBeforeEngage - -- @param #AI_CAS_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_CAS_ZONE] OnAfterEngage - -- @param #AI_CAS_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_CAS_ZONE] Engage - -- @param #AI_CAS_ZONE self - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. - -- If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAS_ZONE] __Engage - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. - -- If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- Use the structure @{DCS#AI.Task.WeaponExpend} to define the amount of weapons to be release at each attack. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging --- @param #AI_CAS_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_CAS_ZONE] OnEnterEngaging --- @param #AI_CAS_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", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_CAS_ZONE] OnBeforeFired - -- @param #AI_CAS_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_CAS_ZONE] OnAfterFired - -- @param #AI_CAS_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_CAS_ZONE] Fired - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAS_ZONE] __Fired - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] OnBeforeDestroy - -- @param #AI_CAS_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_CAS_ZONE] OnAfterDestroy - -- @param #AI_CAS_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_CAS_ZONE] Destroy - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] __Destroy - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_CAS_ZONE] OnBeforeAbort - -- @param #AI_CAS_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_CAS_ZONE] OnAfterAbort - -- @param #AI_CAS_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_CAS_ZONE] Abort - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAS_ZONE] __Abort - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] OnBeforeAccomplish - -- @param #AI_CAS_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_CAS_ZONE] OnAfterAccomplish - -- @param #AI_CAS_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_CAS_ZONE] Accomplish - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] __Accomplish - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - - ---- Set the Engage Zone where the AI is performing CAS. Note that if the EngageZone is changed, the AI needs to re-detect targets. --- @param #AI_CAS_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAS. --- @return #AI_CAS_ZONE self -function AI_CAS_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - - - ---- onafter State Transition for Event Start. --- @param #AI_CAS_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_CAS_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 ) - - self:SetDetectionDeactivated() -- When not engaging, set the detection off. -end - --- @param AI.AI_CAS#AI_CAS_ZONE --- @param Wrapper.Group#GROUP EngageGroup -function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm ) - - EngageGroup:F( { "AI_CAS_ZONE.EngageRoute:", EngageGroup:GetName() } ) - - if EngageGroup:IsAlive() then - Fsm:__Engage( 1, Fsm.EngageSpeed, Fsm.EngageAltitude, Fsm.EngageWeaponExpend, Fsm.EngageAttackQty, Fsm.EngageDirection ) - end -end - - --- @param #AI_CAS_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_CAS_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - --- @param #AI_CAS_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_CAS_ZONE:onafterTarget( Controllable, From, Event, To ) - - if Controllable:IsAlive() then - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - if Detected == true then - self:F( {"Target: ", DetectedUnit } ) - self.DetectedUnits[DetectedUnit] = false - local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) - self.Controllable:PushTask( AttackTask, 1 ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - self:__Target( -10 ) - - end -end - - --- @param #AI_CAS_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_CAS_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - --- @param #AI_CAS_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 #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. --- @param DCS#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. --- @param DCS#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCS#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, - EngageSpeed, - EngageAltitude, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection ) - self:F("onafterEngage") - - self.EngageSpeed = EngageSpeed or 400 - self.EngageAltitude = EngageAltitude or 2000 - self.EngageWeaponExpend = EngageWeaponExpend - self.EngageAttackQty = EngageAttackQty - self.EngageDirection = EngageDirection - - if Controllable:IsAlive() then - - Controllable:OptionROEOpenFire() - Controllable:OptionROTVertical() - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( DetectedUnit ) - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - self:F( {"Engaging ", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit, - true, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection - ) - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - AttackTasks[#AttackTasks+1] = Controllable:TaskFunction( "AI_CAS_ZONE.EngageRoute", self ) - EngageRoute[#EngageRoute].task = Controllable:TaskCombo( AttackTasks ) - - --- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in EngageZone. - local ToTargetVec2 = self.EngageZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToTargetRoutePoint - - Controllable:Route( EngageRoute, 0.5 ) - - self:SetRefreshTimeInterval( 2 ) - self:SetDetectionActivated() - self:__Target( -2 ) -- Start targeting - end -end - - --- @param #AI_CAS_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_CAS_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionDeactivated() -end - - --- @param #AI_CAS_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_CAS_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - - --- @param #AI_CAS_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_CAS_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 - - diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua deleted file mode 100644 index 0bd6ab9ea..000000000 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ /dev/null @@ -1,589 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo --- @image Cargo.JPG - --- @type AI_CARGO --- @extends Core.Fsm#FSM_CONTROLLABLE - - ---- Base class for the dynamic cargo handling capability for AI groups. --- --- Carriers can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI_CARGO module uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- CARGO derived objects must be declared within the mission to make the AI_CARGO object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- The derived classes from this module are: --- --- * @{AI.AI_Cargo_APC} - Cargo transportation using APCs and other vehicles between zones. --- * @{AI.AI_Cargo_Helicopter} - Cargo transportation using helicopters between zones. --- * @{AI.AI_Cargo_Airplane} - Cargo transportation using airplanes to and from airbases. --- --- # 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_CARGO -AI_CARGO = { - ClassName = "AI_CARGO", - Coordinate = nil, -- Core.Point#COORDINATE, - Carrier_Cargo = {}, -} - ---- Creates a new AI_CARGO object. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier Cargo carrier group. --- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport. --- @return #AI_CARGO self -function AI_CARGO:New( Carrier, CargoSet ) - - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO - - self.CargoSet = CargoSet -- Core.Set#SET_CARGO - self.CargoCarrier = Carrier -- Wrapper.Group#GROUP - - self:SetStartState( "Unloaded" ) - - -- Board - self:AddTransition( "Unloaded", "Pickup", "Unloaded" ) - self:AddTransition( "*", "Load", "*" ) - self:AddTransition( "*", "Reload", "*" ) - self:AddTransition( "*", "Board", "*" ) - self:AddTransition( "*", "Loaded", "Loaded" ) - self:AddTransition( "Loaded", "PickedUp", "Loaded" ) - - -- Unload - self:AddTransition( "Loaded", "Deploy", "*" ) - self:AddTransition( "*", "Unload", "*" ) - self:AddTransition( "*", "Unboard", "*" ) - self:AddTransition( "*", "Unloaded", "Unloaded" ) - self:AddTransition( "Unloaded", "Deployed", "Unloaded" ) - - - --- Pickup Handler OnBefore for AI_CARGO - -- @function [parent=#AI_CARGO] OnBeforePickup - -- @param #AI_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - -- @return #boolean - - --- Pickup Handler OnAfter for AI_CARGO - -- @function [parent=#AI_CARGO] OnAfterPickup - -- @param #AI_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Pickup Trigger for AI_CARGO - -- @function [parent=#AI_CARGO] Pickup - -- @param #AI_CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Pickup Asynchronous Trigger for AI_CARGO - -- @function [parent=#AI_CARGO] __Pickup - -- @param #AI_CARGO self - -- @param #number Delay - -- @param Core.Point#COORDINATE Coordinate Pickup place. If not given, loading starts at the current location. - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Deploy Handler OnBefore for AI_CARGO - -- @function [parent=#AI_CARGO] OnBeforeDeploy - -- @param #AI_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - -- @return #boolean - - --- Deploy Handler OnAfter for AI_CARGO - -- @function [parent=#AI_CARGO] OnAfterDeploy - -- @param #AI_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Deploy Trigger for AI_CARGO - -- @function [parent=#AI_CARGO] Deploy - -- @param #AI_CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Deploy Asynchronous Trigger for AI_CARGO - -- @function [parent=#AI_CARGO] __Deploy - -- @param #AI_CARGO self - -- @param #number Delay - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - - --- Loaded Handler OnAfter for AI_CARGO - -- @function [parent=#AI_CARGO] OnAfterLoaded - -- @param #AI_CARGO self - -- @param Wrapper.Group#GROUP Carrier - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Unloaded Handler OnAfter for AI_CARGO - -- @function [parent=#AI_CARGO] OnAfterUnloaded - -- @param #AI_CARGO self - -- @param Wrapper.Group#GROUP Carrier - -- @param #string From - -- @param #string Event - -- @param #string To - - --- On after Deployed event. - -- @function [parent=#AI_CARGO] OnAfterDeployed - -- @param #AI_CARGO self - -- @param Wrapper.Group#GROUP Carrier - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - -- @param #boolean Defend Defend for APCs. - - - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - CarrierUnit:SetCargoBayWeightLimit() - end - - self.Transporting = false - self.Relocating = false - - return self -end - - - -function AI_CARGO:IsTransporting() - - return self.Transporting == true -end - -function AI_CARGO:IsRelocating() - - return self.Relocating == true -end - - ---- On after Pickup event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate of the pickup point. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the home coordinate. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - self.Transporting = false - self.Relocating = true - -end - - ---- On after Deploy event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Deploy place. --- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the deploy coordinate. --- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed. -function AI_CARGO:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - self.Relocating = false - self.Transporting = true - -end - ---- On before Load event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone ) - self:F( { Carrier, From, Event, To } ) - - local Boarding = false - - local LoadInterval = 2 - local LoadDelay = 1 - local Carrier_List = {} - local Carrier_Weight = {} - - if Carrier and Carrier:IsAlive() then - self.Carrier_Cargo = {} - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - - local CargoBayFreeWeight = CarrierUnit:GetCargoBayFreeWeight() - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - - Carrier_List[#Carrier_List+1] = CarrierUnit - Carrier_Weight[CarrierUnit] = CargoBayFreeWeight - end - - local Carrier_Count = #Carrier_List - local Carrier_Index = 1 - - local Loaded = false - - for _, Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - - self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } ) - - -- Try all Carriers, but start from the one according the Carrier_Index - for Carrier_Loop = 1, #Carrier_List do - - local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT - - -- This counters loop through the available Carriers. - Carrier_Index = Carrier_Index + 1 - if Carrier_Index > Carrier_Count then - Carrier_Index = 1 - end - - if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then - if Cargo:IsInLoadRadius( CarrierUnit:GetCoordinate() ) then - self:F( { "In radius", CarrierUnit:GetName() } ) - - local CargoWeight = Cargo:GetWeight() - local CarrierSpace=Carrier_Weight[CarrierUnit] - - -- Only when there is space within the bay to load the next cargo item! - if CarrierSpace > CargoWeight then - Carrier:RouteStop() - --Cargo:Ungroup() - Cargo:__Board( -LoadDelay, CarrierUnit ) - self:__Board( LoadDelay, Cargo, CarrierUnit, PickupZone ) - - LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval - - -- So now this CarrierUnit has Cargo that is being loaded. - -- This will be used further in the logic to follow and to check cargo status. - self.Carrier_Cargo[Cargo] = CarrierUnit - Boarding = true - Carrier_Weight[CarrierUnit] = Carrier_Weight[CarrierUnit] - CargoWeight - Loaded = true - - -- Ok, we loaded a cargo, now we can stop the loop. - break - else - self:T(string.format("WARNING: Cargo too heavy for carrier %s. Cargo=%.1f > %.1f free space", tostring(CarrierUnit:GetName()), CargoWeight, CarrierSpace)) - end - end - end - - end - - end - - if not Loaded == true then - -- No loading happened, so we need to pickup something else. - self.Relocating = false - end - end - - return Boarding - -end - - ---- On before Reload event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onbeforeReload( Carrier, From, Event, To ) - self:F( { Carrier, From, Event, To } ) - - local Boarding = false - - local LoadInterval = 2 - local LoadDelay = 1 - local Carrier_List = {} - local Carrier_Weight = {} - - if Carrier and Carrier:IsAlive() then - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - - Carrier_List[#Carrier_List+1] = CarrierUnit - end - - local Carrier_Count = #Carrier_List - local Carrier_Index = 1 - - local Loaded = false - - for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - - self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } ) - - -- Try all Carriers, but start from the one according the Carrier_Index - for Carrier_Loop = 1, #Carrier_List do - - local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT - - -- This counters loop through the available Carriers. - Carrier_Index = Carrier_Index + 1 - if Carrier_Index > Carrier_Count then - Carrier_Index = 1 - end - - if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then - Carrier:RouteStop() - Cargo:__Board( -LoadDelay, CarrierUnit ) - self:__Board( LoadDelay, Cargo, CarrierUnit ) - - LoadDelay = LoadDelay + Cargo:GetCount() * LoadInterval - - -- So now this CarrierUnit has Cargo that is being loaded. - -- This will be used further in the logic to follow and to check cargo status. - self.Carrier_Cargo[Cargo] = CarrierUnit - Boarding = true - Loaded = true - end - - end - - end - - if not Loaded == true then - -- No loading happened, so we need to pickup something else. - self.Relocating = false - end - end - - return Boarding - -end - ---- On after Board event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Cargo.Cargo#CARGO Cargo Cargo object. --- @param Wrapper.Unit#UNIT CarrierUnit --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onafterBoard( Carrier, From, Event, To, Cargo, CarrierUnit, PickupZone ) - self:F( { Carrier, From, Event, To, Cargo, CarrierUnit:GetName() } ) - - if Carrier and Carrier:IsAlive() then - self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } ) - if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then - self:__Board( -10, Cargo, CarrierUnit, PickupZone ) - return - end - end - - self:__Loaded( 0.1, Cargo, CarrierUnit, PickupZone ) - -end - ---- On after Loaded event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @return #boolean Cargo loaded. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onafterLoaded( Carrier, From, Event, To, Cargo, PickupZone ) - self:F( { Carrier, From, Event, To } ) - - local Loaded = true - - if Carrier and Carrier:IsAlive() then - for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), Carrier:GetName() } ) - if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then - Loaded = false - end - end - end - - if Loaded then - self:__PickedUp( 0.1, PickupZone ) - end - -end - ---- On after PickedUp event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO:onafterPickedUp( Carrier, From, Event, To, PickupZone ) - self:F( { Carrier, From, Event, To } ) - - Carrier:RouteResume() - - local HasCargo = false - if Carrier and Carrier:IsAlive() then - for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do - HasCargo = true - break - end - end - - self.Relocating = false - if HasCargo then - self:F( "Transporting" ) - self.Transporting = true - end - -end - - - - ---- On after Unload event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, DeployZone, Defend = Defend } ) - - local UnboardInterval = 5 - local UnboardDelay = 5 - - if Carrier and Carrier:IsAlive() then - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - Carrier:RouteStop() - for _, Cargo in pairs( CarrierUnit:GetCargo() ) do - self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } ) - if Cargo:IsLoaded() then - Cargo:__UnBoard( UnboardDelay ) - UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval - self:__Unboard( UnboardDelay, Cargo, CarrierUnit, DeployZone, Defend ) - if not Defend == true then - Cargo:SetDeployed( true ) - end - end - end - end - end - -end - ---- On after Unboard event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Cargo.Cargo#CARGO Cargo Cargo object. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } ) - - if Carrier and Carrier:IsAlive() then - if not Cargo:IsUnLoaded() then - self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend ) - return - end - end - - self:Unloaded( Cargo, CarrierUnit, DeployZone, Defend ) - -end - ---- On after Unloaded event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Cargo.Cargo#CARGO Cargo Cargo object. --- @param #boolean Deployed Cargo is deployed. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } ) - - local AllUnloaded = true - - --Cargo:Regroup() - - if Carrier and Carrier:IsAlive() then - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT - local IsEmpty = CarrierUnit:IsCargoEmpty() - self:T({ IsEmpty = IsEmpty }) - if not IsEmpty then - AllUnloaded = false - break - end - end - - if AllUnloaded == true then - if DeployZone == true then - self.Carrier_Cargo = {} - end - self.CargoCarrier = Carrier - end - end - - if AllUnloaded == true then - self:__Deployed( 5, DeployZone, Defend ) - end - -end - ---- On after Deployed event. --- @param #AI_CARGO self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- @param #boolean Defend Defend for APCs. -function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } ) - - if not Defend == true then - self.Transporting = false - else - self:F( "Defending" ) - - end - -end diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua deleted file mode 100644 index fc9037fea..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ /dev/null @@ -1,607 +0,0 @@ ---- **AI** - Models the intelligent transportation of cargo using ground vehicles. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_APC --- @image AI_Cargo_Dispatching_For_APC.JPG - --- @type AI_CARGO_APC --- @extends AI.AI_Cargo#AI_CARGO - - ---- Brings a dynamic cargo handling capability for an AI vehicle group. --- --- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- The AI_CARGO_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_APC object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- ## Cargo loading. --- --- The module will load automatically cargo when the APCs are within boarding or loading radius. --- The boarding or loading radius is specified when the cargo is created in the simulation, and therefore, this radius depends on the type of cargo --- and the specified boarding radius. --- --- ## **Defending** the APCs when enemies nearby. --- --- Cargo will defend the carrier with its available arms, and to avoid cargo being lost within the battlefield. --- --- When the APCs are approaching enemy units, something special is happening. --- The APCs will stop moving, and the loaded infantry will unboard and follow the APCs and will help to defend the group. --- The carrier will hold the route once the unboarded infantry is further than 50 meters from the APCs, --- to ensure that the APCs are not too far away from the following running infantry. --- Once all enemies are cleared, the infantry will board again automatically into the APCs. Once boarded, the APCs will follow its pre-defined route. --- --- A combat radius needs to be specified in meters at the @{#AI_CARGO_APC.New}() method. --- This combat radius will trigger the unboarding of troops when enemies are within the combat radius around the APCs. --- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit radius (effectiveness) versus --- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered. --- A combat radius of 350 meters to 500 meters has been proven to be the most effective and efficient. --- --- However, when the defense of the carrier, is not required, it must be switched off. --- This is done by disabling the defense of the carrier using the method @{#AI_CARGO_APC.SetCombatRadius}(), and providing a combat radius of 0 meters. --- It can be switched on later when required by reenabling the defense using the method and providing a combat radius larger than 0. --- --- ## Infantry or cargo **health**. --- --- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. --- As a result, the unboarding infantry is very _healthy_ every time it unboards. --- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. --- However, infantry that was destroyed when unboarded and following the APCs, won't be respawned again. Destroyed is destroyed. --- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has --- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every --- time is not so much of an issue ... --- --- ## Control the APCs on the map. --- --- It is possible also as a human ground commander to influence the path of the APCs, by pointing a new path using the DCS user interface on the map. --- In this case, the APCs will change the direction towards its new indicated route. However, there is a catch! --- Once the APCs are near the enemy, and infantry is unboarded, the APCs won't be able to hold the route until the infantry could catch up. --- The APCs will simply drive on and won't stop! This is a limitation in ED that prevents user actions being controlled by the scripting engine. --- No workaround is possible on this. --- --- ## Cargo deployment. --- --- Using the @{#AI_CARGO_APC.Deploy}() method, you are able to direct the APCs towards a point on the battlefield to unboard/unload the cargo at the specific coordinate. --- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment. --- --- ## Cargo pickup. --- --- Using the @{#AI_CARGO_APC.Pickup}() method, you are able to direct the APCs towards a point on the battlefield to board/load the cargo at the specific coordinate. --- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment. --- --- --- # 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_CARGO_APC -AI_CARGO_APC = { - ClassName = "AI_CARGO_APC", - Coordinate = nil, -- Core.Point#COORDINATE, -} - ---- Creates a new AI_CARGO_APC object. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC The carrier APC group. --- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported. --- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When the combat radius is 0, no defense will happen of the carrier. --- @return #AI_CARGO_APC -function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) - - local self = BASE:Inherit( self, AI_CARGO:New( APC, CargoSet ) ) -- #AI_CARGO_APC - - self:AddTransition( "*", "Monitor", "*" ) - self:AddTransition( "*", "Follow", "Following" ) - self:AddTransition( "*", "Guard", "Unloaded" ) - self:AddTransition( "*", "Home", "*" ) - self:AddTransition( "*", "Reload", "Boarding" ) - self:AddTransition( "*", "Deployed", "*" ) - self:AddTransition( "*", "PickedUp", "*" ) - self:AddTransition( "*", "Destroyed", "Destroyed" ) - - self:SetCombatRadius( CombatRadius ) - - self:SetCarrier( APC ) - - return self -end - - ---- Set the Carrier. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP CargoCarrier --- @return #AI_CARGO_APC -function AI_CARGO_APC:SetCarrier( CargoCarrier ) - - self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUP - self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_APC", self ) - - CargoCarrier:HandleEvent( EVENTS.Dead ) - - function CargoCarrier:OnEventDead( EventData ) - self:F({"dead"}) - local AICargoTroops = self:GetState( self, "AI_CARGO_APC" ) - self:F({AICargoTroops=AICargoTroops}) - if AICargoTroops then - self:F({}) - if not AICargoTroops:Is( "Loaded" ) then - -- There are enemies within combat radius. Unload the CargoCarrier. - AICargoTroops:Destroyed() - end - end - end - --- CargoCarrier:HandleEvent( EVENTS.Hit ) --- --- function CargoCarrier:OnEventHit( EventData ) --- self:F({"hit"}) --- local AICargoTroops = self:GetState( self, "AI_CARGO_APC" ) --- if AICargoTroops then --- self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } ) --- if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then --- -- There are enemies within combat radius. Unload the CargoCarrier. --- AICargoTroops:Unload( false ) --- end --- end --- end - - self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius ) - self.Coalition = self.CargoCarrier:GetCoalition() - - self:SetControllable( CargoCarrier ) - - self:Guard() - - return self -end - ---- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo. --- @param #AI_CARGO_APC self --- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_APC self -function AI_CARGO_APC:SetOffRoad(Offroad, Formation) - - self:SetPickupOffRoad(Offroad, Formation) - self:SetDeployOffRoad(Offroad, Formation) - - return self -end - ---- Set whether the carrier will *not* use roads to *pickup* the cargo. --- @param #AI_CARGO_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_APC self -function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation) - - self.pickupOffroad=Offroad - self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad - - return self -end - ---- Set whether the carrier will *not* use roads to *deploy* the cargo. --- @param #AI_CARGO_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_APC self -function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation) - - self.deployOffroad=Offroad - self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad - - return self -end - - ---- Find a free Carrier within a radius. --- @param #AI_CARGO_APC self --- @param Core.Point#COORDINATE Coordinate --- @param #number Radius --- @return Wrapper.Group#GROUP NewCarrier -function AI_CARGO_APC:FindCarrier( Coordinate, Radius ) - - local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius ) - CoordinateZone:Scan( { Object.Category.UNIT } ) - for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do - local NearUnit = UNIT:Find( DCSUnit ) - self:F({NearUnit=NearUnit}) - if not NearUnit:GetState( NearUnit, "AI_CARGO_APC" ) then - local Attributes = NearUnit:GetDesc() - self:F({Desc=Attributes}) - if NearUnit:HasAttribute( "Trucks" ) then - return NearUnit:GetGroup() - end - end - end - - return nil - -end - ---- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier). --- This is only valid for APCs and trucks etc, thus ground vehicles. --- @param #AI_CARGO_APC self --- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. --- When the combat radius is 0, no defense will happen of the carrier. --- When the combat radius is not provided, no defense will happen! --- @return #AI_CARGO_APC --- @usage --- --- -- Disembark the infantry when the carrier is under attack. --- AICargoAPC:SetCombatRadius( true ) --- --- -- Keep the cargo in the carrier when the carrier is under attack. --- AICargoAPC:SetCombatRadius( false ) -function AI_CARGO_APC:SetCombatRadius( CombatRadius ) - - self.CombatRadius = CombatRadius or 0 - - if self.CombatRadius > 0 then - self:__Monitor( -5 ) - end - - return self -end - - ---- Follow Infantry to the Carrier. --- @param #AI_CARGO_APC self --- @param #AI_CARGO_APC Me --- @param Wrapper.Unit#UNIT APCUnit --- @param Cargo.CargoGroup#CARGO_GROUP Cargo --- @return #AI_CARGO_APC -function AI_CARGO_APC:FollowToCarrier( Me, APCUnit, CargoGroup ) - - local InfantryGroup = CargoGroup:GetGroup() - - self:F( { self = self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } ) - - --if self:Is( "Following" ) then - - if APCUnit:IsAlive() then - -- We check if the Cargo is near to the CargoCarrier. - if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", APCUnit, 25 ) ) then - - -- The Cargo does not need to follow the Carrier. - Me:Guard() - - else - - self:F( { InfantryGroup = InfantryGroup:GetName() } ) - - if InfantryGroup:IsAlive() then - - self:F( { InfantryGroup = InfantryGroup:GetName() } ) - - local Waypoints = {} - - -- Calculate the new Route. - local FromCoord = InfantryGroup:GetCoordinate() - local FromGround = FromCoord:WaypointGround( 10, "Diamond" ) - self:F({FromGround=FromGround}) - table.insert( Waypoints, FromGround ) - - local ToCoord = APCUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 ) - local ToGround = ToCoord:WaypointGround( 10, "Diamond" ) - self:F({ToGround=ToGround}) - table.insert( Waypoints, ToGround ) - - local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_APC.FollowToCarrier", Me, APCUnit, CargoGroup ) - - self:F({Waypoints = Waypoints}) - local Waypoint = Waypoints[#Waypoints] - InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - - InfantryGroup:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. - end - end - end -end - - ---- On after Monitor event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function AI_CARGO_APC:onafterMonitor( APC, From, Event, To ) - self:F( { APC, From, Event, To, IsTransporting = self:IsTransporting() } ) - - if self.CombatRadius > 0 then - if APC and APC:IsAlive() then - if self.CarrierCoordinate then - if self:IsTransporting() == true then - local Coordinate = APC:GetCoordinate() - if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then - self.Zone:Scan( { Object.Category.UNIT } ) - if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then - if self:Is( "Unloaded" ) then - -- There are no enemies within combat radius. Reload the CargoCarrier. - self:Reload() - end - else - if self:Is( "Loaded" ) then - -- There are enemies within combat radius. Unload the CargoCarrier. - self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy! - else - if self:Is( "Unloaded" ) then - --self:Follow() - end - self:F( "I am here" .. self:GetCurrentState() ) - if self:Is( "Following" ) then - for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - local APCUnit = APCUnit -- Wrapper.Unit#UNIT - if Cargo:IsAlive() then - if not Cargo:IsNear( APCUnit, 40 ) then - APCUnit:RouteStop() - self.CarrierStopped = true - else - if self.CarrierStopped then - if Cargo:IsNear( APCUnit, 25 ) then - APCUnit:RouteResume() - self.CarrierStopped = nil - end - end - end - end - end - end - end - end - end - end - - end - self.CarrierCoordinate = APC:GetCoordinate() - end - - self:__Monitor( -5 ) - end - -end - - ---- On after Follow event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function AI_CARGO_APC:onafterFollow( APC, From, Event, To ) - self:F( { APC, From, Event, To } ) - - self:F( "Follow" ) - if APC and APC:IsAlive() then - for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - if Cargo:IsUnLoaded() then - self:FollowToCarrier( self, APCUnit, Cargo ) - APCUnit:RouteResume() - end - end - end - -end - ---- Pickup task function. Triggers Load event. --- @param Wrapper.Group#GROUP APC The cargo carrier group. --- @param #AI_CARGO_APC sel `AI_CARGO_APC` class. --- @param Core.Point#COORDINATE Coordinate. The coordinate (not used). --- @param #number Speed Speed (not used). --- @param Core.Zone#ZONE PickupZone Pickup zone. -function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone) - - APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } ) - - if APC:IsAlive() then - self:Load( PickupZone ) - end -end - ---- Deploy task function. Triggers Unload event. --- @param Wrapper.Group#GROUP APC The cargo carrier group. --- @param #AI_CARGO_APC self `AI_CARGO_APC` class. --- @param Core.Point#COORDINATE Coordinate. The coordinate (not used). --- @param Core.Zone#ZONE DeployZone Deploy zone. -function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone) - - APC:F( { "AI_CARGO_APC._Deploy:", APC } ) - - if APC:IsAlive() then - self:Unload( DeployZone ) - end -end - - - ---- On after Pickup event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate of the pickup point. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - if APC and APC:IsAlive() then - - if Coordinate then - self.RoutePickup = true - - local _speed=Speed or APC:GetSpeedMax()*0.5 - - -- Route on road. - local Waypoints = {} - - if self.pickupOffroad then - Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation) - Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks) - else - Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true) - end - - - local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone ) - - local Waypoint = Waypoints[#Waypoints] - APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - - APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. - else - AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone ) - end - - self:GetParent( self, AI_CARGO_APC ).onafterPickup( self, APC, From, Event, To, Coordinate, Speed, Height, PickupZone ) - end - -end - - ---- On after Deploy event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Deploy place. --- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the deploy coordinate. This parameter is ignored for APCs. --- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed. -function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - if APC and APC:IsAlive() then - - self.RouteDeploy = true - - -- Set speed in km/h. - local speedmax=APC:GetSpeedMax() - local _speed=Speed or speedmax*0.5 - _speed=math.min(_speed, speedmax) - - -- Route on road. - local Waypoints = {} - - if self.deployOffroad then - Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation) - Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks) - else - Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true) - end - - -- Task function - local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone ) - - -- Last waypoint - local Waypoint = Waypoints[#Waypoints] - - -- Set task function - APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - - -- Route group - APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. - - -- Call parent function. - self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - end - -end - ---- On after Unloaded event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Cargo.Cargo#CARGO Cargo Cargo object. --- @param #boolean Deployed Cargo is deployed. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_APC:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) - self:F( { Carrier, From, Event, To, DeployZone = DeployZone, Defend = Defend } ) - - - self:GetParent( self, AI_CARGO_APC ).onafterUnloaded( self, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) - - -- If Defend == true then we need to scan for possible enemies within combat zone and engage only ground forces. - if Defend == true then - self.Zone:Scan( { Object.Category.UNIT } ) - if not self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then - -- OK, enemies nearby, now find the enemies and attack them. - local AttackUnits = self.Zone:GetScannedUnits() -- #list - local Move = {} - local CargoGroup = Cargo.CargoObject -- Wrapper.Group#GROUP - Move[#Move+1] = CargoGroup:GetCoordinate():WaypointGround( 70, "Custom" ) - for UnitId, AttackUnit in pairs( AttackUnits ) do - local MooseUnit = UNIT:Find( AttackUnit ) - if MooseUnit:GetCoalition() ~= CargoGroup:GetCoalition() then - Move[#Move+1] = MooseUnit:GetCoordinate():WaypointGround( 70, "Line abreast" ) - --MoveTo.Task = CargoGroup:TaskCombo( CargoGroup:TaskAttackUnit( MooseUnit, true ) ) - self:F( { MooseUnit = MooseUnit:GetName(), CargoGroup = CargoGroup:GetName() } ) - end - end - CargoGroup:RoutePush( Move, 0.1 ) - end - - end - -end - ---- On after Deployed event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP Carrier --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_APC:onafterDeployed( APC, From, Event, To, DeployZone, Defend ) - self:F( { APC, From, Event, To, DeployZone = DeployZone, Defend = Defend } ) - - self:__Guard( 0.1 ) - - self:GetParent( self, AI_CARGO_APC ).onafterDeployed( self, APC, From, Event, To, DeployZone, Defend ) - -end - - ---- On after Home event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Home place. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the home coordinate. This parameter is ignored for APCs. -function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate, Speed, Height, HomeZone ) - - if APC and APC:IsAlive() ~= nil then - - self.RouteHome = true - - Speed = Speed or APC:GetSpeedMax()*0.5 - - local Waypoints = APC:TaskGroundOnRoad( Coordinate, Speed, "Line abreast", true ) - - self:F({Waypoints = Waypoints}) - local Waypoint = Waypoints[#Waypoints] - - APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. - - end - -end diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua deleted file mode 100644 index 6666eb379..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ /dev/null @@ -1,510 +0,0 @@ ---- **AI** - Models the intelligent transportation of cargo using airplanes. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Airplane --- @image AI_Cargo_Dispatching_For_Airplanes.JPG - --- @type AI_CARGO_AIRPLANE --- @extends Core.Fsm#FSM_CONTROLLABLE - - ---- Brings a dynamic cargo handling capability for an AI airplane group. --- --- Airplane carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation between airbases. --- --- The AI_CARGO_AIRPLANE module uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- @{Cargo.Cargo} must be declared within the mission to make AI_CARGO_AIRPLANE recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- ## Cargo pickup. --- --- Using the @{#AI_CARGO_AIRPLANE.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate. --- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! --- --- ## Cargo deployment. --- --- Using the @{#AI_CARGO_AIRPLANE.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate. --- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! --- --- ## Infantry health. --- --- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. --- As a result, the unboarding infantry is very _healthy_ every time it unboards. --- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. --- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed. --- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has --- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every --- time is not so much of an issue ... --- --- --- # 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_CARGO_AIRPLANE -AI_CARGO_AIRPLANE = { - ClassName = "AI_CARGO_AIRPLANE", - Coordinate = nil, -- Core.Point#COORDINATE -} - ---- Creates a new AI_CARGO_AIRPLANE object. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Plane used for transportation of cargo. --- @param Core.Set#SET_CARGO CargoSet Cargo set to be transported. --- @return #AI_CARGO_AIRPLANE -function AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) - - local self = BASE:Inherit( self, AI_CARGO:New( Airplane, CargoSet ) ) -- #AI_CARGO_AIRPLANE - - self:AddTransition( "*", "Landed", "*" ) - self:AddTransition( "*", "Home" , "*" ) - - self:AddTransition( "*", "Destroyed", "Destroyed" ) - - --- Pickup Handler OnBefore for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo transport plane. - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up. - -- @param #number Speed in km/h for travelling to pickup base. - -- @return #boolean - - --- Pickup Handler OnAfter for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo transport plane. - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff. - -- @param #number Speed Speed in km/h for travelling to pickup base. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up. - - --- Pickup Trigger for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] Pickup - -- @param #AI_CARGO_AIRPLANE self - -- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff. - -- @param #number Speed Speed in km/h for travelling to pickup base. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up. - - --- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] __Pickup - -- @param #AI_CARGO_AIRPLANE self - -- @param #number Delay Delay in seconds. - -- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff. - -- @param #number Speed Speed in km/h for travelling to pickup base. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up. - - --- Deploy Handler OnBefore for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo plane. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed. - -- @param #number Speed Speed in km/h for travelling to deploy base. - -- @return #boolean - - --- Deploy Handler OnAfter for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo plane. - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff. - -- @param #number Speed Speed in km/h for travelling to the deploy base. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. - - --- Deploy Trigger for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] Deploy - -- @param #AI_CARGO_AIRPLANE self - -- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff. - -- @param #number Speed Speed in km/h for travelling to the deploy base. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. - - --- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE - -- @function [parent=#AI_CARGO_AIRPLANE] __Deploy - -- @param #AI_CARGO_AIRPLANE self - -- @param #number Delay Delay in seconds. - -- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff. - -- @param #number Speed Speed in km/h for travelling to the deploy base. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. - - --- On after Loaded event, i.e. triggered when the cargo is inside the carrier. - -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterLoaded - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo plane. - -- @param From - -- @param Event - -- @param To - - - --- On after Deployed event. - -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeployed - -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Group#GROUP Airplane Cargo plane. - -- @param #string From From state. - -- @param #string Event Event. - -- @param #string To To state. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. - - -- Set carrier. - self:SetCarrier( Airplane ) - - return self -end - - ---- Set the Carrier (controllable). Also initializes events for carrier and defines the coalition. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Transport plane. --- @return #AI_CARGO_AIRPLANE self -function AI_CARGO_AIRPLANE:SetCarrier( Airplane ) - - local AICargo = self - - self.Airplane = Airplane -- Wrapper.Group#GROUP - self.Airplane:SetState( self.Airplane, "AI_CARGO_AIRPLANE", self ) - - self.RoutePickup = false - self.RouteDeploy = false - - Airplane:HandleEvent( EVENTS.Dead ) - Airplane:HandleEvent( EVENTS.Hit ) - Airplane:HandleEvent( EVENTS.EngineShutdown ) - - function Airplane:OnEventDead( EventData ) - local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" ) - self:F({AICargoTroops=AICargoTroops}) - if AICargoTroops then - self:F({}) - if not AICargoTroops:Is( "Loaded" ) then - -- There are enemies within combat range. Unload the Airplane. - AICargoTroops:Destroyed() - end - end - end - - - function Airplane:OnEventHit( EventData ) - local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" ) - if AICargoTroops then - self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } ) - if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then - -- There are enemies within combat range. Unload the Airplane. - AICargoTroops:Unload() - end - end - end - - - function Airplane:OnEventEngineShutdown( EventData ) - AICargo.Relocating = false - AICargo:Landed( self.Airplane ) - end - - self.Coalition = self.Airplane:GetCoalition() - - self:SetControllable( Airplane ) - - return self -end - - ---- Find a free Carrier within a range. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Airbase#AIRBASE Airbase --- @param #number Radius --- @return Wrapper.Group#GROUP NewCarrier -function AI_CARGO_AIRPLANE:FindCarrier( Coordinate, Radius ) - - local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius ) - CoordinateZone:Scan( { Object.Category.UNIT } ) - for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do - local NearUnit = UNIT:Find( DCSUnit ) - self:F({NearUnit=NearUnit}) - if not NearUnit:GetState( NearUnit, "AI_CARGO_AIRPLANE" ) then - local Attributes = NearUnit:GetDesc() - self:F({Desc=Attributes}) - if NearUnit:HasAttribute( "Trucks" ) then - self:SetCarrier( NearUnit ) - break - end - end - end - -end - ---- On after "Landed" event. Called on engine shutdown and initiates the pickup mission or unloading event. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Cargo transport plane. --- @param From --- @param Event --- @param To -function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To ) - - self:F({Airplane, From, Event, To}) - - if Airplane and Airplane:IsAlive()~=nil then - - -- Aircraft was sent to this airbase to pickup troops. Initiate loadling. - if self.RoutePickup == true then - self:Load( self.PickupZone ) - end - - -- Aircraft was send to this airbase to deploy troops. Initiate unloading. - if self.RouteDeploy == true then - self:Unload() - self.RouteDeploy = false - end - - end - -end - - ---- On after "Pickup" event. Routes transport to pickup airbase. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Cargo transport plane. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Point#COORDINATE Coordinate The coordinate where to pickup stuff. --- @param #number Speed Speed in km/h for travelling to pickup base. --- @param #number Height Height in meters to move to the pickup coordinate. --- @param Core.Zone#ZONE_AIRBASE PickupZone The airbase zone where the cargo will be picked up. -function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - if Airplane and Airplane:IsAlive() then - - local airbasepickup=Coordinate:GetClosestAirbase() - - self.PickupZone = PickupZone or ZONE_AIRBASE:New(airbasepickup:GetName()) - - -- Get closest airbase of current position. - local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase() - - -- Two cases. Aircraft spawned in air or at an airbase. - if Airplane:InAir() then - self.Airbase=nil --> route will start in air - else - self.Airbase=ClosestAirbase - end - - -- Set pickup airbase. - local Airbase = self.PickupZone:GetAirbase() - - -- Distance from closest to pickup airbase ==> we need to know if we are already at the pickup airbase. - local Dist = Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate()) - - if Airplane:InAir() or Dist>500 then - - -- Route aircraft to pickup airbase. - self:Route( Airplane, Airbase, Speed, Height ) - - -- Set airbase as starting point in the next Route() call. - self.Airbase = Airbase - - -- Aircraft is on a pickup mission. - self.RoutePickup = true - - else - - -- We are already at the right airbase ==> Landed ==> triggers loading of troops. Is usually called at engine shutdown event. - self.RoutePickup=true - self:Landed() - - end - - self:GetParent( self, AI_CARGO_AIRPLANE ).onafterPickup( self, Airplane, From, Event, To, Coordinate, Speed, Height, self.PickupZone ) - - end - - -end - ---- On after Depoly event. Routes plane to the airbase where the troops are deployed. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Cargo transport plane. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Point#COORDINATE Coordinate Coordinate where to deploy stuff. --- @param #number Speed Speed in km/h for travelling to the deploy base. --- @param #number Height Height in meters to move to the home coordinate. --- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. -function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - if Airplane and Airplane:IsAlive()~=nil then - - local Airbase = Coordinate:GetClosestAirbase() - - if DeployZone then - Airbase=DeployZone:GetAirbase() - end - - -- Activate uncontrolled airplane. - if Airplane:IsAlive()==false then - Airplane:SetCommand({id = 'Start', params = {}}) - end - - -- Route to destination airbase. - self:Route( Airplane, Airbase, Speed, Height ) - - -- Aircraft is on a depoly mission. - self.RouteDeploy = true - - -- Set destination airbase for next :Route() command. - self.Airbase = Airbase - - self:GetParent( self, AI_CARGO_AIRPLANE ).onafterDeploy( self, Airplane, From, Event, To, Coordinate, Speed, Height, DeployZone ) - end - -end - - ---- On after Unload event. Cargo is beeing unloaded, i.e. the unboarding process is started. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Cargo transport plane. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE_AIRBASE DeployZone The airbase zone where the cargo will be deployed. -function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone ) - - local UnboardInterval = 10 - local UnboardDelay = 10 - - if Airplane and Airplane:IsAlive() then - for _, AirplaneUnit in pairs( Airplane:GetUnits() ) do - local Cargos = AirplaneUnit:GetCargo() - for CargoID, Cargo in pairs( Cargos ) do - - local Angle = 180 - local CargoCarrierHeading = Airplane:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployCoordinate = Airplane:GetPointVec2():Translate( 150, CargoDeployHeading ) - - Cargo:__UnBoard( UnboardDelay, CargoDeployCoordinate ) - UnboardDelay = UnboardDelay + UnboardInterval - Cargo:SetDeployed( true ) - self:__Unboard( UnboardDelay, Cargo, AirplaneUnit, DeployZone ) - end - end - end - -end - ---- Route the airplane from one airport or it's current position to another airbase. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane Airplane group to be routed. --- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase. --- @param #number Speed Speed in km/h. Default is 80% of max possible speed the group can do. --- @param #number Height Height in meters to move to the Airbase. --- @param #boolean Uncontrolled If true, spawn group in uncontrolled state. -function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled ) - - if Airplane and Airplane:IsAlive() then - - -- Set takeoff type. - local Takeoff = SPAWN.Takeoff.Cold - - -- Get template of group. - local Template = Airplane:GetTemplate() - - -- Nil check - if Template==nil then - return - end - - -- Waypoints of the route. - local Points={} - - -- To point. - local AirbasePointVec2 = Airbase:GetPointVec2() - local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase) - - --ToWaypoint["airdromeId"] = Airbase:GetID() - --ToWaypoint["speed_locked"] = true - - - -- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned. - if self.Airbase then - - -- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine. - Template.route.points[2] = ToWaypoint - - -- Respawn group at the current airbase. - Airplane:RespawnAtCurrentAirbase(Template, Takeoff, Uncontrolled) - - else - - -- From point. - local GroupPoint = Airplane:GetVec2() - local FromWaypoint = {} - FromWaypoint.x = GroupPoint.x - FromWaypoint.y = GroupPoint.y - FromWaypoint.type = "Turning Point" - FromWaypoint.action = "Turning Point" - FromWaypoint.speed = Airplane:GetSpeedMax()*0.8 - - -- The two route points. - Points[1] = FromWaypoint - Points[2] = ToWaypoint - - local PointVec3 = Airplane:GetPointVec3() - Template.x = PointVec3.x - Template.y = PointVec3.z - - Template.route.points = Points - - local GroupSpawned = Airplane:Respawn(Template) - - end - end -end - ---- On after Home event. Aircraft will be routed to their home base. --- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane The cargo plane. --- @param From From state. --- @param Event Event. --- @param To To State. --- @param Core.Point#COORDINATE Coordinate Home place (not used). --- @param #number Speed Speed in km/h to fly to the home airbase (zone). Default is 80% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the home coordinate. --- @param Core.Zone#ZONE_AIRBASE HomeZone The home airbase (zone) where the plane should return to. -function AI_CARGO_AIRPLANE:onafterHome(Airplane, From, Event, To, Coordinate, Speed, Height, HomeZone ) - if Airplane and Airplane:IsAlive() then - - -- We are going home! - self.RouteHome = true - - -- Home Base. - local HomeBase=HomeZone:GetAirbase() - self.Airbase=HomeBase - - -- Now route the airplane home - self:Route( Airplane, HomeBase, Speed, Height ) - - end - -end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua deleted file mode 100644 index 71b7f9f43..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ /dev/null @@ -1,1238 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo. --- --- ## Features: --- --- * AI_CARGO_DISPATCHER is the **base class** for: --- --- * @{AI.AI_Cargo_Dispatcher_APC#AI_CARGO_DISPATCHER_APC} --- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_CARGO_DISPATCHER_HELICOPTER} --- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_CARGO_DISPATCHER_AIRPLANE} --- --- * Provides the facilities to transport cargo over the battle field for the above classes. --- * Dispatches transport tasks to a common set of cargo transporting groups. --- * Different options can be setup to tweak the cargo transporation behaviour. --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Cargo_Dispatcher) --- --- === --- --- # The dispatcher concept. --- --- Carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI_CARGO_DISPATCHER module uses the @{Cargo.Cargo} capabilities within the MOOSE framework, to enable Carrier GROUP objects --- to transport @{Cargo.Cargo} towards several deploy zones. --- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_DISPATCHER object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- --- ## Why cargo dispatching? --- --- It provides a realistic way of distributing your army forces around the battlefield, and to provide a quick means of cargo transportation. --- Instead of having troops or cargo to "appear" suddenly at certain locations, the dispatchers will pickup the cargo and transport it. --- It also allows to enforce or retreat your army from certain zones when needed, using helicopters or APCs. --- Airplanes can transport cargo over larger distances between the airfields. --- --- --- ## What is a cargo object then? --- --- In order to make use of the MOOSE cargo system, you need to **declare** the DCS objects as MOOSE cargo objects! --- This sounds complicated, but it is actually quite simple. --- --- See here an example: --- --- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) --- --- The above code declares a MOOSE cargo object called `EngineerCargoGroup`. --- It actually just refers to an infantry group created within the sim called `"Engineers"`. --- The infantry group now becomes controlled by the MOOSE cargo object `EngineerCargoGroup`. --- A MOOSE cargo object also has properties, like the type of cargo, the logical name, and the reporting range. --- --- For more information, please consult the @{Cargo.Cargo} module documentation. Please read through it, because it will explain how to setup the cargo objects for use --- within your dispatchers. --- --- --- ## Do I need to do a lot of coding to setup a dispatcher? --- --- No! It requires a bit of studying to set it up, but once you understand the different components that use the cargo dispatcher, it becomes very easy. --- Also, the dispatchers work in a true dynamic environment. The carriers and cargo, pickup and deploy zones can be created dynamically in your mission, --- and will automatically be recognized by the dispatcher. --- --- --- ## Is the dispatcher causing a lot of CPU overhead? --- --- A little yes, but once the cargo is properly loaded into the carrier, the CPU consumption is very little. --- When infantry or vehicles board into a carrier, or unboard from a carrier, you may perceive certain performance lags. --- We are working to minimize the impact of those. --- That being said, the DCS simulator is limited. It is just impossible to deploy hundreds of cargo over the battlefield, hundreds of helicopters transporting, --- without any performance impact. The amount of helicopters that are active and flying in your simulation influences more the performance than the dispatchers. --- It really comes down to trying it out and getting experienced with what is possible and what is not (or too much). --- --- --- ## Are the dispatchers a "black box" in terms of the logic? --- --- No. You can tailor the dispatcher mechanisms using event handlers, and create additional logic to enhance the behaviour and dynamism in your own mission. --- The events are listed below, and so are the options, but here are a couple of examples of what is possible: --- --- * You could handle the **Deployed** event, when all the cargo is unloaded from a carrier in the dispatcher. --- Adding your own code to the event handler, you could move the deployed cargo (infantry) to specific points to engage in the battlefield. --- --- * When a carrier is picking up cargo, the *Pickup** event is triggered, and you can inform the coalition of this event, --- because it is an indication that troops are planned to join. --- --- --- ## Are there options that you can set to modify the behaviour of the carries? --- --- Yes, there are options to configure: --- --- * the location where carriers will park or land near the cargo for pickup. --- * the location where carriers will park or land in the deploy zone for cargo deployment. --- * the height for airborne carriers when they fly to and from pickup and deploy zones. --- * the speed of the carriers. This is an important parameter, because depending on the tactication situation, speed will influence the detection by radars. --- --- --- ## Can the zones be of any zone type? --- --- Yes, please ensure that the zones are declared using the @{Core.Zone} classes. --- Possible zones that function at the moment are ZONE, ZONE_GROUP, ZONE_UNIT, ZONE_POLYGON. --- --- # 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 --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Dispatcher --- @image AI_Cargo_Dispatcher.JPG - - --- @type AI_CARGO_DISPATCHER --- @field Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. --- @field Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @field Core.Zone#SET_ZONE PickupZoneSet The set of pickup zones, which are used to where the cargo can be picked up by the carriers. If nil, then cargo can be picked up everywhere. --- @field Core.Zone#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the carriers. --- @field #number PickupMaxSpeed The maximum speed to move to the cargo pickup location. --- @field #number PickupMinSpeed The minimum speed to move to the cargo pickup location. --- @field #number DeployMaxSpeed The maximum speed to move to the cargo deploy location. --- @field #number DeployMinSpeed The minimum speed to move to the cargo deploy location. --- @field #number PickupMaxHeight The maximum height to fly to the cargo pickup location. --- @field #number PickupMinHeight The minimum height to fly to the cargo pickup location. --- @field #number DeployMaxHeight The maximum height to fly to the cargo deploy location. --- @field #number DeployMinHeight The minimum height to fly to the cargo deploy location. --- @field #number PickupOuterRadius The outer radius in meters around the cargo coordinate to pickup the cargo. --- @field #number PickupInnerRadius The inner radius in meters around the cargo coordinate to pickup the cargo. --- @field #number DeployOuterRadius The outer radius in meters around the cargo coordinate to deploy the cargo. --- @field #number DeployInnerRadius The inner radius in meters around the cargo coordinate to deploy the cargo. --- @field Core.Zone#ZONE_BASE HomeZone The home zone where the carriers will return when there is no more cargo to pickup. --- @field #number MonitorTimeInterval The interval in seconds when the cargo dispatcher will search for new cargo to be picked up. --- @extends Core.Fsm#FSM - - ---- A dynamic cargo handling capability for AI groups. --- --- --- --- --- Carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI_CARGO_DISPATCHER module uses the @{Cargo.Cargo} capabilities within the MOOSE framework, to enable Carrier GROUP objects --- to transport @{Cargo.Cargo} towards several deploy zones. --- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_DISPATCHER object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- # 1) AI_CARGO_DISPATCHER constructor. --- --- * @{#AI_CARGO_DISPATCHER.New}(): Creates a new AI_CARGO_DISPATCHER object. --- --- Find below some examples of AI cargo dispatcher objects created. --- --- ### An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() --- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- AICargoDispatcherHelicopter:SetHomeZone( ZONE:FindByName( "Home" ) ) --- --- ### An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones ) --- AICargoDispatcherAPC:Start() --- --- ### An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases. --- --- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() --- local PickupZoneSet = SET_ZONE:New() --- local DeployZoneSet = SET_ZONE:New() --- --- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) ) --- --- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet ) --- AICargoDispatcherAirplanes:SetHomeZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Kobuleti ) ) --- --- --- --- --- # 2) AI_CARGO_DISPATCHER is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- --- --- --- # 3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Use these methods to capture the events and tailor the events with your own code! --- All classes derived from AI_CARGO_DISPATCHER can capture these events, and you can write your own code. --- --- In order to properly capture the events, it is mandatory that you execute the following actions using your script: --- --- * Copy / Paste the code section into your script. --- * Change the CLASS literal to the object name you have in your script. --- * Within the function, you can now write your own code! --- * IntelliSense will recognize the type of the variables provided by the function. Note: the From, Event and To variables can be safely ignored, --- but you need to declare them as they are automatically provided by the event handling system of MOOSE. --- --- You can send messages or fire off any other events within the code section. The sky is the limit! --- --- Mission AID-CGO-140, AID-CGO-240 and AID-CGO-340 contain examples how these events can be tailored. --- --- For those who don't have the time to check the test missions, find the underlying example of a Deployed event that is tailored. --- --- --- Deployed Handler OnAfter for AI_CARGO_DISPATCHER. --- -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @function OnAfterDeployed --- -- @param #AICargoDispatcherHelicopter self --- -- @param #string From A string that contains the "*from state name*" when the event was fired. --- -- @param #string Event A string that contains the "*event name*" when the event was fired. --- -- @param #string To A string that contains the "*to state name*" when the event was fired. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function AICargoDispatcherHelicopter:OnAfterDeployed( From, Event, To, CarrierGroup, DeployZone ) --- --- MESSAGE:NewType( "Group " .. CarrierGroup:GetName() .. " deployed all cargo in zone " .. DeployZone:GetName(), MESSAGE.Type.Information ):ToAll() --- --- end --- --- --- ## 3.1) Tailor the **Pickup** event --- --- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Pickup event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Point#COORDINATE Coordinate The coordinate of the pickup location. --- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the pickup Coordinate. --- -- @param #number Height Height in meters to move to the pickup coordinate. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterPickup( From, Event, To, CarrierGroup, Coordinate, Speed, Height, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.2) Tailor the **Load** event --- --- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Load event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterLoad( From, Event, To, CarrierGroup, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.3) Tailor the **Loading** event --- --- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Loading event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- Note that this event is triggered repeatedly until all cargo (units) have been boarded into the carrier. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Cargo.Cargo#CARGO Cargo The cargo object. --- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterLoading( From, Event, To, CarrierGroup, Cargo, CarrierUnit, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.4) Tailor the **Loaded** event --- --- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. --- You can use this event handler to post messages to players, or provide status updates etc. --- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. --- --- The function provides the CarrierGroup, which is the main group that was loading the Cargo into the CarrierUnit. --- A CarrierUnit is part of the larger CarrierGroup. --- --- --- --- Loaded event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. --- -- A CarrierUnit can be part of the larger CarrierGroup. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Cargo.Cargo#CARGO Cargo The cargo object. --- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterLoaded( From, Event, To, CarrierGroup, Cargo, CarrierUnit, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.5) Tailor the **PickedUp** event --- --- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- PickedUp event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. --- function CLASS:OnAfterPickedUp( From, Event, To, CarrierGroup, PickupZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.6) Tailor the **Deploy** event --- --- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Deploy event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Point#COORDINATE Coordinate The deploy coordinate. --- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the deploy Coordinate. --- -- @param #number Height Height in meters to move to the deploy coordinate. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterDeploy( From, Event, To, CarrierGroup, Coordinate, Speed, Height, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.7) Tailor the **Unload** event --- --- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Unload event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterUnload( From, Event, To, CarrierGroup, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.8) Tailor the **Unloading** event --- --- --- --- UnLoading event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of unloading or unboarding of a cargo object. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- Note that this event is triggered repeatedly until all cargo (units) have been unboarded from the CarrierUnit. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Cargo.Cargo#CARGO Cargo The cargo object. --- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterUnload( From, Event, To, CarrierGroup, Cargo, CarrierUnit, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.9) Tailor the **Unloaded** event --- --- --- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- Unloaded event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- Note that if more cargo objects were unloading or unboarding from the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. --- -- A CarrierUnit can be part of the larger CarrierGroup. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Cargo.Cargo#CARGO Cargo The cargo object. --- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterUnloaded( From, Event, To, CarrierGroup, Cargo, CarrierUnit, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- --- ## 3.10) Tailor the **Deployed** event --- --- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- --- Deployed event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterDeployed( From, Event, To, CarrierGroup, DeployZone ) --- --- -- Write here your own code. --- --- end --- --- ## 3.11) Tailor the **Home** event --- --- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. --- You can use this event handler to post messages to players, or provide status updates etc. --- --- --- Home event handler OnAfter for CLASS. --- -- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. --- -- You can use this event handler to post messages to players, or provide status updates etc. --- -- If there is no HomeZone is specified, the CarrierGroup will stay at the current location after having deployed all cargo and this event won't be triggered. --- -- @param #CLASS self --- -- @param #string From A string that contains the "*from state name*" when the event was triggered. --- -- @param #string Event A string that contains the "*event name*" when the event was triggered. --- -- @param #string To A string that contains the "*to state name*" when the event was triggered. --- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. --- -- @param Core.Point#COORDINATE Coordinate The home coordinate the Carrier will arrive and stop it's activities. --- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the home Coordinate. --- -- @param #number Height Height in meters to move to the home coordinate. --- -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. --- function CLASS:OnAfterHome( From, Event, To, CarrierGroup, Coordinate, Speed, Height, HomeZone ) --- --- -- Write here your own code. --- --- end --- --- --- --- --- # 4) Set the pickup parameters. --- --- Several parameters can be set to pickup cargo: --- --- * @{#AI_CARGO_DISPATCHER.SetPickupRadius}(): Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius. --- * @{#AI_CARGO_DISPATCHER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. --- * @{#AI_CARGO_DISPATCHER.SetPickupHeight}(): Set the height or randomizes the height in meters to pickup the cargo. --- --- --- --- --- # 5) Set the deploy parameters. --- --- Several parameters can be set to deploy cargo: --- --- * @{#AI_CARGO_DISPATCHER.SetDeployRadius}(): Sets or randomizes the deploy location for the carrier around the cargo coordinate in a radius defined an outer and an optional inner radius. --- * @{#AI_CARGO_DISPATCHER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. --- * @{#AI_CARGO_DISPATCHER.SetDeployHeight}(): Set the height or randomizes the height in meters to deploy the cargo. --- --- --- --- --- # 6) Set the home zone when there isn't any more cargo to pickup. --- --- A home zone can be specified to where the Carriers will move when there isn't any cargo left for pickup. --- Use @{#AI_CARGO_DISPATCHER.SetHomeZone}() to specify the home zone. --- --- If no home zone is specified, the carriers will wait near the deploy zone for a new pickup command. --- --- === --- --- @field #AI_CARGO_DISPATCHER -AI_CARGO_DISPATCHER = { - ClassName = "AI_CARGO_DISPATCHER", - AI_Cargo = {}, - PickupCargo = {} -} - ---- List of AI_Cargo --- @field #list -AI_CARGO_DISPATCHER.AI_Cargo = {} - ---- List of PickupCargo --- @field #list -AI_CARGO_DISPATCHER.PickupCargo = {} - - ---- Creates a new AI_CARGO_DISPATCHER object. --- @param #AI_CARGO_DISPATCHER self --- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the carriers. If nil, then cargo can be picked up everywhere. --- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the carriers. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() --- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- AICargoDispatcherHelicopter:Start() --- --- @usage --- --- -- An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones ) --- AICargoDispatcherAPC:Start() --- --- @usage --- --- -- An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases. --- --- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() --- local PickupZoneSet = SET_ZONE:New() --- local DeployZoneSet = SET_ZONE:New() --- --- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) ) --- --- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet ) --- AICargoDispatcherAirplanes:Start() --- -function AI_CARGO_DISPATCHER:New( CarrierSet, CargoSet, PickupZoneSet, DeployZoneSet ) - - local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER - - self.SetCarrier = CarrierSet -- Core.Set#SET_GROUP - self.SetCargo = CargoSet -- Core.Set#SET_CARGO - - - self.PickupZoneSet=PickupZoneSet - self.DeployZoneSet=DeployZoneSet - - self:SetStartState( "Idle" ) - - self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) - - self:AddTransition( "Idle", "Start", "Monitoring" ) - self:AddTransition( "Monitoring", "Stop", "Idle" ) - - - self:AddTransition( "Monitoring", "Pickup", "Monitoring" ) - self:AddTransition( "Monitoring", "Load", "Monitoring" ) - self:AddTransition( "Monitoring", "Loading", "Monitoring" ) - self:AddTransition( "Monitoring", "Loaded", "Monitoring" ) - self:AddTransition( "Monitoring", "PickedUp", "Monitoring" ) - - self:AddTransition( "Monitoring", "Transport", "Monitoring" ) - - self:AddTransition( "Monitoring", "Deploy", "Monitoring" ) - self:AddTransition( "Monitoring", "Unload", "Monitoring" ) - self:AddTransition( "Monitoring", "Unloading", "Monitoring" ) - self:AddTransition( "Monitoring", "Unloaded", "Monitoring" ) - self:AddTransition( "Monitoring", "Deployed", "Monitoring" ) - - self:AddTransition( "Monitoring", "Home", "Monitoring" ) - - self:SetMonitorTimeInterval( 30 ) - - self:SetDeployRadius( 500, 200 ) - - self.PickupCargo = {} - self.CarrierHome = {} - - -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. - function self.SetCarrier.OnAfterRemoved( SetCarrier, From, Event, To, CarrierName, Carrier ) - self:F( { Carrier = Carrier:GetName() } ) - self.PickupCargo[Carrier] = nil - self.CarrierHome[Carrier] = nil - end - - return self -end - - ---- Set the monitor time interval. --- @param #AI_CARGO_DISPATCHER self --- @param #number MonitorTimeInterval The interval in seconds when the cargo dispatcher will search for new cargo to be picked up. --- @return #AI_CARGO_DISPATCHER -function AI_CARGO_DISPATCHER:SetMonitorTimeInterval( MonitorTimeInterval ) - - self.MonitorTimeInterval = MonitorTimeInterval - - return self -end - - ---- Set the home zone. --- When there is nothing anymore to pickup, the carriers will go to a random coordinate in this zone. --- They will await here new orders. --- @param #AI_CARGO_DISPATCHER self --- @param Core.Zone#ZONE_BASE HomeZone The home zone where the carriers will return when there is no more cargo to pickup. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the home coordinate --- local HomeZone = ZONE:New( "Home" ) --- AICargoDispatcherHelicopter:SetHomeZone( HomeZone ) --- -function AI_CARGO_DISPATCHER:SetHomeZone( HomeZone ) - - self.HomeZone = HomeZone - - return self -end - - ---- Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius. --- This radius is influencing the location where the carrier will land to pickup the cargo. --- There are two aspects that are very important to remember and take into account: --- --- - Ensure that the outer and inner radius are within reporting radius set by the cargo. --- For example, if the cargo has a reporting radius of 400 meters, and the outer and inner radius is set to 500 and 450 respectively, --- then no cargo will be loaded!!! --- - Also take care of the potential cargo position and possible reasons to crash the carrier. This is especially important --- for locations which are crowded with other objects, like in the middle of villages or cities. --- So, for the best operation of cargo operations, always ensure that the cargo is located at open spaces. --- --- The default radius is 0, so the center. In case of a polygon zone, a random location will be selected as the center in the zone. --- @param #AI_CARGO_DISPATCHER self --- @param #number OuterRadius The outer radius in meters around the cargo coordinate. --- @param #number InnerRadius (optional) The inner radius in meters around the cargo coordinate. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters! --- AICargoDispatcherHelicopter:SetPickupRadius( 500, 300 ) --- -function AI_CARGO_DISPATCHER:SetPickupRadius( OuterRadius, InnerRadius ) - - OuterRadius = OuterRadius or 0 - InnerRadius = InnerRadius or OuterRadius - - self.PickupOuterRadius = OuterRadius - self.PickupInnerRadius = InnerRadius - - return self -end - - ---- Set the speed or randomizes the speed in km/h to pickup the cargo. --- @param #AI_CARGO_DISPATCHER self --- @param #number MaxSpeed (optional) The maximum speed to move to the cargo pickup location. --- @param #number MinSpeed The minimum speed to move to the cargo pickup location. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the minimum pickup speed to be 100 km/h and the maximum speed to be 200 km/h. --- AICargoDispatcherHelicopter:SetPickupSpeed( 200, 100 ) --- -function AI_CARGO_DISPATCHER:SetPickupSpeed( MaxSpeed, MinSpeed ) - - MaxSpeed = MaxSpeed or 999 - MinSpeed = MinSpeed or MaxSpeed - - self.PickupMinSpeed = MinSpeed - self.PickupMaxSpeed = MaxSpeed - - return self -end - - ---- Sets or randomizes the deploy location for the carrier around the cargo coordinate in a radius defined an outer and an optional inner radius. --- This radius is influencing the location where the carrier will land to deploy the cargo. --- There is an aspect that is very important to remember and take into account: --- --- - Take care of the potential cargo position and possible reasons to crash the carrier. This is especially important --- for locations which are crowded with other objects, like in the middle of villages or cities. --- So, for the best operation of cargo operations, always ensure that the cargo is located at open spaces. --- --- The default radius is 0, so the center. In case of a polygon zone, a random location will be selected as the center in the zone. --- @param #AI_CARGO_DISPATCHER self --- @param #number OuterRadius The outer radius in meters around the cargo coordinate. --- @param #number InnerRadius (optional) The inner radius in meters around the cargo coordinate. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters! --- AICargoDispatcherHelicopter:SetDeployRadius( 500, 300 ) --- -function AI_CARGO_DISPATCHER:SetDeployRadius( OuterRadius, InnerRadius ) - - OuterRadius = OuterRadius or 0 - InnerRadius = InnerRadius or OuterRadius - - self.DeployOuterRadius = OuterRadius - self.DeployInnerRadius = InnerRadius - - return self -end - - ---- Sets or randomizes the speed in km/h to deploy the cargo. --- @param #AI_CARGO_DISPATCHER self --- @param #number MaxSpeed The maximum speed to move to the cargo deploy location. --- @param #number MinSpeed (optional) The minimum speed to move to the cargo deploy location. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the minimum deploy speed to be 100 km/h and the maximum speed to be 200 km/h. --- AICargoDispatcherHelicopter:SetDeploySpeed( 200, 100 ) --- -function AI_CARGO_DISPATCHER:SetDeploySpeed( MaxSpeed, MinSpeed ) - - MaxSpeed = MaxSpeed or 999 - MinSpeed = MinSpeed or MaxSpeed - - self.DeployMinSpeed = MinSpeed - self.DeployMaxSpeed = MaxSpeed - - return self -end - - ---- Set the height or randomizes the height in meters to fly and pickup the cargo. The default height is 200 meters. --- @param #AI_CARGO_DISPATCHER self --- @param #number MaxHeight (optional) The maximum height to fly to the cargo pickup location. --- @param #number MinHeight (optional) The minimum height to fly to the cargo pickup location. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the minimum pickup fly height to be 50 meters and the maximum height to be 200 meters. --- AICargoDispatcherHelicopter:SetPickupHeight( 200, 50 ) --- -function AI_CARGO_DISPATCHER:SetPickupHeight( MaxHeight, MinHeight ) - - MaxHeight = MaxHeight or 200 - MinHeight = MinHeight or MaxHeight - - self.PickupMinHeight = MinHeight - self.PickupMaxHeight = MaxHeight - - return self -end - - ---- Set the height or randomizes the height in meters to fly and deploy the cargo. The default height is 200 meters. --- @param #AI_CARGO_DISPATCHER self --- @param #number MaxHeight (optional) The maximum height to fly to the cargo deploy location. --- @param #number MinHeight (optional) The minimum height to fly to the cargo deploy location. --- @return #AI_CARGO_DISPATCHER --- @usage --- --- -- Create a new cargo dispatcher --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- --- -- Set the minimum deploy fly height to be 50 meters and the maximum height to be 200 meters. --- AICargoDispatcherHelicopter:SetDeployHeight( 200, 50 ) --- -function AI_CARGO_DISPATCHER:SetDeployHeight( MaxHeight, MinHeight ) - - MaxHeight = MaxHeight or 200 - MinHeight = MinHeight or MaxHeight - - self.DeployMinHeight = MinHeight - self.DeployMaxHeight = MaxHeight - - return self -end - - ---- The Start trigger event, which actually takes action at the specified time interval. --- @param #AI_CARGO_DISPATCHER self -function AI_CARGO_DISPATCHER:onafterMonitor() - - self:F("Carriers") - self.SetCarrier:Flush() - - for CarrierGroupName, Carrier in pairs( self.SetCarrier:GetSet() ) do - local Carrier = Carrier -- Wrapper.Group#GROUP - if Carrier:IsAlive() ~= nil then - local AI_Cargo = self.AI_Cargo[Carrier] - if not AI_Cargo then - - -- ok, so this Carrier does not have yet an AI_CARGO handling object... - -- let's create one and also declare the Loaded and UnLoaded handlers. - self.AI_Cargo[Carrier] = self:AICargo( Carrier, self.SetCargo, self.CombatRadius ) - AI_Cargo = self.AI_Cargo[Carrier] - - --- Pickup event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickup - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The coordinate of the pickup location. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the pickup Coordinate. - -- @param #number Height Height in meters to move to the pickup coordinate. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - function AI_Cargo.OnAfterPickup( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, Height, PickupZone ) - self:Pickup( CarrierGroup, Coordinate, Speed, Height, PickupZone ) - end - - --- Load event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoad - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterLoad( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) - self:Load( CarrierGroup, PickupZone ) - end - - --- Loading event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that this event is triggered repeatedly until all cargo (units) have been boarded into the carrier. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoading - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterBoard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) - self:Loading( CarrierGroup, Cargo, CarrierUnit, PickupZone ) - end - - --- Loaded event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. - -- A CarrierUnit can be part of the larger CarrierGroup. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoaded - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterLoaded( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) - self:Loaded( CarrierGroup, Cargo, CarrierUnit, PickupZone ) - end - - --- PickedUp event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickedUp - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. - - function AI_Cargo.OnAfterPickedUp( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) - self:PickedUp( CarrierGroup, PickupZone ) - self:Transport( CarrierGroup ) - end - - - --- Deploy event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeploy - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The deploy coordinate. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the deploy Coordinate. - -- @param #number Height Height in meters to move to the deploy coordinate. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterDeploy( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, Height, DeployZone ) - self:Deploy( CarrierGroup, Coordinate, Speed, Height, DeployZone ) - end - - - --- Unload event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnload - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnload( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloading( Carrier, Cargo, CarrierUnit, DeployZone ) - end - - --- UnLoading event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of unloading or unboarding of a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that this event is triggered repeatedly until all cargo (units) have been unboarded from the CarrierUnit. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloading - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnboard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloading( CarrierGroup, Cargo, CarrierUnit, DeployZone ) - end - - - --- Unloaded event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- Note that if more cargo objects were unloading or unboarding from the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. - -- A CarrierUnit can be part of the larger CarrierGroup. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloaded - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Cargo.Cargo#CARGO Cargo The cargo object. - -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) - self:Unloaded( Carrier, Cargo, CarrierUnit, DeployZone ) - end - - --- Deployed event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeployed - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterDeployed( AI_Cargo, Carrier, From, Event, To, DeployZone ) - self:Deployed( Carrier, DeployZone ) - end - - --- Home event handler OnAfter for AI_CARGO_DISPATCHER. - -- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- If there is no HomeZone is specified, the CarrierGroup will stay at the current location after having deployed all cargo. - -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterHome - -- @param #AI_CARGO_DISPATCHER self - -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. - -- @param Core.Point#COORDINATE Coordinate The home coordinate the Carrier will arrive and stop it's activities. - -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the home Coordinate. - -- @param #number Height Height in meters to move to the home coordinate. - -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - - function AI_Cargo.OnAfterHome( AI_Cargo, Carrier, From, Event, To, Coordinate, Speed, Height, HomeZone ) - self:Home( Carrier, Coordinate, Speed, Height, HomeZone ) - end - end - - -- The Pickup sequence ... - -- Check if this Carrier need to go and Pickup something... - -- So, if the cargo bay is not full yet with cargo to be loaded ... - self:T( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } ) - if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then - -- ok, so there is a free Carrier - -- now find the first cargo that is Unloaded - - local PickupCargo = nil - local PickupZone = nil - - self.SetCargo:Flush() - for CargoName, Cargo in UTILS.spairs( self.SetCargo:GetSet(), function( t, a, b ) return t[a]:GetWeight() < t[b]:GetWeight() end ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - self:F( { Cargo = Cargo:GetName(), UnLoaded = Cargo:IsUnLoaded(), Deployed = Cargo:IsDeployed(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) - if Cargo:IsUnLoaded() == true and Cargo:IsDeployed() == false then - local CargoCoordinate = Cargo:GetCoordinate() - local CoordinateFree = true - --self.PickupZoneSet:Flush() - --PickupZone = self.PickupZoneSet:GetRandomZone() - PickupZone = self.PickupZoneSet and self.PickupZoneSet:IsCoordinateInZone( CargoCoordinate ) - if not self.PickupZoneSet or PickupZone then - for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do - if CarrierPickup:IsAlive() == true then - if CargoCoordinate:Get2DDistance( Coordinate ) <= 25 then - self:F( { "Coordinate not free for ", Cargo = Cargo:GetName(), Carrier:GetName(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) - CoordinateFree = false - break - end - else - self.PickupCargo[CarrierPickup] = nil - end - end - if CoordinateFree == true then - -- Check if this cargo can be picked-up by at least one carrier unit of AI_Cargo. - local LargestLoadCapacity = 0 - for _, Carrier in pairs( Carrier:GetUnits() ) do - local LoadCapacity = Carrier:GetCargoBayFreeWeight() - if LargestLoadCapacity < LoadCapacity then - LargestLoadCapacity = LoadCapacity - end - end - -- So if there is a carrier that has the required load capacity to load the total weight of the cargo, dispatch the carrier. - -- Otherwise break and go to the next carrier. - -- This will skip cargo which is too large to be able to be loaded by carriers - -- and will secure an efficient dispatching scheme. - if LargestLoadCapacity >= Cargo:GetWeight() then - self.PickupCargo[Carrier] = CargoCoordinate - PickupCargo = Cargo - break - else - local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.", - tostring(Cargo:GetName()), Cargo:GetWeight(), LargestLoadCapacity, tostring(Carrier:GetName())) - self:T(text) - end - end - end - end - end - - if PickupCargo then - self.CarrierHome[Carrier] = nil - local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius ) - AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), math.random( self.PickupMinHeight, self.PickupMaxHeight ), PickupZone ) - break - else - if self.HomeZone then - if not self.CarrierHome[Carrier] then - self.CarrierHome[Carrier] = true - AI_Cargo:Home( self.HomeZone:GetRandomPointVec2(), math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), math.random( self.PickupMinHeight, self.PickupMaxHeight ), self.HomeZone ) - end - end - end - end - end - end - - self:__Monitor( self.MonitorTimeInterval ) -end - - ---- Start Trigger for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] Start --- @param #AI_CARGO_DISPATCHER self - ---- Start Asynchronous Trigger for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] __Start --- @param #AI_CARGO_DISPATCHER self --- @param #number Delay - -function AI_CARGO_DISPATCHER:onafterStart( From, Event, To ) - self:__Monitor( -1 ) -end - - ---- Stop Trigger for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] Stop --- @param #AI_CARGO_DISPATCHER self - ---- Stop Asynchronous Trigger for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] __Stop --- @param #AI_CARGO_DISPATCHER self --- @param #number Delay - - ---- Make a Carrier run for a cargo deploy action after the cargo has been loaded, by default. --- @param #AI_CARGO_DISPATCHER self --- @param From --- @param Event --- @param To --- @param Wrapper.Group#GROUP Carrier --- @param Cargo.Cargo#CARGO Cargo --- @return #AI_CARGO_DISPATCHER -function AI_CARGO_DISPATCHER:onafterTransport( From, Event, To, Carrier, Cargo ) - - if self.DeployZoneSet then - if self.AI_Cargo[Carrier]:IsTransporting() == true then - local DeployZone = self.DeployZoneSet:GetRandomZone() - - local DeployCoordinate = DeployZone:GetCoordinate():GetRandomCoordinateInRadius( self.DeployOuterRadius, self.DeployInnerRadius ) - self.AI_Cargo[Carrier]:__Deploy( 0.1, DeployCoordinate, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ), math.random( self.DeployMinHeight, self.DeployMaxHeight ), DeployZone ) - end - end - - self:F( { Carrier = Carrier:GetName(), PickupCargo = self.PickupCargo } ) - self.PickupCargo[Carrier] = nil -end - diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua deleted file mode 100644 index 3d98522e1..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua +++ /dev/null @@ -1,263 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo using APCs. --- --- ## Features: --- --- * Quickly transport cargo to various deploy zones using ground vehicles (APCs, trucks ...). --- * Various @{Cargo.Cargo#CARGO} types can be transported. These are infantry groups and crates. --- * Define a list of deploy zones of various types to transport the cargo to. --- * The vehicles follow the roads to ensure the fastest possible cargo transportation over the ground. --- * Multiple vehicles can transport multiple cargo as one vehicle group. --- * Multiple vehicle groups can be enabled as one collaborating transportation process. --- * Infantry loaded as cargo, will unboard in case enemies are nearby and will help defending the vehicles. --- * Different ranges can be setup for enemy defenses. --- * Different options can be setup to tweak the cargo transporation behaviour. --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/] --- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching) --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Dispatcher_APC --- @image AI_Cargo_Dispatching_For_APC.JPG - --- @type AI_CARGO_DISPATCHER_APC --- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - - ---- A dynamic cargo transportation capability for AI groups. --- --- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- The AI_CARGO_DISPATCHER_APC module is derived from the AI_CARGO_DISPATCHER module. --- --- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_APC class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!! --- --- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! --- --- On top, the AI_CARGO_DISPATCHER_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. --- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. --- --- --- # 1) AI_CARGO_DISPATCHER_APC constructor. --- --- * @{#AI_CARGO_DISPATCHER_APC.New}(): Creates a new AI_CARGO_DISPATCHER_APC object. --- --- --- --- --- # 2) AI_CARGO_DISPATCHER_APC is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Within your mission, you can capture these events when triggered, and tailor the events with your own code! --- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. --- --- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** --- --- --- --- --- # 3) Set the pickup parameters. --- --- Several parameters can be set to pickup cargo: --- --- * @{#AI_CARGO_DISPATCHER_APC.SetPickupRadius}(): Sets or randomizes the pickup location for the APC around the cargo coordinate in a radius defined an outer and optional inner radius. --- * @{#AI_CARGO_DISPATCHER_APC.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. --- --- # 4) Set the deploy parameters. --- --- Several parameters can be set to deploy cargo: --- --- * @{#AI_CARGO_DISPATCHER_APC.SetDeployRadius}(): Sets or randomizes the deploy location for the APC around the cargo coordinate in a radius defined an outer and an optional inner radius. --- * @{#AI_CARGO_DISPATCHER_APC.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. --- --- # 5) Set the home zone when there isn't any more cargo to pickup. --- --- A home zone can be specified to where the APCs will move when there isn't any cargo left for pickup. --- Use @{#AI_CARGO_DISPATCHER_APC.SetHomeZone}() to specify the home zone. --- --- If no home zone is specified, the APCs will wait near the deploy zone for a new pickup command. --- --- # 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_CARGO_DISPATCHER_APC -AI_CARGO_DISPATCHER_APC = { - ClassName = "AI_CARGO_DISPATCHER_APC", -} - ---- Creates a new AI_CARGO_DISPATCHER_APC object. --- @param #AI_CARGO_DISPATCHER_APC self --- @param Core.Set#SET_GROUP APCSet The set of @{Wrapper.Group#GROUP} objects of vehicles, trucks, APCs that will transport the cargo. --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere. --- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the APCs. --- @param DCS#Distance CombatRadius The cargo will be unloaded from the APC and engage the enemy if the enemy is within CombatRadius range. The radius is in meters, the default value is 500 meters. --- @return #AI_CARGO_DISPATCHER_APC --- @usage --- --- -- An AI dispatcher object for a vehicle squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherAPC = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargoInfantry, nil, SetDeployZones ) --- AICargoDispatcherAPC:Start() --- -function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet, CombatRadius ) - - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_APC - - self:SetDeploySpeed( 120, 70 ) - self:SetPickupSpeed( 120, 70 ) - self:SetPickupRadius( 0, 0 ) - self:SetDeployRadius( 0, 0 ) - - self:SetPickupHeight() - self:SetDeployHeight() - - self:SetCombatRadius( CombatRadius ) - - return self -end - - ---- AI cargo --- @param #AI_CARGO_DISPATCHER_APC self --- @param Wrapper.Group#GROUP APC The APC carrier. --- @param Core.Set#SET_CARGO CargoSet Cargo set. --- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object. -function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet ) - - local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius) - - aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation) - aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation) - - return aicargoapc -end - ---- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier). --- This is only valid for APCs and trucks etc, thus ground vehicles. --- @param #AI_CARGO_DISPATCHER_APC self --- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. --- When the combat radius is 0 (default), no defense will happen of the carrier. --- When the combat radius is not provided, no defense will happen! --- @return #AI_CARGO_DISPATCHER_APC --- @usage --- --- -- Disembark the infantry when the carrier is under attack. --- AICargoDispatcher:SetCombatRadius( 500 ) --- --- -- Keep the cargo in the carrier when the carrier is under attack. --- AICargoDispatcher:SetCombatRadius( 0 ) -function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius ) - - self.CombatRadius = CombatRadius or 0 - - return self -end - ---- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo. --- @param #AI_CARGO_DISPATCHER_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_DISPATCHER_APC self -function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation) - - self:SetPickupOffRoad(Offroad, Formation) - self:SetDeployOffRoad(Offroad, Formation) - - return self -end - ---- Set whether the carrier will *not* use roads to *pickup* the cargo. --- @param #AI_CARGO_DISPATCHER_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_DISPATCHER_APC self -function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation) - - self.pickupOffroad=Offroad - self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad - - return self -end - ---- Set whether the carrier will *not* use roads to *deploy* the cargo. --- @param #AI_CARGO_DISPATCHER_APC self --- @param #boolean Offroad If true, carrier will not use roads. --- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. --- @return #AI_CARGO_DISPATCHER_APC self -function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation) - - self.deployOffroad=Offroad - self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad - - return self -end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua deleted file mode 100644 index d3a7c78ac..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua +++ /dev/null @@ -1,169 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo using Planes. --- --- ## Features: --- --- * The airplanes will fly towards the pickup airbases to pickup the cargo. --- * The airplanes will fly towards the deploy airbases to deploy the cargo. --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/] --- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching) --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Dispatcher_Airplane --- @image AI_Cargo_Dispatching_For_Airplanes.JPG - - --- @type AI_CARGO_DISPATCHER_AIRPLANE --- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - - ---- Brings a dynamic cargo handling capability for AI groups. --- --- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- The AI_CARGO_DISPATCHER_AIRPLANE module is derived from the AI_CARGO_DISPATCHER module. --- --- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_AIRPLANE class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!** --- --- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! --- --- On top, the AI_CARGO_DISPATCHER_AIRPLANE class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. --- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. --- --- # 1) AI_CARGO_DISPATCHER_AIRPLANE constructor. --- --- * @{#AI_CARGO_DISPATCHER_AIRPLANE.New}(): Creates a new AI_CARGO_DISPATCHER_AIRPLANE object. --- --- --- --- --- # 2) AI_CARGO_DISPATCHER_AIRPLANE is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Within your mission, you can capture these events when triggered, and tailor the events with your own code! --- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. --- --- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** --- --- --- # 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_CARGO_DISPATCHER_AIRPLANE -AI_CARGO_DISPATCHER_AIRPLANE = { - ClassName = "AI_CARGO_DISPATCHER_AIRPLANE", -} - ---- Creates a new AI_CARGO_DISPATCHER_AIRPLANE object. --- @param #AI_CARGO_DISPATCHER_AIRPLANE self --- @param Core.Set#SET_GROUP AirplaneSet The set of @{Wrapper.Group#GROUP} objects of airplanes that will transport the cargo. --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @param Core.Zone#SET_ZONE PickupZoneSet The set of zone airbases where the cargo has to be picked up. --- @param Core.Zone#SET_ZONE DeployZoneSet The set of zone airbases where the cargo is deployed. Choice for each cargo is random. --- @return #AI_CARGO_DISPATCHER_AIRPLANE self --- @usage --- --- -- An AI dispatcher object for an airplane squadron, moving infantry and vehicles from pickup airbases to deploy airbases. --- --- local CargoInfantrySet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local AirplanesSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() --- local PickupZoneSet = SET_ZONE:New() --- local DeployZoneSet = SET_ZONE:New() --- --- PickupZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Gudauta ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Sochi_Adler ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Maykop_Khanskaya ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Mineralnye_Vody ) ) --- DeployZoneSet:AddZone( ZONE_AIRBASE:New( AIRBASE.Caucasus.Vaziani ) ) --- --- AICargoDispatcherAirplanes = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplanesSet, CargoInfantrySet, PickupZoneSet, DeployZoneSet ) --- AICargoDispatcherAirplanes:Start() --- -function AI_CARGO_DISPATCHER_AIRPLANE:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) - - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE - - self:SetPickupSpeed( 1200, 600 ) - self:SetDeploySpeed( 1200, 600 ) - - self:SetPickupRadius( 0, 0 ) - self:SetDeployRadius( 0, 0 ) - - self:SetPickupHeight( 8000, 6000 ) - self:SetDeployHeight( 8000, 6000 ) - - self:SetMonitorTimeInterval( 600 ) - - return self -end - -function AI_CARGO_DISPATCHER_AIRPLANE:AICargo( Airplane, CargoSet ) - - return AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) -end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua deleted file mode 100644 index b219c78b0..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua +++ /dev/null @@ -1,199 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo using Helicopters. --- --- ## Features: --- --- * The helicopters will fly towards the pickup locations to pickup the cargo. --- * The helicopters will fly towards the deploy zones to deploy the cargo. --- * Precision deployment as well as randomized deployment within the deploy zones are possible. --- * Helicopters will orbit the deploy zones when there is no space for landing until the deploy zone is free. --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [FlightControl-Master/MOOSE_MISSIONS/AID - AI Dispatching/AID-CGO - AI Cargo Dispatching/] --- (https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/AID%20-%20AI%20Dispatching/AID-CGO%20-%20AI%20Cargo%20Dispatching) --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Dispatcher_Helicopter --- @image AI_Cargo_Dispatching_For_Helicopters.JPG - --- @type AI_CARGO_DISPATCHER_HELICOPTER --- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - - ---- A dynamic cargo handling capability for AI helicopter groups. --- --- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- --- The AI_CARGO_DISPATCHER_HELICOPTER module is derived from the AI_CARGO_DISPATCHER module. --- --- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_HELICOPTER class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!** --- --- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! --- --- On top, the AI_CARGO_DISPATCHER_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. --- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. --- --- --- --- --- # 1. AI\_CARGO\_DISPATCHER\_HELICOPTER constructor. --- --- * @{#AI_CARGO_DISPATCHER\_HELICOPTER.New}(): Creates a new AI\_CARGO\_DISPATCHER\_HELICOPTER object. --- --- --- --- --- # 2. AI\_CARGO\_DISPATCHER\_HELICOPTER is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Within your mission, you can capture these events when triggered, and tailor the events with your own code! --- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. --- --- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** --- --- --- --- --- ## 3. Set the pickup parameters. --- --- Several parameters can be set to pickup cargo: --- --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupRadius}(): Sets or randomizes the pickup location for the helicopter around the cargo coordinate in a radius defined an outer and optional inner radius. --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetPickupHeight}(): Set the height or randomizes the height in meters to pickup the cargo. --- --- --- --- --- ## 4. Set the deploy parameters. --- --- Several parameters can be set to deploy cargo: --- --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployRadius}(): Sets or randomizes the deploy location for the helicopter around the cargo coordinate in a radius defined an outer and an optional inner radius. --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. --- * @{#AI_CARGO_DISPATCHER_HELICOPTER.SetDeployHeight}(): Set the height or randomizes the height in meters to deploy the cargo. --- --- --- --- --- ## 5. Set the home zone when there isn't any more cargo to pickup. --- --- A home zone can be specified to where the Helicopters will move when there isn't any cargo left for pickup. --- Use @{#AI_CARGO_DISPATCHER_HELICOPTER.SetHomeZone}() to specify the home zone. --- --- If no home zone is specified, the helicopters will wait near the deploy zone for a new pickup command. --- --- # 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_CARGO_DISPATCHER_HELICOPTER -AI_CARGO_DISPATCHER_HELICOPTER = { - ClassName = "AI_CARGO_DISPATCHER_HELICOPTER", -} - ---- Creates a new AI_CARGO_DISPATCHER_HELICOPTER object. --- @param #AI_CARGO_DISPATCHER_HELICOPTER self --- @param Core.Set#SET_GROUP HelicopterSet The set of @{Wrapper.Group#GROUP} objects of helicopters that will transport the cargo. --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. --- @param Core.Set#SET_ZONE PickupZoneSet (optional) The set of pickup zones, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere. --- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones, which are used to where the cargo will be deployed by the Helicopters. --- @return #AI_CARGO_DISPATCHER_HELICOPTER --- @usage --- --- -- An AI dispatcher object for a helicopter squadron, moving infantry from pickup zones to deploy zones. --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() --- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- --- AICargoDispatcherHelicopter = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargoInfantry, SetPickupZones, SetDeployZones ) --- AICargoDispatcherHelicopter:Start() --- -function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) - - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER - - self:SetPickupSpeed( 350, 150 ) - self:SetDeploySpeed( 350, 150 ) - - self:SetPickupRadius( 40, 12 ) - self:SetDeployRadius( 40, 12 ) - - self:SetPickupHeight( 500, 200 ) - self:SetDeployHeight( 500, 200 ) - - return self -end - - -function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet ) - - local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) - dispatcher:SetLandingSpeedAndHeight(27, 6) - return dispatcher - -end - diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua deleted file mode 100644 index 6fc670e40..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua +++ /dev/null @@ -1,198 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo using Ships. --- --- ## Features: --- --- * Transport cargo to various deploy zones using naval vehicles. --- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates. --- * Define a deploy zone of various types to determine the destination of the cargo. --- * Ships will follow shipping lanes as defined in the Mission Editor. --- * Multiple ships can transport multiple cargo as a single group. --- --- === --- --- ## Test Missions: --- --- NEED TO DO --- --- === --- --- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl) --- --- === --- --- @module AI.AI_Cargo_Dispatcher_Ship --- @image AI_Cargo_Dispatcher.JPG - --- @type AI_CARGO_DISPATCHER_SHIP --- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - - ---- A dynamic cargo transportation capability for AI groups. --- --- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation. --- --- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module. --- --- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!! --- --- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**. --- --- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. --- CARGO derived objects must generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo. --- --- --- # 1) AI_CARGO_DISPATCHER_SHIP constructor. --- --- * @{#AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object. --- --- --- --- --- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine. --- --- This section must be read as follows... Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- * Monitoring => Monitor => Monitoring --- * Monitoring => Stop => Idle --- --- * Monitoring => Pickup => Monitoring --- * Monitoring => Load => Monitoring --- * Monitoring => Loading => Monitoring --- * Monitoring => Loaded => Monitoring --- * Monitoring => PickedUp => Monitoring --- * Monitoring => Deploy => Monitoring --- * Monitoring => Unload => Monitoring --- * Monitoring => Unloaded => Monitoring --- * Monitoring => Deployed => Monitoring --- * Monitoring => Home => Monitoring --- --- --- ## 2.1) AI_CARGO_DISPATCHER States. --- --- * **Monitoring**: The process is dispatching. --- * **Idle**: The process is idle. --- --- ## 2.2) AI_CARGO_DISPATCHER Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- * **Pickup**: Pickup cargo. --- * **Load**: Load the cargo. --- * **Loading**: The dispatcher is coordinating the loading of a cargo. --- * **Loaded**: Flag that the cargo is loaded. --- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. --- * **Deploy**: Deploy cargo to a location. --- * **Unload**: Unload the cargo. --- * **Unloaded**: Flag that the cargo is unloaded. --- * **Deployed**: All cargo is unloaded from the carriers in the group. --- * **Home**: A Carrier is going home. --- --- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! --- --- Within your mission, you can capture these events when triggered, and tailor the events with your own code! --- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. --- --- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** --- --- --- --- --- # 3) Set the pickup parameters. --- --- Several parameters can be set to pickup cargo: --- --- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius. --- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. --- --- # 4) Set the deploy parameters. --- --- Several parameters can be set to deploy cargo: --- --- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius. --- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. --- --- # 5) Set the home zone when there isn't any more cargo to pickup. --- --- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup. --- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone. --- --- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command. --- --- # 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_CARGO_DISPATCHER_SHIP -AI_CARGO_DISPATCHER_SHIP = { - ClassName = "AI_CARGO_DISPATCHER_SHIP" - } - ---- Creates a new AI_CARGO_DISPATCHER_SHIP object. --- @param #AI_CARGO_DISPATCHER_SHIP self --- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo --- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects. --- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship. --- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship. --- @param #table ShippingLane Table containing list of Shipping Lanes to be used --- @return #AI_CARGO_DISPATCHER_SHIP --- @usage --- --- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane --- --- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart() --- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() --- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- NEED MORE THOUGHT - ShippingLane is part of Warehouse....... --- local ShippingLane = SET_GROUP:New():FilterPrefixes( "ShippingLane" ):FilterOnce():GetSetObjects() --- --- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane ) --- AICargoDispatcherShip:Start() --- -function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane ) - - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) - - self:SetPickupSpeed( 60, 10 ) - self:SetDeploySpeed( 60, 10 ) - - self:SetPickupRadius( 500, 6000 ) - self:SetDeployRadius( 500, 6000 ) - - self:SetPickupHeight( 0, 0 ) - self:SetDeployHeight( 0, 0 ) - - self:SetShippingLane( ShippingLane ) - - self:SetMonitorTimeInterval( 600 ) - - return self -end - -function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane ) - self.ShippingLane = ShippingLane - - return self - -end - -function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet ) - - return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane ) -end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua deleted file mode 100644 index cea586679..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ /dev/null @@ -1,661 +0,0 @@ ---- **AI** - Models the intelligent transportation of cargo using helicopters. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Cargo_Helicopter --- @image AI_Cargo_Dispatching_For_Helicopters.JPG - --- @type AI_CARGO_HELICOPTER --- @extends Core.Fsm#FSM_CONTROLLABLE - - ---- Brings a dynamic cargo handling capability for an AI helicopter group. --- --- Helicopter carriers can be mobilized to intelligently transport infantry and other cargo within the simulation. --- --- The AI_CARGO_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_HELICOPTER object recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- ## Cargo pickup. --- --- Using the @{#AI_CARGO_HELICOPTER.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate. --- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! --- --- ## Cargo deployment. --- --- Using the @{#AI_CARGO_HELICOPTER.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate. --- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! --- --- ## Infantry health. --- --- When infantry is unboarded from the helicopters, the infantry is actually respawned into the battlefield. --- As a result, the unboarding infantry is very _healthy_ every time it unboards. --- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. --- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed. --- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has --- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every --- time is not so much of an issue ... --- --- --- # 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_CARGO_HELICOPTER -AI_CARGO_HELICOPTER = { - ClassName = "AI_CARGO_HELICOPTER", - Coordinate = nil, -- Core.Point#COORDINATE, -} - -AI_CARGO_QUEUE = {} - ---- Creates a new AI_CARGO_HELICOPTER object. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param Core.Set#SET_CARGO CargoSet --- @return #AI_CARGO_HELICOPTER -function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) - - local self = BASE:Inherit( self, AI_CARGO:New( Helicopter, CargoSet ) ) -- #AI_CARGO_HELICOPTER - - self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) - - self:SetStartState( "Unloaded" ) - -- Boarding - self:AddTransition( "Unloaded", "Pickup", "Unloaded" ) - self:AddTransition( "*", "Landed", "*" ) - self:AddTransition( "*", "Load", "*" ) - self:AddTransition( "*", "Loaded", "Loaded" ) - self:AddTransition( "Loaded", "PickedUp", "Loaded" ) - - -- Unboarding - self:AddTransition( "Loaded", "Deploy", "*" ) - self:AddTransition( "*", "Queue", "*" ) - self:AddTransition( "*", "Orbit" , "*" ) - self:AddTransition( "*", "Destroyed", "*" ) - self:AddTransition( "*", "Unload", "*" ) - self:AddTransition( "*", "Unloaded", "Unloaded" ) - self:AddTransition( "Unloaded", "Deployed", "Unloaded" ) - - -- RTB - self:AddTransition( "*", "Home" , "*" ) - - --- Pickup Handler OnBefore for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean - - --- Pickup Handler OnAfter for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickup - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp - -- @param #AI_CARGO_HELICOPTER self - -- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object - - --- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object. - -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object - - --- Pickup Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] Pickup - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] __Pickup - -- @param #AI_CARGO_HELICOPTER self - -- @param #number Delay Delay in seconds. - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to go to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Deploy Handler OnBefore for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnBeforeDeploy - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed. - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - -- @return #boolean - - --- Deploy Handler OnAfter for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeploy - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Deployed Handler OnAfter for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed - -- @param #AI_CARGO_HELICOPTER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Deploy Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] Deploy - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] __Deploy - -- @param #number Delay Delay in seconds. - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. - -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - - --- Home Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] Home - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. - -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - -- @param #number Height (optional) Height the Helicopter should be flying at. - - --- Home Asynchronous Trigger for AI_CARGO_HELICOPTER - -- @function [parent=#AI_CARGO_HELICOPTER] __Home - -- @param #number Delay Delay in seconds. - -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. - -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - -- @param #number Height (optional) Height the Helicopter should be flying at. - - -- We need to capture the Crash events for the helicopters. - -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. - -- So, we need to unlock this when the helo is not anymore ... - Helicopter:HandleEvent( EVENTS.Crash, - function( Helicopter, EventData ) - AI_CARGO_QUEUE[Helicopter] = nil - end - ) - - -- We need to capture the Land events for the helicopters. - -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. - -- So, we need to unlock this when the helo has landed, which can be anywhere ... - -- But only free the landing coordinate after 1 minute, to ensure that all helos have left. - Helicopter:HandleEvent( EVENTS.Land, - function( Helicopter, EventData ) - self:ScheduleOnce( 60, - function( Helicopter ) - AI_CARGO_QUEUE[Helicopter] = nil - end, Helicopter - ) - end - ) - - self:SetCarrier( Helicopter ) - - self.landingspeed = 15 -- kph - self.landingheight = 5.5 -- meter - - return self -end - - - - - ---- Set the Carrier. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @return #AI_CARGO_HELICOPTER -function AI_CARGO_HELICOPTER:SetCarrier( Helicopter ) - - local AICargo = self - - self.Helicopter = Helicopter -- Wrapper.Group#GROUP - self.Helicopter:SetState( self.Helicopter, "AI_CARGO_HELICOPTER", self ) - - self.RoutePickup = false - self.RouteDeploy = false - - Helicopter:HandleEvent( EVENTS.Dead ) - Helicopter:HandleEvent( EVENTS.Hit ) - Helicopter:HandleEvent( EVENTS.Land ) - - function Helicopter:OnEventDead( EventData ) - local AICargoTroops = self:GetState( self, "AI_CARGO_HELICOPTER" ) - self:F({AICargoTroops=AICargoTroops}) - if AICargoTroops then - self:F({}) - if not AICargoTroops:Is( "Loaded" ) then - -- There are enemies within combat range. Unload the Helicopter. - AICargoTroops:Destroyed() - end - end - end - - function Helicopter:OnEventLand( EventData ) - AICargo:Landed() - end - - self.Coalition = self.Helicopter:GetCoalition() - - self:SetControllable( Helicopter ) - - return self -end - ---- Set landingspeed and -height for helicopter landings. Adjust after tracing if your helis get stuck after landing. --- @param #AI_CARGO_HELICOPTER self --- @param #number speed Landing speed in kph(!), e.g. 15 --- @param #number height Landing height in meters(!), e.g. 5.5 --- @return #AI_CARGO_HELICOPTER self --- @usage If your choppers get stuck, add tracing to your script to determine if they hit the right parameters like so: --- --- BASE:TraceOn() --- BASE:TraceClass("AI_CARGO_HELICOPTER") --- --- Watch the DCS.log for entries stating `Helicopter:, Height = Helicopter:, Velocity = Helicopter:` --- Adjust if necessary. -function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height) - local _speed = speed or 15 - local _height = height or 5.5 - self.landingheight = _height - self.landingspeed = _speed - return self -end - --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To -function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) - self:F({From, Event, To}) - Helicopter:F( { Name = Helicopter:GetName() } ) - - if Helicopter and Helicopter:IsAlive() then - - -- S_EVENT_LAND is directly called in two situations: - -- 1 - When the helo lands normally on the ground. - -- 2 - when the helo is hit and goes RTB or even when it is destroyed. - -- For point 2, this is an issue, the infantry may not unload in this case! - -- So we check if the helo is on the ground, and velocity< 15. - -- Only then the infantry can unload (and load too, for consistency)! - - self:T( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) - - if self.RoutePickup == true then - if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then - --self:Load( Helicopter:GetPointVec2() ) - self:Load( self.PickupZone ) - self.RoutePickup = false - end - end - - if self.RouteDeploy == true then - if Helicopter:GetHeight( true ) <= self.landingheight then --and Helicopter:GetVelocityKMH() < self.landingspeed then - self:Unload( self.DeployZone ) - self.RouteDeploy = false - end - end - - end - -end - --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate --- @param #number Speed -function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone ) - self:F({From, Event, To, Coordinate, Speed, DeployZone}) - local HelicopterInZone = false - - if Helicopter and Helicopter:IsAlive() == true then - - local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() ) - - if Distance > 2000 then - self:__Queue( -10, Coordinate, Speed, DeployZone ) - else - - local ZoneFree = true - - for Helicopter, ZoneQueue in pairs( AI_CARGO_QUEUE ) do - local ZoneQueue = ZoneQueue -- Core.Zone#ZONE_RADIUS - if ZoneQueue:IsCoordinateInZone( Coordinate ) then - ZoneFree = false - end - end - - self:F({ZoneFree=ZoneFree}) - - if ZoneFree == true then - - local ZoneQueue = ZONE_RADIUS:New( Helicopter:GetName(), Coordinate:GetVec2(), 100 ) - - AI_CARGO_QUEUE[Helicopter] = ZoneQueue - - local Route = {} - --- local CoordinateFrom = Helicopter:GetCoordinate() --- local WaypointFrom = CoordinateFrom:WaypointAir( --- "RADIO", --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, --- Speed, --- true --- ) --- Route[#Route+1] = WaypointFrom - local CoordinateTo = Coordinate - - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir( - "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - 50, - true - ) - Route[#Route+1] = WaypointTo - - local Tasks = {} - Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route( Route, 0 ) - - -- Keep the DeployZone, because when the helo has landed, we want to provide the DeployZone to the mission designer as part of the Unloaded event. - self.DeployZone = DeployZone - - else - self:__Queue( -10, Coordinate, Speed, DeployZone ) - end - end - else - AI_CARGO_QUEUE[Helicopter] = nil - end -end - - --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate --- @param #number Speed -function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate ) - self:F({From, Event, To, Coordinate}) - - if Helicopter and Helicopter:IsAlive() then - - local Route = {} - - local CoordinateTo = Coordinate - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true) - Route[#Route+1] = WaypointTo - - local Tasks = {} - Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 80 ), 150, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route(Route, 0) - end -end - - - ---- On after Deployed event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Cargo.Cargo#CARGO Cargo Cargo object. --- @param #boolean Deployed Cargo is deployed. --- @return #boolean True if all cargo has been unloaded. -function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone ) - self:F( { From, Event, To, DeployZone = DeployZone } ) - - self:Orbit( Helicopter:GetCoordinate(), 50 ) - - -- Free the coordinate zone after 30 seconds, so that the original helicopter can fly away first. - self:ScheduleOnce( 30, - function( Helicopter ) - AI_CARGO_QUEUE[Helicopter] = nil - end, Helicopter - ) - - self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone ) - -end - ---- On after Pickup event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Pickup place. --- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs. --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. -function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone ) - self:F({Coordinate, Speed, Height, PickupZone }) - - if Helicopter and Helicopter:IsAlive() ~= nil then - - Helicopter:Activate() - - self.RoutePickup = true - Coordinate.y = Height - - local _speed=Speed or Helicopter:GetSpeedMax()*0.5 - - local Route = {} - - --- Calculate the target route point. - local CoordinateFrom = Helicopter:GetCoordinate() - - --- Create a route point of type air. - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) - - --- Create a route point of type air. - local CoordinateTo = Coordinate - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true) - - Route[#Route+1] = WaypointFrom - Route[#Route+1] = WaypointTo - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - Helicopter:WayPointInitialize( Route ) - - local Tasks = {} - - Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route( Route, 1 ) - - self.PickupZone = PickupZone - - self:GetParent( self, AI_CARGO_HELICOPTER ).onafterPickup( self, Helicopter, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - end - -end - ---- Depoloy function and queue. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP AICargoHelicopter --- @param Core.Point#COORDINATE Coordinate Coordinate -function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate, DeployZone ) - AICargoHelicopter:__Queue( -10, Coordinate, 100, DeployZone ) -end - ---- On after Deploy event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter Transport helicopter. --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. --- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the deploy coordinate. -function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone ) - self:F({From, Event, To, Coordinate, Speed, Height, DeployZone}) - if Helicopter and Helicopter:IsAlive() ~= nil then - - self.RouteDeploy = true - - - local Route = {} - - --- Calculate the target route point. - - Coordinate.y = Height - - local _speed=Speed or Helicopter:GetSpeedMax()*0.5 - - --- Create a route point of type air. - local CoordinateFrom = Helicopter:GetCoordinate() - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) - Route[#Route+1] = WaypointFrom - Route[#Route+1] = WaypointFrom - - --- Create a route point of type air. - - local CoordinateTo = Coordinate - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) - - Route[#Route+1] = WaypointTo - Route[#Route+1] = WaypointTo - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - Helicopter:WayPointInitialize( Route ) - - local Tasks = {} - - -- The _Deploy function does not exist. - Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone ) - - Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) ) - - --Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route( Route, 0 ) - - self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeploy( self, Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone ) - end - -end - - ---- On after Home event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Home place. --- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- @param #number Height Height in meters to move to the home coordinate. --- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone ) - self:F({From, Event, To, Coordinate, Speed, Height}) - - if Helicopter and Helicopter:IsAlive() ~= nil then - - self.RouteHome = true - - local Route = {} - - --- Calculate the target route point. - - --Coordinate.y = Height - Height = Height or 50 - - Speed = Speed or Helicopter:GetSpeedMax()*0.5 - - --- Create a route point of type air. - local CoordinateFrom = Helicopter:GetCoordinate() - - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true) - Route[#Route+1] = WaypointFrom - - --- Create a route point of type air. - local CoordinateTo = Coordinate - local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + Height -- flight height should be 50m above ground - - local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true) - - Route[#Route+1] = WaypointTo - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - Helicopter:WayPointInitialize( Route ) - - local Tasks = {} - - Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo - - -- Now route the helicopter - Helicopter:Route(Route, 0) - end - -end - diff --git a/Moose Development/Moose/AI/AI_Cargo_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Ship.lua deleted file mode 100644 index 5639c52da..000000000 --- a/Moose Development/Moose/AI/AI_Cargo_Ship.lua +++ /dev/null @@ -1,402 +0,0 @@ ---- **AI** - Models the intelligent transportation of infantry and other cargo. --- --- === --- --- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl) --- --- === --- --- @module AI.AI_Cargo_Ship --- @image AI_Cargo_Dispatcher.JPG - --- @type AI_CARGO_SHIP --- @extends AI.AI_Cargo#AI_CARGO - ---- Brings a dynamic cargo handling capability for an AI naval group. --- --- Naval ships can be utilized to transport cargo around the map following naval shipping lanes. --- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. --- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo. --- Please consult the @{Cargo.Cargo} module for more information. --- --- ## Cargo loading. --- --- The module will automatically load cargo when the Ship is within boarding or loading radius. --- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of --- cargo and the specified boarding radius. --- --- ## Defending the Ship when enemies are nearby --- This is not supported for naval cargo because most tanks don't float. Protect your transports... --- --- ## Infantry or cargo **health**. --- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield. --- As a result, the unboarding cargo is very _healthy_ every time it unboards. --- This is due to the limitation of the DCS simulator, which is not able to specify the health of newly spawned units as a parameter. --- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for --- naval cargo due to the lack of support for defending the Ship mentioned above). Destroyed is destroyed. --- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance --- this has marginal impact on the overall battlefield simulation. Given the relatively short duration of DCS missions and the somewhat --- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting --- between warehouses. --- --- ## Control the Ships on the map. --- --- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission --- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module. --- --- ## Cargo deployment. --- --- Using the @{#AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the --- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment. --- --- ## Cargo pickup. --- --- Using the @{#AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified --- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment. --- --- --- # 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_CARGO_SHIP -AI_CARGO_SHIP = { - ClassName = "AI_CARGO_SHIP", - Coordinate = nil -- Core.Point#COORDINATE -} - ---- Creates a new AI_CARGO_SHIP object. --- @param #AI_CARGO_SHIP self --- @param Wrapper.Group#GROUP Ship The carrier Ship group --- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported --- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur. --- @param #table ShippingLane Table containing list of Shipping Lanes to be used --- @return #AI_CARGO_SHIP -function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane ) - - local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP - - self:AddTransition( "*", "Monitor", "*" ) - self:AddTransition( "*", "Destroyed", "Destroyed" ) - self:AddTransition( "*", "Home", "*" ) - - self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0 - self:SetShippingLane ( ShippingLane ) - - self:SetCarrier( Ship ) - - return self -end - ---- Set the Carrier --- @param #AI_CARGO_SHIP self --- @param Wrapper.Group#GROUP CargoCarrier --- @return #AI_CARGO_SHIP -function AI_CARGO_SHIP:SetCarrier( CargoCarrier ) - self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP - self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self ) - - CargoCarrier:HandleEvent( EVENTS.Dead ) - - function CargoCarrier:OnEventDead( EventData ) - self:F({"dead"}) - local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" ) - self:F({AICargoTroops=AICargoTroops}) - if AICargoTroops then - self:F({}) - if not AICargoTroops:Is( "Loaded" ) then - -- Better hope they can swim! - AICargoTroops:Destroyed() - end - end - end - - self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius ) - self.Coalition = self.CargoCarrier:GetCoalition() - - self:SetControllable( CargoCarrier ) - - return self -end - - ---- FInd a free Carrier within a radius --- @param #AI_CARGO_SHIP self --- @param Core.Point#COORDINATE Coordinate --- @param #number Radius --- @return Wrapper.Group#GROUP NewCarrier -function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius ) - - local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius ) - CoordinateZone:Scan( { Object.Category.UNIT } ) - for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do - local NearUnit = UNIT:Find( DCSUnit ) - self:F({NearUnit=NearUnit}) - if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then - local Attributes = NearUnit:GetDesc() - self:F({Desc=Attributes}) - if NearUnit:HasAttributes( "Trucks" ) then - return NearUnit:GetGroup() - end - end - end - - return nil -end - -function AI_CARGO_SHIP:SetShippingLane( ShippingLane ) - self.ShippingLane = ShippingLane - - return self -end - -function AI_CARGO_SHIP:SetCombatRadius( CombatRadius ) - self.CombatRadius = CombatRadius or 0 - - return self -end - - ---- Follow Infantry to the Carrier --- @param #AI_CARGO_SHIP self --- @param #AI_CARGO_SHIP Me --- @param Wrapper.Unit#UNIT ShipUnit --- @param Cargo.CargoGroup#CARGO_GROUP Cargo --- @return #AI_CARGO_SHIP -function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup ) - - local InfantryGroup = CargoGroup:GetGroup() - - self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } ) - - if ShipUnit:IsAlive() then - -- Check if the Cargo is near the CargoCarrier - if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then - - -- Cargo does not need to navigate to Carrier - Me:Guard() - else - - self:F( { InfantryGroup = InfantryGroup:GetName() } ) - if InfantryGroup:IsAlive() then - - self:F( { InfantryGroup = InfantryGroup:GetName() } ) - local Waypoints = {} - - -- Calculate new route - local FromCoord = InfantryGroup:GetCoordinate() - local FromGround = FromCoord:WaypointGround( 10, "Diamond" ) - self:F({FromGround=FromGround}) - table.insert( Waypoints, FromGround ) - - local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 ) - local ToGround = ToCoord:WaypointGround( 10, "Diamond" ) - self:F({ToGround=ToGround}) - table.insert( Waypoints, ToGround ) - - local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup ) - - self:F({Waypoints=Waypoints}) - local Waypoint = Waypoints[#Waypoints] - InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone - - InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details - end - end - end -end - - -function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To ) - self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } ) - - if self.CombatRadius > 0 then - -- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0. - -- This is to avoid Unloading the Ship in the middle of the sea. - if Ship and Ship:IsAlive() then - if self.CarrierCoordinate then - if self:IsTransporting() == true then - local Coordinate = Ship:GetCoordinate() - if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then - self.Zone:Scan( { Object.Category.UNIT } ) - if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then - if self:Is( "Unloaded" ) then - -- There are no enemies within combat radius. Reload the CargoCarrier. - self:Reload() - end - else - if self:Is( "Loaded" ) then - -- There are enemies within combat radius. Unload the CargoCarrier. - self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy! - else - if self:Is( "Unloaded" ) then - --self:Follow() - end - self:F( "I am here" .. self:GetCurrentState() ) - if self:Is( "Following" ) then - for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT - if Cargo:IsAlive() then - if not Cargo:IsNear( ShipUnit, 40 ) then - ShipUnit:RouteStop() - self.CarrierStopped = true - else - if self.CarrierStopped then - if Cargo:IsNear( ShipUnit, 25 ) then - ShipUnit:RouteResume() - self.CarrierStopped = nil - end - end - end - end - end - end - end - end - end - end - end - self.CarrierCoordinate = Ship:GetCoordinate() - end - self:__Monitor( -5 ) - end -end - ---- Check if cargo ship is alive and trigger Load event --- @param Wrapper.Group#Group Ship --- @param #AI_CARGO_SHIP self -function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone ) - - Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } ) - - if Ship:IsAlive() then - self:Load( PickupZone ) - end -end - ---- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad --- @param Wrapper.Group#GROUP Ship --- @param #AI_CARGO_SHIP self -function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone ) - Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } ) - - if Ship:IsAlive() then - self:Unload( DeployZone ) - end -end - ---- on after Pickup event. --- @param AI_CARGO_SHIP Ship --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate of the pickup point --- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit --- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships --- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided -function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone ) - - if Ship and Ship:IsAlive() then - AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone ) - self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone ) - end -end - ---- On after Deploy event. --- @param #AI_CARGO_SHIP self --- @param Wrapper.Group#GROUP SHIP --- @param From --- @param Event --- @param To --- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point --- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit --- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships --- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed. -function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone ) - - if Ship and Ship:IsAlive() then - - Speed = Speed or Ship:GetSpeedMax()*0.8 - local lane = self.ShippingLane - - if lane then - local Waypoints = {} - - for i=1, #lane do - local coord = lane[i] - local Waypoint = coord:WaypointGround(_speed) - table.insert(Waypoints, Waypoint) - end - - local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone ) - local Waypoint = Waypoints[#Waypoints] - Ship:SetTaskWaypoint( Waypoint, TaskFunction ) - Ship:Route(Waypoints, 1) - self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone ) - else - self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") - end - end -end - ---- On after Unload event. --- @param #AI_CARGO_SHIP self --- @param Wrapper.Group#GROUP Ship --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. -function AI_CARGO_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend ) - self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } ) - - local UnboardInterval = 5 - local UnboardDelay = 5 - - if Ship and Ship:IsAlive() then - for _, ShipUnit in pairs( Ship:GetUnits() ) do - local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT - Ship:RouteStop() - for _, Cargo in pairs( ShipUnit:GetCargo() ) do - self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } ) - if Cargo:IsLoaded() then - local unboardCoord = DeployZone:GetRandomPointVec2() - Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000) - UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval - self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend ) - if not Defend == true then - Cargo:SetDeployed( true ) - end - end - end - end - end -end - -function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone ) - if Ship and Ship:IsAlive() then - - self.RouteHome = true - Speed = Speed or Ship:GetSpeedMax()*0.8 - local lane = self.ShippingLane - - if lane then - local Waypoints = {} - - -- Need to find a more generalized way to do this instead of reversing the shipping lane. - -- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1 - for i=#lane, 1, -1 do - local coord = lane[i] - local Waypoint = coord:WaypointGround(_speed) - table.insert(Waypoints, Waypoint) - end - - local Waypoint = Waypoints[#Waypoints] - Ship:Route(Waypoints, 1) - - else - self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") - end - end -end diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua deleted file mode 100644 index ad325ed94..000000000 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ /dev/null @@ -1,2191 +0,0 @@ ---- **AI** - Taking the lead of AI escorting your flight or of other AI. --- --- === --- --- ## Features: --- --- * Escort navigation commands. --- * Escort hold at position commands. --- * Escorts reporting detected targets. --- * Escorts scanning targets in advance. --- * Escorts attacking specific targets. --- * Request assistance from other groups for attack. --- * Manage rule of engagement of escorts. --- * Manage the allowed evasion techniques of escorts. --- * Make escort to execute a defined mission or path. --- * Escort tactical situation reporting. --- --- === --- --- ## Missions: --- --- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Escort) --- --- === --- --- Allows you to interact with escorting AI on your flight and take the lead. --- --- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10). --- --- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes. --- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. --- --- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval. --- Once targets are reported, each escort has these targets as menu options to command the attack of these targets. --- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered. --- --- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available. --- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target. --- --- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location. --- In this way, you can spread out the escorts over the battle field before a coordinated attack. --- --- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight. --- --- ## Leading the flight --- --- When leading the flight, you are expected to guide the escorts towards the target areas, --- and carefully coordinate the attack based on the threat levels reported, and the available weapons --- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack. --- --- ## Following the flight --- --- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead. --- You as a player, are following the escorts, and are commanding them to progress the mission while --- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets --- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target --- type. Once the attack is finished, the escort will resume the mission it was assigned. --- In other words, you can use the escorts for reconnaissance, and for guiding the attack. --- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are --- following. You are in control. The ka-50s detect targets, report them, and you command how the attack --- will commence and from where. You can control where the escorts are holding position and which targets --- are attacked first. You are in control how the ka-50s will follow their mission path. --- --- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control. --- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the --- attack is well coordinated. --- --- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism --- it brings in your DCS world simulations. --- --- # RADIO MENUs that can be created: --- --- Find a summary below of the current available commands: --- --- ## Navigation ...: --- --- Escort group navigation functions: --- --- * **"Join-Up":** The escort group fill follow you in the assigned formation. --- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. --- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. --- --- ## Hold position ...: --- --- Escort group navigation functions: --- --- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter. --- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter. --- --- ## Report targets ...: --- --- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below). --- --- * **"Report now":** Will report the current detected targets. --- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list. --- * **"Report targets off":** Will stop detecting targets. --- --- ## Attack targets ...: --- --- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. --- This menu will be available in Flight menu or in each Escort menu. --- --- ## Scan targets ...: --- --- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation. --- --- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. --- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. --- --- ## Request assistance from ...: --- --- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**. --- This menu item allows to request attack support from other ground based escorts supporting the current escort. --- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. --- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. --- --- ## ROE ...: --- --- Sets the Rules of Engagement (ROE) of the escort group when in flight. --- --- * **"Hold Fire":** The escort group will hold fire. --- * **"Return Fire":** The escort group will return fire. --- * **"Open Fire":** The escort group will open fire on designated targets. --- * **"Weapon Free":** The escort group will engage with any target. --- --- ## Evasion ...: --- --- Will define the evasion techniques that the escort group will perform during flight or combat. --- --- * **"Fight until death":** The escort group will have no reaction to threats. --- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed. --- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing. --- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres. --- --- ## Resume Mission ...: --- --- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. --- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. --- --- === --- --- ### Authors: **FlightControl** --- --- === --- --- @module AI.AI_Escort --- @image Escorting.JPG - - ---- --- @type AI_ESCORT --- @extends AI.AI_Formation#AI_FORMATION - - --- TODO: Add the menus when the class Start method is activated. --- TODO: Remove the menus when the class Stop method is called. - ---- AI_ESCORT class --- --- # AI_ESCORT construction methods. --- --- Create a new AI_ESCORT object with the @{#AI_ESCORT.New} method: --- --- * @{#AI_ESCORT.New}: Creates a new AI_ESCORT object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. --- --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = SET_GROUP:New():FilterPrefixes("Escort"):FilterOnce() -- The the group name of the escorts contains "Escort". --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = AI_ESCORT:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- EscortPlanes:MenusAirplanes() -- create menus for airplanes --- EscortPlanes:__Start(2) --- --- # 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_ESCORT -AI_ESCORT = { - ClassName = "AI_ESCORT", - EscortName = nil, -- The Escort Name - EscortUnit = nil, - EscortGroup = nil, - EscortMode = 1, - Targets = {}, -- The identified targets - FollowScheduler = nil, - ReportTargets = true, - OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, - OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, - SmokeDirectionVector = false, - TaskPoints = {} -} - --- @field Functional.Detection#DETECTION_AREAS -AI_ESCORT.Detection = nil - ---- AI_ESCORT class constructor for an AI group --- @param #AI_ESCORT self --- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup. --- @param Core.Set#SET_GROUP EscortGroupSet The set of group AI escorting the EscortUnit. --- @param #string EscortName Name of the escort. --- @param #string EscortBriefing A text showing the AI_ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #AI_ESCORT self --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = SET_GROUP:New():FilterPrefixes("Escort"):FilterOnce() -- The the group name of the escorts contains "Escort". --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = AI_ESCORT:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- EscortPlanes:MenusAirplanes() -- create menus for airplanes --- EscortPlanes:__Start(2) --- --- -function AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) - - local self = BASE:Inherit( self, AI_FORMATION:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT - self:F( { EscortUnit, EscortGroupSet } ) - - self.PlayerUnit = self.FollowUnit -- Wrapper.Unit#UNIT - self.PlayerGroup = self.FollowUnit:GetGroup() -- Wrapper.Group#GROUP - - self.EscortName = EscortName - self.EscortGroupSet = EscortGroupSet - - self.EscortGroupSet:SetSomeIteratorLimit( 8 ) - - self.EscortBriefing = EscortBriefing - - self.Menu = {} - self.Menu.HoldAtEscortPosition = self.Menu.HoldAtEscortPosition or {} - self.Menu.HoldAtLeaderPosition = self.Menu.HoldAtLeaderPosition or {} - self.Menu.Flare = self.Menu.Flare or {} - self.Menu.Smoke = self.Menu.Smoke or {} - self.Menu.Targets = self.Menu.Targets or {} - self.Menu.ROE = self.Menu.ROE or {} - self.Menu.ROT = self.Menu.ROT or {} - --- if not EscortBriefing then --- EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") reporting! " .. --- "We're escorting your flight. " .. --- "Use the Radio Menu and F10 and use the options under + " .. EscortName .. "\n", --- 60, EscortUnit --- ) --- else --- EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") " .. EscortBriefing, --- 60, EscortUnit --- ) --- end - - self.FollowDistance = 100 - self.CT1 = 0 - self.GT1 = 0 - - - EscortGroupSet:ForEachGroup( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - -- Set EscortGroup known at EscortUnit. - if not self.PlayerUnit._EscortGroups then - self.PlayerUnit._EscortGroups = {} - end - - if not self.PlayerUnit._EscortGroups[EscortGroup:GetName()] then - self.PlayerUnit._EscortGroups[EscortGroup:GetName()] = {} - self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortGroup = EscortGroup - self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortName = self.EscortName - self.PlayerUnit._EscortGroups[EscortGroup:GetName()].Detection = self.Detection - end - end - ) - - self:SetFlightReportType( self.__Enum.ReportType.All ) - - return self -end - - -function AI_ESCORT:_InitFlightMenus() - - self:SetFlightMenuJoinUp() - self:SetFlightMenuFormation( "Trail" ) - self:SetFlightMenuFormation( "Stack" ) - self:SetFlightMenuFormation( "LeftLine" ) - self:SetFlightMenuFormation( "RightLine" ) - self:SetFlightMenuFormation( "LeftWing" ) - self:SetFlightMenuFormation( "RightWing" ) - self:SetFlightMenuFormation( "Vic" ) - self:SetFlightMenuFormation( "Box" ) - - self:SetFlightMenuHoldAtEscortPosition() - self:SetFlightMenuHoldAtLeaderPosition() - - self:SetFlightMenuFlare() - self:SetFlightMenuSmoke() - - self:SetFlightMenuROE() - self:SetFlightMenuROT() - - self:SetFlightMenuTargets() - self:SetFlightMenuReportType() - -end - -function AI_ESCORT:_InitEscortMenus( EscortGroup ) - - EscortGroup.EscortMenu = MENU_GROUP:New( self.PlayerGroup, EscortGroup:GetCallsign(), self.MainMenu ) - - self:SetEscortMenuJoinUp( EscortGroup ) - self:SetEscortMenuResumeMission( EscortGroup ) - - self:SetEscortMenuHoldAtEscortPosition( EscortGroup ) - self:SetEscortMenuHoldAtLeaderPosition( EscortGroup ) - - self:SetEscortMenuFlare( EscortGroup ) - self:SetEscortMenuSmoke( EscortGroup ) - - self:SetEscortMenuROE( EscortGroup ) - self:SetEscortMenuROT( EscortGroup ) - - self:SetEscortMenuTargets( EscortGroup ) - -end - -function AI_ESCORT:_InitEscortRoute( EscortGroup ) - - EscortGroup.MissionRoute = EscortGroup:GetTaskRoute() - -end - - --- @param #AI_ESCORT self --- @param Core.Set#SET_GROUP EscortGroupSet -function AI_ESCORT:onafterStart( EscortGroupSet ) - - self:F() - - EscortGroupSet:ForEachGroup( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - EscortGroup:WayPointInitialize() - - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEOpenFire() - end - ) - - -- TODO:Revise this... - local LeaderEscort = EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP - if LeaderEscort then - local Report = REPORT:New( "Escort reporting:" ) - Report:Add( "Joining Up " .. EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.PlayerUnit ) ) - LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit ) - end - - self.Detection = DETECTION_AREAS:New( EscortGroupSet, 5000 ) - - -- This only makes the escort report detections made by the escort, not through DLINK. - -- These must be enquired using other facilities. - -- In this way, the escort will report the target areas that are relevant for the mission. - self.Detection:InitDetectVisual( true ) - self.Detection:InitDetectIRST( true ) - self.Detection:InitDetectOptical( true ) - self.Detection:InitDetectRadar( true ) - self.Detection:InitDetectRWR( true ) - - self.Detection:SetAcceptRange( 100000 ) - - self.Detection:__Start( 30 ) - - self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName ) - self.FlightMenu = MENU_GROUP:New( self.PlayerGroup, "Flight", self.MainMenu ) - - self:_InitFlightMenus() - - self.EscortGroupSet:ForSomeGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - - self:_InitEscortMenus( EscortGroup ) - self:_InitEscortRoute( EscortGroup ) - - self:SetFlightModeFormation( EscortGroup ) - - -- @param #AI_ESCORT self - -- @param Core.Event#EVENTDATA EventData - function EscortGroup:OnEventDeadOrCrash( EventData ) - self:F( { "EventDead", EventData } ) - self.EscortMenu:Remove() - end - - EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash ) - EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash ) - - end - ) - - -end - --- @param #AI_ESCORT self --- @param Core.Set#SET_GROUP EscortGroupSet -function AI_ESCORT:onafterStop( EscortGroupSet ) - - self:F() - - EscortGroupSet:ForEachGroup( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - EscortGroup:WayPointInitialize() - - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEOpenFire() - end - ) - - self.Detection:Stop() - - self.MainMenu:Remove() - -end - ---- Set a Detection method for the EscortUnit to be reported upon. --- Detection methods are based on the derived classes from DETECTION_BASE. --- @param #AI_ESCORT self --- @param Functional.Detection#DETECTION_AREAS Detection -function AI_ESCORT:SetDetection( Detection ) - - self.Detection = Detection - self.EscortGroup.Detection = self.Detection - self.PlayerUnit._EscortGroups[self.EscortGroup:GetName()].Detection = self.EscortGroup.Detection - - Detection:__Start( 1 ) - -end - ---- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This allows to visualize where the escort is flying to. --- @param #AI_ESCORT self --- @param #boolean SmokeDirection If true, then the direction vector will be smoked. -function AI_ESCORT:TestSmokeDirectionVector( SmokeDirection ) - self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false -end - - ---- Defines the default menus for helicopters. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @param #number ZLevels The amount of levels on the Z-axis. --- @return #AI_ESCORT -function AI_ESCORT:MenusHelicopters( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) - self:F() - --- self:MenuScanForTargets( 100, 60 ) - - self.XStart = XStart or 50 - self.XSpace = XSpace or 50 - self.YStart = YStart or 50 - self.YSpace = YSpace or 50 - self.ZStart = ZStart or 50 - self.ZSpace = ZSpace or 50 - self.ZLevels = ZLevels or 10 - - self:MenuJoinUp() - self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) - self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) - self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) - self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) - - self:MenuHoldAtEscortPosition( 30 ) - self:MenuHoldAtEscortPosition( 100 ) - self:MenuHoldAtEscortPosition( 500 ) - self:MenuHoldAtLeaderPosition( 30, 500 ) - - self:MenuFlare() - self:MenuSmoke() - - self:MenuTargets( 60 ) - self:MenuAssistedAttack() - self:MenuROE() - self:MenuROT() - - return self -end - - ---- Defines the default menus for airplanes. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @param #number ZLevels The amount of levels on the Z-axis. --- @return #AI_ESCORT -function AI_ESCORT:MenusAirplanes( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) - self:F() - --- self:MenuScanForTargets( 100, 60 ) - - self.XStart = XStart or 50 - self.XSpace = XSpace or 50 - self.YStart = YStart or 50 - self.YSpace = YSpace or 50 - self.ZStart = ZStart or 50 - self.ZSpace = ZSpace or 50 - self.ZLevels = ZLevels or 10 - - self:MenuJoinUp() - self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) - self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) - self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) - self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) - self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) - - self:MenuHoldAtEscortPosition( 1000, 500 ) - self:MenuHoldAtLeaderPosition( 1000, 500 ) - - self:MenuFlare() - self:MenuSmoke() - - self:MenuTargets( 60 ) - self:MenuAssistedAttack() - self:MenuROE() - self:MenuROT() - - return self -end - - -function AI_ESCORT:SetFlightMenuFormation( Formation ) - - local FormationID = "Formation" .. Formation - - local MenuFormation = self.Menu[FormationID] - - if MenuFormation then - local Arguments = MenuFormation.Arguments - --self:T({Arguments=unpack(Arguments)}) - local FlightMenuFormation = MENU_GROUP:New( self.PlayerGroup, "Formation", self.MainMenu ) - local MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation, - function ( self, Formation, ... ) - self.EscortGroupSet:ForSomeGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup, self, Formation, Arguments ) - if EscortGroup:IsAir() then - self:E({FormationID=FormationID}) - self[FormationID]( self, unpack(Arguments) ) - end - end, self, Formation, Arguments - ) - end, self, Formation, Arguments - ) - end - - return self -end - - -function AI_ESCORT:MenuFormation( Formation, ... ) - - local FormationID = "Formation"..Formation - self.Menu[FormationID] = self.Menu[FormationID] or {} - self.Menu[FormationID].Arguments = arg - -end - - ---- Defines a menu slot to let the escort to join in a trail formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationTrail( XStart, XSpace, YStart ) - - self:MenuFormation( "Trail", XStart, XSpace, YStart ) - - return self -end - ---- Defines a menu slot to let the escort to join in a stacked formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationStack( XStart, XSpace, YStart, YSpace ) - - self:MenuFormation( "Stack", XStart, XSpace, YStart, YSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a leFt wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationLeftLine( XStart, YStart, ZStart, ZSpace ) - - self:MenuFormation( "LeftLine", XStart, YStart, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a right line formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationRightLine( XStart, YStart, ZStart, ZSpace ) - - self:MenuFormation( "RightLine", XStart, YStart, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a left wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationLeftWing( XStart, XSpace, YStart, ZStart, ZSpace ) - - self:MenuFormation( "LeftWing", XStart, XSpace, YStart, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a right wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationRightWing( XStart, XSpace, YStart, ZStart, ZSpace ) - - self:MenuFormation( "RightWing", XStart, XSpace, YStart, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a center wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationCenterWing( XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) - - self:MenuFormation( "CenterWing", XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a vic formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationVic( XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) - - self:MenuFormation( "Vic", XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) - - return self -end - - ---- Defines a menu slot to let the escort to join in a box formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @param #number ZLevels The amount of levels on the Z-axis. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationBox( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) - - self:MenuFormation( "Box", XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) - - return self -end - -function AI_ESCORT:SetFlightMenuJoinUp() - - if self.Menu.JoinUp == true then - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - local FlightMenuJoinUp = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Join Up", FlightMenuReportNavigation, AI_ESCORT._FlightJoinUp, self ) - end - -end - - ---- Sets a menu slot to join formation for an escort. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:SetEscortMenuJoinUp( EscortGroup ) - - if self.Menu.JoinUp == true then - if EscortGroup:IsAir() then - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - local EscortMenuJoinUp = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Join Up", EscortMenuReportNavigation, AI_ESCORT._JoinUp, self, EscortGroup ) - end - end -end - - - ---- Defines --- Defines a menu slot to let the escort to join formation. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuJoinUp() - - self.Menu.JoinUp = true - - return self -end - - -function AI_ESCORT:SetFlightMenuHoldAtEscortPosition() - - for _, MenuHoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition or {} ) do - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - - local FlightMenuHoldPosition = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - MenuHoldAtEscortPosition.MenuText, - FlightMenuReportNavigation, - AI_ESCORT._FlightHoldPosition, - self, - nil, - MenuHoldAtEscortPosition.Height, - MenuHoldAtEscortPosition.Speed - ) - - end - return self -end - -function AI_ESCORT:SetEscortMenuHoldAtEscortPosition( EscortGroup ) - - for _, HoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition or {}) do - if EscortGroup:IsAir() then - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - local EscortMenuHoldPosition = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - HoldAtEscortPosition.MenuText, - EscortMenuReportNavigation, - AI_ESCORT._HoldPosition, - self, - EscortGroup, - EscortGroup, - HoldAtEscortPosition.Height, - HoldAtEscortPosition.Speed - ) - end - end - - return self -end - - ---- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Hold position**. --- @param #AI_ESCORT self --- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCS#Time Speed Optional parameter that lets the escort orbit with a specified speed. The default value is a speed that is average for the type of airplane or helicopter. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuHoldAtEscortPosition( Height, Speed, MenuTextFormat ) - self:F( { Height, Speed, MenuTextFormat } ) - - if not Height then - Height = 30 - end - - if not Speed then - Speed = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Speed == 0 then - MenuText = string.format( "Hold at %d meter", Height ) - else - MenuText = string.format( "Hold at %d meter at %d", Height, Speed ) - end - else - if Speed == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Speed ) - end - end - - self.Menu.HoldAtEscortPosition = self.Menu.HoldAtEscortPosition or {} - self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition+1] = {} - self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Height = Height - self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Speed = Speed - self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].MenuText = MenuText - - return self -end - - -function AI_ESCORT:SetFlightMenuHoldAtLeaderPosition() - - for _, MenuHoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition or {}) do - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - - local FlightMenuHoldAtLeaderPosition = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - MenuHoldAtLeaderPosition.MenuText, - FlightMenuReportNavigation, - AI_ESCORT._FlightHoldPosition, - self, - self.PlayerGroup, - MenuHoldAtLeaderPosition.Height, - MenuHoldAtLeaderPosition.Speed - ) - end - - return self -end - -function AI_ESCORT:SetEscortMenuHoldAtLeaderPosition( EscortGroup ) - - for _, HoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - - local EscortMenuHoldAtLeaderPosition = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - HoldAtLeaderPosition.MenuText, - EscortMenuReportNavigation, - AI_ESCORT._HoldPosition, - self, - self.PlayerGroup, - EscortGroup, - HoldAtLeaderPosition.Height, - HoldAtLeaderPosition.Speed - ) - end - end - - return self -end - ---- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Navigation**. --- @param #AI_ESCORT self --- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCS#Time Speed Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuHoldAtLeaderPosition( Height, Speed, MenuTextFormat ) - self:F( { Height, Speed, MenuTextFormat } ) - - if not Height then - Height = 30 - end - - if not Speed then - Speed = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Speed == 0 then - MenuText = string.format( "Rejoin and hold at %d meter", Height ) - else - MenuText = string.format( "Rejoin and hold at %d meter at %d", Height, Speed ) - end - else - if Speed == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Speed ) - end - end - - self.Menu.HoldAtLeaderPosition = self.Menu.HoldAtLeaderPosition or {} - self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition+1] = {} - self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Height = Height - self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Speed = Speed - self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].MenuText = MenuText - - return self -end - ---- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. --- This menu will appear under **Scan targets**. --- @param #AI_ESCORT self --- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuScan then - self.EscortMenuScan = MENU_GROUP:New( self.PlayerGroup, "Scan for targets", self.EscortMenu ) - end - - if not Height then - Height = 100 - end - - if not Seconds then - Seconds = 30 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "At %d meter", Height ) - else - MenuText = string.format( "At %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuScanForTargets then - self.EscortMenuScanForTargets = {} - end - - self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_GROUP_COMMAND - :New( - self.PlayerGroup, - MenuText, - self.EscortMenuScan, - AI_ESCORT._ScanTargets, - self, - 30 - ) - end - - return self -end - - -function AI_ESCORT:SetFlightMenuFlare() - - for _, MenuFlare in pairs( self.Menu.Flare or {}) do - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - local FlightMenuFlare = MENU_GROUP:New( self.PlayerGroup, MenuFlare.MenuText, FlightMenuReportNavigation ) - - local FlightMenuFlareGreenFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Green, "Released a green flare!" ) - local FlightMenuFlareRedFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Red, "Released a red flare!" ) - local FlightMenuFlareWhiteFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.White, "Released a white flare!" ) - local FlightMenuFlareYellowFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release yellow flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Yellow, "Released a yellow flare!" ) - end - - return self -end - -function AI_ESCORT:SetEscortMenuFlare( EscortGroup ) - - for _, MenuFlare in pairs( self.Menu.Flare or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - local EscortMenuFlare = MENU_GROUP:New( self.PlayerGroup, MenuFlare.MenuText, EscortMenuReportNavigation ) - - local EscortMenuFlareGreen = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Green, "Released a green flare!" ) - local EscortMenuFlareRed = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Red, "Released a red flare!" ) - local EscortMenuFlareWhite = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.White, "Released a white flare!" ) - local EscortMenuFlareYellow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release yellow flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Yellow, "Released a yellow flare!" ) - end - end - - return self -end - - - ---- Defines a menu slot to let the escort disperse a flare in a certain color. --- This menu will appear under **Navigation**. --- The flare will be fired from the first unit in the group. --- @param #AI_ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuFlare( MenuTextFormat ) - self:F() - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Flare" - else - MenuText = MenuTextFormat - end - - self.Menu.Flare = self.Menu.Flare or {} - self.Menu.Flare[#self.Menu.Flare+1] = {} - self.Menu.Flare[#self.Menu.Flare].MenuText = MenuText - - return self -end - - -function AI_ESCORT:SetFlightMenuSmoke() - - for _, MenuSmoke in pairs( self.Menu.Smoke or {}) do - local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) - local FlightMenuSmoke = MENU_GROUP:New( self.PlayerGroup, MenuSmoke.MenuText, FlightMenuReportNavigation ) - - local FlightMenuSmokeGreenFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Green, "Releasing green smoke!" ) - local FlightMenuSmokeRedFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Red, "Releasing red smoke!" ) - local FlightMenuSmokeWhiteFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.White, "Releasing white smoke!" ) - local FlightMenuSmokeOrangeFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release orange smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Orange, "Releasing orange smoke!" ) - local FlightMenuSmokeBlueFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release blue smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Blue, "Releasing blue smoke!" ) - end - - return self -end - - -function AI_ESCORT:SetEscortMenuSmoke( EscortGroup ) - - for _, MenuSmoke in pairs( self.Menu.Smoke or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) - local EscortMenuSmoke = MENU_GROUP:New( self.PlayerGroup, MenuSmoke.MenuText, EscortMenuReportNavigation ) - - local EscortMenuSmokeGreen = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Green, "Releasing green smoke!" ) - local EscortMenuSmokeRed = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Red, "Releasing red smoke!" ) - local EscortMenuSmokeWhite = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.White, "Releasing white smoke!" ) - local EscortMenuSmokeOrange = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release orange smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Orange, "Releasing orange smoke!" ) - local EscortMenuSmokeBlue = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release blue smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Blue, "Releasing blue smoke!" ) - end - end - - return self -end - - ---- Defines a menu slot to let the escort disperse a smoke in a certain color. --- This menu will appear under **Navigation**. --- Note that smoke menu options will only be displayed for ships and ground units. Not for air units. --- The smoke will be fired from the first unit in the group. --- @param #AI_ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #AI_ESCORT -function AI_ESCORT:MenuSmoke( MenuTextFormat ) - self:F() - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Smoke" - else - MenuText = MenuTextFormat - end - - self.Menu.Smoke = self.Menu.Smoke or {} - self.Menu.Smoke[#self.Menu.Smoke+1] = {} - self.Menu.Smoke[#self.Menu.Smoke].MenuText = MenuText - - return self -end - -function AI_ESCORT:SetFlightMenuReportType() - - local FlightMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", self.FlightMenu ) - local MenuStamp = FlightMenuReportTargets:GetStamp() - - local FlightReportType = self:GetFlightReportType() - - if FlightReportType ~= self.__Enum.ReportType.All then - local FlightMenuReportTargetsAll = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report all targets", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportTypeAll, self ) - :SetTag( "ReportType" ) - :SetStamp( MenuStamp ) - end - - if FlightReportType == self.__Enum.ReportType.All or FlightReportType ~= self.__Enum.ReportType.Airborne then - local FlightMenuReportTargetsAirborne = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report airborne targets", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportTypeAirborne, self ) - :SetTag( "ReportType" ) - :SetStamp( MenuStamp ) - end - - if FlightReportType == self.__Enum.ReportType.All or FlightReportType ~= self.__Enum.ReportType.GroundRadar then - local FlightMenuReportTargetsGroundRadar = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report gound radar targets", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportTypeGroundRadar, self ) - :SetTag( "ReportType" ) - :SetStamp( MenuStamp ) - end - if FlightReportType == self.__Enum.ReportType.All or FlightReportType ~= self.__Enum.ReportType.Ground then - local FlightMenuReportTargetsGround = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report ground targets", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportTypeGround, self ) - :SetTag( "ReportType" ) - :SetStamp( MenuStamp ) - end - - FlightMenuReportTargets:RemoveSubMenus( MenuStamp, "ReportType" ) - -end - - -function AI_ESCORT:SetFlightMenuTargets() - - local FlightMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", self.FlightMenu ) - - -- Report Targets - local FlightMenuReportTargetsNow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets now!", FlightMenuReportTargets, AI_ESCORT._FlightReportNearbyTargetsNow, self ) - local FlightMenuReportTargetsOn = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets on", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportNearbyTargets, self, true ) - local FlightMenuReportTargetsOff = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets off", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportNearbyTargets, self, false ) - - -- Attack Targets - self.FlightMenuAttack = MENU_GROUP:New( self.PlayerGroup, "Attack targets", self.FlightMenu ) - local FlightMenuAttackNearby = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Attack nearest targets", self.FlightMenuAttack, AI_ESCORT._FlightAttackNearestTarget, self ):SetTag( "Attack" ) - local FlightMenuAttackNearbyAir = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Attack nearest airborne targets", self.FlightMenuAttack, AI_ESCORT._FlightAttackNearestTarget, self, self.__Enum.ReportType.Air ):SetTag( "Attack" ) - local FlightMenuAttackNearbyGround = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Attack nearest ground targets", self.FlightMenuAttack, AI_ESCORT._FlightAttackNearestTarget, self, self.__Enum.ReportType.Ground ):SetTag( "Attack" ) - - for _, MenuTargets in pairs( self.Menu.Targets or {}) do - MenuTargets.FlightReportTargetsScheduler = SCHEDULER:New( self, self._FlightReportTargetsScheduler, {}, MenuTargets.Interval, MenuTargets.Interval ) - end - - return self -end - - -function AI_ESCORT:SetEscortMenuTargets( EscortGroup ) - - for _, MenuTargets in pairs( self.Menu.Targets or {} or {}) do - if EscortGroup:IsAir() then - local EscortGroupName = EscortGroup:GetName() - --local EscortMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", EscortGroup.EscortMenu ) - - -- Report Targets - EscortGroup.EscortMenuReportNearbyTargetsNow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets", EscortGroup.EscortMenu, AI_ESCORT._ReportNearbyTargetsNow, self, EscortGroup, true ) - --EscortGroup.EscortMenuReportNearbyTargetsOn = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets on", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._SwitchReportNearbyTargets, self, EscortGroup, true ) - --EscortGroup.EscortMenuReportNearbyTargetsOff = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets off", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._SwitchReportNearbyTargets, self, EscortGroup, false ) - - -- Attack Targets - --local EscortMenuAttackTargets = MENU_GROUP:New( self.PlayerGroup, "Attack targets", EscortGroup.EscortMenu ) - - EscortGroup.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, { EscortGroup }, 1, MenuTargets.Interval ) - EscortGroup.ResumeScheduler = SCHEDULER:New( self, self._ResumeScheduler, { EscortGroup }, 1, 60 ) - end - end - - return self -end - - - ---- Defines a menu slot to let the escort report their current detected targets with a specified time interval in seconds. --- This menu will appear under **Report targets**. --- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. --- @param #AI_ESCORT self --- @param DCS#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. --- @return #AI_ESCORT -function AI_ESCORT:MenuTargets( Seconds ) - self:F( { Seconds } ) - - if not Seconds then - Seconds = 30 - end - - self.Menu.Targets = self.Menu.Targets or {} - self.Menu.Targets[#self.Menu.Targets+1] = {} - self.Menu.Targets[#self.Menu.Targets].Interval = Seconds - - return self -end - ---- Defines a menu slot to let the escort attack its detected targets using assisted attack from another escort joined also with the client. --- This menu will appear under **Request assistance from**. --- Note that this method needs to be preceded with the method MenuTargets. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuAssistedAttack() - self:F() - - self.EscortGroupSet:ForSomeGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if not EscortGroup:IsAir() then - -- Request assistance from other escorts. - -- This is very useful to let f.e. an escorting ship attack a target detected by an escorting plane... - self.EscortMenuTargetAssistance = MENU_GROUP:New( self.PlayerGroup, "Request assistance from", EscortGroup.EscortMenu ) - end - end - ) - - return self -end - -function AI_ESCORT:SetFlightMenuROE() - - for _, MenuROE in pairs( self.Menu.ROE or {}) do - local FlightMenuROE = MENU_GROUP:New( self.PlayerGroup, "Rule Of Engagement", self.FlightMenu ) - - local FlightMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Hold fire", FlightMenuROE, AI_ESCORT._FlightROEHoldFire, self, "Holding weapons!" ) - local FlightMenuROEReturnFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Return fire", FlightMenuROE, AI_ESCORT._FlightROEReturnFire, self, "Returning fire!" ) - local FlightMenuROEOpenFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open Fire", FlightMenuROE, AI_ESCORT._FlightROEOpenFire, self, "Open fire at designated targets!" ) - local FlightMenuROEWeaponFree = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Engage all targets", FlightMenuROE, AI_ESCORT._FlightROEWeaponFree, self, "Engaging all targets!" ) - end - - return self -end - - -function AI_ESCORT:SetEscortMenuROE( EscortGroup ) - - for _, MenuROE in pairs( self.Menu.ROE or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuROE = MENU_GROUP:New( self.PlayerGroup, "Rule Of Engagement", EscortGroup.EscortMenu ) - - if EscortGroup:OptionROEHoldFirePossible() then - local EscortMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Hold fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEHoldFire, "Holding weapons!" ) - end - if EscortGroup:OptionROEReturnFirePossible() then - local EscortMenuROEReturnFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Return fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEReturnFire, "Returning fire!" ) - end - if EscortGroup:OptionROEOpenFirePossible() then - EscortGroup.EscortMenuROEOpenFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open Fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEOpenFire, "Opening fire on designated targets!!" ) - end - if EscortGroup:OptionROEWeaponFreePossible() then - EscortGroup.EscortMenuROEWeaponFree = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Engage all targets", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEWeaponFree, "Opening fire on targets of opportunity!" ) - end - end - end - - return self -end - - ---- Defines a menu to let the escort set its rules of engagement. --- All rules of engagement will appear under the menu **ROE**. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuROE() - self:F() - - self.Menu.ROE = self.Menu.ROE or {} - self.Menu.ROE[#self.Menu.ROE+1] = {} - - return self -end - - -function AI_ESCORT:SetFlightMenuROT() - - for _, MenuROT in pairs( self.Menu.ROT or {}) do - local FlightMenuROT = MENU_GROUP:New( self.PlayerGroup, "Reaction On Threat", self.FlightMenu ) - - local FlightMenuROTNoReaction = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Fight until death", FlightMenuROT, AI_ESCORT._FlightROTNoReaction, self, "Fighting until death!" ) - local FlightMenuROTPassiveDefense = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Use flares, chaff and jammers", FlightMenuROT, AI_ESCORT._FlightROTPassiveDefense, self, "Defending using jammers, chaff and flares!" ) - local FlightMenuROTEvadeFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open fire", FlightMenuROT, AI_ESCORT._FlightROTEvadeFire, self, "Evading on enemy fire!" ) - local FlightMenuROTVertical = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Avoid radar and evade fire", FlightMenuROT, AI_ESCORT._FlightROTVertical, self, "Evading on enemy fire with vertical manoeuvres!" ) - end - - return self -end - - -function AI_ESCORT:SetEscortMenuROT( EscortGroup ) - - for _, MenuROT in pairs( self.Menu.ROT or {}) do - if EscortGroup:IsAir() then - - local EscortGroupName = EscortGroup:GetName() - local EscortMenuROT = MENU_GROUP:New( self.PlayerGroup, "Reaction On Threat", EscortGroup.EscortMenu ) - - if not EscortGroup.EscortMenuEvasion then - -- Reaction to Threats - if EscortGroup:OptionROTNoReactionPossible() then - local EscortMenuEvasionNoReaction = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Fight until death", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTNoReaction, "Fighting until death!" ) - end - if EscortGroup:OptionROTPassiveDefensePossible() then - local EscortMenuEvasionPassiveDefense = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Use flares, chaff and jammers", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTPassiveDefense, "Defending using jammers, chaff and flares!" ) - end - if EscortGroup:OptionROTEvadeFirePossible() then - local EscortMenuEvasionEvadeFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open fire", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTEvadeFire, "Evading on enemy fire!" ) - end - if EscortGroup:OptionROTVerticalPossible() then - local EscortMenuOptionEvasionVertical = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Avoid radar and evade fire", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTVertical, "Evading on enemy fire with vertical manoeuvres!" ) - end - end - end - end - - return self -end - - - ---- Defines a menu to let the escort set its evasion when under threat. --- All rules of engagement will appear under the menu **Evasion**. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuROT( MenuTextFormat ) - self:F( MenuTextFormat ) - - self.Menu.ROT = self.Menu.ROT or {} - self.Menu.ROT[#self.Menu.ROT+1] = {} - - return self -end - ---- Defines a menu to let the escort resume its mission from a waypoint on its route. --- All rules of engagement will appear under the menu **Resume mission from**. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:SetEscortMenuResumeMission( EscortGroup ) - self:F() - - if EscortGroup:IsAir() then - local EscortGroupName = EscortGroup:GetName() - EscortGroup.EscortMenuResumeMission = MENU_GROUP:New( self.PlayerGroup, "Resume from", EscortGroup.EscortMenu ) - end - - return self -end - - --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP OrbitGroup --- @param Wrapper.Group#GROUP EscortGroup --- @param #number OrbitHeight --- @param #number OrbitSeconds -function AI_ESCORT:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSeconds ) - - local EscortUnit = self.PlayerUnit - - local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT - - self:SetFlightModeMission( EscortGroup ) - - local PointFrom = {} - local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() - PointFrom = {} - PointFrom.x = GroupVec3.x - PointFrom.y = GroupVec3.z - PointFrom.speed = 250 - PointFrom.type = AI.Task.WaypointType.TURNING_POINT - PointFrom.alt = GroupVec3.y - PointFrom.alt_type = AI.Task.AltitudeType.BARO - - local OrbitPoint = OrbitUnit:GetVec2() - local PointTo = {} - PointTo.x = OrbitPoint.x - PointTo.y = OrbitPoint.y - PointTo.speed = 250 - PointTo.type = AI.Task.WaypointType.TURNING_POINT - PointTo.alt = OrbitHeight - PointTo.alt_type = AI.Task.AltitudeType.BARO - PointTo.task = EscortGroup:TaskOrbitCircleAtVec2( OrbitPoint, OrbitHeight, 0 ) - - local Points = { PointFrom, PointTo } - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTPassiveDefense() - - EscortGroup:SetTask( EscortGroup:TaskRoute( Points ), 1 ) - EscortGroup:MessageTypeToGroup( "Orbiting at current location.", MESSAGE.Type.Information, EscortUnit:GetGroup() ) - -end - - --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP OrbitGroup --- @param #number OrbitHeight --- @param #number OrbitSeconds -function AI_ESCORT:_FlightHoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) - - local EscortUnit = self.PlayerUnit - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup, OrbitGroup ) - if EscortGroup:IsAir() then - if OrbitGroup == nil then - OrbitGroup = EscortGroup - end - self:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSeconds ) - end - end, OrbitGroup - ) - -end - - - -function AI_ESCORT:_JoinUp( EscortGroup ) - - local EscortUnit = self.PlayerUnit - - self:SetFlightModeFormation( EscortGroup ) - - EscortGroup:MessageTypeToGroup( "Joining up!", MESSAGE.Type.Information, EscortUnit:GetGroup() ) -end - - -function AI_ESCORT:_FlightJoinUp() - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_JoinUp( EscortGroup ) - end - end - ) - -end - - ---- Lets the escort to join in a trail formation. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @return #AI_ESCORT -function AI_ESCORT:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart ) - - self:FormationTrail( XStart, XSpace, YStart ) - -end - - -function AI_ESCORT:_FlightFormationTrail( XStart, XSpace, YStart ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart ) - end - end - ) - -end - ---- Lets the escort to join in a stacked formation. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @return #AI_ESCORT -function AI_ESCORT:_EscortFormationStack( EscortGroup, XStart, XSpace, YStart, YSpace ) - - self:FormationStack( XStart, XSpace, YStart, YSpace ) - -end - - -function AI_ESCORT:_FlightFormationStack( XStart, XSpace, YStart, YSpace ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_EscortFormationStack( EscortGroup, XStart, XSpace, YStart, YSpace ) - end - end - ) - -end - - -function AI_ESCORT:_Flare( EscortGroup, Color, Message ) - - local EscortUnit = self.PlayerUnit - - EscortGroup:GetUnit(1):Flare( Color ) - EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) -end - - -function AI_ESCORT:_FlightFlare( Color, Message ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_Flare( EscortGroup, Color, Message ) - end - end - ) - -end - - - -function AI_ESCORT:_Smoke( EscortGroup, Color, Message ) - - local EscortUnit = self.PlayerUnit - - EscortGroup:GetUnit(1):Smoke( Color ) - EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) -end - -function AI_ESCORT:_FlightSmoke( Color, Message ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_Smoke( EscortGroup, Color, Message ) - end - end - ) - -end - - -function AI_ESCORT:_ReportNearbyTargetsNow( EscortGroup ) - - local EscortUnit = self.PlayerUnit - - self:_ReportTargetsScheduler( EscortGroup ) - -end - - -function AI_ESCORT:_FlightReportNearbyTargetsNow() - - self:_FlightReportTargetsScheduler() - -end - - - -function AI_ESCORT:_FlightSwitchReportNearbyTargets( ReportTargets ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_EscortSwitchReportNearbyTargets( EscortGroup, ReportTargets ) - end - end - ) - -end - -function AI_ESCORT:SetFlightReportType( ReportType ) - - self.FlightReportType = ReportType - -end - -function AI_ESCORT:GetFlightReportType() - - return self.FlightReportType - -end - -function AI_ESCORT:_FlightSwitchReportTypeAll() - - self:SetFlightReportType( self.__Enum.ReportType.All ) - self:SetFlightMenuReportType() - - local EscortGroup = self.EscortGroupSet:GetFirst() - EscortGroup:MessageTypeToGroup( "Reporting all targets.", MESSAGE.Type.Information, self.PlayerGroup ) - -end - -function AI_ESCORT:_FlightSwitchReportTypeAirborne() - - self:SetFlightReportType( self.__Enum.ReportType.Airborne ) - self:SetFlightMenuReportType() - - local EscortGroup = self.EscortGroupSet:GetFirst() - EscortGroup:MessageTypeToGroup( "Reporting airborne targets.", MESSAGE.Type.Information, self.PlayerGroup ) - -end - -function AI_ESCORT:_FlightSwitchReportTypeGroundRadar() - - self:SetFlightReportType( self.__Enum.ReportType.Ground ) - self:SetFlightMenuReportType() - - local EscortGroup = self.EscortGroupSet:GetFirst() - EscortGroup:MessageTypeToGroup( "Reporting ground radar targets.", MESSAGE.Type.Information, self.PlayerGroup ) - -end - -function AI_ESCORT:_FlightSwitchReportTypeGround() - - self:SetFlightReportType( self.__Enum.ReportType.Ground ) - self:SetFlightMenuReportType() - - local EscortGroup = self.EscortGroupSet:GetFirst() - EscortGroup:MessageTypeToGroup( "Reporting ground targets.", MESSAGE.Type.Information, self.PlayerGroup ) - -end - - -function AI_ESCORT:_ScanTargets( ScanDuration ) - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - local EscortUnit = self.PlayerUnit - - self.FollowScheduler:Stop( self.FollowSchedule ) - - if EscortGroup:IsHelicopter() then - EscortGroup:PushTask( - EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 200, 20 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ), 1 ) - elseif EscortGroup:IsAirPlane() then - EscortGroup:PushTask( - EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 1000, 500 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ), 1 ) - end - - EscortGroup:MessageToClient( "Scanning targets for " .. ScanDuration .. " seconds.", ScanDuration, EscortUnit ) - - if self.EscortMode == AI_ESCORT.MODE.FOLLOW then - self.FollowScheduler:Start( self.FollowSchedule ) - end - -end - --- @param Wrapper.Group#GROUP EscortGroup --- @param #AI_ESCORT self -function AI_ESCORT.___Resume( EscortGroup, self ) - - self:F( { self=self } ) - - local PlayerGroup = self.PlayerGroup - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTVertical() - - EscortGroup:SetState( EscortGroup, "Mode", EscortGroup:GetState( EscortGroup, "PreviousMode" ) ) - - if EscortGroup:GetState( EscortGroup, "Mode" ) == self.__Enum.Mode.Mission then - EscortGroup:MessageTypeToGroup( "Resuming route.", MESSAGE.Type.Information, PlayerGroup ) - else - EscortGroup:MessageTypeToGroup( "Rejoining formation.", MESSAGE.Type.Information, PlayerGroup ) - end - -end - - --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup --- @param #number WayPoint -function AI_ESCORT:_ResumeMission( EscortGroup, WayPoint ) - - --self.FollowScheduler:Stop( self.FollowSchedule ) - - self:SetFlightModeMission( EscortGroup ) - - local WayPoints = EscortGroup.MissionRoute - self:T( WayPoint, WayPoints ) - - for WayPointIgnore = 1, WayPoint do - table.remove( WayPoints, 1 ) - end - - EscortGroup:SetTask( EscortGroup:TaskRoute( WayPoints ), 1 ) - - EscortGroup:MessageTypeToGroup( "Resuming mission from waypoint ", MESSAGE.Type.Information, self.PlayerGroup ) -end - - --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup The escort group that will attack the detected item. --- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) - - self:F( EscortGroup ) - - self:SetFlightModeAttack( EscortGroup ) - - if EscortGroup:IsAir() then - EscortGroup:OptionROEOpenFire() - EscortGroup:OptionROTVertical() - EscortGroup:SetState( EscortGroup, "Escort", self ) - - local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local Tasks = {} - local AttackUnitTasks = {} - - DetectedSet:ForEachUnit( - -- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - AttackUnitTasks[#AttackUnitTasks+1] = EscortGroup:TaskAttackUnit( DetectedUnit ) - end - end, Tasks - ) - - Tasks[#Tasks+1] = EscortGroup:TaskCombo( AttackUnitTasks ) - Tasks[#Tasks+1] = EscortGroup:TaskFunction( "AI_ESCORT.___Resume", self ) - - EscortGroup:PushTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - else - - local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - -- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) - end - end, Tasks - ) - - EscortGroup:PushTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - end - - local DetectedTargetsReport = REPORT:New( "Engaging target:\n" ) - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, self.PlayerGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) - local ReportSummary = DetectedItemReportSummary:Text(", ") - DetectedTargetsReport:AddIndent( ReportSummary, "-" ) - - EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text(), MESSAGE.Type.Information, self.PlayerGroup ) -end - - -function AI_ESCORT:_FlightAttackTarget( DetectedItem ) - - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup, DetectedItem ) - if EscortGroup:IsAir() then - self:_AttackTarget( EscortGroup, DetectedItem ) - end - end, DetectedItem - ) - -end - - -function AI_ESCORT:_FlightAttackNearestTarget( TargetType ) - - self.Detection:Detect() - self:_FlightReportTargetsScheduler() - - local EscortGroup = self.EscortGroupSet:GetFirst() - local AttackDetectedItem = nil - local DetectedItems = self.Detection:GetDetectedItems() - - for DetectedItemIndex, DetectedItem in UTILS.spairs( DetectedItems, function( t, a, b ) return self:Distance( self.PlayerUnit, t[a] ) < self:Distance( self.PlayerUnit, t[b] ) end ) do - - local DetectedItemSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local HasGround = DetectedItemSet:HasGroundUnits() > 0 - local HasAir = DetectedItemSet:HasAirUnits() > 0 - - local FlightReportType = self:GetFlightReportType() - - if ( TargetType and TargetType == self.__Enum.ReportType.Ground and HasGround ) or - ( TargetType and TargetType == self.__Enum.ReportType.Air and HasAir ) or - ( TargetType == nil ) then - AttackDetectedItem = DetectedItem - break - end - end - - if AttackDetectedItem then - self:_FlightAttackTarget( AttackDetectedItem ) - else - EscortGroup:MessageTypeToGroup( "Nothing to attack!", MESSAGE.Type.Information, self.PlayerGroup ) - end - -end - - ---- --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup The escort group that will attack the detected item. --- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -function AI_ESCORT:_AssistTarget( EscortGroup, DetectedItem ) - - local EscortUnit = self.PlayerUnit - - local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - -- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) - end - end, Tasks - ) - - EscortGroup:SetTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - - EscortGroup:MessageTypeToGroup( "Assisting attack!", MESSAGE.Type.Information, EscortUnit:GetGroup() ) - -end - -function AI_ESCORT:_ROE( EscortGroup, EscortROEFunction, EscortROEMessage ) - pcall( function() EscortROEFunction( EscortGroup ) end ) - EscortGroup:MessageTypeToGroup( EscortROEMessage, MESSAGE.Type.Information, self.PlayerGroup ) -end - - -function AI_ESCORT:_FlightROEHoldFire( EscortROEMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROE( EscortGroup, EscortGroup.OptionROEHoldFire, EscortROEMessage ) - end - ) -end - -function AI_ESCORT:_FlightROEOpenFire( EscortROEMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROE( EscortGroup, EscortGroup.OptionROEOpenFire, EscortROEMessage ) - end - ) -end - -function AI_ESCORT:_FlightROEReturnFire( EscortROEMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROE( EscortGroup, EscortGroup.OptionROEReturnFire, EscortROEMessage ) - end - ) -end - -function AI_ESCORT:_FlightROEWeaponFree( EscortROEMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROE( EscortGroup, EscortGroup.OptionROEWeaponFree, EscortROEMessage ) - end - ) -end - - -function AI_ESCORT:_ROT( EscortGroup, EscortROTFunction, EscortROTMessage ) - pcall( function() EscortROTFunction( EscortGroup ) end ) - EscortGroup:MessageTypeToGroup( EscortROTMessage, MESSAGE.Type.Information, self.PlayerGroup ) -end - - -function AI_ESCORT:_FlightROTNoReaction( EscortROTMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROT( EscortGroup, EscortGroup.OptionROTNoReaction, EscortROTMessage ) - end - ) -end - -function AI_ESCORT:_FlightROTPassiveDefense( EscortROTMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROT( EscortGroup, EscortGroup.OptionROTPassiveDefense, EscortROTMessage ) - end - ) -end - -function AI_ESCORT:_FlightROTEvadeFire( EscortROTMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROT( EscortGroup, EscortGroup.OptionROTEvadeFire, EscortROTMessage ) - end - ) -end - -function AI_ESCORT:_FlightROTVertical( EscortROTMessage ) - self.EscortGroupSet:ForEachGroupAlive( - -- @param Wrapper.Group#GROUP EscortGroup - function( EscortGroup ) - self:_ROT( EscortGroup, EscortGroup.OptionROTVertical, EscortROTMessage ) - end - ) -end - ---- Registers the waypoints --- @param #AI_ESCORT self --- @return #table -function AI_ESCORT:RegisterRoute() - self:F() - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - - local TaskPoints = EscortGroup:GetTaskRoute() - - self:T( TaskPoints ) - - return TaskPoints -end - ---- Resume Scheduler. --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup -function AI_ESCORT:_ResumeScheduler( EscortGroup ) - self:F( EscortGroup:GetName() ) - - if EscortGroup:IsAlive() and self.PlayerUnit:IsAlive() then - - - local EscortGroupName = EscortGroup:GetCallsign() - - if EscortGroup.EscortMenuResumeMission then - EscortGroup.EscortMenuResumeMission:RemoveSubMenus() - - local TaskPoints = EscortGroup.MissionRoute - - for WayPointID, WayPoint in pairs( TaskPoints ) do - local EscortVec3 = EscortGroup:GetVec3() - local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + - ( WayPoint.y - EscortVec3.z )^2 - ) ^ 0.5 / 1000 - MENU_GROUP_COMMAND:New( self.PlayerGroup, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", EscortGroup.EscortMenuResumeMission, AI_ESCORT._ResumeMission, self, EscortGroup, WayPointID ) - end - end - end -end - - ---- Measure distance between coordinate player and coordinate detected item. --- @param #AI_ESCORT self -function AI_ESCORT:Distance( PlayerUnit, DetectedItem ) - - local DetectedCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - local PlayerCoordinate = PlayerUnit:GetCoordinate() - - return DetectedCoordinate:Get3DDistance( PlayerCoordinate ) - -end - ---- Report Targets Scheduler. --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup -function AI_ESCORT:_ReportTargetsScheduler( EscortGroup, Report ) - self:F( EscortGroup:GetName() ) - - if EscortGroup:IsAlive() and self.PlayerUnit:IsAlive() then - - local EscortGroupName = EscortGroup:GetCallsign() - - local DetectedTargetsReport = REPORT:New( "Reporting targets:\n" ) -- A new report to display the detected targets as a message to the player. - - - if EscortGroup.EscortMenuTargetAssistance then - EscortGroup.EscortMenuTargetAssistance:RemoveSubMenus() - end - - local DetectedItems = self.Detection:GetDetectedItems() - - local ClientEscortTargets = self.Detection - - local TimeUpdate = timer.getTime() - - local EscortMenuAttackTargets = MENU_GROUP:New( self.PlayerGroup, "Attack targets", EscortGroup.EscortMenu ) - - local DetectedTargets = false - - for DetectedItemIndex, DetectedItem in UTILS.spairs( DetectedItems, function( t, a, b ) return self:Distance( self.PlayerUnit, t[a] ) < self:Distance( self.PlayerUnit, t[b] ) end ) do - --for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do - - local DetectedItemSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local HasGround = DetectedItemSet:HasGroundUnits() > 0 - local HasGroundRadar = HasGround and DetectedItemSet:HasRadar() > 0 - local HasAir = DetectedItemSet:HasAirUnits() > 0 - - local FlightReportType = self:GetFlightReportType() - - - if ( FlightReportType == self.__Enum.ReportType.All ) or - ( FlightReportType == self.__Enum.ReportType.Airborne and HasAir ) or - ( FlightReportType == self.__Enum.ReportType.Ground and HasGround ) or - ( FlightReportType == self.__Enum.ReportType.GroundRadar and HasGroundRadar ) then - - DetectedTargets = true - - local DetectedMenu = self.Detection:DetectedItemReportMenu( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ):Text("\n") - - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) - local ReportSummary = DetectedItemReportSummary:Text(", ") - DetectedTargetsReport:AddIndent( ReportSummary, "-" ) - - if EscortGroup:IsAir() then - - MENU_GROUP_COMMAND:New( self.PlayerGroup, - DetectedMenu, - EscortMenuAttackTargets, - AI_ESCORT._AttackTarget, - self, - EscortGroup, - DetectedItem - ):SetTag( "Escort" ):SetTime( TimeUpdate ) - else - if self.EscortMenuTargetAssistance then - local MenuTargetAssistance = MENU_GROUP:New( self.PlayerGroup, EscortGroupName, EscortGroup.EscortMenuTargetAssistance ) - MENU_GROUP_COMMAND:New( self.PlayerGroup, - DetectedMenu, - MenuTargetAssistance, - AI_ESCORT._AssistTarget, - self, - EscortGroup, - DetectedItem - ) - end - end - end - end - - EscortMenuAttackTargets:RemoveSubMenus( TimeUpdate, "Escort" ) - - if Report then - if DetectedTargets then - EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text( "\n" ), MESSAGE.Type.Information, self.PlayerGroup ) - else - EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.PlayerGroup ) - end - end - - return true - end - - return false -end - ---- Report Targets Scheduler for the flight. The report is generated from the perspective of the player plane, and is reported by the first plane in the formation set. --- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup -function AI_ESCORT:_FlightReportTargetsScheduler() - - self:F("FlightReportTargetScheduler") - - local EscortGroup = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP - - local DetectedTargetsReport = REPORT:New( "Reporting your targets:\n" ) -- A new report to display the detected targets as a message to the player. - - if EscortGroup and ( self.PlayerUnit:IsAlive() and EscortGroup:IsAlive() ) then - - local TimeUpdate = timer.getTime() - - local DetectedItems = self.Detection:GetDetectedItems() - - local DetectedTargets = false - - local ClientEscortTargets = self.Detection - - for DetectedItemIndex, DetectedItem in UTILS.spairs( DetectedItems, function( t, a, b ) return self:Distance( self.PlayerUnit, t[a] ) < self:Distance( self.PlayerUnit, t[b] ) end ) do - - self:F("FlightReportTargetScheduler Targets") - - local DetectedItemSet = self.Detection:GetDetectedItemSet( DetectedItem ) - - local HasGround = DetectedItemSet:HasGroundUnits() > 0 - local HasGroundRadar = HasGround and DetectedItemSet:HasRadar() > 0 - local HasAir = DetectedItemSet:HasAirUnits() > 0 - - local FlightReportType = self:GetFlightReportType() - - - if ( FlightReportType == self.__Enum.ReportType.All ) or - ( FlightReportType == self.__Enum.ReportType.Airborne and HasAir ) or - ( FlightReportType == self.__Enum.ReportType.Ground and HasGround ) or - ( FlightReportType == self.__Enum.ReportType.GroundRadar and HasGroundRadar ) then - - - DetectedTargets = true -- There are detected targets, when the content of the for loop is executed. We use it to display a message. - - local DetectedItemReportMenu = self.Detection:DetectedItemReportMenu( DetectedItem, self.PlayerGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) - local ReportMenuText = DetectedItemReportMenu:Text(", ") - - MENU_GROUP_COMMAND:New( self.PlayerGroup, - ReportMenuText, - self.FlightMenuAttack, - AI_ESCORT._FlightAttackTarget, - self, - DetectedItem - ):SetTag( "Flight" ):SetTime( TimeUpdate ) - - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, self.PlayerGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) - local ReportSummary = DetectedItemReportSummary:Text(", ") - DetectedTargetsReport:AddIndent( ReportSummary, "-" ) - end - end - - self.FlightMenuAttack:RemoveSubMenus( TimeUpdate, "Flight" ) - - if DetectedTargets then - EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text( "\n" ), MESSAGE.Type.Information, self.PlayerGroup ) --- else --- EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.PlayerGroup ) - end - - return true - end - - return false -end diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua deleted file mode 100644 index ff4c0ddfe..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua +++ /dev/null @@ -1,190 +0,0 @@ ---- **AI** - Models the automatic assignment of AI escorts to player flights. --- --- ## Features: --- -- --- * Provides the facilities to trigger escorts when players join flight slots. --- * --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Escort_Dispatcher --- @image MOOSE.JPG - - --- @type AI_ESCORT_DISPATCHER --- @extends Core.Fsm#FSM - - ---- Models the automatic assignment of AI escorts to player flights. --- --- # 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_ESCORT_DISPATCHER -AI_ESCORT_DISPATCHER = { - ClassName = "AI_ESCORT_DISPATCHER", -} - --- @field #list -AI_ESCORT_DISPATCHER.AI_Escorts = {} - - ---- Creates a new AI_ESCORT_DISPATCHER object. --- @param #AI_ESCORT_DISPATCHER self --- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are spawned in. --- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts. --- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned. --- @param #string EscortName Name of the escort, which will also be the name of the escort menu. --- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #AI_ESCORT_DISPATCHER --- @usage --- --- -- Create a new escort when a player joins an SU-25T plane. --- Create a carrier set, which contains the player slots that can be joined by the players, for which escorts will be defined. --- local Red_SU25T_CarrierSet = SET_GROUP:New():FilterPrefixes( "Red A2G Player Su-25T" ):FilterStart() --- --- -- Create a spawn object that will spawn in the escorts, once the player has joined the player slot. --- local Red_SU25T_EscortSpawn = SPAWN:NewWithAlias( "Red A2G Su-25 Escort", "Red AI A2G SU-25 Escort" ):InitLimit( 10, 10 ) --- --- -- Create an airbase object, where the escorts will be spawned. --- local Red_SU25T_Airbase = AIRBASE:FindByName( AIRBASE.Caucasus.Maykop_Khanskaya ) --- --- -- Park the airplanes at the airbase, visible before start. --- Red_SU25T_EscortSpawn:ParkAtAirbase( Red_SU25T_Airbase, AIRBASE.TerminalType.OpenMedOrBig ) --- --- -- New create the escort dispatcher, using the carrier set, the escort spawn object at the escort airbase. --- -- Provide a name of the escort, which will be also the name appearing on the radio menu for the group. --- -- And a briefing to appear when the player joins the player slot. --- Red_SU25T_EscortDispatcher = AI_ESCORT_DISPATCHER:New( Red_SU25T_CarrierSet, Red_SU25T_EscortSpawn, Red_SU25T_Airbase, "Escort Su-25", "You Su-25T is escorted by one Su-25. Use the radio menu to control the escorts." ) --- --- -- The dispatcher needs to be started using the :Start() method. --- Red_SU25T_EscortDispatcher:Start() -function AI_ESCORT_DISPATCHER:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) - - local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER - - self.CarrierSet = CarrierSet - self.EscortSpawn = EscortSpawn - self.EscortAirbase = EscortAirbase - self.EscortName = EscortName - self.EscortBriefing = EscortBriefing - - self:SetStartState( "Idle" ) - - self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) - - self:AddTransition( "Idle", "Start", "Monitoring" ) - self:AddTransition( "Monitoring", "Stop", "Idle" ) - - -- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset. - function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier ) - self:F( { Carrier = Carrier:GetName() } ) - end - - return self -end - -function AI_ESCORT_DISPATCHER:onafterStart( From, Event, To ) - - self:HandleEvent( EVENTS.Birth ) - - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit ) - self:HandleEvent( EVENTS.Crash, self.OnEventExit ) - self:HandleEvent( EVENTS.Dead, self.OnEventExit ) - -end - --- @param #AI_ESCORT_DISPATCHER self --- @param Core.Event#EVENTDATA EventData -function AI_ESCORT_DISPATCHER:OnEventExit( EventData ) - - local PlayerGroupName = EventData.IniGroupName - local PlayerGroup = EventData.IniGroup - local PlayerUnit = EventData.IniUnit - - self:T({EscortAirbase= self.EscortAirbase } ) - self:T({PlayerGroupName = PlayerGroupName } ) - self:T({PlayerGroup = PlayerGroup}) - self:T({FirstGroup = self.CarrierSet:GetFirst()}) - self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) - - if self.CarrierSet:FindGroup( PlayerGroupName ) then - if self.AI_Escorts[PlayerGroupName] then - self.AI_Escorts[PlayerGroupName]:Stop() - self.AI_Escorts[PlayerGroupName] = nil - end - end - -end - --- @param #AI_ESCORT_DISPATCHER self --- @param Core.Event#EVENTDATA EventData -function AI_ESCORT_DISPATCHER:OnEventBirth( EventData ) - - local PlayerGroupName = EventData.IniGroupName - local PlayerGroup = EventData.IniGroup - local PlayerUnit = EventData.IniUnit - - self:T({EscortAirbase= self.EscortAirbase } ) - self:T({PlayerGroupName = PlayerGroupName } ) - self:T({PlayerGroup = PlayerGroup}) - self:T({FirstGroup = self.CarrierSet:GetFirst()}) - self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) - - if self.CarrierSet:FindGroup( PlayerGroupName ) then - if not self.AI_Escorts[PlayerGroupName] then - local LeaderUnit = PlayerUnit - local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) - self:T({EscortGroup = EscortGroup}) - - self:ScheduleOnce( 1, - function( EscortGroup ) - local EscortSet = SET_GROUP:New() - EscortSet:AddGroup( EscortGroup ) - self.AI_Escorts[PlayerGroupName] = AI_ESCORT:New( LeaderUnit, EscortSet, self.EscortName, self.EscortBriefing ) - self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 ) - if EscortGroup:IsHelicopter() then - self.AI_Escorts[PlayerGroupName]:MenusHelicopters() - else - self.AI_Escorts[PlayerGroupName]:MenusAirplanes() - end - self.AI_Escorts[PlayerGroupName]:__Start( 0.1 ) - end, EscortGroup - ) - end - end - -end - - ---- Start Trigger for AI_ESCORT_DISPATCHER --- @function [parent=#AI_ESCORT_DISPATCHER] Start --- @param #AI_ESCORT_DISPATCHER self - ---- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER --- @function [parent=#AI_ESCORT_DISPATCHER] __Start --- @param #AI_ESCORT_DISPATCHER self --- @param #number Delay - ---- Stop Trigger for AI_ESCORT_DISPATCHER --- @function [parent=#AI_ESCORT_DISPATCHER] Stop --- @param #AI_ESCORT_DISPATCHER self - ---- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER --- @function [parent=#AI_ESCORT_DISPATCHER] __Stop --- @param #AI_ESCORT_DISPATCHER self --- @param #number Delay - - - - - - diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua deleted file mode 100644 index 160c2beed..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua +++ /dev/null @@ -1,151 +0,0 @@ ---- **AI** - Models the assignment of AI escorts to player flights upon request using the radio menu. --- --- ## Features: --- --- * Provides the facilities to trigger escorts when players join flight units. --- * Provide a menu for which escorts can be requested. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_Escort_Dispatcher_Request --- @image MOOSE.JPG - - --- @type AI_ESCORT_DISPATCHER_REQUEST --- @extends Core.Fsm#FSM - - ---- Models the assignment of AI escorts to player flights upon request using the radio menu. --- --- # 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_ESCORT_DISPATCHER_REQUEST -AI_ESCORT_DISPATCHER_REQUEST = { - ClassName = "AI_ESCORT_DISPATCHER_REQUEST", -} - --- @field #list -AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts = {} - - ---- Creates a new AI_ESCORT_DISPATCHER_REQUEST object. --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers for which escorts are requested. --- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts. --- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned. --- @param #string EscortName Name of the escort, which will also be the name of the escort menu. --- @param #string EscortBriefing A text showing the briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #AI_ESCORT_DISPATCHER_REQUEST -function AI_ESCORT_DISPATCHER_REQUEST:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) - - local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER_REQUEST - - self.CarrierSet = CarrierSet - self.EscortSpawn = EscortSpawn - self.EscortAirbase = EscortAirbase - self.EscortName = EscortName - self.EscortBriefing = EscortBriefing - - self:SetStartState( "Idle" ) - - self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) - - self:AddTransition( "Idle", "Start", "Monitoring" ) - self:AddTransition( "Monitoring", "Stop", "Idle" ) - - -- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset. - function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier ) - self:F( { Carrier = Carrier:GetName() } ) - end - - return self -end - -function AI_ESCORT_DISPATCHER_REQUEST:onafterStart( From, Event, To ) - - self:HandleEvent( EVENTS.Birth ) - - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventExit ) - self:HandleEvent( EVENTS.Crash, self.OnEventExit ) - self:HandleEvent( EVENTS.Dead, self.OnEventExit ) - -end - --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param Core.Event#EVENTDATA EventData -function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData ) - - local PlayerGroupName = EventData.IniGroupName - local PlayerGroup = EventData.IniGroup - local PlayerUnit = EventData.IniUnit - - if self.CarrierSet:FindGroup( PlayerGroupName ) then - if self.AI_Escorts[PlayerGroupName] then - self.AI_Escorts[PlayerGroupName]:Stop() - self.AI_Escorts[PlayerGroupName] = nil - end - end - -end - --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param Core.Event#EVENTDATA EventData -function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData ) - - local PlayerGroupName = EventData.IniGroupName - local PlayerGroup = EventData.IniGroup - local PlayerUnit = EventData.IniUnit - - if self.CarrierSet:FindGroup( PlayerGroupName ) then - if not self.AI_Escorts[PlayerGroupName] then - local LeaderUnit = PlayerUnit - self:ScheduleOnce( 0.1, - function() - self.AI_Escorts[PlayerGroupName] = AI_ESCORT_REQUEST:New( LeaderUnit, self.EscortSpawn, self.EscortAirbase, self.EscortName, self.EscortBriefing ) - self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 ) - if PlayerGroup:IsHelicopter() then - self.AI_Escorts[PlayerGroupName]:MenusHelicopters() - else - self.AI_Escorts[PlayerGroupName]:MenusAirplanes() - end - self.AI_Escorts[PlayerGroupName]:__Start( 0.1 ) - end - ) - end - end - -end - - ---- Start Trigger for AI_ESCORT_DISPATCHER_REQUEST --- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Start --- @param #AI_ESCORT_DISPATCHER_REQUEST self - ---- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST --- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Start --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param #number Delay - ---- Stop Trigger for AI_ESCORT_DISPATCHER_REQUEST --- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Stop --- @param #AI_ESCORT_DISPATCHER_REQUEST self - ---- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST --- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Stop --- @param #AI_ESCORT_DISPATCHER_REQUEST self --- @param #number Delay - - - - - - diff --git a/Moose Development/Moose/AI/AI_Escort_Request.lua b/Moose Development/Moose/AI/AI_Escort_Request.lua deleted file mode 100644 index 3251d3717..000000000 --- a/Moose Development/Moose/AI/AI_Escort_Request.lua +++ /dev/null @@ -1,321 +0,0 @@ ---- **AI** - Taking the lead of AI escorting your flight or of other AI, upon request using the menu. --- --- === --- --- ## Features: --- --- * Escort navigation commands. --- * Escort hold at position commands. --- * Escorts reporting detected targets. --- * Escorts scanning targets in advance. --- * Escorts attacking specific targets. --- * Request assistance from other groups for attack. --- * Manage rule of engagement of escorts. --- * Manage the allowed evasion techniques of escorts. --- * Make escort to execute a defined mission or path. --- * Escort tactical situation reporting. --- --- === --- --- ## Missions: --- --- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Escort) --- --- === --- --- Allows you to interact with escorting AI on your flight and take the lead. --- --- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10). --- --- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes. --- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. --- --- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval. --- Once targets are reported, each escort has these targets as menu options to command the attack of these targets. --- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered. --- --- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available. --- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target. --- --- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location. --- In this way, you can spread out the escorts over the battle field before a coordinated attack. --- --- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight. --- --- ## Leading the flight --- --- When leading the flight, you are expected to guide the escorts towards the target areas, --- and carefully coordinate the attack based on the threat levels reported, and the available weapons --- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack. --- --- ## Following the flight --- --- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead. --- You as a player, are following the escorts, and are commanding them to progress the mission while --- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets --- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target --- type. Once the attack is finished, the escort will resume the mission it was assigned. --- In other words, you can use the escorts for reconnaissance, and for guiding the attack. --- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are --- following. You are in control. The ka-50s detect targets, report them, and you command how the attack --- will commence and from where. You can control where the escorts are holding position and which targets --- are attacked first. You are in control how the ka-50s will follow their mission path. --- --- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control. --- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the --- attack is well coordinated. --- --- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism --- it brings in your DCS world simulations. --- --- # RADIO MENUs that can be created: --- --- Find a summary below of the current available commands: --- --- ## Navigation ...: --- --- Escort group navigation functions: --- --- * **"Join-Up":** The escort group fill follow you in the assigned formation. --- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. --- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. --- --- ## Hold position ...: --- --- Escort group navigation functions: --- --- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter. --- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter. --- --- ## Report targets ...: --- --- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below). --- --- * **"Report now":** Will report the current detected targets. --- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list. --- * **"Report targets off":** Will stop detecting targets. --- --- ## Attack targets ...: --- --- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. --- This menu will be available in Flight menu or in each Escort menu. --- --- ## Scan targets ...: --- --- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation. --- --- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. --- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. --- --- ## Request assistance from ...: --- --- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**. --- This menu item allows to request attack support from other ground based escorts supporting the current escort. --- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. --- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. --- --- ## ROE ...: --- --- Sets the Rules of Engagement (ROE) of the escort group when in flight. --- --- * **"Hold Fire":** The escort group will hold fire. --- * **"Return Fire":** The escort group will return fire. --- * **"Open Fire":** The escort group will open fire on designated targets. --- * **"Weapon Free":** The escort group will engage with any target. --- --- ## Evasion ...: --- --- Will define the evasion techniques that the escort group will perform during flight or combat. --- --- * **"Fight until death":** The escort group will have no reaction to threats. --- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed. --- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing. --- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres. --- --- ## Resume Mission ...: --- --- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. --- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. - -- - -- # 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 - -- --- === --- --- ### Authors: **FlightControl** --- --- === --- --- @module AI.AI_Escort_Request --- @image Escorting.JPG - - - --- @type AI_ESCORT_REQUEST --- @extends AI.AI_Escort#AI_ESCORT - ---- AI_ESCORT_REQUEST class --- --- # AI_ESCORT_REQUEST construction methods. --- --- Create a new AI_ESCORT_REQUEST object with the @{#AI_ESCORT_REQUEST.New} method: --- --- * @{#AI_ESCORT_REQUEST.New}: Creates a new AI_ESCORT_REQUEST object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. --- --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = AI_ESCORT_REQUEST:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- --- @field #AI_ESCORT_REQUEST -AI_ESCORT_REQUEST = { - ClassName = "AI_ESCORT_REQUEST", -} - ---- AI_ESCORT_REQUEST.Mode class --- @type AI_ESCORT_REQUEST.MODE --- @field #number FOLLOW --- @field #number MISSION - ---- MENUPARAM type --- @type MENUPARAM --- @field #AI_ESCORT_REQUEST ParamSelf --- @field #Distance ParamDistance --- @field #function ParamFunction --- @field #string ParamMessage - ---- AI_ESCORT_REQUEST class constructor for an AI group --- @param #AI_ESCORT_REQUEST self --- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup. --- @param Core.Spawn#SPAWN EscortSpawn The spawn object of AI, escorting the EscortUnit. --- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where escorts will be spawned once requested. --- @param #string EscortName Name of the escort. --- @param #string EscortBriefing A text showing the AI_ESCORT_REQUEST briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #AI_ESCORT_REQUEST --- @usage --- EscortSpawn = SPAWN:NewWithAlias( "Red A2G Escort Template", "Red A2G Escort AI" ):InitLimit( 10, 10 ) --- EscortSpawn:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Sochi_Adler ), AIRBASE.TerminalType.OpenBig ) --- --- local EscortUnit = UNIT:FindByName( "Red A2G Pilot" ) --- --- Escort = AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, AIRBASE:FindByName(AIRBASE.Caucasus.Sochi_Adler), "A2G", "Briefing" ) --- Escort:FormationTrail( 50, 100, 100 ) --- Escort:Menus() --- Escort:__Start( 5 ) -function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) - - local EscortGroupSet = SET_GROUP:New():FilterDeads():FilterCrashes() - local self = BASE:Inherit( self, AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT_REQUEST - - self.EscortGroupSet = EscortGroupSet - self.EscortSpawn = EscortSpawn - self.EscortAirbase = EscortAirbase - - self.LeaderGroup = self.PlayerUnit:GetGroup() - - self.Detection = DETECTION_AREAS:New( self.EscortGroupSet, 5000 ) - self.Detection:__Start( 30 ) - - self.SpawnMode = self.__Enum.Mode.Mission - - return self -end - --- @param #AI_ESCORT_REQUEST self -function AI_ESCORT_REQUEST:SpawnEscort() - - local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) - - self:ScheduleOnce( 0.1, - function( EscortGroup ) - - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEHoldFire() - - self.EscortGroupSet:AddGroup( EscortGroup ) - - local LeaderEscort = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP - local Report = REPORT:New() - Report:Add( "Joining Up " .. self.EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.EscortUnit ) ) - LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit ) - - self:SetFlightModeFormation( EscortGroup ) - self:FormationTrail() - - self:_InitFlightMenus() - self:_InitEscortMenus( EscortGroup ) - self:_InitEscortRoute( EscortGroup ) - - -- @param #AI_ESCORT self - -- @param Core.Event#EVENTDATA EventData - function EscortGroup:OnEventDeadOrCrash( EventData ) - self:F( { "EventDead", EventData } ) - self.EscortMenu:Remove() - end - - EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash ) - EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash ) - - end, EscortGroup - ) - -end - --- @param #AI_ESCORT_REQUEST self --- @param Core.Set#SET_GROUP EscortGroupSet -function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet ) - - self:F() - - if not self.MenuRequestEscort then - self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName ) - self.MenuRequestEscort = MENU_GROUP_COMMAND:New( self.LeaderGroup, "Request new escort ", self.MainMenu, - function() - self:SpawnEscort() - end - ) - end - - self:GetParent( self ).onafterStart( self, EscortGroupSet ) - - self:HandleEvent( EVENTS.Dead, self.OnEventDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self.OnEventDeadOrCrash ) - -end - --- @param #AI_ESCORT_REQUEST self --- @param Core.Set#SET_GROUP EscortGroupSet -function AI_ESCORT_REQUEST:onafterStop( EscortGroupSet ) - - self:F() - - EscortGroupSet:ForEachGroup( - -- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - EscortGroup:WayPointInitialize() - - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEOpenFire() - end - ) - - self.Detection:Stop() - - self.MainMenu:Remove() - -end - ---- Set the spawn mode to be mission execution. --- @param #AI_ESCORT_REQUEST self -function AI_ESCORT_REQUEST:SetEscortSpawnMission() - - self.SpawnMode = self.__Enum.Mode.Mission - -end diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua deleted file mode 100644 index 4b01217c3..000000000 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ /dev/null @@ -1,1279 +0,0 @@ ---- **AI** - Build large airborne formations of aircraft. --- --- **Features:** --- --- * Build in-air formations consisting of more than 40 aircraft as one group. --- * Build different formation types. --- * Assign a group leader that will guide the large formation path. --- --- === --- --- ## Additional Material: --- --- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Formation) --- * **YouTube videos:** [Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0bFIJ9jIdYM22uaWmIN4oz) --- * **Guides:** None --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module AI.AI_Formation --- @image AI_Large_Formations.JPG - ---- AI_FORMATION class --- @type AI_FORMATION --- @extends Core.Fsm#FSM_SET --- @field Wrapper.Unit#UNIT FollowUnit --- @field Core.Set#SET_GROUP FollowGroupSet --- @field #string FollowName --- @field #AI_FORMATION.MODE FollowMode The mode the escort is in. --- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. --- @field #number FollowDistance The current follow distance. --- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup. --- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup. --- @field #number dtFollow Time step between position updates. - - ---- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader. --- --- AI_FORMATION makes AI @{Wrapper.Group#GROUP}s fly in formation of various compositions. --- The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!! --- The purpose of the class is to: --- --- * Make formation building a process that can be managed while in flight, rather than a task. --- * Human players can guide formations, consisting of larget planes. --- * Build large formations (like a large bomber field). --- * Form formations that DCS does not support off the shelve. --- --- A few remarks: --- --- * Depending on the type of plane, the change in direction by the leader may result in the formation getting disentangled while in flight and needs to be rebuild. --- * Formations are vulnerable to collissions, but is depending on the type of plane, the distance between the planes and the speed and angle executed by the leader. --- * Formations may take a while to build up. --- --- As a result, the AI_FORMATION is not perfect, but is very useful to: --- --- * Model large formations when flying straight line. You can build close formations when doing this. --- * Make humans guide a large formation, when the planes are wide from each other. --- --- ## AI_FORMATION construction --- --- Create a new SPAWN object with the @{#AI_FORMATION.New} method: --- --- * @{#AI_FORMATION.New}(): Creates a new AI_FORMATION object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT} or a @{Wrapper.Unit#UNIT}, with an optional briefing text. --- --- ## Formation methods --- --- The following methods can be used to set or change the formation: --- --- * @{#AI_FORMATION.FormationLine}(): Form a line formation (core formation function). --- * @{#AI_FORMATION.FormationTrail}(): Form a trail formation. --- * @{#AI_FORMATION.FormationLeftLine}(): Form a left line formation. --- * @{#AI_FORMATION.FormationRightLine}(): Form a right line formation. --- * @{#AI_FORMATION.FormationRightWing}(): Form a right wing formation. --- * @{#AI_FORMATION.FormationLeftWing}(): Form a left wing formation. --- * @{#AI_FORMATION.FormationCenterWing}(): Form a center wing formation. --- * @{#AI_FORMATION.FormationCenterVic}(): Form a Vic formation (same as CenterWing. --- * @{#AI_FORMATION.FormationCenterBoxed}(): Form a center boxed formation. --- --- ## Randomization --- --- Use the method @{AI.AI_Formation#AI_FORMATION.SetFlightRandomization}() to simulate the formation flying errors that pilots make while in formation. Is a range set in meters. --- --- @usage --- local FollowGroupSet = SET_GROUP:New():FilterCategories("plane"):FilterCoalitions("blue"):FilterPrefixes("Follow"):FilterStart() --- FollowGroupSet:Flush() --- local LeaderUnit = UNIT:FindByName( "Leader" ) --- local LargeFormation = AI_FORMATION:New( LeaderUnit, FollowGroupSet, "Center Wing Formation", "Briefing" ) --- LargeFormation:FormationCenterWing( 500, 50, 0, 250, 250 ) --- LargeFormation:__Start( 1 ) - -- - -- # 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_FORMATION -AI_FORMATION = { - ClassName = "AI_FORMATION", - FollowName = nil, -- The Follow Name - FollowUnit = nil, - FollowGroupSet = nil, - FollowMode = 1, - MODE = { - FOLLOW = 1, - MISSION = 2, - }, - FollowScheduler = nil, - OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, - OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, - dtFollow = 0.5, -} - -AI_FORMATION.__Enum = {} - --- @type AI_FORMATION.__Enum.Formation --- @field #number None --- @field #number Line --- @field #number Trail --- @field #number Stack --- @field #number LeftLine --- @field #number RightLine --- @field #number LeftWing --- @field #number RightWing --- @field #number Vic --- @field #number Box -AI_FORMATION.__Enum.Formation = { - None = 0, - Mission = 1, - Line = 2, - Trail = 3, - Stack = 4, - LeftLine = 5, - RightLine = 6, - LeftWing = 7, - RightWing = 8, - Vic = 9, - Box = 10, -} - --- @type AI_FORMATION.__Enum.Mode --- @field #number Mission --- @field #number Formation -AI_FORMATION.__Enum.Mode = { - Mission = "M", - Formation = "F", - Attack = "A", - Reconnaissance = "R", -} - --- @type AI_FORMATION.__Enum.ReportType --- @field #number All --- @field #number Airborne --- @field #number GroundRadar --- @field #number Ground -AI_FORMATION.__Enum.ReportType = { - All = "*", - Airborne = "A", - GroundRadar = "R", - Ground = "G", -} - ---- AI_FORMATION class constructor for an AI group --- @param #AI_FORMATION self --- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet. --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string FollowName Name of the escort. --- @param #string FollowBriefing Briefing. --- @return #AI_FORMATION self -function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefing ) --R2.1 - local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) ) - self:F( { FollowUnit, FollowGroupSet, FollowName } ) - - self.FollowUnit = FollowUnit -- Wrapper.Unit#UNIT - self.FollowGroupSet = FollowGroupSet -- Core.Set#SET_GROUP - - self.FollowGroupSet:ForEachGroup( - function( FollowGroup ) - --self:E("Following") - FollowGroup:SetState( self, "Mode", self.__Enum.Mode.Formation ) - end - ) - - self:SetFlightModeFormation() - - self:SetFlightRandomization( 2 ) - - self:SetStartState( "None" ) - - self:AddTransition( "*", "Stop", "Stopped" ) - - self:AddTransition( {"None", "Stopped"}, "Start", "Following" ) - - self:AddTransition( "*", "FormationLine", "*" ) - --- FormationLine Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationLine Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLine Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationLine - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLine Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationLine - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationTrail", "*" ) - --- FormationTrail Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationTrail - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @return #boolean - - --- FormationTrail Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationTrail - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - - --- FormationTrail Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationTrail - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - - --- FormationTrail Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationTrail - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - - self:AddTransition( "*", "FormationStack", "*" ) - --- FormationStack Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationStack - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @return #boolean - - --- FormationStack Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationStack - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - - --- FormationStack Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationStack - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - - --- FormationStack Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationStack - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationLeftLine", "*" ) - --- FormationLeftLine Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationLeftLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationLeftLine Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationLeftLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLeftLine Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationLeftLine - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLeftLine Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationLeftLine - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationRightLine", "*" ) - --- FormationRightLine Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationRightLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationRightLine Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationRightLine - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationRightLine Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationRightLine - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationRightLine Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationRightLine - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationLeftWing", "*" ) - --- FormationLeftWing Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationLeftWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationLeftWing Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationLeftWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLeftWing Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationLeftWing - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationLeftWing Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationLeftWing - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationRightWing", "*" ) - --- FormationRightWing Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationRightWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationRightWing Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationRightWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationRightWing Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationRightWing - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationRightWing Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationRightWing - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationCenterWing", "*" ) - --- FormationCenterWing Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationCenterWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationCenterWing Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationCenterWing - -- @param #AI_FORMATION self - -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationCenterWing Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationCenterWing - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationCenterWing Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationCenterWing - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationVic", "*" ) - --- FormationVic Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationVic - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @return #boolean - - --- FormationVic Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationVic - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationVic Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationVic - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - --- FormationVic Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationVic - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - - self:AddTransition( "*", "FormationBox", "*" ) - --- FormationBox Handler OnBefore for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnBeforeFormationBox - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @param #number ZLevels The amount of levels on the Z-axis. - -- @return #boolean - - --- FormationBox Handler OnAfter for AI_FORMATION - -- @function [parent=#AI_FORMATION] OnAfterFormationBox - -- @param #AI_FORMATION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @param #number ZLevels The amount of levels on the Z-axis. - - --- FormationBox Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] FormationBox - -- @param #AI_FORMATION self - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @param #number ZLevels The amount of levels on the Z-axis. - - --- FormationBox Asynchronous Trigger for AI_FORMATION - -- @function [parent=#AI_FORMATION] __FormationBox - -- @param #AI_FORMATION self - -- @param #number Delay - -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #number YStart The start position on the Y-axis in meters for the first group. - -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #number ZStart The start position on the Z-axis in meters for the first group. - -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. - -- @param #number ZLevels The amount of levels on the Z-axis. - - - self:AddTransition( "*", "Follow", "Following" ) - - self:FormationLeftLine( 500, 0, 250, 250 ) - - self.FollowName = FollowName - self.FollowBriefing = FollowBriefing - - - self.CT1 = 0 - self.GT1 = 0 - - self.FollowMode = AI_FORMATION.MODE.MISSION - - return self -end - - ---- Set time interval between updates of the formation. --- @param #AI_FORMATION self --- @param #number dt Time step in seconds between formation updates. Default is every 0.5 seconds. --- @return #AI_FORMATION -function AI_FORMATION:SetFollowTimeInterval(dt) --R2.1 - self.dtFollow=dt or 0.5 - return self -end - ---- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This allows to visualize where the escort is flying to. --- @param #AI_FORMATION self --- @param #boolean SmokeDirection If true, then the direction vector will be smoked. --- @return #AI_FORMATION -function AI_FORMATION:TestSmokeDirectionVector( SmokeDirection ) --R2.1 - self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false - return self -end - ---- FormationLine Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation ) --R2.1 - self:F( { FollowGroupSet, From , Event ,To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation } ) - - XStart = XStart or self.XStart - XSpace = XSpace or self.XSpace - YStart = YStart or self.YStart - YSpace = YSpace or self.YSpace - ZStart = ZStart or self.ZStart - ZSpace = ZSpace or self.ZSpace - - FollowGroupSet:Flush( self ) - - local FollowSet = FollowGroupSet:GetSet() - - local i = 1 --FF i=0 caused first unit to have no XSpace! Probably needs further adjustments. This is just a quick work around. - - for FollowID, FollowGroup in pairs( FollowSet ) do - - local PointVec3 = POINT_VEC3:New() - PointVec3:SetX( XStart + i * XSpace ) - PointVec3:SetY( YStart + i * YSpace ) - PointVec3:SetZ( ZStart + i * ZSpace ) - - local Vec3 = PointVec3:GetVec3() - FollowGroup:SetState( self, "FormationVec3", Vec3 ) - i = i + 1 - - FollowGroup:SetState( FollowGroup, "Formation", Formation ) - end - - return self - -end - ---- FormationTrail Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0, self.__Enum.Formation.Trail ) - - return self -end - - ---- FormationStack Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0, self.__Enum.Formation.Stack ) - - return self -end - - - - ---- FormationLeftLine Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace, self.__Enum.Formation.LeftLine ) - - return self -end - - ---- FormationRightLine Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightLine) - - return self -end - - ---- FormationLeftWing Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -function AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftWing) - - return self -end - - ---- FormationRightWing Handler OnAfter for AI_FORMATION --- @function [parent=#AI_FORMATION] OnAfterFormationRightWing --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -function AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 - - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightWing) - - return self -end - - ---- FormationCenterWing Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -function AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 - - local FollowSet = FollowGroupSet:GetSet() - - local i = 0 - - for FollowID, FollowGroup in pairs( FollowSet ) do - - local PointVec3 = POINT_VEC3:New() - - local Side = ( i % 2 == 0 ) and 1 or -1 - local Row = i / 2 + 1 - - PointVec3:SetX( XStart + Row * XSpace ) - PointVec3:SetY( YStart ) - PointVec3:SetZ( Side * ( ZStart + i * ZSpace ) ) - - local Vec3 = PointVec3:GetVec3() - FollowGroup:SetState( self, "FormationVec3", Vec3 ) - i = i + 1 - FollowGroup:SetState( FollowGroup, "Formation", self.__Enum.Formation.Vic ) - end - - return self -end - - ---- FormationVic Handle for AI_FORMATION --- @param #AI_FORMATION self --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 - - self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) - - return self -end - ---- FormationBox Handler OnAfter for AI_FORMATION --- @param #AI_FORMATION self --- @param #string From --- @param #string Event --- @param #string To --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #number YStart The start position on the Y-axis in meters for the first group. --- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #number ZStart The start position on the Z-axis in meters for the first group. --- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- @param #number ZLevels The amount of levels on the Z-axis. --- @return #AI_FORMATION -function AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) --R2.1 - - local FollowSet = FollowGroupSet:GetSet() - - local i = 0 - - for FollowID, FollowGroup in pairs( FollowSet ) do - - local PointVec3 = POINT_VEC3:New() - - local ZIndex = i % ZLevels - local XIndex = math.floor( i / ZLevels ) - local YIndex = math.floor( i / ZLevels ) - - PointVec3:SetX( XStart + XIndex * XSpace ) - PointVec3:SetY( YStart + YIndex * YSpace ) - PointVec3:SetZ( -ZStart - (ZSpace * ZLevels / 2 ) + ZSpace * ZIndex ) - - local Vec3 = PointVec3:GetVec3() - FollowGroup:SetState( self, "FormationVec3", Vec3 ) - i = i + 1 - FollowGroup:SetState( FollowGroup, "Formation", self.__Enum.Formation.Box ) - end - - return self -end - - ---- Use the method @{AI.AI_Formation#AI_FORMATION.SetFlightRandomization}() to make the air units in your formation randomize their flight a bit while in formation. --- @param #AI_FORMATION self --- @param #number FlightRandomization The formation flying errors that pilots can make while in formation. Is a range set in meters. --- @return #AI_FORMATION -function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1 - - self.FlightRandomization = FlightRandomization - - return self -end - - ---- Gets your escorts to flight mode. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup FollowGroup. --- @return #AI_FORMATION -function AI_FORMATION:GetFlightMode( FollowGroup ) - - if FollowGroup then - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) - end - - - return FollowGroup:GetState( FollowGroup, "Mode" ) -end - - - ---- This sets your escorts to fly a mission. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup FollowGroup. --- @return #AI_FORMATION -function AI_FORMATION:SetFlightModeMission( FollowGroup ) - - if FollowGroup then - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) - else - self.FollowGroupSet:ForSomeGroupAlive( - -- @param Core.Group#GROUP EscortGroup - function( FollowGroup ) - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) - end - ) - end - - - return self -end - - ---- This sets your escorts to execute an attack. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup FollowGroup. --- @return #AI_FORMATION -function AI_FORMATION:SetFlightModeAttack( FollowGroup ) - - if FollowGroup then - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) - else - self.FollowGroupSet:ForSomeGroupAlive( - -- @param Core.Group#GROUP EscortGroup - function( FollowGroup ) - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) - end - ) - end - - - return self -end - - ---- This sets your escorts to fly in a formation. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup FollowGroup. --- @return #AI_FORMATION -function AI_FORMATION:SetFlightModeFormation( FollowGroup ) - - if FollowGroup then - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) - else - self.FollowGroupSet:ForSomeGroupAlive( - -- @param Core.Group#GROUP EscortGroup - function( FollowGroup ) - FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) - FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) - end - ) - end - - return self -end - - - - ---- Stop function. Formation will not be updated any more. --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. --- @param #string From From state. --- @param #string Event Event. --- @param #string To The to state. -function AI_FORMATION:onafterStop(FollowGroupSet, From, Event, To) --R2.1 - self:E("Stopping formation.") -end - ---- Follow event fuction. Check if coming from state "stopped". If so the transition is rejected. --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. --- @param #string From From state. --- @param #string Event Event. --- @param #string To The to state. -function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1 - if From=="Stopped" then - return false -- Deny transition. - end - return true -end - ---- Enter following state. --- @param #AI_FORMATION self --- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. --- @param #string From From state. --- @param #string Event Event. --- @param #string To The to state. -function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 - - if self.FollowUnit:IsAlive() then - - local ClientUnit = self.FollowUnit - - local CT1, CT2, CV1, CV2 - CT1 = ClientUnit:GetState( self, "CT1" ) - - local CuVec3=ClientUnit:GetVec3() - - if CT1 == nil or CT1 == 0 then - ClientUnit:SetState( self, "CV1", CuVec3) - ClientUnit:SetState( self, "CT1", timer.getTime() ) - else - CT1 = ClientUnit:GetState( self, "CT1" ) - CT2 = timer.getTime() - CV1 = ClientUnit:GetState( self, "CV1" ) - CV2 = CuVec3 - - ClientUnit:SetState( self, "CT1", CT2 ) - ClientUnit:SetState( self, "CV1", CV2 ) - end - - --FollowGroupSet:ForEachGroupAlive( bla, self, ClientUnit, CT1, CV1, CT2, CV2) - - for _,_group in pairs(FollowGroupSet:GetSet()) do - local group=_group --Wrapper.Group#GROUP - if group and group:IsAlive() then - self:FollowMe(group, ClientUnit, CT1, CV1, CT2, CV2) - end - end - - self:__Follow( -self.dtFollow ) - end - -end - - ---- Follow me. --- @param #AI_FORMATION self --- @param Wrapper.Group#GROUP FollowGroup Follow group. --- @param Wrapper.Unit#UNIT ClientUnit Client Unit. --- @param DCS#Time CT1 Time --- @param DCS#Vec3 CV1 Vec3 --- @param DCS#Time CT2 Time --- @param DCS#Vec3 CV2 Vec3 -function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2) - - if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then - - self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) - - FollowGroup:OptionROTEvadeFire() - FollowGroup:OptionROEReturnFire() - - local GroupUnit = FollowGroup:GetUnit( 1 ) - - local GuVec3=GroupUnit:GetVec3() - - local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) - - if FollowFormation then - local FollowDistance = FollowFormation.x - - local GT1 = GroupUnit:GetState( self, "GT1" ) - - if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then - GroupUnit:SetState( self, "GV1", GuVec3) - GroupUnit:SetState( self, "GT1", timer.getTime() ) - else - local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 - local CT = CT2 - CT1 - - local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 - - local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } - local Ca = math.atan2( CDv.x, CDv.z ) - - local GT1 = GroupUnit:GetState( self, "GT1" ) - local GT2 = timer.getTime() - - local GV1 = GroupUnit:GetState( self, "GV1" ) - local GV2 = GuVec3 - - --[[ - GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - ]] - - GV2.x=GV2.x+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) - GV2.y=GV2.y+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) - GV2.z=GV2.z+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) - - - GroupUnit:SetState( self, "GT1", GT2 ) - GroupUnit:SetState( self, "GV1", GV2 ) - - - local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 - local GT = GT2 - GT1 - - - -- Calculate the distance - local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } - local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) - local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T - local Position = math.cos( Alpha_R ) - local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 - local Distance = GD * Position + - CS * 0.5 - - -- Calculate the group direction vector - local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } - - -- Calculate GH2, GH2 with the same height as CV2. - local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } - - -- Calculate the angle of GV to the orthonormal plane - local alpha = math.atan2( GV.x, GV.z ) - - local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) - local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) - - - -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. - -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) - local Inclination = ( Distance + FollowFormation.x ) / 10 - if Inclination < -30 then - Inclination = - 30 - end - - local CVI = { - x = CV2.x + CS * 10 * math.sin(Ca), - y = GH2.y + Inclination, -- + FollowFormation.y, - --y = GH2.y, - z = CV2.z + CS * 10 * math.cos(Ca), - } - - -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. - local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } - - -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. - -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. - -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... - local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } - - -- Now we can calculate the group destination vector GDV. - local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } - - local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) - local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) - - local GDV_Formation = { - x = GDV.x - GVx, - y = GDV.y, - z = GDV.z - GVz - } - - -- Debug smoke. - if self.SmokeDirectionVector == true then - trigger.action.smoke( GDV, trigger.smokeColor.Green ) - trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) - end - - - - local Time = 120 - - local Speed = - ( Distance + FollowFormation.x ) / Time - - if Distance > -10000 then - Speed = - ( Distance + FollowFormation.x ) / 60 - end - - if Distance > -2500 then - Speed = - ( Distance + FollowFormation.x ) / 20 - end - - local GS = Speed + CS - - --self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } ) - - -- Now route the escort to the desired point with the desired speed. - FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second) - - end - end - end -end diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua deleted file mode 100644 index d5ce61d72..000000000 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ /dev/null @@ -1,939 +0,0 @@ ---- **AI** - Perform Air Patrolling 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. --- --- === --- --- AI PATROL classes makes AI Controllables execute an Patrol. --- --- There are the following types of PATROL classes defined: --- --- * @{#AI_PATROL_ZONE}: Perform a PATROL in a zone. --- --- === --- --- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/AI_Patrol) --- --- === --- --- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl35HvYZKA6G22WMt7iI3zky) --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- * **Dutch_Baron**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- * **Pikey**: Testing and API concept review. --- --- === --- --- @module AI.AI_Patrol --- @image AI_Air_Patrolling.JPG - ---- AI_PATROL_ZONE class --- @type AI_PATROL_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. --- @field Core.Zone#ZONE_BASE PatrolZone The @{Core.Zone} where the patrol needs to be executed. --- @field DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @field DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @field Core.Spawn#SPAWN CoordTest --- @extends Core.Fsm#FSM_CONTROLLABLE - ---- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}. --- --- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) --- --- The AI_PATROL_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_PATROL\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_PATROL\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_PATROL\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia9.JPG) --- ----- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or --- use derived AI_ classes to model AI offensive or defensive behaviour. --- --- ![Process](..\Presentations\AI_PATROL\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_PATROL\Dia11.JPG) --- --- ## 1. AI_PATROL_ZONE constructor --- --- * @{#AI_PATROL_ZONE.New}(): Creates a new AI_PATROL_ZONE object. --- --- ## 2. AI_PATROL_ZONE is a FSM --- --- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) --- --- ### 2.1. AI_PATROL_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Returning** ( Group ): The AI is returning to Base. --- * **Stopped** ( Group ): The process is stopped. --- * **Crashed** ( Group ): The AI has crashed or is dead. --- --- ### 2.2. AI_PATROL_ZONE Events --- --- * **Start** ( Group ): Start the process. --- * **Stop** ( Group ): Stop the process. --- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone. --- * **RTB** ( Group ): Route the AI to the home base. --- * **Detect** ( Group ): The AI is detecting targets. --- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the thresholds have been reached, the AI will RTB. --- --- ## 3. Set or Get the AI controllable --- --- * @{#AI_PATROL_ZONE.SetControllable}(): Set the AIControllable. --- * @{#AI_PATROL_ZONE.GetControllable}(): Get the AIControllable. --- --- ## 4. Set the Speed and Altitude boundaries of the AI controllable --- --- * @{#AI_PATROL_ZONE.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol. --- * @{#AI_PATROL_ZONE.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol. --- --- ## 5. Manage the detection process of the AI controllable --- --- The detection process of the AI controllable can be manipulated. --- Detection requires an amount of CPU power, which has an impact on your mission performance. --- Only put detection on when absolutely necessary, and the frequency of the detection can also be set. --- --- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. --- --- The detection frequency can be set with @{#AI_PATROL_ZONE.SetRefreshTimeInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection. --- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Wrapper.Unit}s detected by the AI. --- --- The detection can be filtered to potential targets in a specific zone. --- Use the method @{#AI_PATROL_ZONE.SetDetectionZone}() to set the zone where targets need to be detected. --- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected --- according the weather conditions. --- --- ## 6. Manage the "out of fuel" in the AI_PATROL_ZONE --- --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. --- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, --- while a new AI is targeted to the AI_PATROL_ZONE. --- Once the time is finished, the old AI will return to the base. --- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this process in place. --- --- ## 7. Manage "damage" behaviour of the AI in the AI_PATROL_ZONE --- --- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on. --- Therefore, when the damage threshold is reached, the AI will return immediately to the home base (RTB). --- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this process in place. --- --- # 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_PATROL_ZONE -AI_PATROL_ZONE = { - ClassName = "AI_PATROL_ZONE", -} - ---- Creates a new AI_PATROL_ZONE object --- @param #AI_PATROL_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_PATROL_ZONE self --- @usage --- -- Define a new AI_PATROL_ZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolSpawn = SPAWN:New( 'Patrol Group' ) --- PatrolArea = AI_PATROL_ZONE:New( PatrolZone, 3000, 6000, 600, 900 ) -function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_PATROL_ZONE - - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - -- defafult PatrolAltType to "BARO" if not specified - self.PatrolAltType = PatrolAltType or "BARO" - - self:SetRefreshTimeInterval( 30 ) - - self.CheckStatus = true - - self:ManageFuel( .2, 60 ) - self:ManageDamage( 1 ) - - - self.DetectedUnits = {} -- This table contains the targets detected during patrol. - - self:SetStartState( "None" ) - - self:AddTransition( "*", "Stop", "Stopped" ) - ---- OnLeave Transition Handler for State Stopped. --- @function [parent=#AI_PATROL_ZONE] OnLeaveStopped --- @param #AI_PATROL_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 Stopped. --- @function [parent=#AI_PATROL_ZONE] OnEnterStopped --- @param #AI_PATROL_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. - ---- OnBefore Transition Handler for Event Stop. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStop --- @param #AI_PATROL_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 Stop. --- @function [parent=#AI_PATROL_ZONE] OnAfterStop --- @param #AI_PATROL_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 Stop. --- @function [parent=#AI_PATROL_ZONE] Stop --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Stop. --- @function [parent=#AI_PATROL_ZONE] __Stop --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "None", "Start", "Patrolling" ) - ---- OnBefore Transition Handler for Event Start. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStart --- @param #AI_PATROL_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 Start. --- @function [parent=#AI_PATROL_ZONE] OnAfterStart --- @param #AI_PATROL_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 Start. --- @function [parent=#AI_PATROL_ZONE] Start --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Start. --- @function [parent=#AI_PATROL_ZONE] __Start --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Patrolling. --- @function [parent=#AI_PATROL_ZONE] OnLeavePatrolling --- @param #AI_PATROL_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 Patrolling. --- @function [parent=#AI_PATROL_ZONE] OnEnterPatrolling --- @param #AI_PATROL_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( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Route. --- @function [parent=#AI_PATROL_ZONE] OnBeforeRoute --- @param #AI_PATROL_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 Route. --- @function [parent=#AI_PATROL_ZONE] OnAfterRoute --- @param #AI_PATROL_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 Route. --- @function [parent=#AI_PATROL_ZONE] Route --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Route. --- @function [parent=#AI_PATROL_ZONE] __Route --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Status. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStatus --- @param #AI_PATROL_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 Status. --- @function [parent=#AI_PATROL_ZONE] OnAfterStatus --- @param #AI_PATROL_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 Status. --- @function [parent=#AI_PATROL_ZONE] Status --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Status. --- @function [parent=#AI_PATROL_ZONE] __Status --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Detect", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Detect. --- @function [parent=#AI_PATROL_ZONE] OnBeforeDetect --- @param #AI_PATROL_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 Detect. --- @function [parent=#AI_PATROL_ZONE] OnAfterDetect --- @param #AI_PATROL_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 Detect. --- @function [parent=#AI_PATROL_ZONE] Detect --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Detect. --- @function [parent=#AI_PATROL_ZONE] __Detect --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Detected", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Detected. --- @function [parent=#AI_PATROL_ZONE] OnBeforeDetected --- @param #AI_PATROL_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 Detected. --- @function [parent=#AI_PATROL_ZONE] OnAfterDetected --- @param #AI_PATROL_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 Detected. --- @function [parent=#AI_PATROL_ZONE] Detected --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Detected. --- @function [parent=#AI_PATROL_ZONE] __Detected --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "RTB", "Returning" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event RTB. --- @function [parent=#AI_PATROL_ZONE] OnBeforeRTB --- @param #AI_PATROL_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 RTB. --- @function [parent=#AI_PATROL_ZONE] OnAfterRTB --- @param #AI_PATROL_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 RTB. --- @function [parent=#AI_PATROL_ZONE] RTB --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event RTB. --- @function [parent=#AI_PATROL_ZONE] __RTB --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Returning. --- @function [parent=#AI_PATROL_ZONE] OnLeaveReturning --- @param #AI_PATROL_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 Returning. --- @function [parent=#AI_PATROL_ZONE] OnEnterReturning --- @param #AI_PATROL_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( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - - self:AddTransition( "*", "Eject", "*" ) - self:AddTransition( "*", "Crash", "Crashed" ) - self:AddTransition( "*", "PilotDead", "*" ) - - return self -end - - - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_PATROL_ZONE self --- @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. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_PATROL_ZONE self --- @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. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - --- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. - ---- Set the detection on. The AI will detect for targets. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionOn() - self:F2() - - self.DetectOn = true -end - ---- Set the detection off. The AI will NOT detect for targets. --- However, the list of already detected targets will be kept and can be enquired! --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionOff() - self:F2() - - self.DetectOn = false -end - ---- Set the status checking off. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetStatusOff() - self:F2() - - self.CheckStatus = false -end - ---- Activate the detection. The AI will detect for targets if the Detection is switched On. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionActivated() - self:F2() - - self:ClearDetectedUnits() - self.DetectActivated = true - self:__Detect( -self.DetectInterval ) -end - ---- Deactivate the detection. The AI will NOT detect for targets. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionDeactivated() - self:F2() - - self:ClearDetectedUnits() - self.DetectActivated = false -end - ---- Set the interval in seconds between each detection executed by the AI. --- The list of already detected targets will be kept and updated. --- Newly detected targets will be added, but already detected targets that were --- not detected in this cycle, will NOT be removed! --- The default interval is 30 seconds. --- @param #AI_PATROL_ZONE self --- @param #number Seconds The interval in seconds. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetRefreshTimeInterval( Seconds ) - self:F2() - - if Seconds then - self.DetectInterval = Seconds - else - self.DetectInterval = 30 - end -end - ---- Set the detection zone where the AI is detecting targets. --- @param #AI_PATROL_ZONE self --- @param Core.Zone#ZONE DetectionZone The zone where to detect targets. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionZone( DetectionZone ) - self:F2() - - if DetectionZone then - self.DetectZone = DetectionZone - else - self.DetectZone = nil - end -end - ---- Gets a list of @{Wrapper.Unit#UNIT}s that were detected by the AI. --- No filtering is applied, so, ANY detected UNIT can be in this list. --- It is up to the mission designer to use the @{Wrapper.Unit} class and methods to filter the targets. --- @param #AI_PATROL_ZONE self --- @return #table The list of @{Wrapper.Unit#UNIT}s -function AI_PATROL_ZONE:GetDetectedUnits() - self:F2() - - return self.DetectedUnits -end - ---- Clears the list of @{Wrapper.Unit#UNIT}s that were detected by the AI. --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ClearDetectedUnits() - self:F2() - self.DetectedUnits = {} -end - ---- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel threshold is calculated. --- When the fuel threshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targeted to the AI_PATROL_ZONE. --- Once the time is finished, the old AI will return to the base. --- @param #AI_PATROL_ZONE self --- @param #number PatrolFuelThresholdPercentage The threshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ManageFuel( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - return self -end - ---- When the AI is damaged beyond a certain threshold, it is required that the AI returns to the home base. --- However, damage cannot be foreseen early on. --- Therefore, when the damage threshold is reached, --- the AI will return immediately to the home base (RTB). --- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage threshold will be 0.25. --- @param #AI_PATROL_ZONE self --- @param #number PatrolDamageThreshold The threshold in percentage (between 0 and 1) when the AI is considered to be damaged. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ManageDamage( PatrolDamageThreshold ) - - self.PatrolManageDamage = true - self.PatrolDamageThreshold = PatrolDamageThreshold - - return self -end - ---- Defines a new patrol route using the @{#AI_PATROL_ZONE} parameters and settings. --- @param #AI_PATROL_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 #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To ) - self:F2() - - self:__Route( 1 ) -- Route to the patrol point. The asynchronous trigger is important, because a spawned group and units takes at least one second to come live. - self:__Status( 60 ) -- Check status status every 30 seconds. - self:SetDetectionActivated() - - self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead ) - self:HandleEvent( EVENTS.Crash, self.OnCrash ) - self:HandleEvent( EVENTS.Ejection, self.OnEjection ) - - Controllable:OptionROEHoldFire() - Controllable:OptionROTVertical() - - self.Controllable:OnReSpawn( - function( PatrolGroup ) - self:T( "ReSpawn" ) - self:__Reset( 1 ) - self:__Route( 5 ) - end - ) - - self:SetDetectionOn() - -end - - --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable+ -function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To ) - - return self.DetectOn and self.DetectActivated -end - --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable -function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To ) - - local Detected = false - - local DetectedTargets = Controllable:GetDetectedTargets() - for TargetID, Target in pairs( DetectedTargets or {} ) do - local TargetObject = Target.object - - if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then - - local TargetUnit = UNIT:Find( TargetObject ) - - -- Check that target is alive due to issue https://github.com/FlightControl-Master/MOOSE/issues/1234 - if TargetUnit and TargetUnit:IsAlive() then - - local TargetUnitName = TargetUnit:GetName() - - if self.DetectionZone then - if TargetUnit:IsInZone( self.DetectionZone ) then - self:T( {"Detected ", TargetUnit } ) - if self.DetectedUnits[TargetUnit] == nil then - self.DetectedUnits[TargetUnit] = true - end - Detected = true - end - else - if self.DetectedUnits[TargetUnit] == nil then - self.DetectedUnits[TargetUnit] = true - end - Detected = true - end - - end - end - end - - self:__Detect( -self.DetectInterval ) - - if Detected == true then - self:__Detected( 1.5 ) - end - -end - --- @param Wrapper.Controllable#CONTROLLABLE AIControllable --- This static method is called from the route path within the last task at the last waypoint of the Controllable. --- Note that this method is required, as triggers the next route when patrolling for the Controllable. -function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable ) - - local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROL_ZONE - PatrolZone:__Route( 1 ) -end - - ---- Defines a new patrol route using the @{#AI_PATROL_ZONE} parameters and settings. --- @param #AI_PATROL_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_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) - - self:F2() - - -- When RTB, don't allow anymore the routing. - if From == "RTB" then - return - end - - local life = self.Controllable:GetLife() or 0 - if self.Controllable:IsAlive() and life > 1 then - -- Determine if the AIControllable is within the PatrolZone. - -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. - - local PatrolRoute = {} - - -- Calculate the current route point of the controllable as the start point of the route. - -- However, when the controllable is not in the air, - -- the controllable current waypoint is probably the airbase... - -- Thus, if we would take the current waypoint as the startpoint, upon take-off, the controllable flies - -- immediately back to the airbase, and this is not correct. - -- Therefore, when on a runway, get as the current route point a random point within the PatrolZone. - -- This will make the plane fly immediately to the patrol zone. - - if self.Controllable:InAir() == false then - self:T( "Not in the air, finding route path within PatrolZone" ) - local CurrentVec2 = self.Controllable:GetVec2() - if not CurrentVec2 then return end - --Done: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TakeOffParking, - POINT_VEC3.RoutePointAction.FromParkingArea, - ToPatrolZoneSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - else - self:T( "In the air, finding route path within PatrolZone" ) - local CurrentVec2 = self.Controllable:GetVec2() - if not CurrentVec2 then return end - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - end - - - --- Define a random point in the @{Core.Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - 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 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - --self.CoordTest:SpawnFromVec3( ToTargetPointVec3:GetVec3() ) - - --ToTargetPointVec3:SmokeRed() - - PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( PatrolRoute ) - - --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "PatrolZone", self ) - self.Controllable:WayPointFunction( #PatrolRoute, 1, "AI_PATROL_ZONE:_NewPatrolRoute" ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 2 ) - end - -end - --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onbeforeStatus() - - return self.CheckStatus -end - --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterStatus() - self:F2() - - if self.Controllable and self.Controllable:IsAlive() then - - local RTB = false - - local Fuel = self.Controllable:GetFuelMin() - if Fuel < self.PatrolFuelThresholdPercentage then - self:T( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" ) - local OldAIControllable = self.Controllable - - local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldAIControllable:SetTask( TimedOrbitTask, 10 ) - - RTB = true - else - end - - -- TODO: Check GROUP damage function. - local Damage = self.Controllable:GetLife() - if Damage <= self.PatrolDamageThreshold then - self:T( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" ) - RTB = true - end - - if RTB == true then - self:RTB() - else - self:__Status( 60 ) -- Execute the Patrol event after 30 seconds. - end - end -end - --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterRTB() - self:F2() - - if self.Controllable and self.Controllable:IsAlive() then - - self:SetDetectionOff() - self.CheckStatus = false - - local PatrolRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - if not CurrentVec2 then return end - --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). - --local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( PatrolRoute ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 1 ) - - end - -end - --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterDead() - self:SetDetectionOff() - self:SetStatusOff() -end - --- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnCrash( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - if #self.Controllable:GetUnits() == 1 then - self:__Crash( 1, EventData ) - end - end -end - --- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnEjection( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__Eject( 1, EventData ) - end -end - --- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnPilotDead( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__PilotDead( 1, EventData ) - end -end diff --git a/Moose Development/Moose/Actions/Act_Account.lua b/Moose Development/Moose/Actions/Act_Account.lua deleted file mode 100644 index a72a7b445..000000000 --- a/Moose Development/Moose/Actions/Act_Account.lua +++ /dev/null @@ -1,310 +0,0 @@ ---- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occurring on UNITs. --- --- ![Banner Image](..\Presentations\ACT_ACCOUNT\Dia1.JPG) --- --- === --- --- @module Actions.Act_Account --- @image MOOSE.JPG - -do -- ACT_ACCOUNT - - --- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS} - -- - -- ## ACT_ACCOUNT state machine: - -- - -- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. - -- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. - -- Each derived class follows exactly the same process, using the same events and following the same state transitions, - -- but will have **different implementation behaviour** upon each event or state transition. - -- - -- ### ACT_ACCOUNT States - -- - -- * **Assigned**: The player is assigned. - -- * **Waiting**: Waiting for an event. - -- * **Report**: Reporting. - -- * **Account**: Account for an event. - -- * **Accounted**: All events have been accounted for, end of the process. - -- * **Failed**: Failed the process. - -- - -- ### ACT_ACCOUNT Events - -- - -- * **Start**: Start the process. - -- * **Wait**: Wait for an event. - -- * **Report**: Report the status of the accounting. - -- * **Event**: An event happened, process the event. - -- * **More**: More targets. - -- * **NoMore (*)**: No more targets. - -- * **Fail (*)**: The action process has failed. - -- - -- (*) End states of the process. - -- - -- ### ACT_ACCOUNT state transition methods: - -- - -- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. - -- There are 2 moments when state transition methods will be called by the state machine: - -- - -- * **Before** the state transition. - -- The state transition method needs to start with the name **OnBefore + the name of the state**. - -- If the state transition method returns false, then the processing of the state transition will not be done! - -- If you want to change the behaviour of the AIControllable at this event, return false, - -- but then you'll need to specify your own logic using the AIControllable! - -- - -- * **After** the state transition. - -- The state transition method needs to start with the name **OnAfter + the name of the state**. - -- These state transition methods need to provide a return value, which is specified at the function description. - -- - -- # 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 - -- - -- @type ACT_ACCOUNT - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Core.Fsm#FSM_PROCESS - ACT_ACCOUNT = { - ClassName = "ACT_ACCOUNT", - TargetSetUnit = nil, - } - - --- Creates a new DESTROY process. - -- @param #ACT_ACCOUNT self - -- @return #ACT_ACCOUNT - function ACT_ACCOUNT:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "Assigned", "Start", "Waiting" ) - self:AddTransition( "*", "Wait", "Waiting" ) - self:AddTransition( "*", "Report", "Report" ) - self:AddTransition( "*", "Event", "Account" ) - self:AddTransition( "Account", "Player", "AccountForPlayer" ) - self:AddTransition( "Account", "Other", "AccountForOther" ) - self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "More", "Wait" ) - self:AddTransition( { "Account", "AccountForPlayer", "AccountForOther" }, "NoMore", "Accounted" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Failed" ) - - self:SetStartState( "Assigned" ) - - return self - end - - --- Process Events - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onafterStart( ProcessUnit, From, Event, To ) - - self:HandleEvent( EVENTS.Dead, self.onfuncEventDead ) - self:HandleEvent( EVENTS.Crash, self.onfuncEventCrash ) - self:HandleEvent( EVENTS.Hit ) - - self:__Wait( 1 ) - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onenterWaiting( ProcessUnit, From, Event, To ) - - if self.DisplayCount >= self.DisplayInterval then - self:Report() - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event ) - - self:__NoMore( 1 ) - end - -end -- ACT_ACCOUNT - -do -- ACT_ACCOUNT_DEADS - - --- # @{#ACT_ACCOUNT_DEADS} FSM class, extends @{#ACT_ACCOUNT} - -- - -- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units. - -- The process is given a @{Core.Set} of units that will be tracked upon successful destruction. - -- The process will end after each target has been successfully destroyed. - -- Each successful dead will trigger an Account state transition that can be scored, modified or administered. - -- - -- - -- ## ACT_ACCOUNT_DEADS constructor: - -- - -- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object. - -- - -- @type ACT_ACCOUNT_DEADS - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends #ACT_ACCOUNT - ACT_ACCOUNT_DEADS = { - ClassName = "ACT_ACCOUNT_DEADS", - } - - --- Creates a new DESTROY process. - -- @param #ACT_ACCOUNT_DEADS self - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskName - function ACT_ACCOUNT_DEADS:New() - -- Inherits from BASE - local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - return self - end - - function ACT_ACCOUNT_DEADS:Init( FsmAccount ) - - self.Task = self:GetTask() - self.TaskName = self.Task:GetName() - end - - --- Process Events - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Task, From, Event, To ) - - local MessageText = "Your group with assigned " .. self.TaskName .. " task has " .. Task.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." - self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param Tasking.Task#TASK Task - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Task, From, Event, To, EventData ) - self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } ) - - if Task.TargetSetUnit:FindUnit( EventData.IniUnitName ) then - local PlayerName = ProcessUnit:GetPlayerName() - local PlayerHit = self.PlayerHits and self.PlayerHits[EventData.IniUnitName] - if PlayerHit == PlayerName then - self:Player( EventData ) - else - self:Other( EventData ) - end - end - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param Tasking.Task#TASK Task - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onenterAccountForPlayer( ProcessUnit, Task, From, Event, To, EventData ) - self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } ) - - local TaskGroup = ProcessUnit:GetGroup() - - Task.TargetSetUnit:Remove( EventData.IniUnitName ) - - local MessageText = "You have destroyed a target.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." - self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - - local PlayerName = ProcessUnit:GetPlayerName() - Task:AddProgress( PlayerName, "Destroyed " .. EventData.IniTypeName, timer.getTime(), 1 ) - - if Task.TargetSetUnit:Count() > 0 then - self:__More( 1 ) - else - self:__NoMore( 1 ) - end - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param Tasking.Task#TASK Task - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onenterAccountForOther( ProcessUnit, Task, From, Event, To, EventData ) - self:T( { ProcessUnit:GetName(), Task:GetName(), From, Event, To, EventData } ) - - local TaskGroup = ProcessUnit:GetGroup() - Task.TargetSetUnit:Remove( EventData.IniUnitName ) - - local MessageText = "One of the task targets has been destroyed.\nYour group assigned with task " .. self.TaskName .. " has\n" .. Task.TargetSetUnit:Count() .. " targets ( " .. Task.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." - self:GetCommandCenter():MessageTypeToGroup( MessageText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - - if Task.TargetSetUnit:Count() > 0 then - self:__More( 1 ) - else - self:__NoMore( 1 ) - end - end - - --- DCS Events - - -- @param #ACT_ACCOUNT_DEADS self - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:OnEventHit( EventData ) - self:T( { "EventDead", EventData } ) - - if EventData.IniPlayerName and EventData.TgtDCSUnitName then - self.PlayerHits = self.PlayerHits or {} - self.PlayerHits[EventData.TgtDCSUnitName] = EventData.IniPlayerName - end - end - - -- @param #ACT_ACCOUNT_DEADS self - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) - self:T( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - self:Event( EventData ) - end - end - - --- DCS Events - - -- @param #ACT_ACCOUNT_DEADS self - -- @param Core.Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData ) - self:T( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - self:Event( EventData ) - end - end - -end -- ACT_ACCOUNT DEADS diff --git a/Moose Development/Moose/Actions/Act_Assign.lua b/Moose Development/Moose/Actions/Act_Assign.lua deleted file mode 100644 index 3b261cfb1..000000000 --- a/Moose Development/Moose/Actions/Act_Assign.lua +++ /dev/null @@ -1,292 +0,0 @@ ---- (SP) (MP) (FSM) Accept or reject process for player (task) assignments. --- --- === --- --- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS} --- --- ## ACT_ASSIGN state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ASSIGN **Events**: --- --- These are the events defined in this class: --- --- * **Start**: Start the tasking acceptance process. --- * **Assign**: Assign the task. --- * **Reject**: Reject the task.. --- --- ### ACT_ASSIGN **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ASSIGN **States**: --- --- * **UnAssigned**: The player has not accepted the task. --- * **Assigned (*)**: The player has accepted the task. --- * **Rejected (*)**: The player has not accepted the task. --- * **Waiting**: The process is awaiting player feedback. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ASSIGN state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- # 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 --- --- === --- --- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Core.Fsm#ACT_ASSIGN} --- --- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task. --- --- ## 1.1) ACT_ASSIGN_ACCEPT constructor: --- --- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object. --- --- === --- --- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Core.Fsm#ACT_ASSIGN} --- --- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option. --- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. --- The assignment type also allows to reject the task. --- --- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor: --- ----------------------------------------- --- --- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object. --- --- === --- --- @module Actions.Act_Assign --- @image MOOSE.JPG - - -do -- ACT_ASSIGN - - --- ACT_ASSIGN class - -- @type ACT_ASSIGN - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends Core.Fsm#FSM_PROCESS - ACT_ASSIGN = { - ClassName = "ACT_ASSIGN", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #ACT_ASSIGN self - -- @return #ACT_ASSIGN The task acceptance process. - function ACT_ASSIGN:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "UnAssigned", "Start", "Waiting" ) - self:AddTransition( "Waiting", "Assign", "Assigned" ) - self:AddTransition( "Waiting", "Reject", "Rejected" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Assigned" ) - self:AddEndState( "Rejected" ) - self:AddEndState( "Failed" ) - - self:SetStartState( "UnAssigned" ) - - return self - end - -end -- ACT_ASSIGN - - - -do -- ACT_ASSIGN_ACCEPT - - --- ACT_ASSIGN_ACCEPT class - -- @type ACT_ASSIGN_ACCEPT - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIGN - ACT_ASSIGN_ACCEPT = { - ClassName = "ACT_ASSIGN_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #ACT_ASSIGN_ACCEPT self - -- @param #string TaskBriefing - function ACT_ASSIGN_ACCEPT:New( TaskBriefing ) - - local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT - - self.TaskBriefing = TaskBriefing - - return self - end - - function ACT_ASSIGN_ACCEPT:Init( FsmAssign ) - - self.TaskBriefing = FsmAssign.TaskBriefing - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To ) - - self:__Assign( 1 ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup ) - - self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() ) - end - -end -- ACT_ASSIGN_ACCEPT - - -do -- ACT_ASSIGN_MENU_ACCEPT - - --- ACT_ASSIGN_MENU_ACCEPT class - -- @type ACT_ASSIGN_MENU_ACCEPT - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIGN - ACT_ASSIGN_MENU_ACCEPT = { - ClassName = "ACT_ASSIGN_MENU_ACCEPT", - } - - --- Init. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskBriefing - -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:New( TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT - - self.TaskBriefing = TaskBriefing - - return self - end - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskBriefing - -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing ) - - self.TaskBriefing = TaskBriefing - - return self - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To ) - - self:GetCommandCenter():MessageToGroup( "Task " .. self.Task:GetName() .. " has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!", ProcessUnit:GetGroup(), 120 ) - - local TaskGroup = ProcessUnit:GetGroup() - - self.Menu = MENU_GROUP:New( TaskGroup, "Task " .. self.Task:GetName() .. " CONFIRMATION" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( TaskGroup, "Accept task " .. self.Task:GetName(), self.Menu, self.MenuAssign, self, TaskGroup ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( TaskGroup, "Reject task " .. self.Task:GetName(), self.Menu, self.MenuReject, self, TaskGroup ) - - self:__Reject( 120, TaskGroup ) - end - - --- Menu function. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuAssign( TaskGroup ) - - self:__Assign( -1, TaskGroup ) - end - - --- Menu function. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup ) - - self:__Reject( -1, TaskGroup ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup ) - - self.Menu:Remove() - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Task, From, Event, To, TaskGroup ) - self:F( { TaskGroup = TaskGroup } ) - - self.Menu:Remove() - --TODO: need to resolve this problem ... it has to do with the events ... - --self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event - self.Task:RejectGroup( TaskGroup ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup ) - - --self.Task:AssignToGroup( TaskGroup ) - self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() ) - end - -end -- ACT_ASSIGN_MENU_ACCEPT diff --git a/Moose Development/Moose/Actions/Act_Assist.lua b/Moose Development/Moose/Actions/Act_Assist.lua deleted file mode 100644 index 2ae132ac1..000000000 --- a/Moose Development/Moose/Actions/Act_Assist.lua +++ /dev/null @@ -1,219 +0,0 @@ ---- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. --- --- ## ACT_ASSIST state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ASSIST **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. --- * **Next**: The process is smoking the targets in the given zone. --- --- ### ACT_ASSIST **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ASSIST **States**: --- --- * **None**: The controllable did not receive route commands. --- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone. --- * **Smoking (*)**: The process is smoking the targets in the zone. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ASSIST state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{#ACT_ASSIST} --- --- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Core.Zone}. --- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour. --- At random intervals, a new target is smoked. --- --- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor: --- --- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object. --- --- # 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 --- --- === --- --- @module Actions.Act_Assist --- @image MOOSE.JPG - - -do -- ACT_ASSIST - - --- ACT_ASSIST class - -- @type ACT_ASSIST - -- @extends Core.Fsm#FSM_PROCESS - ACT_ASSIST = { - ClassName = "ACT_ASSIST", - } - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST self - -- @return #ACT_ASSIST - function ACT_ASSIST:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "None", "Start", "AwaitSmoke" ) - self:AddTransition( "AwaitSmoke", "Next", "Smoking" ) - self:AddTransition( "Smoking", "Next", "AwaitSmoke" ) - self:AddTransition( "*", "Stop", "Success" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Failed" ) - self:AddEndState( "Success" ) - - self:SetStartState( "None" ) - - return self - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ASSIST self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST:onafterStart( ProcessUnit, From, Event, To ) - - local ProcessGroup = ProcessUnit:GetGroup() - local MissionMenu = self:GetMission():GetMenu( ProcessGroup ) - - local function MenuSmoke( MenuParam ) - local self = MenuParam.self - local SmokeColor = MenuParam.SmokeColor - self.SmokeColor = SmokeColor - self:__Next( 1 ) - end - - self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) - self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) - self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) - self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) - self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) - self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIST self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST:onafterStop( ProcessUnit, From, Event, To ) - - self.Menu:Remove() -- When stopped, remove the menus - end - -end - -do -- ACT_ASSIST_SMOKE_TARGETS_ZONE - - --- ACT_ASSIST_SMOKE_TARGETS_ZONE class - -- @type ACT_ASSIST_SMOKE_TARGETS_ZONE - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIST - ACT_ASSIST_SMOKE_TARGETS_ZONE = { - ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE", - } - --- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor() --- self:E("_Destructor") --- --- self.Menu:Remove() --- self:EventRemoveAll() --- end - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param Core.Zone#ZONE_BASE TargetZone - function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone ) - local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - return self - end - - function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke ) - - self.TargetSetUnit = FsmSmoke.TargetSetUnit - self.TargetZone = FsmSmoke.TargetZone - end - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param Core.Zone#ZONE_BASE TargetZone - -- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self - function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone ) - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - return self - end - - --- StateMachine callback function - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To ) - - self.TargetSetUnit:ForEachUnit( - -- @param Wrapper.Unit#UNIT SmokeUnit - function( SmokeUnit ) - if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then - SCHEDULER:New( self, - function() - if SmokeUnit:IsAlive() then - SmokeUnit:Smoke( self.SmokeColor, 150 ) - end - end, {}, math.random( 10, 60 ) - ) - end - end - ) - - end - -end diff --git a/Moose Development/Moose/Actions/Act_Route.lua b/Moose Development/Moose/Actions/Act_Route.lua deleted file mode 100644 index b387b6584..000000000 --- a/Moose Development/Moose/Actions/Act_Route.lua +++ /dev/null @@ -1,483 +0,0 @@ ---- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. --- --- === --- --- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS} --- --- ## ACT_ROUTE state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ROUTE **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. The process will go into the Report state. --- * **Report**: The process is reporting to the player the route to be followed. --- * **Route**: The process is routing the controllable. --- * **Pause**: The process is pausing the route of the controllable. --- * **Arrive**: The controllable has arrived at a route point. --- * **More**: There are more route points that need to be followed. The process will go back into the Report state. --- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state. --- --- ### ACT_ROUTE **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ROUTE **States**: --- --- * **None**: The controllable did not receive route commands. --- * **Arrived (*)**: The controllable has arrived at a route point. --- * **Aborted (*)**: The controllable has aborted the route path. --- * **Routing**: The controllable is understay to the route point. --- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around. --- * **Success (*)**: All route points were reached. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ROUTE state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ROUTE_ZONE} class, extends @{#ACT_ROUTE} --- --- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Wrapper.Controllable} player @{Wrapper.Unit} to a @{Core.Zone}. --- The player receives on perioding times messages with the coordinates of the route to follow. --- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended. --- --- # 1.1) ACT_ROUTE_ZONE constructor: --- --- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object. --- --- # 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 --- --- === --- --- @module Actions.Act_Route --- @image MOOSE.JPG - - -do -- ACT_ROUTE - - --- ACT_ROUTE class - -- @type ACT_ROUTE - -- @field Tasking.Task#TASK TASK - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE Zone - -- @field Core.Point#COORDINATE Coordinate - -- @extends Core.Fsm#FSM_PROCESS - ACT_ROUTE = { - ClassName = "ACT_ROUTE", - } - - - --- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE. - -- @param #ACT_ROUTE self - -- @return #ACT_ROUTE self - function ACT_ROUTE:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "*", "Reset", "None" ) - self:AddTransition( "None", "Start", "Routing" ) - self:AddTransition( "*", "Report", "*" ) - self:AddTransition( "Routing", "Route", "Routing" ) - self:AddTransition( "Routing", "Pause", "Pausing" ) - self:AddTransition( "Routing", "Arrive", "Arrived" ) - self:AddTransition( "*", "Cancel", "Cancelled" ) - self:AddTransition( "Arrived", "Success", "Success" ) - self:AddTransition( "*", "Fail", "Failed" ) - self:AddTransition( "", "", "" ) - self:AddTransition( "", "", "" ) - - self:AddEndState( "Arrived" ) - self:AddEndState( "Failed" ) - self:AddEndState( "Cancelled" ) - - self:SetStartState( "None" ) - - self:SetRouteMode( "C" ) - - return self - end - - --- Set a Cancel Menu item. - -- @param #ACT_ROUTE self - -- @return #ACT_ROUTE - function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag ) - - self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New( - MenuGroup, - MenuText, - ParentMenu, - self.MenuCancel, - self - ):SetTime( MenuTime ):SetTag( MenuTag ) - - ParentMenu:SetTime( MenuTime ) - - ParentMenu:Remove( MenuTime, MenuTag ) - - return self - end - - --- Set the route mode. - -- There are 2 route modes supported: - -- - -- * SetRouteMode( "B" ): Route mode is Bearing and Range. - -- * SetRouteMode( "C" ): Route mode is LL or MGRS according coordinate system setup. - -- - -- @param #ACT_ROUTE self - -- @return #ACT_ROUTE - function ACT_ROUTE:SetRouteMode( RouteMode ) - - self.RouteMode = RouteMode - - return self - end - - --- Get the routing text to be displayed. - -- The route mode determines the text displayed. - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT Controllable - -- @return #string - function ACT_ROUTE:GetRouteText( Controllable ) - - local RouteText = "" - - local Coordinate = nil -- Core.Point#COORDINATE - - if self.Coordinate then - Coordinate = self.Coordinate - end - - if self.Zone then - Coordinate = self.Zone:GetPointVec3( self.Altitude ) - Coordinate:SetHeading( self.Heading ) - end - - - local Task = self:GetTask() -- This is to dermine that the coordinates are for a specific task mode (A2A or A2G). - local CC = self:GetTask():GetMission():GetCommandCenter() - if CC then - if CC:IsModeWWII() then - -- Find closest reference point to the target. - local ShortestDistance = 0 - local ShortestReferencePoint = nil - local ShortestReferenceName = "" - self:F( { CC.ReferencePoints } ) - for ZoneName, Zone in pairs( CC.ReferencePoints ) do - self:F( { ZoneName = ZoneName } ) - local Zone = Zone -- Core.Zone#ZONE - local ZoneCoord = Zone:GetCoordinate() - local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate ) - self:F( { ShortestDistance, ShortestReferenceName } ) - if ShortestDistance == 0 or ZoneDistance < ShortestDistance then - ShortestDistance = ZoneDistance - ShortestReferencePoint = ZoneCoord - ShortestReferenceName = CC.ReferenceNames[ZoneName] - end - end - if ShortestReferencePoint then - RouteText = Coordinate:ToStringFromRP( ShortestReferencePoint, ShortestReferenceName, Controllable ) - end - else - RouteText = Coordinate:ToString( Controllable, nil, Task ) - end - end - - return RouteText - end - - - function ACT_ROUTE:MenuCancel() - self:F("Cancelled") - self.CancelMenuGroupCommand:Remove() - self:__Cancel( 1 ) - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE:onafterStart( ProcessUnit, From, Event, To ) - - - self:__Route( 1 ) - end - - --- Check if the controllable has arrived. - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @return #boolean - function ACT_ROUTE:onfuncHasArrived( ProcessUnit ) - return false - end - - --- StateMachine callback function - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To ) - - if ProcessUnit:IsAlive() then - local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic - if self.DisplayCount >= self.DisplayInterval then - self:T( { HasArrived = HasArrived } ) - if not HasArrived then - self:Report() - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - if HasArrived then - self:__Arrive( 1 ) - else - self:__Route( 1 ) - end - - return HasArrived -- if false, then the event will not be executed... - end - - return false - - end - -end -- ACT_ROUTE - - -do -- ACT_ROUTE_POINT - - --- ACT_ROUTE_POINT class - -- @type ACT_ROUTE_POINT - -- @field Tasking.Task#TASK TASK - -- @extends #ACT_ROUTE - ACT_ROUTE_POINT = { - ClassName = "ACT_ROUTE_POINT", - } - - - --- Creates a new routing state machine. - -- The task will route a controllable to a Coordinate until the controllable is within the Range. - -- @param #ACT_ROUTE_POINT self - -- @param Core.Point#COORDINATE The Coordinate to Target. - -- @param #number Range The Distance to Target. - -- @param Core.Zone#ZONE_BASE Zone - function ACT_ROUTE_POINT:New( Coordinate, Range ) - local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_POINT - - self.Coordinate = Coordinate - self.Range = Range or 0 - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - - return self - end - - --- Creates a new routing state machine. - -- The task will route a controllable to a Coordinate until the controllable is within the Range. - -- @param #ACT_ROUTE_POINT self - function ACT_ROUTE_POINT:Init( FsmRoute ) - - self.Coordinate = FsmRoute.Coordinate - self.Range = FsmRoute.Range or 0 - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self:SetStartState("None") - end - - --- Set Coordinate - -- @param #ACT_ROUTE_POINT self - -- @param Core.Point#COORDINATE Coordinate The Coordinate to route to. - function ACT_ROUTE_POINT:SetCoordinate( Coordinate ) - self:F2( { Coordinate } ) - self.Coordinate = Coordinate - end - - --- Get Coordinate - -- @param #ACT_ROUTE_POINT self - -- @return Core.Point#COORDINATE Coordinate The Coordinate to route to. - function ACT_ROUTE_POINT:GetCoordinate() - self:F2( { self.Coordinate } ) - return self.Coordinate - end - - --- Set Range around Coordinate - -- @param #ACT_ROUTE_POINT self - -- @param #number Range The Range to consider the arrival. Default is 10000 meters. - function ACT_ROUTE_POINT:SetRange( Range ) - self:F2( { Range } ) - self.Range = Range or 10000 - end - - --- Get Range around Coordinate - -- @param #ACT_ROUTE_POINT self - -- @return #number The Range to consider the arrival. Default is 10000 meters. - function ACT_ROUTE_POINT:GetRange() - self:F2( { self.Range } ) - return self.Range - end - - --- Method override to check if the controllable has arrived. - -- @param #ACT_ROUTE_POINT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @return #boolean - function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit ) - - if ProcessUnit:IsAlive() then - local Distance = self.Coordinate:Get2DDistance( ProcessUnit:GetCoordinate() ) - - if Distance <= self.Range then - local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived." - self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - return true - end - end - - return false - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE_POINT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE_POINT:onafterReport( ProcessUnit, From, Event, To ) - - local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit ) - - self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update ) - end - -end -- ACT_ROUTE_POINT - - -do -- ACT_ROUTE_ZONE - - --- ACT_ROUTE_ZONE class - -- @type ACT_ROUTE_ZONE - -- @field Tasking.Task#TASK TASK - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE Zone - -- @extends #ACT_ROUTE - ACT_ROUTE_ZONE = { - ClassName = "ACT_ROUTE_ZONE", - } - - - --- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE. - -- @param #ACT_ROUTE_ZONE self - -- @param Core.Zone#ZONE_BASE Zone - function ACT_ROUTE_ZONE:New( Zone ) - local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE - - self.Zone = Zone - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - - return self - end - - function ACT_ROUTE_ZONE:Init( FsmRoute ) - - self.Zone = FsmRoute.Zone - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - end - - --- Set Zone - -- @param #ACT_ROUTE_ZONE self - -- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to. - -- @param #number Altitude - -- @param #number Heading - function ACT_ROUTE_ZONE:SetZone( Zone, Altitude, Heading ) -- R2.2 Added altitude and heading - self.Zone = Zone - self.Altitude = Altitude - self.Heading = Heading - end - - --- Get Zone - -- @param #ACT_ROUTE_ZONE self - -- @return Core.Zone#ZONE_BASE Zone The Zone object where to route to. - function ACT_ROUTE_ZONE:GetZone() - return self.Zone - end - - --- Method override to check if the controllable has arrived. - -- @param #ACT_ROUTE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @return #boolean - function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit ) - - if ProcessUnit:IsInZone( self.Zone ) then - local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", you have arrived within the zone." - self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Information ) - end - - return ProcessUnit:IsInZone( self.Zone ) - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE_ZONE self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE_ZONE:onafterReport( ProcessUnit, From, Event, To ) - self:F( { ProcessUnit = ProcessUnit } ) - - local RouteText = "Task \"" .. self:GetTask():GetName() .. "\", " .. self:GetRouteText( ProcessUnit ) - self:GetCommandCenter():MessageTypeToGroup( RouteText, ProcessUnit:GetGroup(), MESSAGE.Type.Update ) - end - -end -- ACT_ROUTE_ZONE diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua deleted file mode 100644 index 8b7d6040e..000000000 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ /dev/null @@ -1,1399 +0,0 @@ ---- **Cargo** - Management of CARGO logistics, that can be transported from and to transportation carriers. --- --- === --- --- # 1) MOOSE Cargo System. --- --- #### Those who have used the mission editor, know that the DCS mission editor provides cargo facilities. --- However, these are merely static objects. Wouldn't it be nice if cargo could bring a new dynamism into your --- simulations? Where various objects of various types could be treated also as cargo? --- --- This is what MOOSE brings to you, a complete new cargo object model that used the cargo capabilities of --- DCS world, but enhances it. --- --- MOOSE Cargo introduces also a new concept, called a "carrier". These can be: --- --- - Helicopters --- - Planes --- - Ground Vehicles --- - Ships --- --- With the MOOSE Cargo system, you can: --- --- - Take full control of the cargo as objects within your script (see below). --- - Board/Unboard infantry into carriers. Also other objects can be boarded, like mortars. --- - Load/Unload dcs world cargo objects into carriers. --- - Load/Unload other static objects into carriers (like tires etc). --- - Slingload cargo objects. --- - Board units one by one... --- --- # 2) MOOSE Cargo Objects. --- --- In order to make use of the MOOSE cargo system, you need to **declare** the DCS objects as MOOSE cargo objects! --- --- This sounds complicated, but it is actually quite simple. --- --- See here an example: --- --- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) --- --- The above code declares a MOOSE cargo object called `EngineerCargoGroup`. --- It actually just refers to an infantry group created within the sim called `"Engineers"`. --- The infantry group now becomes controlled by the MOOSE cargo object `EngineerCargoGroup`. --- A MOOSE cargo object also has properties, like the type of cargo, the logical name, and the reporting range. --- --- There are 4 types of MOOSE cargo objects possible, each represented by its own class: --- --- - @{Cargo.CargoGroup#CARGO_GROUP}: A MOOSE cargo that is represented by a DCS world GROUP object. --- - @{Cargo.CargoCrate#CARGO_CRATE}: A MOOSE cargo that is represented by a DCS world cargo object (static object). --- - @{Cargo.CargoUnit#CARGO_UNIT}: A MOOSE cargo that is represented by a DCS world unit object or static object. --- - @{Cargo.CargoSlingload#CARGO_SLINGLOAD}: A MOOSE cargo that is represented by a DCS world cargo object (static object), that can be slingloaded. --- --- Note that a CARGO crate is not meant to be slingloaded (it can, but it is not **meant** to be handled like that. --- Instead, a CARGO_CRATE is able to load itself into the bays of a carrier. --- --- Each of these MOOSE cargo objects behave in its own way, and have methods to be handled. --- --- local InfantryGroup = GROUP:FindByName( "Infantry" ) --- local InfantryCargo = CARGO_GROUP:New( InfantryGroup, "Engineers", "Infantry Engineers", 2000 ) --- local CargoCarrier = UNIT:FindByName( "Carrier" ) --- -- This call will make the Cargo run to the CargoCarrier. --- -- Upon arrival at the CargoCarrier, the Cargo will be Loaded into the Carrier. --- -- This process is now fully automated. --- InfantryCargo:Board( CargoCarrier, 25 ) --- --- The above would create a MOOSE cargo object called `InfantryCargo`, and using that object, --- you can board the cargo into the carrier `CargoCarrier`. --- Simple, isn't it? Told you, and this is only the beginning. --- --- The boarding, unboarding, loading, unloading of cargo is however something that is not meant to be coded manually by mission designers. --- It would be too low-level and not end-user friendly to deal with cargo handling complexity. --- Things can become really complex if you want to make cargo being handled and behave in multiple scenarios. --- --- # 3) Cargo Handling Classes, the main engines for mission designers! --- --- For this reason, the MOOSE Cargo System is heavily used by 3 important **cargo handling class hierarchies** within MOOSE, --- that make cargo come "alive" within your mission in a full automatic manner! --- --- ## 3.1) AI Cargo handlers. --- --- - @{AI.AI_Cargo_APC} will create for you the capability to make an APC group handle cargo. --- - @{AI.AI_Cargo_Helicopter} will create for you the capability to make a Helicopter group handle cargo. --- --- --- ## 3.2) AI Cargo transportation dispatchers. --- --- There are also dispatchers that make AI work together to transport cargo automatically!!! --- --- - @{AI.AI_Cargo_Dispatcher_APC} derived classes will create for your dynamic cargo handlers controlled by AI ground vehicle groups (APCs) to transport cargo between sites. --- - @{AI.AI_Cargo_Dispatcher_Helicopter} derived classes will create for your dynamic cargo handlers controlled by AI helicopter groups to transport cargo between sites. --- --- ## 3.3) Cargo transportation tasking. --- --- And there is cargo transportation tasking for human players. --- --- - @{Tasking.Task_CARGO} derived classes will create for you cargo transportation tasks, that allow human players to interact with MOOSE cargo objects to complete tasks. --- --- Please refer to the documentation reflected within these modules to understand the detailed capabilities. --- --- # 4) Cargo SETs. --- --- To make life a bit more easy, MOOSE cargo objects can be grouped into a @{Core.Set#SET_CARGO}. --- This is a collection of MOOSE cargo objects. --- --- This would work as follows: --- --- -- Define the cargo set. --- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() --- --- -- Now add cargo the cargo set. --- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) --- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) --- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) --- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) --- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) --- --- This is a very powerful concept! --- Instead of having to deal with multiple MOOSE cargo objects yourself, the cargo set capability will group cargo objects into one set. --- The key is the **cargo type** name given at each cargo declaration! --- In the above example, the cargo type name is `"Workmaterials"`. Each cargo object declared is given that type name. (the 2nd parameter). --- What happens now is that the cargo set `CargoSetWorkmaterials` will be added with each cargo object **dynamically** when the cargo object is created. --- In other words, the cargo set `CargoSetWorkmaterials` will incorporate any `"Workmaterials"` dynamically into its set. --- --- The cargo sets are extremely important for the AI cargo transportation dispatchers and the cargo transporation tasking. --- --- # 5) Declare cargo directly in the mission editor! --- --- But I am not finished! There is something more, that is even more great! --- Imagine the mission designers having to code all these lines every time it wants to embed cargo within a mission. --- --- -- Now add cargo the cargo set. --- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) --- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) --- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) --- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) --- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) --- --- This would be extremely tiring and a huge overload. --- However, the MOOSE framework allows to declare MOOSE cargo objects within the mission editor!!! --- --- So, at mission startup, MOOSE will search for objects following a special naming convention, and will **create** for you **dynamically --- cargo objects** at **mission start**!!! -- These cargo objects can then be automatically incorporated within cargo set(s)!!! --- In other words, your mission will be reduced to about a few lines of code, providing you with a full dynamic cargo handling mission! --- --- ## 5.1) Use \#CARGO tags in the mission editor: --- --- MOOSE can create automatically cargo objects, if the name of the cargo contains the **\#CARGO** tag. --- When a mission starts, MOOSE will scan all group and static objects it found for the presence of the \#CARGO tag. --- When found, MOOSE will declare the object as cargo (create in the background a CARGO_ object, like CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD. --- The creation of these CARGO_ objects will allow to be filtered and automatically added in SET_CARGO objects. --- In other words, with very minimal code as explained in the above code section, you are able to create vast amounts of cargo objects just from within the editor. --- --- What I talk about is this: --- --- -- BEFORE THIS SCRIPT STARTS, MOOSE WILL ALREADY HAVE SCANNED FOR OBJECTS WITH THE #CARGO TAG IN THE NAME. --- -- FOR EACH OF THESE OBJECT, MOOSE WILL HAVE CREATED CARGO_ OBJECTS LIKE CARGO_GROUP, CARGO_CRATE AND CARGO_SLINGLOAD. --- --- HQ = GROUP:FindByName( "HQ", "Bravo" ) --- --- CommandCenter = COMMANDCENTER --- :New( HQ, "Lima" ) --- --- Mission = MISSION --- :New( CommandCenter, "Operation Cargo Fun", "Tactical", "Transport Cargo", coalition.side.RED ) --- --- TransportGroups = SET_GROUP:New():FilterCoalitions( "blue" ):FilterPrefixes( "Transport" ):FilterStart() --- --- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups ) --- --- -- This is the most important now. You setup a new SET_CARGO filtering the relevant type. --- -- The actual cargo objects are now created by MOOSE in the background. --- -- Each cargo is setup in the Mission Editor using the #CARGO tag in the group name. --- -- This allows a truly dynamic setup. --- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() --- --- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." ) --- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) ) --- --- The above code example has the `CargoSetWorkmaterials`, which is a SET_CARGO collection and will include the CARGO_ objects of the type "Workmaterials". --- And there is NO cargo object actually declared within the script! However, if you would open the mission, there would be hundreds of cargo objects... --- --- The \#CARGO tag even allows for several options to be specified, which are important to learn. --- --- ## 5.2) The \#CARGO tag to create CARGO_GROUP objects: --- --- You can also use the \#CARGO tag on **group** objects of the mission editor. --- --- For example, the following #CARGO naming in the **group name** of the object, will create a CARGO_GROUP object when the mission starts. --- --- `Infantry #CARGO(T=Workmaterials,RR=500,NR=25)` --- --- This will create a CARGO_GROUP object: --- --- * with the group name `Infantry #CARGO` --- * is of type `Workmaterials` --- * will report when a carrier is within 500 meters --- * will board to carriers when the carrier is within 500 meters from the cargo object --- * will disappear when the cargo is within 25 meters from the carrier during boarding --- --- So the overall syntax of the #CARGO naming tag and arguments are: --- --- `GroupName #CARGO(T=CargoTypeName,RR=Range,NR=Range)` --- --- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object. --- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier. --- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters. --- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. --- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters. --- --- ## 5.2) The \#CARGO tag to create CARGO_CRATE or CARGO_SLINGLOAD objects: --- --- You can also use the \#CARGO tag on **static** objects, including **static cargo** objects of the mission editor. --- --- For example, the following #CARGO naming in the **static name** of the object, will create a CARGO_CRATE object when the mission starts. --- --- `Static #CARGO(T=Workmaterials,C=CRATE,RR=500,NR=25)` --- --- This will create a CARGO_CRATE object: --- --- * with the group name `Static #CARGO` --- * is of type `Workmaterials` --- * is of category `CRATE` (as opposed to `SLING`) --- * will report when a carrier is within 500 meters --- * will board to carriers when the carrier is within 500 meters from the cargo object --- * will disappear when the cargo is within 25 meters from the carrier during boarding --- --- So the overall syntax of the #CARGO naming tag and arguments are: --- --- `StaticName #CARGO(T=CargoTypeName,C=Category,RR=Range,NR=Range)` --- --- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object. --- * **C=** Provide either `CRATE` or `SLING` to have this static created as a CARGO_CRATE or CARGO_SLINGLOAD respectively. --- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier. --- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters. --- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding. --- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters. --- --- # 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 --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.Cargo --- @image Cargo.JPG - --- Events - --- Board - ---- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#CARGO] Board --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. --- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). - ---- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#CARGO] __Board --- @param #CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. --- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). - - --- UnBoard - ---- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. --- The cargo must be in the **Loaded** state. --- @function [parent=#CARGO] UnBoard --- @param #CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. - ---- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. --- The cargo must be in the **Loaded** state. --- @function [parent=#CARGO] __UnBoard --- @param #CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. - - --- Load - ---- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#CARGO] Load --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - ---- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#CARGO] __Load --- @param #CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - - --- UnLoad - ---- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. --- The cargo must be in the **Loaded** state. --- @function [parent=#CARGO] UnLoad --- @param #CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. - ---- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. --- The cargo must be in the **Loaded** state. --- @function [parent=#CARGO] __UnLoad --- @param #CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. - --- State Transition Functions - --- UnLoaded - ---- @function [parent=#CARGO] OnLeaveUnLoaded --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#CARGO] OnEnterUnLoaded --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- Loaded - ---- @function [parent=#CARGO] OnLeaveLoaded --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#CARGO] OnEnterLoaded --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- Boarding - ---- @function [parent=#CARGO] OnLeaveBoarding --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#CARGO] OnEnterBoarding --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). - --- UnBoarding - ---- @function [parent=#CARGO] OnLeaveUnBoarding --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#CARGO] OnEnterUnBoarding --- @param #CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - - --- TODO: Find all Carrier objects and make the type of the Carriers Wrapper.Unit#UNIT in the documentation. - -CARGOS = {} - -do -- 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. - -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. - -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. - -- @field Wrapper.Unit#UNIT CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... - -- @field Wrapper.Client#CLIENT CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... - -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. - -- @field #boolean Moveable This flag defines if the cargo is moveable. - -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. - -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. - - --- Defines the core functions that defines a cargo object within MOOSE. - -- - -- A cargo is a **logical object** defined that is available for transport, and has a life status within a simulation. - -- - -- CARGO is not meant to be used directly by mission designers, but provides a base class for **concrete cargo implementation classes** to handle: - -- - -- * Cargo **group objects**, implemented by the @{Cargo.CargoGroup#CARGO_GROUP} class. - -- * Cargo **Unit objects**, implemented by the @{Cargo.CargoUnit#CARGO_UNIT} class. - -- * Cargo **Crate objects**, implemented by the @{Cargo.CargoCrate#CARGO_CRATE} class. - -- * Cargo **Sling Load objects**, implemented by the @{Cargo.CargoSlingload#CARGO_SLINGLOAD} class. - -- - -- The above cargo classes are used by the AI\_CARGO\_ classes to allow AI groups to transport cargo: - -- - -- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC#AI_CARGO_APC} class. - -- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter#AI_CARGO_HELICOPTER} class. - -- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane#AI_CARGO_AIRPLANE} class. - -- * AI Ships is planned. - -- - -- The above cargo classes are also used by the TASK\_CARGO\_ classes to allow human players to transport cargo as part of a tasking: - -- - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players. - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players. - -- - -- - -- The CARGO is a state machine: it manages the different events and states of the cargo. - -- All derived classes from CARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states. - -- - -- ## CARGO Events: - -- - -- * @{#CARGO.Board}( ToCarrier ): Boards the cargo to a carrier. - -- * @{#CARGO.Load}( ToCarrier ): Loads the cargo into a carrier, regardless of its position. - -- * @{#CARGO.UnBoard}( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2. - -- * @{#CARGO.UnLoad}( ToPointVec2 ): UnLoads the cargo from a carrier. - -- * @{#CARGO.Destroyed}( Controllable ): The cargo is dead. The cargo process will be ended. - -- - -- @field #CARGO - CARGO = { - ClassName = "CARGO", - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, - Reported = {}, - } - - -- @type CARGO.CargoObjects - -- @map < #string, Wrapper.Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. - - --- CARGO Constructor. This class is an abstract class and should not be instantiated. - -- @param #CARGO self - -- @param #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO - function CARGO:New( Type, Name, Weight, LoadRadius, NearRadius ) --R2.1 - - local self = BASE:Inherit( self, FSM:New() ) -- #CARGO - self:T( { Type, Name, Weight, LoadRadius, NearRadius } ) - - self:SetStartState( "UnLoaded" ) - self:AddTransition( { "UnLoaded", "Boarding" }, "Board", "Boarding" ) - self:AddTransition( "Boarding" , "Boarding", "Boarding" ) - self:AddTransition( "Boarding", "CancelBoarding", "UnLoaded" ) - self:AddTransition( "Boarding", "Load", "Loaded" ) - self:AddTransition( "UnLoaded", "Load", "Loaded" ) - self:AddTransition( "Loaded", "UnBoard", "UnBoarding" ) - self:AddTransition( "UnBoarding", "UnBoarding", "UnBoarding" ) - self:AddTransition( "UnBoarding", "UnLoad", "UnLoaded" ) - self:AddTransition( "Loaded", "UnLoad", "UnLoaded" ) - self:AddTransition( "*", "Damaged", "Damaged" ) - self:AddTransition( "*", "Destroyed", "Destroyed" ) - self:AddTransition( "*", "Respawn", "UnLoaded" ) - self:AddTransition( "*", "Reset", "UnLoaded" ) - - self.Type = Type - self.Name = Name - self.Weight = Weight or 0 - self.CargoObject = nil - self.CargoCarrier = nil -- Wrapper.Client#CLIENT - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - self.CargoLimit = 0 - - self.LoadRadius = LoadRadius or 500 - --self.NearRadius = NearRadius or 25 - - self:SetDeployed( false ) - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.Name] = self - - return self - end - - --- Find a CARGO in the _DATABASE. - -- @param #CARGO self - -- @param #string CargoName The Cargo Name. - -- @return #CARGO self - function CARGO:FindByName( CargoName ) - - local CargoFound = _DATABASE:FindCargo( CargoName ) - return CargoFound - end - - --- Get the x position of the cargo. - -- @param #CARGO self - -- @return #number - function CARGO:GetX() - if self:IsLoaded() then - return self.CargoCarrier:GetCoordinate().x - else - return self.CargoObject:GetCoordinate().x - end - end - - --- Get the y position of the cargo. - -- @param #CARGO self - -- @return #number - function CARGO:GetY() - if self:IsLoaded() then - return self.CargoCarrier:GetCoordinate().z - else - return self.CargoObject:GetCoordinate().z - end - end - - --- Get the heading of the cargo. - -- @param #CARGO self - -- @return #number - function CARGO:GetHeading() - if self:IsLoaded() then - return self.CargoCarrier:GetHeading() - else - return self.CargoObject:GetHeading() - end - end - - --- Check if the cargo can be Slingloaded. - -- @param #CARGO self - function CARGO:CanSlingload() - return false - end - - --- Check if the cargo can be Boarded. - -- @param #CARGO self - function CARGO:CanBoard() - return true - end - - --- Check if the cargo can be Unboarded. - -- @param #CARGO self - function CARGO:CanUnboard() - return true - end - - --- Check if the cargo can be Loaded. - -- @param #CARGO self - function CARGO:CanLoad() - return true - end - - --- Check if the cargo can be Unloaded. - -- @param #CARGO self - function CARGO:CanUnload() - return true - end - - --- Destroy the cargo. - -- @param #CARGO self - function CARGO:Destroy() - if self.CargoObject then - self.CargoObject:Destroy() - end - self:Destroyed() - end - - --- Get the name of the Cargo. - -- @param #CARGO self - -- @return #string The name of the Cargo. - function CARGO:GetName() --R2.1 - return self.Name - end - - --- Get the current active object representing or being the Cargo. - -- @param #CARGO self - -- @return Wrapper.Positionable#POSITIONABLE The object representing or being the Cargo. - function CARGO:GetObject() - if self:IsLoaded() then - return self.CargoCarrier - else - return self.CargoObject - end - end - - --- Get the object name of the Cargo. - -- @param #CARGO self - -- @return #string The object name of the Cargo. - function CARGO:GetObjectName() --R2.1 - if self:IsLoaded() then - return self.CargoCarrier:GetName() - else - return self.CargoObject:GetName() - end - end - - --- Get the amount of Cargo. - -- @param #CARGO self - -- @return #number The amount of Cargo. - function CARGO:GetCount() - return 1 - end - - --- Get the type of the Cargo. - -- @param #CARGO self - -- @return #string The type of the Cargo. - function CARGO:GetType() - return self.Type - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO self - -- @return #string The transportation method of the Cargo. - function CARGO:GetTransportationMethod() - return self.TransportationMethod - end - - --- Get the coalition of the Cargo. - -- @param #CARGO self - -- @return Coalition - function CARGO:GetCoalition() - if self:IsLoaded() then - return self.CargoCarrier:GetCoalition() - else - return self.CargoObject:GetCoalition() - end - end - - --- Get the current coordinates of the Cargo. - -- @param #CARGO self - -- @return Core.Point#COORDINATE The coordinates of the Cargo. - function CARGO:GetCoordinate() - return self.CargoObject:GetCoordinate() - end - - --- Check if cargo is destroyed. - -- @param #CARGO self - -- @return #boolean true if destroyed - function CARGO:IsDestroyed() - return self:Is( "Destroyed" ) - end - - --- Check if cargo is loaded. - -- @param #CARGO self - -- @return #boolean true if loaded - function CARGO:IsLoaded() - return self:Is( "Loaded" ) - end - - --- Check if cargo is loaded. - -- @param #CARGO self - -- @param Wrapper.Unit#UNIT Carrier - -- @return #boolean true if loaded - function CARGO:IsLoadedInCarrier( Carrier ) - return self.CargoCarrier and self.CargoCarrier:GetName() == Carrier:GetName() - end - - --- Check if cargo is unloaded. - -- @param #CARGO self - -- @return #boolean true if unloaded - function CARGO:IsUnLoaded() - return self:Is( "UnLoaded" ) - end - - --- Check if cargo is boarding. - -- @param #CARGO self - -- @return #boolean true if boarding - function CARGO:IsBoarding() - return self:Is( "Boarding" ) - end - - --- Check if cargo is unboarding. - -- @param #CARGO self - -- @return #boolean true if unboarding - function CARGO:IsUnboarding() - return self:Is( "UnBoarding" ) - end - - --- Check if cargo is alive. - -- @param #CARGO self - -- @return #boolean true if unloaded - function CARGO:IsAlive() - - if self:IsLoaded() then - return self.CargoCarrier:IsAlive() - else - return self.CargoObject:IsAlive() - end - end - - --- Set the cargo as deployed. - -- @param #CARGO self - -- @param #boolean Deployed true if the cargo is to be deployed. false or nil otherwise. - function CARGO:SetDeployed( Deployed ) - self.Deployed = Deployed - end - - --- Is the cargo deployed - -- @param #CARGO self - -- @return #boolean - function CARGO:IsDeployed() - return self.Deployed - end - - --- Template method to spawn a new representation of the CARGO in the simulator. - -- @param #CARGO self - -- @return #CARGO - function CARGO:Spawn( PointVec2 ) - self:T() - - end - - --- Signal a flare at the position of the CARGO. - -- @param #CARGO self - -- @param Utilities.Utils#FLARECOLOR FlareColor - function CARGO:Flare( FlareColor ) - if self:IsUnLoaded() then - trigger.action.signalFlare( self.CargoObject:GetVec3(), FlareColor , 0 ) - end - end - - --- Signal a white flare at the position of the CARGO. - -- @param #CARGO self - function CARGO:FlareWhite() - self:Flare( trigger.flareColor.White ) - end - - --- Signal a yellow flare at the position of the CARGO. - -- @param #CARGO self - function CARGO:FlareYellow() - self:Flare( trigger.flareColor.Yellow ) - end - - --- Signal a green flare at the position of the CARGO. - -- @param #CARGO self - function CARGO:FlareGreen() - self:Flare( trigger.flareColor.Green ) - end - - --- Signal a red flare at the position of the CARGO. - -- @param #CARGO self - function CARGO:FlareRed() - self:Flare( trigger.flareColor.Red ) - end - - --- Smoke the CARGO. - -- @param #CARGO self - -- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke. - -- @param #number Radius The radius of randomization around the center of the Cargo. - function CARGO:Smoke( SmokeColor, Radius ) - if self:IsUnLoaded() then - if Radius then - trigger.action.smoke( self.CargoObject:GetRandomVec3( Radius ), SmokeColor ) - else - trigger.action.smoke( self.CargoObject:GetVec3(), SmokeColor ) - end - end - end - - --- Smoke the CARGO Green. - -- @param #CARGO self - function CARGO:SmokeGreen() - self:Smoke( trigger.smokeColor.Green, Range ) - end - - --- Smoke the CARGO Red. - -- @param #CARGO self - function CARGO:SmokeRed() - self:Smoke( trigger.smokeColor.Red, Range ) - end - - --- Smoke the CARGO White. - -- @param #CARGO self - function CARGO:SmokeWhite() - self:Smoke( trigger.smokeColor.White, Range ) - end - - --- Smoke the CARGO Orange. - -- @param #CARGO self - function CARGO:SmokeOrange() - self:Smoke( trigger.smokeColor.Orange, Range ) - end - - --- Smoke the CARGO Blue. - -- @param #CARGO self - function CARGO:SmokeBlue() - self:Smoke( trigger.smokeColor.Blue, Range ) - end - - --- Set the Load radius, which is the radius till when the Cargo can be loaded. - -- @param #CARGO self - -- @param #number LoadRadius The radius till Cargo can be loaded. - -- @return #CARGO - function CARGO:SetLoadRadius( LoadRadius ) - self.LoadRadius = LoadRadius or 150 - end - - --- Get the Load radius, which is the radius till when the Cargo can be loaded. - -- @param #CARGO self - -- @return #number The radius till Cargo can be loaded. - function CARGO:GetLoadRadius() - return self.LoadRadius - end - - --- Check if Cargo is in the LoadRadius for the Cargo to be Boarded or Loaded. - -- @param #CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the CargoGroup is within the loading radius. - function CARGO:IsInLoadRadius( Coordinate ) - self:T( { Coordinate, LoadRadius = self.LoadRadius } ) - - local Distance = 0 - if self:IsUnLoaded() then - local CargoCoordinate = self.CargoObject:GetCoordinate() - Distance = Coordinate:Get2DDistance( CargoCoordinate ) - self:T( Distance ) - if Distance <= self.LoadRadius then - return true - end - end - - return false - end - - --- Check if the Cargo can report itself to be Boarded or Loaded. - -- @param #CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo can report itself. - function CARGO:IsInReportRadius( Coordinate ) - self:T( { Coordinate } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - self:T( Distance ) - if Distance <= self.LoadRadius then - return true - end - end - - return false - end - - - --- Check if CargoCarrier is near the coordinate within NearRadius. - -- @param #CARGO self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). - -- @return #boolean - function CARGO:IsNear( Coordinate, NearRadius ) - --self:T( { PointVec2 = PointVec2, NearRadius = NearRadius } ) - - if self.CargoObject:IsAlive() then - --local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() ) - --self:T( { CargoObjectName = self.CargoObject:GetName() } ) - --self:T( { CargoObjectVec2 = self.CargoObject:GetVec2() } ) - --self:T( { PointVec2 = PointVec2:GetVec2() } ) - local Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - --self:T( { Distance = Distance, NearRadius = NearRadius or "nil" } ) - - if Distance <= NearRadius then - --self:T( { PointVec2 = PointVec2, NearRadius = NearRadius, IsNear = true } ) - return true - end - end - - --self:T( { PointVec2 = PointVec2, NearRadius = NearRadius, IsNear = false } ) - return false - end - - --- Check if Cargo is the given @{Core.Zone}. - -- @param #CARGO self - -- @param Core.Zone#ZONE_BASE Zone - -- @return #boolean **true** if cargo is in the Zone, **false** if cargo is not in the Zone. - function CARGO:IsInZone( Zone ) - --self:T( { Zone } ) - - if self:IsLoaded() then - return Zone:IsPointVec2InZone( self.CargoCarrier:GetPointVec2() ) - else - --self:T( { Size = self.CargoObject:GetSize(), Units = self.CargoObject:GetUnits() } ) - if self.CargoObject:GetSize() ~= 0 then - return Zone:IsPointVec2InZone( self.CargoObject:GetPointVec2() ) - else - return false - end - end - - return nil - - end - - --- Get the current PointVec2 of the cargo. - -- @param #CARGO self - -- @return Core.Point#POINT_VEC2 - function CARGO:GetPointVec2() - return self.CargoObject:GetPointVec2() - end - - --- Get the current Coordinate of the cargo. - -- @param #CARGO self - -- @return Core.Point#COORDINATE - function CARGO:GetCoordinate() - return self.CargoObject:GetCoordinate() - end - - --- Get the weight of the cargo. - -- @param #CARGO self - -- @return #number Weight The weight in kg. - function CARGO:GetWeight() - return self.Weight - end - - --- Set the weight of the cargo. - -- @param #CARGO self - -- @param #number Weight The weight in kg. - -- @return #CARGO - function CARGO:SetWeight( Weight ) - self.Weight = Weight - return self - end - - --- Get the volume of the cargo. - -- @param #CARGO self - -- @return #number Volume The volume in kg. - function CARGO:GetVolume() - return self.Volume - end - - --- Set the volume of the cargo. - -- @param #CARGO self - -- @param #number Volume The volume in kg. - -- @return #CARGO - function CARGO:SetVolume( Volume ) - self.Volume = Volume - return self - end - - --- Send a CC message to a @{Wrapper.Group}. - -- @param #CARGO self - -- @param #string Message - -- @param Wrapper.Group#GROUP CarrierGroup The Carrier Group. - -- @param #string Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown. - function CARGO:MessageToGroup( Message, CarrierGroup, Name ) - - MESSAGE:New( Message, 20, "Cargo " .. self:GetName() ):ToGroup( CarrierGroup ) - - end - - --- Report to a Carrier Group. - -- @param #CARGO self - -- @param #string Action The string describing the action for the cargo. - -- @param Wrapper.Group#GROUP CarrierGroup The Carrier Group to send the report to. - -- @return #CARGO - function CARGO:Report( ReportText, Action, CarrierGroup ) - - if not self.Reported[CarrierGroup] or not self.Reported[CarrierGroup][Action] then - self.Reported[CarrierGroup] = {} - self.Reported[CarrierGroup][Action] = true - self:MessageToGroup( ReportText, CarrierGroup ) - if self.ReportFlareColor then - if not self.Reported[CarrierGroup]["Flaring"] then - self:Flare( self.ReportFlareColor ) - self.Reported[CarrierGroup]["Flaring"] = true - end - end - if self.ReportSmokeColor then - if not self.Reported[CarrierGroup]["Smoking"] then - self:Smoke( self.ReportSmokeColor ) - self.Reported[CarrierGroup]["Smoking"] = true - end - end - end - end - - --- Report to a Carrier Group with a Flaring signal. - -- @param #CARGO self - -- @param Utilities.Utils#UTILS.FlareColor FlareColor the color of the flare. - -- @return #CARGO - function CARGO:ReportFlare( FlareColor ) - - self.ReportFlareColor = FlareColor - end - - --- Report to a Carrier Group with a Smoking signal. - -- @param #CARGO self - -- @param Utilities.Utils#UTILS.SmokeColor SmokeColor the color of the smoke. - -- @return #CARGO - function CARGO:ReportSmoke( SmokeColor ) - - self.ReportSmokeColor = SmokeColor - end - - --- Reset the reporting for a Carrier Group. - -- @param #CARGO self - -- @param #string Action The string describing the action for the cargo. - -- @param Wrapper.Group#GROUP CarrierGroup The Carrier Group to send the report to. - -- @return #CARGO - function CARGO:ReportReset( Action, CarrierGroup ) - - self.Reported[CarrierGroup][Action] = nil - end - - --- Reset all the reporting for a Carrier Group. - -- @param #CARGO self - -- @param Wrapper.Group#GROUP CarrierGroup The Carrier Group to send the report to. - -- @return #CARGO - function CARGO:ReportResetAll( CarrierGroup ) - - self.Reported[CarrierGroup] = nil - end - - --- Respawn the cargo when destroyed - -- @param #CARGO self - -- @param #boolean RespawnDestroyed - function CARGO:RespawnOnDestroyed( RespawnDestroyed ) - - if RespawnDestroyed then - self.onenterDestroyed = function( self ) - self:Respawn() - end - else - self.onenterDestroyed = nil - end - - end - -end -- CARGO - -do -- CARGO_REPRESENTABLE - - -- @type CARGO_REPRESENTABLE - -- @extends #CARGO - -- @field test - - --- Models CARGO that is representable by a Unit. - -- @field #CARGO_REPRESENTABLE CARGO_REPRESENTABLE - CARGO_REPRESENTABLE = { - ClassName = "CARGO_REPRESENTABLE" - } - - --- CARGO_REPRESENTABLE Constructor. - -- @param #CARGO_REPRESENTABLE self - -- @param Wrapper.Positionable#POSITIONABLE CargoObject The cargo object. - -- @param #string Type Type name - -- @param #string Name Name. - -- @param #number LoadRadius (optional) Radius in meters. - -- @param #number NearRadius (optional) Radius in meters when the cargo is loaded into the carrier. - -- @return #CARGO_REPRESENTABLE - function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius ) - - -- Inherit CARGO. - local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE - self:T( { Type, Name, LoadRadius, NearRadius } ) - - -- Descriptors. - local Desc=CargoObject:GetDesc() - self:T({Desc=Desc}) - - -- Weight. - local Weight = math.random( 80, 120 ) - - -- Adjust weight.. - if Desc then - if Desc.typeName == "2B11 mortar" then - Weight = 210 - else - Weight = Desc.massEmpty - end - end - - -- Set weight. - self:SetWeight( Weight ) - - return self - end - - --- CARGO_REPRESENTABLE Destructor. - -- @param #CARGO_REPRESENTABLE self - -- @return #CARGO_REPRESENTABLE - function CARGO_REPRESENTABLE:Destroy() - - -- Cargo objects are deleted from the _DATABASE and SET_CARGO objects. - self:T( { CargoName = self:GetName() } ) - --_EVENTDISPATCHER:CreateEventDeleteCargo( self ) - - return self - end - - --- Route a cargo unit to a PointVec2. - -- @param #CARGO_REPRESENTABLE self - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number Speed - -- @return #CARGO_REPRESENTABLE - function CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) - self:F2( ToPointVec2 ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:WaypointGround( Speed ) - Points[#Points+1] = ToPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - return self - end - - --- Send a message to a @{Wrapper.Group} through a communication channel near the cargo. - -- @param #CARGO_REPRESENTABLE self - -- @param #string Message - -- @param Wrapper.Group#GROUP TaskGroup - -- @param #string Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown. - function CARGO_REPRESENTABLE:MessageToGroup( Message, TaskGroup, Name ) - - local CoordinateZone = ZONE_RADIUS:New( "Zone" , self:GetCoordinate():GetVec2(), 500 ) - CoordinateZone:Scan( { Object.Category.UNIT } ) - for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do - local NearUnit = UNIT:Find( DCSUnit ) - self:T({NearUnit=NearUnit}) - local NearUnitCoalition = NearUnit:GetCoalition() - local CargoCoalition = self:GetCoalition() - if NearUnitCoalition == CargoCoalition then - local Attributes = NearUnit:GetDesc() - self:T({Desc=Attributes}) - if NearUnit:HasAttribute( "Trucks" ) then - MESSAGE:New( Message, 20, NearUnit:GetCallsign() .. " reporting - Cargo " .. self:GetName() ):ToGroup( TaskGroup ) - break - end - end - end - - end - -end -- CARGO_REPRESENTABLE - -do -- CARGO_REPORTABLE - - -- @type CARGO_REPORTABLE - -- @extends #CARGO - CARGO_REPORTABLE = { - ClassName = "CARGO_REPORTABLE" - } - - --- CARGO_REPORTABLE Constructor. - -- @param #CARGO_REPORTABLE self - -- @param #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_REPORTABLE - function CARGO_REPORTABLE:New( Type, Name, Weight, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO:New( Type, Name, Weight, LoadRadius, NearRadius ) ) -- #CARGO_REPORTABLE - self:T( { Type, Name, Weight, LoadRadius, NearRadius } ) - - return self - end - - --- Send a CC message to a @{Wrapper.Group}. - -- @param #CARGO_REPORTABLE self - -- @param #string Message - -- @param Wrapper.Group#GROUP TaskGroup - -- @param #string Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown. - function CARGO_REPORTABLE:MessageToGroup( Message, TaskGroup, Name ) - - MESSAGE:New( Message, 20, "Cargo " .. self:GetName() .. " reporting" ):ToGroup( TaskGroup ) - - end - -end - -do -- CARGO_PACKAGE - - -- @type CARGO_PACKAGE - -- @extends #CARGO_REPRESENTABLE - CARGO_PACKAGE = { - ClassName = "CARGO_PACKAGE" - } - ---- CARGO_PACKAGE Constructor. --- @param #CARGO_PACKAGE self --- @param Wrapper.Unit#UNIT CargoCarrier The UNIT carrying the package. --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number LoadRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_PACKAGE -function CARGO_PACKAGE:New( CargoCarrier, Type, Name, Weight, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoCarrier, Type, Name, Weight, LoadRadius, NearRadius ) ) -- #CARGO_PACKAGE - self:T( { Type, Name, Weight, LoadRadius, NearRadius } ) - - self:T( CargoCarrier ) - self.CargoCarrier = CargoCarrier - - return self -end - ---- Board Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number Angle -function CARGO_PACKAGE:onafterOnBoard( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:T() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. - if not self.CargoInAir then - - local Points = {} - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:Boarded( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO_PACKAGE self --- @param Wrapper.Unit#UNIT CargoCarrier --- @return #boolean -function CARGO_PACKAGE:IsNear( CargoCarrier ) - self:T() - - local CargoCarrierPoint = CargoCarrier:GetCoordinate() - - local Distance = CargoCarrierPoint:Get2DDistance( self.CargoCarrier:GetCoordinate() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - ---- Boarded Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number LoadDistance --- @param #number Angle -function CARGO_PACKAGE:onafterOnBoarded( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:T() - - if self:IsNear( CargoCarrier ) then - self:__Load( 1, CargoCarrier, Speed, LoadDistance, Angle ) - else - self:__Boarded( 1, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - end -end - ---- UnBoard Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number UnLoadDistance --- @param #number UnBoardDistance --- @param #number Radius --- @param #number Angle -function CARGO_PACKAGE:onafterUnBoard( From, Event, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) - self:T() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) - - local Points = {} - - local StartPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = CargoCarrier:TaskRoute( Points ) - CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:__UnBoarded( 1 , CargoCarrier, Speed ) - -end - ---- UnBoarded Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed -function CARGO_PACKAGE:onafterUnBoarded( From, Event, To, CargoCarrier, Speed ) - self:T() - - if self:IsNear( CargoCarrier ) then - self:__UnLoad( 1, CargoCarrier, Speed ) - else - self:__UnBoarded( 1, CargoCarrier, Speed ) - end -end - ---- Load Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number LoadDistance --- @param #number Angle -function CARGO_PACKAGE:onafterLoad( From, Event, To, CargoCarrier, Speed, LoadDistance, Angle ) - self:T() - - self.CargoCarrier = CargoCarrier - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) - - local Points = {} - Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - ---- UnLoad Event. --- @param #CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number Distance --- @param #number Angle -function CARGO_PACKAGE:onafterUnLoad( From, Event, To, CargoCarrier, Speed, Distance, Angle ) - self:T() - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - self.CargoCarrier = CargoCarrier - - local Points = {} - Points[#Points+1] = StartPointVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - -end diff --git a/Moose Development/Moose/Cargo/CargoCrate.lua b/Moose Development/Moose/Cargo/CargoCrate.lua deleted file mode 100644 index 430032d0b..000000000 --- a/Moose Development/Moose/Cargo/CargoCrate.lua +++ /dev/null @@ -1,337 +0,0 @@ ---- **Cargo** - Management of single cargo crates, which are based on a STATIC object. --- --- === --- --- ### [Demo Missions]() --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.CargoCrate --- @image Cargo_Crates.JPG - -do -- CARGO_CRATE - - --- Models the behaviour of cargo crates, which can be slingloaded and boarded on helicopters. - -- @type CARGO_CRATE - -- @extends Cargo.Cargo#CARGO_REPRESENTABLE - - --- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. - -- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_CRATE objects to and from carriers. - -- - -- The above cargo classes are used by the following AI_CARGO_ classes to allow AI groups to transport cargo: - -- - -- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC} module. - -- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter} module. - -- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane} module. - -- * AI Ships is planned. - -- - -- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking: - -- - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players. - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players. - -- - -- # 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 #CARGO_CRATE - CARGO_CRATE = { - ClassName = "CARGO_CRATE" - } - - --- CARGO_CRATE Constructor. - -- @param #CARGO_CRATE self - -- @param Wrapper.Static#STATIC CargoStatic - -- @param #string Type - -- @param #string Name - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_CRATE - function CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_CRATE - self:T( { Type, Name, NearRadius } ) - - self.CargoObject = CargoStatic -- Wrapper.Static#STATIC - - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - - self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead ) - - self:SetEventPriority( 4 ) - - self.NearRadius = NearRadius or 25 - - return self - end - - -- @param #CARGO_CRATE self - -- @param Core.Event#EVENTDATA EventData - function CARGO_CRATE:OnEventCargoDead( EventData ) - - local Destroyed = false - - if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() then - if self.CargoObject:GetName() == EventData.IniUnitName then - if not self.NoDestroy then - Destroyed = true - end - end - else - if self:IsLoaded() then - local CarrierName = self.CargoCarrier:GetName() - if CarrierName == EventData.IniDCSUnitName then - MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll() - Destroyed = true - self.CargoCarrier:ClearCargo() - end - end - end - - if Destroyed then - self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } ) - self:Destroyed() - end - - end - - - --- Enter UnLoaded State. - -- @param #CARGO_CRATE self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 - function CARGO_CRATE:onenterUnLoaded( From, Event, To, ToPointVec2 ) - --self:T( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 10 - - if From == "Loaded" then - local StartCoordinate = self.CargoCarrier:GetCoordinate() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployCoord = StartCoordinate:Translate( Distance, CargoDeployHeading ) - - ToPointVec2 = ToPointVec2 or COORDINATE:NewFromVec2( { x= CargoDeployCoord.x, y = CargoDeployCoord.z } ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawnAt( ToPointVec2, 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - - end - - - --- Loaded State. - -- @param #CARGO_CRATE self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - function CARGO_CRATE:onenterLoaded( From, Event, To, CargoCarrier ) - --self:T( { From, Event, To, CargoCarrier } ) - - self.CargoCarrier = CargoCarrier - - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self:T("Destroying") - self.NoDestroy = true - self.CargoObject:Destroy( false ) -- Do not generate a remove unit event, because we want to keep the template for later respawn in the database. - --local Coordinate = self.CargoObject:GetCoordinate():GetRandomCoordinateInRadius( 50, 20 ) - --self.CargoObject:ReSpawnAt( Coordinate, 0 ) - end - end - - --- Check if the cargo can be Boarded. - -- @param #CARGO_CRATE self - function CARGO_CRATE:CanBoard() - return false - end - - --- Check if the cargo can be Unboarded. - -- @param #CARGO_CRATE self - function CARGO_CRATE:CanUnboard() - return false - end - - --- Check if the cargo can be sling loaded. - -- @param #CARGO_CRATE self - function CARGO_CRATE:CanSlingload() - return false - end - - --- Check if Cargo Crate is in the radius for the Cargo to be reported. - -- @param #CARGO_CRATE self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo Crate is within the report radius. - function CARGO_CRATE:IsInReportRadius( Coordinate ) - --self:T( { Coordinate, LoadRadius = self.LoadRadius } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - --self:T( Distance ) - if Distance <= self.LoadRadius then - return true - end - end - - return false - end - - - --- Check if Cargo Crate is in the radius for the Cargo to be Boarded or Loaded. - -- @param #CARGO_CRATE self - -- @param Core.Point#Coordinate Coordinate - -- @return #boolean true if the Cargo Crate is within the loading radius. - function CARGO_CRATE:IsInLoadRadius( Coordinate ) - --self:T( { Coordinate, LoadRadius = self.NearRadius } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - --self:T( Distance ) - if Distance <= self.NearRadius then - return true - end - end - - return false - end - - - - --- Get the current Coordinate of the CargoGroup. - -- @param #CARGO_CRATE self - -- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup. - -- @return #nil There is no valid Cargo in the CargoGroup. - function CARGO_CRATE:GetCoordinate() - --self:T() - - return self.CargoObject:GetCoordinate() - end - - --- Check if the CargoGroup is alive. - -- @param #CARGO_CRATE self - -- @return #boolean true if the CargoGroup is alive. - -- @return #boolean false if the CargoGroup is dead. - function CARGO_CRATE:IsAlive() - - local Alive = true - - -- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive. - -- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive. - if self:IsLoaded() then - Alive = Alive == true and self.CargoCarrier:IsAlive() - else - Alive = Alive == true and self.CargoObject:IsAlive() - end - - return Alive - - end - - - --- Route Cargo to Coordinate and randomize locations. - -- @param #CARGO_CRATE self - -- @param Core.Point#COORDINATE Coordinate - function CARGO_CRATE:RouteTo( Coordinate ) - self:T( {Coordinate = Coordinate } ) - - end - - - --- Check if Cargo is near to the Carrier. - -- The Cargo is near to the Carrier within NearRadius. - -- @param #CARGO_CRATE self - -- @param Wrapper.Group#GROUP CargoCarrier - -- @param #number NearRadius - -- @return #boolean The Cargo is near to the Carrier. - -- @return #nil The Cargo is not near to the Carrier. - function CARGO_CRATE:IsNear( CargoCarrier, NearRadius ) - self:T( {NearRadius = NearRadius } ) - - return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) - end - - --- Respawn the CargoGroup. - -- @param #CARGO_CRATE self - function CARGO_CRATE:Respawn() - - self:T( { "Respawning crate " .. self:GetName() } ) - - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event. - self:__Reset( -0.1 ) - end - - - end - - - --- Respawn the CargoGroup. - -- @param #CARGO_CRATE self - function CARGO_CRATE:onafterReset() - - self:T( { "Reset crate " .. self:GetName() } ) - - - -- Respawn the group... - if self.CargoObject then - self:SetDeployed( false ) - self:SetStartState( "UnLoaded" ) - self.CargoCarrier = nil - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - end - - - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO_CRATE self - -- @return #string The transportation method of the Cargo. - function CARGO_CRATE:GetTransportationMethod() - if self:IsLoaded() then - return "for unloading" - else - if self:IsUnLoaded() then - return "for loading" - else - if self:IsDeployed() then - return "delivered" - end - end - end - return "" - end - -end - diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua deleted file mode 100644 index 0af776c05..000000000 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ /dev/null @@ -1,773 +0,0 @@ ---- **Cargo** - Management of grouped cargo logistics, which are based on a GROUP object. --- --- === --- --- ### [Demo Missions]() --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.CargoGroup --- @image Cargo_Groups.JPG - - -do -- CARGO_GROUP - - --- @type CARGO_GROUP - -- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects. - -- @field #string GroupName The name of the CargoGroup. - -- @extends Cargo.Cargo#CARGO_REPORTABLE - - --- Defines a cargo that is represented by a @{Wrapper.Group} object within the simulator. - -- The cargo can be Loaded, UnLoaded, Boarded, UnBoarded to and from Carriers. - -- - -- The above cargo classes are used by the following AI_CARGO_ classes to allow AI groups to transport cargo: - -- - -- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC} module. - -- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter} module. - -- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Airplane} module. - -- * AI Ships is planned. - -- - -- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking: - -- - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players. - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players. - -- - -- # 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 #CARGO_GROUP CARGO_GROUP - -- - CARGO_GROUP = { - ClassName = "CARGO_GROUP", - } - - --- CARGO_GROUP constructor. - -- This make a new CARGO_GROUP from a @{Wrapper.Group} object. - -- It will "ungroup" the group object within the sim, and will create a @{Core.Set} of individual Unit objects. - -- @param #CARGO_GROUP self - -- @param Wrapper.Group#GROUP CargoGroup Group to be transported as cargo. - -- @param #string Type Cargo type, e.g. "Infantry". This is the type used in SET_CARGO:New():FilterTypes("Infantry") to define the valid cargo groups of the set. - -- @param #string Name A user defined name of the cargo group. This name CAN be the same as the group object but can also have a different name. This name MUST be unique! - -- @param #number LoadRadius (optional) Distance in meters until which a cargo is loaded into the carrier. Cargo outside this radius has to be routed by other means to within the radius to be loaded. - -- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene. - -- @return #CARGO_GROUP Cargo group object. - function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) - - -- Inherit CAROG_REPORTABLE - local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP - self:T( { Type, Name, LoadRadius } ) - - self.CargoSet = SET_CARGO:New() - self.CargoGroup = CargoGroup - self.Grouped = true - self.CargoUnitTemplate = {} - - self.NearRadius = NearRadius - - self:SetDeployed( false ) - - local WeightGroup = 0 - local VolumeGroup = 0 - - self.CargoGroup:Destroy() -- destroy and generate a unit removal event, so that the database gets cleaned, and the linked sets get properly cleaned. - - local GroupName = CargoGroup:GetName() - self.CargoName = Name - self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) ) - - -- Deactivate late activation. - self.CargoTemplate.lateActivation=false - - self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) - self.GroupTemplate.name = self.CargoName .. "#CARGO" - self.GroupTemplate.groupId = nil - - self.GroupTemplate.units = {} - - for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do - UnitTemplate.name = UnitTemplate.name .. "#CARGO" - local CargoUnitName = UnitTemplate.name - self.CargoUnitTemplate[CargoUnitName] = UnitTemplate - - self.GroupTemplate.units[#self.GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName] - self.GroupTemplate.units[#self.GroupTemplate.units].unitId = nil - - -- And we register the spawned unit as part of the CargoSet. - local Unit = UNIT:Register( CargoUnitName ) - - end - - -- Then we register the new group in the database - self.CargoGroup = GROUP:NewTemplate( self.GroupTemplate, self.GroupTemplate.CoalitionID, self.GroupTemplate.CategoryID, self.GroupTemplate.CountryID ) - - -- Now we spawn the new group based on the template created. - self.CargoObject = _DATABASE:Spawn( self.GroupTemplate ) - - for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do - - - local CargoUnitName = CargoUnit:GetName() - - local Cargo = CARGO_UNIT:New( CargoUnit, Type, CargoUnitName, LoadRadius, NearRadius ) - self.CargoSet:Add( CargoUnitName, Cargo ) - - WeightGroup = WeightGroup + Cargo:GetWeight() - - end - - self:SetWeight( WeightGroup ) - - self:T( { "Weight Cargo", WeightGroup } ) - - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - - self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead ) - - self:SetEventPriority( 4 ) - - return self - end - - - --- Respawn the CargoGroup. - -- @param #CARGO_GROUP self - function CARGO_GROUP:Respawn() - - self:T( { "Respawning" } ) - - for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do - local Cargo = CargoData -- Cargo.Cargo#CARGO - Cargo:Destroy() -- Destroy the cargo and generate a remove unit event to update the sets. - Cargo:SetStartState( "UnLoaded" ) - end - - -- Now we spawn the new group based on the template created. - _DATABASE:Spawn( self.GroupTemplate ) - - for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do - - local CargoUnitName = CargoUnit:GetName() - - local Cargo = CARGO_UNIT:New( CargoUnit, self.Type, CargoUnitName, self.LoadRadius ) - self.CargoSet:Add( CargoUnitName, Cargo ) - - end - - self:SetDeployed( false ) - self:SetStartState( "UnLoaded" ) - - end - - --- Ungroup the cargo group into individual groups with one unit. - -- This is required because by default a group will move in formation and this is really an issue for group control. - -- Therefore this method is made to be able to ungroup a group. - -- This works for ground only groups. - -- @param #CARGO_GROUP self - function CARGO_GROUP:Ungroup() - - if self.Grouped == true then - - self.Grouped = false - - self.CargoGroup:Destroy() - - for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do - local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT - - if CargoUnit:IsUnLoaded() then - local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) - --local GroupName = env.getValueDictByKey( GroupTemplate.name ) - - -- We create a new group object with one unit... - -- First we prepare the template... - GroupTemplate.name = self.CargoName .. "#CARGO#" .. CargoUnitName - GroupTemplate.groupId = nil - - if CargoUnit:IsUnLoaded() then - GroupTemplate.units = {} - GroupTemplate.units[1] = self.CargoUnitTemplate[CargoUnitName] - GroupTemplate.units[#GroupTemplate.units].unitId = nil - GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX() - GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY() - GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading() - end - - - -- Then we register the new group in the database - local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID) - - -- Now we spawn the new group based on the template created. - _DATABASE:Spawn( GroupTemplate ) - end - end - - self.CargoObject = nil - end - - - end - - --- Regroup the cargo group into one group with multiple unit. - -- This is required because by default a group will move in formation and this is really an issue for group control. - -- Therefore this method is made to be able to regroup a group. - -- This works for ground only groups. - -- @param #CARGO_GROUP self - function CARGO_GROUP:Regroup() - - self:T("Regroup") - - if self.Grouped == false then - - self.Grouped = true - - local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) - GroupTemplate.name = self.CargoName .. "#CARGO" - GroupTemplate.groupId = nil - GroupTemplate.units = {} - - for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do - local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT - - self:T( { CargoUnit:GetName(), UnLoaded = CargoUnit:IsUnLoaded() } ) - - if CargoUnit:IsUnLoaded() then - - CargoUnit.CargoObject:Destroy() - - GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName] - GroupTemplate.units[#GroupTemplate.units].unitId = nil - GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX() - GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY() - GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading() - end - end - - -- Then we register the new group in the database - self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID ) - - self:T( { "Regroup", GroupTemplate } ) - - -- Now we spawn the new group based on the template created. - self.CargoObject = _DATABASE:Spawn( GroupTemplate ) - end - - end - - - --- @param #CARGO_GROUP self - -- @param Core.Event#EVENTDATA EventData - function CARGO_GROUP:OnEventCargoDead( EventData ) - - self:T(EventData) - - local Destroyed = false - - if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() or self:IsUnboarding() then - Destroyed = true - for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do - local Cargo = CargoData -- Cargo.Cargo#CARGO - if Cargo:IsAlive() then - Destroyed = false - else - Cargo:Destroyed() - end - end - else - local CarrierName = self.CargoCarrier:GetName() - if CarrierName == EventData.IniDCSUnitName then - MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll() - Destroyed = true - self.CargoCarrier:ClearCargo() - end - end - - if Destroyed then - self:Destroyed() - self:T( { "Cargo group destroyed" } ) - end - - end - - --- After Board Event. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. - function CARGO_GROUP:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) - self:T( { CargoCarrier.UnitName, From, Event, To, NearRadius = NearRadius } ) - - NearRadius = NearRadius or self.NearRadius - - -- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo, ... ) - self:T( { "Board Unit", Cargo:GetName( ), Cargo:IsDestroyed(), Cargo.CargoObject:IsAlive() } ) - local CargoGroup = Cargo.CargoObject --Wrapper.Group#GROUP - CargoGroup:OptionAlarmStateGreen() - Cargo:__Board( 1, CargoCarrier, NearRadius, ... ) - end, ... - ) - - self:__Boarding( -1, CargoCarrier, NearRadius, ... ) - - end - - --- Enter Loaded State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - function CARGO_GROUP:onafterLoad( From, Event, To, CargoCarrier, ... ) - --self:T( { From, Event, To, CargoCarrier, ...} ) - - if From == "UnLoaded" then - -- For each Cargo object within the CARGO_GROUP, load each cargo to the CargoCarrier. - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - if not Cargo:IsDestroyed() then - Cargo:Load( CargoCarrier ) - end - end - end - - --self.CargoObject:Destroy() - self.CargoCarrier = CargoCarrier - self.CargoCarrier:AddCargo( self ) - - end - - --- Leave Boarding State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. - function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - --self:T( { CargoCarrier.UnitName, From, Event, To } ) - - local Boarded = true - local Cancelled = false - local Dead = true - - self.CargoSet:Flush() - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - --self:T( { Cargo:GetName(), Cargo.current } ) - - - if not Cargo:is( "Loaded" ) - and (not Cargo:is( "Destroyed" )) then -- If one or more units of a group defined as CARGO_GROUP died, the CARGO_GROUP:Board() command does not trigger the CARGO_GRUOP:OnEnterLoaded() function. - Boarded = false - end - - if Cargo:is( "UnLoaded" ) then - Cancelled = true - end - - if not Cargo:is( "Destroyed" ) then - Dead = false - end - - end - - if not Dead then - - if not Cancelled then - if not Boarded then - self:__Boarding( -5, CargoCarrier, NearRadius, ... ) - else - self:T("Group Cargo is loaded") - self:__Load( 1, CargoCarrier, ... ) - end - else - self:__CancelBoarding( 1, CargoCarrier, NearRadius, ... ) - end - else - self:__Destroyed( 1, CargoCarrier, NearRadius, ... ) - end - - end - - --- Enter UnBoarding State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. - function CARGO_GROUP:onafterUnBoard( From, Event, To, ToPointVec2, NearRadius, ... ) - self:T( {From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - local Timer = 1 - - if From == "Loaded" then - - if self.CargoObject then - self.CargoObject:Destroy() - end - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - --- @param Cargo.Cargo#CARGO Cargo - function( Cargo, NearRadius ) - if not Cargo:IsDestroyed() then - local ToVec=nil - if ToPointVec2==nil then - ToVec=self.CargoCarrier:GetPointVec2():GetRandomPointVec2InRadius(2*NearRadius, NearRadius) - else - ToVec=ToPointVec2 - end - Cargo:__UnBoard( Timer, ToVec, NearRadius ) - Timer = Timer + 1 - end - end, { NearRadius } - ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) - end - - end - - --- Leave UnBoarding State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. - function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) - --self:T( { From, Event, To, ToPointVec2, NearRadius } ) - - --local NearRadius = NearRadius or 25 - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - local UnBoarded = true - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - self:T( { Cargo:GetName(), Cargo.current } ) - if not Cargo:is( "UnLoaded" ) and not Cargo:IsDestroyed() then - UnBoarded = false - end - end - - if UnBoarded then - self:__UnLoad( 1, ToPointVec2, ... ) - else - self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) - end - - return false - end - - end - - --- Enter UnLoaded State. - -- @param #CARGO_GROUP self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... ) - --self:T( { From, Event, To, ToPointVec2 } ) - - if From == "Loaded" then - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - --Cargo:UnLoad( ToPointVec2 ) - local RandomVec2=nil - if ToPointVec2 then - RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10) - end - Cargo:UnBoard( RandomVec2 ) - end - ) - - end - - self.CargoCarrier:RemoveCargo( self ) - self.CargoCarrier = nil - - end - - - --- Get the current Coordinate of the CargoGroup. - -- @param #CARGO_GROUP self - -- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup. - -- @return #nil There is no valid Cargo in the CargoGroup. - function CARGO_GROUP:GetCoordinate() - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - return Cargo.CargoObject:GetCoordinate() - end - - return nil - end - - --- Get the x position of the cargo. - -- @param #CARGO_GROUP self - -- @return #number - function CARGO:GetX() - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - return Cargo:GetCoordinate().x - end - - return nil - end - - --- Get the y position of the cargo. - -- @param #CARGO_GROUP self - -- @return #number - function CARGO:GetY() - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - return Cargo:GetCoordinate().z - end - - return nil - end - - - - --- Check if the CargoGroup is alive. - -- @param #CARGO_GROUP self - -- @return #boolean true if the CargoGroup is alive. - -- @return #boolean false if the CargoGroup is dead. - function CARGO_GROUP:IsAlive() - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - return Cargo ~= nil - - end - - - --- Get the first alive Cargo Unit of the Cargo Group. - -- @param #CARGO_GROUP self - -- @return #CARGO_GROUP - function CARGO_GROUP:GetFirstAlive() - - local CargoFirstAlive = nil - - for _, Cargo in pairs( self.CargoSet:GetSet() ) do - if not Cargo:IsDestroyed() then - CargoFirstAlive = Cargo - break - end - end - return CargoFirstAlive - end - - - --- Get the amount of cargo units in the group. - -- @param #CARGO_GROUP self - -- @return #CARGO_GROUP - function CARGO_GROUP:GetCount() - return self.CargoSet:Count() - end - - - --- Get the underlying GROUP object from the CARGO_GROUP. - -- @param #CARGO_GROUP self - -- @return #CARGO_GROUP - function CARGO_GROUP:GetGroup( Cargo ) - local Cargo = Cargo or self:GetFirstAlive() -- Cargo.Cargo#CARGO - return Cargo.CargoObject:GetGroup() - end - - - --- Route Cargo to Coordinate and randomize locations. - -- @param #CARGO_GROUP self - -- @param Core.Point#COORDINATE Coordinate - function CARGO_GROUP:RouteTo( Coordinate ) - --self:T( {Coordinate = Coordinate } ) - - -- For each Cargo within the CargoSet, route each object to the Coordinate - self.CargoSet:ForEach( - function( Cargo ) - Cargo.CargoObject:RouteGroundTo( Coordinate, 10, "vee", 0 ) - end - ) - - end - - --- Check if Cargo is near to the Carrier. - -- The Cargo is near to the Carrier if the first unit of the Cargo Group is within NearRadius. - -- @param #CARGO_GROUP self - -- @param Wrapper.Group#GROUP CargoCarrier - -- @param #number NearRadius - -- @return #boolean The Cargo is near to the Carrier or #nil if the Cargo is not near to the Carrier. - function CARGO_GROUP:IsNear( CargoCarrier, NearRadius ) - self:T( {NearRadius = NearRadius } ) - - for _, Cargo in pairs( self.CargoSet:GetSet() ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - if Cargo:IsAlive() then - if Cargo:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) then - self:T( "Near" ) - return true - end - end - end - - return nil - end - - --- Check if Cargo Group is in the radius for the Cargo to be Boarded. - -- @param #CARGO_GROUP self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo Group is within the load radius. - function CARGO_GROUP:IsInLoadRadius( Coordinate ) - --self:T( { Coordinate } ) - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - local Distance = 0 - local CargoCoordinate - if Cargo:IsLoaded() then - CargoCoordinate = Cargo.CargoCarrier:GetCoordinate() - else - CargoCoordinate = Cargo.CargoObject:GetCoordinate() - end - - -- FF check if coordinate could be obtained. This was commented out for some (unknown) reason. But the check seems valid! - if CargoCoordinate then - Distance = Coordinate:Get2DDistance( CargoCoordinate ) - else - return false - end - - self:T( { Distance = Distance, LoadRadius = self.LoadRadius } ) - if Distance <= self.LoadRadius then - return true - else - return false - end - end - - return nil - - end - - - --- Check if Cargo Group is in the report radius. - -- @param #CARGO_GROUP self - -- @param Core.Point#Coordinate Coordinate - -- @return #boolean true if the Cargo Group is within the report radius. - function CARGO_GROUP:IsInReportRadius( Coordinate ) - --self:T( { Coordinate } ) - - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO - - if Cargo then - self:T( { Cargo } ) - local Distance = 0 - if Cargo:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() ) - --self:T( Distance ) - if Distance <= self.LoadRadius then - return true - end - end - end - - return nil - - end - - - --- Signal a flare at the position of the CargoGroup. - -- @param #CARGO_GROUP self - -- @param Utilities.Utils#FLARECOLOR FlareColor - function CARGO_GROUP:Flare( FlareColor ) - - local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO - if Cargo then - Cargo:Flare( FlareColor ) - end - end - - --- Smoke the CargoGroup. - -- @param #CARGO_GROUP self - -- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke. - -- @param #number Radius The radius of randomization around the center of the first element of the CargoGroup. - function CARGO_GROUP:Smoke( SmokeColor, Radius ) - - local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO - - if Cargo then - Cargo:Smoke( SmokeColor, Radius ) - end - end - - --- Check if the first element of the CargoGroup is the given @{Core.Zone}. - -- @param #CARGO_GROUP self - -- @param Core.Zone#ZONE_BASE Zone - -- @return #boolean **true** if the first element of the CargoGroup is in the Zone - -- @return #boolean **false** if there is no element of the CargoGroup in the Zone. - function CARGO_GROUP:IsInZone( Zone ) - --self:T( { Zone } ) - - local Cargo = self.CargoSet:GetFirst() -- Cargo.Cargo#CARGO - - if Cargo then - return Cargo:IsInZone( Zone ) - end - - return nil - - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO_GROUP self - -- @return #string The transportation method of the Cargo. - function CARGO_GROUP:GetTransportationMethod() - if self:IsLoaded() then - return "for unboarding" - else - if self:IsUnLoaded() then - return "for boarding" - else - if self:IsDeployed() then - return "delivered" - end - end - end - return "" - end - - - -end -- CARGO_GROUP diff --git a/Moose Development/Moose/Cargo/CargoSlingload.lua b/Moose Development/Moose/Cargo/CargoSlingload.lua deleted file mode 100644 index 81bc5d95e..000000000 --- a/Moose Development/Moose/Cargo/CargoSlingload.lua +++ /dev/null @@ -1,275 +0,0 @@ ---- **Cargo** - Management of single cargo crates, which are based on a STATIC object. The cargo can only be slingloaded. --- --- === --- --- ### [Demo Missions]() --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.CargoSlingload --- @image Cargo_Slingload.JPG - - -do -- CARGO_SLINGLOAD - - --- Models the behaviour of cargo crates, which can only be slingloaded. - -- @type CARGO_SLINGLOAD - -- @extends Cargo.Cargo#CARGO_REPRESENTABLE - - --- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. - -- - -- The above cargo classes are also used by the TASK_CARGO_ classes to allow human players to transport cargo as part of a tasking: - -- - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players. - -- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players. - -- - -- # 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 #CARGO_SLINGLOAD - CARGO_SLINGLOAD = { - ClassName = "CARGO_SLINGLOAD" - } - - --- CARGO_SLINGLOAD Constructor. - -- @param #CARGO_SLINGLOAD self - -- @param Wrapper.Static#STATIC CargoStatic - -- @param #string Type - -- @param #string Name - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_SLINGLOAD - function CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_SLINGLOAD - self:T( { Type, Name, NearRadius } ) - - self.CargoObject = CargoStatic - - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - - self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead ) - --self:HandleEvent( EVENTS.RemoveUnit, self.OnEventCargoDead ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead ) - - self:SetEventPriority( 4 ) - - self.NearRadius = NearRadius or 25 - - return self - end - - - -- @param #CARGO_SLINGLOAD self - -- @param Core.Event#EVENTDATA EventData - function CARGO_SLINGLOAD:OnEventCargoDead( EventData ) - - local Destroyed = false - - if self:IsDestroyed() or self:IsUnLoaded() then - if self.CargoObject:GetName() == EventData.IniUnitName then - if not self.NoDestroy then - Destroyed = true - end - end - end - - if Destroyed then - self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } ) - self:Destroyed() - end - - end - - - --- Check if the cargo can be Slingloaded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanSlingload() - return true - end - - --- Check if the cargo can be Boarded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanBoard() - return false - end - - --- Check if the cargo can be Unboarded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanUnboard() - return false - end - - --- Check if the cargo can be Loaded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanLoad() - return false - end - - --- Check if the cargo can be Unloaded. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:CanUnload() - return false - end - - - --- Check if Cargo Crate is in the radius for the Cargo to be reported. - -- @param #CARGO_SLINGLOAD self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo Crate is within the report radius. - function CARGO_SLINGLOAD:IsInReportRadius( Coordinate ) - --self:T( { Coordinate, LoadRadius = self.LoadRadius } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - if Distance <= self.LoadRadius then - return true - end - end - - return false - end - - - --- Check if Cargo Slingload is in the radius for the Cargo to be Boarded or Loaded. - -- @param #CARGO_SLINGLOAD self - -- @param Core.Point#COORDINATE Coordinate - -- @return #boolean true if the Cargo Slingload is within the loading radius. - function CARGO_SLINGLOAD:IsInLoadRadius( Coordinate ) - --self:T( { Coordinate } ) - - local Distance = 0 - if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) - if Distance <= self.NearRadius then - return true - end - end - - return false - end - - - - --- Get the current Coordinate of the CargoGroup. - -- @param #CARGO_SLINGLOAD self - -- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup. - -- @return #nil There is no valid Cargo in the CargoGroup. - function CARGO_SLINGLOAD:GetCoordinate() - --self:T() - - return self.CargoObject:GetCoordinate() - end - - --- Check if the CargoGroup is alive. - -- @param #CARGO_SLINGLOAD self - -- @return #boolean true if the CargoGroup is alive. - -- @return #boolean false if the CargoGroup is dead. - function CARGO_SLINGLOAD:IsAlive() - - local Alive = true - - -- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive. - -- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive. - if self:IsLoaded() then - Alive = Alive == true and self.CargoCarrier:IsAlive() - else - Alive = Alive == true and self.CargoObject:IsAlive() - end - - return Alive - - end - - - --- Route Cargo to Coordinate and randomize locations. - -- @param #CARGO_SLINGLOAD self - -- @param Core.Point#COORDINATE Coordinate - function CARGO_SLINGLOAD:RouteTo( Coordinate ) - --self:T( {Coordinate = Coordinate } ) - - end - - - --- Check if Cargo is near to the Carrier. - -- The Cargo is near to the Carrier within NearRadius. - -- @param #CARGO_SLINGLOAD self - -- @param Wrapper.Group#GROUP CargoCarrier - -- @param #number NearRadius - -- @return #boolean The Cargo is near to the Carrier. - -- @return #nil The Cargo is not near to the Carrier. - function CARGO_SLINGLOAD:IsNear( CargoCarrier, NearRadius ) - --self:T( {NearRadius = NearRadius } ) - - return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) - end - - - --- Respawn the CargoGroup. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:Respawn() - - --self:T( { "Respawning slingload " .. self:GetName() } ) - - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event. - self:__Reset( -0.1 ) - end - - - end - - - --- Respawn the CargoGroup. - -- @param #CARGO_SLINGLOAD self - function CARGO_SLINGLOAD:onafterReset() - - --self:T( { "Reset slingload " .. self:GetName() } ) - - - -- Respawn the group... - if self.CargoObject then - self:SetDeployed( false ) - self:SetStartState( "UnLoaded" ) - self.CargoCarrier = nil - -- Cargo objects are added to the _DATABASE and SET_CARGO objects. - _EVENTDISPATCHER:CreateEventNewCargo( self ) - end - - - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO_SLINGLOAD self - -- @return #string The transportation method of the Cargo. - function CARGO_SLINGLOAD:GetTransportationMethod() - if self:IsLoaded() then - return "for sling loading" - else - if self:IsUnLoaded() then - return "for sling loading" - else - if self:IsDeployed() then - return "delivered" - end - end - end - return "" - end - -end diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua deleted file mode 100644 index a1d86dd49..000000000 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ /dev/null @@ -1,395 +0,0 @@ ---- **Cargo** - Management of single cargo logistics, which are based on a UNIT object. --- --- === --- --- ### [Demo Missions]() --- --- ### [YouTube Playlist]() --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- --- === --- --- @module Cargo.CargoUnit --- @image Cargo_Units.JPG - -do -- CARGO_UNIT - - --- Models CARGO in the form of units, which can be boarded, unboarded, loaded, unloaded. - -- @type CARGO_UNIT - -- @extends Cargo.Cargo#CARGO_REPRESENTABLE - - --- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. - -- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO_UNIT objects to and from carriers. - -- Note that ground forces behave in a group, and thus, act in formation, regardless if one unit is commanded to move. - -- - -- This class is used in CARGO_GROUP, and is not meant to be used by mission designers individually. - -- - -- # 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 #CARGO_UNIT CARGO_UNIT - -- - CARGO_UNIT = { - ClassName = "CARGO_UNIT" - } - - --- CARGO_UNIT Constructor. - -- @param #CARGO_UNIT self - -- @param Wrapper.Unit#UNIT CargoUnit - -- @param #string Type - -- @param #string Name - -- @param #number Weight - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) - -- @return #CARGO_UNIT - function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) - - -- Inherit CARGO_REPRESENTABLE. - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT - - -- Debug info. - self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius}) - - -- Set cargo object. - self.CargoObject = CargoUnit - - -- Set event prio. - self:SetEventPriority( 5 ) - - return self - end - - --- Enter UnBoarding State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius (optional) Defaut 25 m. - function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:T( { From, Event, To, ToPointVec2, NearRadius } ) - - local Angle = 180 - local Speed = 60 - local DeployDistance = 9 - local RouteDistance = 60 - - if From == "Loaded" then - - if not self:IsDestroyed() then - - local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE - - if CargoCarrier:IsAlive() then - - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - - - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - - -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 - local FromDirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3( ToPointVec2 or CargoRoutePointVec2 ) - local FromAngle = CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3) - local FromPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, FromAngle ) - --local CargoDeployPointVec2 = CargoCarrierPointVec2:GetRandomCoordinateInRadius( 10, 5 ) - - ToPointVec2 = ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius( NearRadius, DeployDistance ) - - -- Respawn the group... - if self.CargoObject then - if CargoCarrier:IsShip() then - -- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead. - self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading ) - else - self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading ) - end - self:T( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) - self.CargoCarrier = nil - - local Points = {} - - -- From - Points[#Points+1] = FromPointVec2:WaypointGround( Speed, "Vee" ) - - -- To - Points[#Points+1] = ToPointVec2:WaypointGround( Speed, "Vee" ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius ) - end - else - -- the Carrier is dead. This cargo is dead too! - self:Destroyed() - end - end - end - - end - - --- Leave UnBoarding State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius (optional) Defaut 100 m. - function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:T( { From, Event, To, ToPointVec2, NearRadius } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - --if self:IsNear( ToPointVec2, NearRadius ) then - return true - --else - - --self:__UnBoarding( 1, ToPointVec2, NearRadius ) - --end - --return false - end - - end - - --- UnBoard Event. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 - -- @param #number NearRadius (optional) Defaut 100 m. - function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) - self:T( { From, Event, To, ToPointVec2, NearRadius } ) - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - end - - self:__UnLoad( 1, ToPointVec2, NearRadius ) - - end - - - - --- Enter UnLoaded State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Core.Point#POINT_VEC2 - function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 ) - self:T( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "Loaded" then - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployCoord = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - ToPointVec2 = ToPointVec2 or COORDINATE:New( CargoDeployCoord.x, CargoDeployCoord.z ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawnAt( ToPointVec2, 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - - end - - --- Board Event. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Group#GROUP CargoCarrier - -- @param #number NearRadius - function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) - self:T( { From, Event, To, CargoCarrier, NearRadius = NearRadius } ) - - self.CargoInAir = self.CargoObject:InAir() - - local Desc = self.CargoObject:GetDesc() - local MaxSpeed = Desc.speedMaxOffRoad - local TypeName = Desc.typeName - - --self:T({Unit=self.CargoObject:GetName()}) - - -- A cargo unit can only be boarded if it is not dead - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - -- If NearRadius is given, then use the given NearRadius, otherwise calculate the NearRadius - -- based upon the Carrier bounding radius, which is calculated from the bounding rectangle on the Y axis. - local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius() + 5 - if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then - self:Load( CargoCarrier, NearRadius, ... ) - else - if MaxSpeed and MaxSpeed == 0 or TypeName and TypeName == "Stinger comm" then - self:Load( CargoCarrier, NearRadius, ... ) - else - - local Speed = 90 - local Angle = 180 - local Distance = 0 - - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - -- Set the CargoObject to state Green to ensure it is boarding! - self.CargoObject:OptionAlarmStateGreen() - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:WaypointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - self:__Boarding( -5, CargoCarrier, NearRadius, ... ) - self.RunCount = 0 - end - end - end - end - - - --- Boarding Event. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Client#CLIENT CargoCarrier - -- @param #number NearRadius Default 25 m. - function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:T( { From, Event, To, CargoCarrier:GetName(), NearRadius = NearRadius } ) - - self:T( { IsAlive=self.CargoObject:IsAlive() } ) - - if CargoCarrier and CargoCarrier:IsAlive() then -- and self.CargoObject and self.CargoObject:IsAlive() then - if (CargoCarrier:IsAir() and not CargoCarrier:InAir()) or true then - local NearRadius = NearRadius or CargoCarrier:GetBoundingRadius( NearRadius ) + 5 - if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then - self:__Load( -1, CargoCarrier, ... ) - else - if self:IsNear( CargoCarrier:GetPointVec2(), 20 ) then - self:__Boarding( -1, CargoCarrier, NearRadius, ... ) - self.RunCount = self.RunCount + 1 - else - self:__Boarding( -2, CargoCarrier, NearRadius, ... ) - self.RunCount = self.RunCount + 2 - end - if self.RunCount >= 40 then - self.RunCount = 0 - local Speed = 90 - local Angle = 180 - local Distance = 0 - - --self:T({Unit=self.CargoObject:GetName()}) - - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - -- Set the CargoObject to state Green to ensure it is boarding! - self.CargoObject:OptionAlarmStateGreen() - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:WaypointGround( Speed, "Off road" ) - Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed, "Off road" ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 0.2 ) - end - end - else - self.CargoObject:MessageToGroup( "Cancelling Boarding... Get back on the ground!", 5, CargoCarrier:GetGroup(), self:GetName() ) - self:CancelBoarding( CargoCarrier, NearRadius, ... ) - self.CargoObject:SetCommand( self.CargoObject:CommandStopRoute( true ) ) - end - else - self:T("Something is wrong") - end - - end - - - --- Loaded State. - -- @param #CARGO_UNIT self - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Wrapper.Unit#UNIT CargoCarrier - function CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier ) - self:T( { From, Event, To, CargoCarrier } ) - - self.CargoCarrier = CargoCarrier - - --self:T({Unit=self.CargoObject:GetName()}) - - -- Only destroy the CargoObject if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self.CargoObject:Destroy( false ) - --self.CargoObject:ReSpawnAt( COORDINATE:NewFromVec2( {x=0,y=0} ), 0 ) - end - end - - --- Get the transportation method of the Cargo. - -- @param #CARGO_UNIT self - -- @return #string The transportation method of the Cargo. - function CARGO_UNIT:GetTransportationMethod() - if self:IsLoaded() then - return "for unboarding" - else - if self:IsUnLoaded() then - return "for boarding" - else - if self:IsDeployed() then - return "delivered" - end - end - end - return "" - end - -end -- CARGO_UNIT diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 969dde2e6..91cfd2830 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -2,7 +2,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Enums.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Utils.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Profiler.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Templates.lua' ) ---__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/STTS.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/FiFo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' ) @@ -119,43 +118,6 @@ __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/AI/AI_Balancer.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air_Patrol.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air_Engage.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Patrol.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Cap.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Gci.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2A_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_BAI.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_CAS.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_SEAD.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_A2G_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Patrol.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_CAP.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_CAS.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_BAI.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Formation.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Request.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Escort_Dispatcher_Request.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_APC.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Helicopter.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Airplane.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Ship.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_APC.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' ) - -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assign.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Route.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Account.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assist.lua' ) - __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/ShapeBase.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Cube.lua' ) @@ -171,21 +133,4 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/RadioQueue.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/RadioSpeech.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/SRS.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/CommandCenter.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Mission.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/TaskInfo.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Manager.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/DetectionManager.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2G_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2G.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2A_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_A2A.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_CARGO.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Transport.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_CSAR.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Cargo_Dispatcher.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Zone.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Tasking/Task_Capture_Dispatcher.lua' ) - __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Globals.lua' ) diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index 5a6b6c0e9..b6e3d1b74 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -1,7 +1,6 @@ __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' ) @@ -116,43 +115,6 @@ __Moose.Include( 'Ops\\FlightControl.lua' ) __Moose.Include( 'Ops\\PlayerRecce.lua' ) __Moose.Include( 'Ops\\EasyGCICAP.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' ) @@ -160,21 +122,6 @@ __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( 'Globals.lua' ) diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua deleted file mode 100644 index 59ab36a3c..000000000 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ /dev/null @@ -1,821 +0,0 @@ ---- **Tasking** - A command center governs multiple missions, and takes care of the reporting and communications. --- --- **Features:** --- --- * Govern multiple missions. --- * Communicate to coalitions, groups. --- * Assign tasks. --- * Manage the menus. --- * Manage reference zones. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.CommandCenter --- @image Task_Command_Center.JPG - - ---- The COMMANDCENTER class --- @type COMMANDCENTER --- @field Wrapper.Group#GROUP HQ --- @field DCS#coalition CommandCenterCoalition --- @list Missions --- @extends Core.Base#BASE - - ---- Governs multiple missions, the tasking and the reporting. --- --- Command centers govern missions, communicates the task assignments between human players of the coalition, and manages the menu flow. --- It can assign a random task to a player when requested. --- The commandcenter provides the facilitites to communicate between human players online, executing a task. --- --- ## 1. Create a command center object. --- --- * @{#COMMANDCENTER.New}(): Creates a new COMMANDCENTER object. --- --- ## 2. Command center mission management. --- --- Command centers manage missions. These can be added, removed and provides means to retrieve missions. --- These methods are heavily used by the task dispatcher classes. --- --- * @{#COMMANDCENTER.AddMission}(): Adds a mission to the commandcenter control. --- * @{#COMMANDCENTER.RemoveMission}(): Removes a mission to the commandcenter control. --- * @{#COMMANDCENTER.GetMissions}(): Retrieves the missions table controlled by the commandcenter. --- --- ## 3. Communication management between players. --- --- Command center provide means of communication between players. --- Because a command center is a central object governing multiple missions, --- there are several levels at which communication needs to be done. --- Within MOOSE, communication is facilitated using the message system within the DCS simulator. --- --- Messages can be sent between players at various levels: --- --- - On a global level, to all players. --- - On a coalition level, only to the players belonging to the same coalition. --- - On a group level, to the players belonging to the same group. --- --- Messages can be sent to **all players** by the command center using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToAll}(). --- --- To send messages to **the coalition of the command center**, there are two methods available: --- --- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToCoalition}() to send a specific message to the coalition, with a given message display duration. --- - You can send a specific type of message using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageTypeToCoalition}(). --- This will send a message of a specific type to the coalition, and as a result its display duration will be flexible according the message display time selection by the human player. --- --- To send messages **to the group** of human players, there are also two methods available: --- --- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.MessageToGroup}() to send a specific message to a group, with a given message display duration. --- - You can send a specific type of message using the method @{Tasking.CommandCenter#COMMANDCENTER.MessageTypeToGroup}(). --- This will send a message of a specific type to the group, and as a result its display duration will be flexible according the message display time selection by the human player . --- --- Messages are considered to be sometimes disturbing for human players, therefore, the settings menu provides the means to activate or deactivate messages. --- For more information on the message types and display timings that can be selected and configured using the menu, refer to the @{Core.Settings} menu description. --- --- ## 4. Command center detailed methods. --- --- Various methods are added to manage command centers. --- --- ### 4.1. Naming and description. --- --- There are 3 methods that can be used to retrieve the description of a command center: --- --- - Use the method @{Tasking.CommandCenter#COMMANDCENTER.GetName}() to retrieve the name of the command center. --- This is the name given as part of the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. --- The returned name using this method, is not to be used for message communication. --- --- A textual description can be retrieved that provides the command center name to be used within message communication: --- --- - @{Tasking.CommandCenter#COMMANDCENTER.GetShortText}() returns the command center name as `CC [CommandCenterName]`. --- - @{Tasking.CommandCenter#COMMANDCENTER.GetText}() returns the command center name as `Command Center [CommandCenterName]`. --- --- ### 4.2. The coalition of the command center. --- --- The method @{Tasking.CommandCenter#COMMANDCENTER.GetCoalition}() returns the coalition of the command center. --- The return value is an enumeration of the type @{DCS#coalition.side}, which contains the RED, BLUE and NEUTRAL coalition. --- --- ### 4.3. The command center is a real object. --- --- The command center must be represented by a live object within the DCS simulator. As a result, the command center --- can be a @{Wrapper.Unit}, a @{Wrapper.Group}, an @{Wrapper.Airbase} or a @{Wrapper.Static} object. --- --- Using the method @{Tasking.CommandCenter#COMMANDCENTER.GetPositionable}() you retrieve the polymorphic positionable object representing --- the command center, but just be aware that you should be able to use the representable object derivation methods. --- --- ### 5. Command center reports. --- --- Because a command center giverns multiple missions, there are several reports available that are generated by command centers. --- These reports are generated using the following methods: --- --- - @{Tasking.CommandCenter#COMMANDCENTER.ReportSummary}(): Creates a summary report of all missions governed by the command center. --- - @{Tasking.CommandCenter#COMMANDCENTER.ReportDetails}(): Creates a detailed report of all missions governed by the command center. --- - @{Tasking.CommandCenter#COMMANDCENTER.ReportMissionPlayers}(): Creates a report listing the players active at the missions governed by the command center. --- --- ## 6. Reference Zones. --- --- Command Centers may be aware of certain Reference Zones within the battleground. These Reference Zones can refer to --- known areas, recognizable buildings or sites, or any other point of interest. --- Command Centers will use these Reference Zones to help pilots with defining coordinates in terms of navigation --- during the WWII era. --- The Reference Zones are related to the WWII mode that the Command Center will operate in. --- Use the method @{#COMMANDCENTER.SetModeWWII}() to set the mode of communication to the WWII mode. --- --- In WWII mode, the Command Center will receive detected targets, and will select for each target the closest --- nearby Reference Zone. This allows pilots to navigate easier through the battle field readying for combat. --- --- The Reference Zones need to be set by the Mission Designer in the Mission Editor. --- Reference Zones are set by normal trigger zones. One can color the zones in a specific color, --- and the radius of the zones doesn't matter, only the point is important. Place the center of these Reference Zones at --- specific scenery objects or points of interest (like cities, rivers, hills, crossing etc). --- The trigger zones indicating a Reference Zone need to follow a specific syntax. --- The name of each trigger zone expressing a Reference Zone need to start with a classification name of the object, --- followed by a #, followed by a symbolic name of the Reference Zone. --- A few examples: --- --- * A church at Tskinvali would be indicated as: *Church#Tskinvali* --- * A train station near Kobuleti would be indicated as: *Station#Kobuleti* --- --- The COMMANDCENTER class contains a method to indicate which trigger zones need to be used as Reference Zones. --- This is done by using the method @{#COMMANDCENTER.SetReferenceZones}(). --- For the moment, only one Reference Zone class can be specified, but in the future, more classes will become possible. --- --- ## 7. Tasks. --- --- ### 7.1. Automatically assign tasks. --- --- One of the most important roles of the command center is the management of tasks. --- The command center can assign automatically tasks to the players using the @{Tasking.CommandCenter#COMMANDCENTER.SetAutoAssignTasks}() method. --- When this method is used with a parameter true; the command center will scan at regular intervals which players in a slot are not having a task assigned. --- For those players; the tasking is enabled to assign automatically a task. --- An Assign Menu will be accessible for the player under the command center menu, to configure the automatic tasking to switched on or off. --- --- ### 7.2. Automatically accept assigned tasks. --- --- When a task is assigned; the mission designer can decide if players are immediately assigned to the task; or they can accept/reject the assigned task. --- Use the method @{Tasking.CommandCenter#COMMANDCENTER.SetAutoAcceptTasks}() to configure this behaviour. --- If the tasks are not automatically accepted; the player will receive a message that he needs to access the command center menu and --- choose from 2 added menu options either to accept or reject the assigned task within 30 seconds. --- If the task is not accepted within 30 seconds; the task will be cancelled and a new task will be assigned. --- --- --- # 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 #COMMANDCENTER -COMMANDCENTER = { - ClassName = "COMMANDCENTER", - CommandCenterName = "", - CommandCenterCoalition = nil, - CommandCenterPositionable = nil, - Name = "", - ReferencePoints = {}, - ReferenceNames = {}, - CommunicationMode = "80", -} - - ---- --- @type COMMANDCENTER.AutoAssignMethods -COMMANDCENTER.AutoAssignMethods = { - ["Random"] = 1, - ["Distance"] = 2, - ["Priority"] = 3, - } - ---- The constructor takes an IDENTIFIABLE as the HQ command center. --- @param #COMMANDCENTER self --- @param Wrapper.Positionable#POSITIONABLE CommandCenterPositionable --- @param #string CommandCenterName --- @return #COMMANDCENTER -function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) - - local self = BASE:Inherit( self, BASE:New() ) -- #COMMANDCENTER - - self.CommandCenterPositionable = CommandCenterPositionable - self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName() - self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition() - - self.Missions = {} - - self:SetAutoAssignTasks( false ) - self:SetAutoAcceptTasks( true ) - self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) - self:SetFlashStatus( false ) - self:SetMessageDuration(10) - - self:HandleEvent( EVENTS.Birth, - -- @param #COMMANDCENTER self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - if EventData.IniObjectCategory == 1 then - local EventGroup = GROUP:Find( EventData.IniDCSGroup ) - --self:E( { CommandCenter = self:GetName(), EventGroup = EventGroup:GetName(), HasGroup = self:HasGroup( EventGroup ), EventData = EventData } ) - if EventGroup and EventGroup:IsAlive() and self:HasGroup( EventGroup ) then - local CommandCenterMenu = MENU_GROUP:New( EventGroup, self:GetText() ) - local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu ) - local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup ) - local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup ) - --self:ReportSummary( EventGroup ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled! - Mission:JoinUnit( PlayerUnit, PlayerGroup ) - end - self:SetMenu() - end - end - - end - ) - --- -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do. --- -- For these elements, it will= --- -- - Set the correct menu. --- -- - Assign the PlayerUnit to the Task if required. --- -- - Send a message to the other players in the group that this player has joined. --- self:HandleEvent( EVENTS.PlayerEnterUnit, --- -- @param #COMMANDCENTER self --- -- @param Core.Event#EVENTDATA EventData --- function( self, EventData ) --- local PlayerUnit = EventData.IniUnit --- for MissionID, Mission in pairs( self:GetMissions() ) do --- local Mission = Mission -- Tasking.Mission#MISSION --- local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled! --- Mission:JoinUnit( PlayerUnit, PlayerGroup ) --- end --- self:SetMenu() --- end --- ) - - -- Handle when a player leaves a slot and goes back to spectators ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.MissionEnd, - -- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:Stop() - end - end - ) - - -- Handle when a player leaves a slot and goes back to spectators ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.PlayerLeaveUnit, - -- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:IsENGAGED() then - Mission:AbortUnit( PlayerUnit ) - end - end - end - ) - - -- Handle when a player crashes ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.Crash, - -- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:IsENGAGED() then - Mission:CrashUnit( PlayerUnit ) - end - end - end - ) - - self:SetMenu() - - _SETTINGS:SetSystemMenu( CommandCenterPositionable ) - - self:SetCommandMenu() - - return self -end - ---- Gets the name of the HQ command center. --- @param #COMMANDCENTER self --- @return #string -function COMMANDCENTER:GetName() - - return self.CommandCenterName -end - ---- Gets the text string of the HQ command center. --- @param #COMMANDCENTER self --- @return #string -function COMMANDCENTER:GetText() - - return "Command Center [" .. self.CommandCenterName .. "]" -end - ---- Gets the short text string of the HQ command center. --- @param #COMMANDCENTER self --- @return #string -function COMMANDCENTER:GetShortText() - - return "CC [" .. self.CommandCenterName .. "]" -end - - ---- Gets the coalition of the command center. --- @param #COMMANDCENTER self --- @return #number Coalition of the command center. -function COMMANDCENTER:GetCoalition() - - return self.CommandCenterCoalition -end - - ---- Gets the POSITIONABLE of the HQ command center. --- @param #COMMANDCENTER self --- @return Wrapper.Positionable#POSITIONABLE -function COMMANDCENTER:GetPositionable() - return self.CommandCenterPositionable -end - ---- Get the Missions governed by the HQ command center. --- @param #COMMANDCENTER self --- @return #list -function COMMANDCENTER:GetMissions() - - return self.Missions or {} -end - ---- Add a MISSION to be governed by the HQ command center. --- @param #COMMANDCENTER self --- @param Tasking.Mission#MISSION Mission --- @return Tasking.Mission#MISSION -function COMMANDCENTER:AddMission( Mission ) - - self.Missions[Mission] = Mission - - return Mission -end - ---- Removes a MISSION to be governed by the HQ command center. --- The given Mission is not nilified. --- @param #COMMANDCENTER self --- @param Tasking.Mission#MISSION Mission --- @return Tasking.Mission#MISSION -function COMMANDCENTER:RemoveMission( Mission ) - - self.Missions[Mission] = nil - - return Mission -end - ---- Set special Reference Zones known by the Command Center to guide airborne pilots during WWII. --- --- These Reference Zones are normal trigger zones, with a special naming. --- The Reference Zones need to be set by the Mission Designer in the Mission Editor. --- Reference Zones are set by normal trigger zones. One can color the zones in a specific color, --- and the radius of the zones doesn't matter, only the center of the zone is important. Place the center of these Reference Zones at --- specific scenery objects or points of interest (like cities, rivers, hills, crossing etc). --- The trigger zones indicating a Reference Zone need to follow a specific syntax. --- The name of each trigger zone expressing a Reference Zone need to start with a classification name of the object, --- followed by a #, followed by a symbolic name of the Reference Zone. --- A few examples: --- --- * A church at Tskinvali would be indicated as: *Church#Tskinvali* --- * A train station near Kobuleti would be indicated as: *Station#Kobuleti* --- --- Taking the above example, this is how this method would be used: --- --- CC:SetReferenceZones( "Church" ) --- CC:SetReferenceZones( "Station" ) --- --- --- @param #COMMANDCENTER self --- @param #string ReferenceZonePrefix The name before the #-mark indicating the class of the Reference Zones. --- @return #COMMANDCENTER -function COMMANDCENTER:SetReferenceZones( ReferenceZonePrefix ) - local MatchPattern = "(.*)#(.*)" - self:F( { MatchPattern = MatchPattern } ) - for ReferenceZoneName in pairs( _DATABASE.ZONENAMES ) do - local ZoneName, ReferenceName = string.match( ReferenceZoneName, MatchPattern ) - self:F( { ZoneName = ZoneName, ReferenceName = ReferenceName } ) - if ZoneName and ReferenceName and ZoneName == ReferenceZonePrefix then - self.ReferencePoints[ReferenceZoneName] = ZONE:New( ReferenceZoneName ) - self.ReferenceNames[ReferenceZoneName] = ReferenceName - end - end - return self -end - ---- Set the commandcenter operations in WWII mode --- This will disable LL, MGRS, BRA, BULLS navigatin messages sent by the Command Center, --- and will be replaced by a navigation using Reference Zones. --- It will also disable the settings at the settings menu for these. --- @param #COMMANDCENTER self --- @return #COMMANDCENTER -function COMMANDCENTER:SetModeWWII() - self.CommunicationMode = "WWII" - return self -end - - ---- Returns if the commandcenter operations is in WWII mode --- @param #COMMANDCENTER self --- @return #boolean true if in WWII mode. -function COMMANDCENTER:IsModeWWII() - return self.CommunicationMode == "WWII" -end - - - - ---- Sets the menu structure of the Missions governed by the HQ command center. --- @param #COMMANDCENTER self -function COMMANDCENTER:SetMenu() - self:F2() - - local MenuTime = timer.getTime() - for MissionID, Mission in pairs( self:GetMissions() or {} ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:SetMenu( MenuTime ) - end - - for MissionID, Mission in pairs( self:GetMissions() or {} ) do - Mission = Mission -- Tasking.Mission#MISSION - Mission:RemoveMenu( MenuTime ) - end - -end - ---- Gets the commandcenter menu structure governed by the HQ command center. --- @param #COMMANDCENTER self --- @param Wrapper.Group#Group TaskGroup Task Group. --- @return Core.Menu#MENU_COALITION -function COMMANDCENTER:GetMenu( TaskGroup ) - - local MenuTime = timer.getTime() - - self.CommandCenterMenus = self.CommandCenterMenus or {} - local CommandCenterMenu - - local CommandCenterText = self:GetText() - CommandCenterMenu = MENU_GROUP:New( TaskGroup, CommandCenterText ):SetTime(MenuTime) - self.CommandCenterMenus[TaskGroup] = CommandCenterMenu - - if self.AutoAssignTasks == false then - local AssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task", CommandCenterMenu, self.AssignTask, self, TaskGroup ):SetTime(MenuTime):SetTag("AutoTask") - end - CommandCenterMenu:Remove( MenuTime, "AutoTask" ) - - return self.CommandCenterMenus[TaskGroup] -end - - ---- Assigns a random task to a TaskGroup. --- @param #COMMANDCENTER self --- @return #COMMANDCENTER -function COMMANDCENTER:AssignTask( TaskGroup ) - - local Tasks = {} - local AssignPriority = 99999999 - local AutoAssignMethod = self.AutoAssignMethod - - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - local MissionTasks = Mission:GetGroupTasks( TaskGroup ) - for MissionTaskName, MissionTask in pairs( MissionTasks or {} ) do - local MissionTask = MissionTask -- Tasking.Task#TASK - if MissionTask:IsStatePlanned() or MissionTask:IsStateReplanned() or MissionTask:IsStateAssigned() then - local TaskPriority = MissionTask:GetAutoAssignPriority( self.AutoAssignMethod, self, TaskGroup ) - if TaskPriority < AssignPriority then - AssignPriority = TaskPriority - Tasks = {} - end - if TaskPriority == AssignPriority then - Tasks[#Tasks+1] = MissionTask - end - end - end - end - - local Task = Tasks[ math.random( 1, #Tasks ) ] -- Tasking.Task#TASK - - if Task then - - self:T( "Assigning task " .. Task:GetName() .. " using auto assign method " .. self.AutoAssignMethod .. " to " .. TaskGroup:GetName() .. " with task priority " .. AssignPriority ) - - if not self.AutoAcceptTasks == true then - Task:SetAutoAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) ) - end - - Task:AssignToGroup( TaskGroup ) - - end - -end - - ---- Sets the menu of the command center. --- This command is called within the :New() method. --- @param #COMMANDCENTER self -function COMMANDCENTER:SetCommandMenu() - - local MenuTime = timer.getTime() - - if self.CommandCenterPositionable and self.CommandCenterPositionable:IsInstanceOf(GROUP) then - local CommandCenterText = self:GetText() - local CommandCenterMenu = MENU_GROUP:New( self.CommandCenterPositionable, CommandCenterText ):SetTime(MenuTime) - - if self.AutoAssignTasks == false then - local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( self.CommandCenterPositionable, "Assign Task On", CommandCenterMenu, self.SetAutoAssignTasks, self, true ):SetTime(MenuTime):SetTag("AutoTask") - else - local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( self.CommandCenterPositionable, "Assign Task Off", CommandCenterMenu, self.SetAutoAssignTasks, self, false ):SetTime(MenuTime):SetTag("AutoTask") - end - CommandCenterMenu:Remove( MenuTime, "AutoTask" ) - end - -end - - - ---- Automatically assigns tasks to all TaskGroups. --- One of the most important roles of the command center is the management of tasks. --- When this method is used with a parameter true; the command center will scan at regular intervals which players in a slot are not having a task assigned. --- For those players; the tasking is enabled to assign automatically a task. --- An Assign Menu will be accessible for the player under the command center menu, to configure the automatic tasking to switched on or off. --- @param #COMMANDCENTER self --- @param #boolean AutoAssign true for ON and false or nil for OFF. -function COMMANDCENTER:SetAutoAssignTasks( AutoAssign ) - - self.AutoAssignTasks = AutoAssign or false - - if self.AutoAssignTasks == true then - self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self ) - else - self:ScheduleStop() - -- FF this is not the schedule ID - --self:ScheduleStop( self.AssignTasks ) - end - -end - ---- Automatically accept tasks for all TaskGroups. --- When a task is assigned; the mission designer can decide if players are immediately assigned to the task; or they can accept/reject the assigned task. --- If the tasks are not automatically accepted; the player will receive a message that he needs to access the command center menu and --- choose from 2 added menu options either to accept or reject the assigned task within 30 seconds. --- If the task is not accepted within 30 seconds; the task will be cancelled and a new task will be assigned. --- @param #COMMANDCENTER self --- @param #boolean AutoAccept true for ON and false or nil for OFF. -function COMMANDCENTER:SetAutoAcceptTasks( AutoAccept ) - - self.AutoAcceptTasks = AutoAccept or false - -end - - ---- Define the method to be used to assign automatically a task from the available tasks in the mission. --- There are 3 types of methods that can be applied for the moment: --- --- 1. Random - assigns a random task in the mission to the player. --- 2. Distance - assigns a task based on a distance evaluation from the player. The closest are to be assigned first. --- 3. Priority - assigns a task based on the priority as defined by the mission designer, using the SetTaskPriority parameter. --- --- The different task classes implement the logic to determine the priority of automatic task assignment to a player, depending on one of the above methods. --- The method @{Tasking.Task#TASK.GetAutoAssignPriority} calculate the priority of the tasks to be assigned. --- @param #COMMANDCENTER self --- @param #COMMANDCENTER.AutoAssignMethods AutoAssignMethod A selection of an assign method from the COMMANDCENTER.AutoAssignMethods enumeration. -function COMMANDCENTER:SetAutoAssignMethod( AutoAssignMethod ) - - self.AutoAssignMethod = AutoAssignMethod or COMMANDCENTER.AutoAssignMethods.Random - -end - ---- Automatically assigns tasks to all TaskGroups. --- @param #COMMANDCENTER self -function COMMANDCENTER:AssignTasks() - - local GroupSet = self:AddGroups() - - for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - - if TaskGroup:IsAlive() then - self:GetMenu( TaskGroup ) - - if self:IsGroupAssigned( TaskGroup ) then - else - -- Only groups with planes or helicopters will receive automatic tasks. - -- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696 - if TaskGroup:IsAir() then - self:AssignTask( TaskGroup ) - end - end - end - end - -end - - ---- Get all the Groups active within the command center. --- @param #COMMANDCENTER self --- @return Core.Set#SET_GROUP The set of groups active within the command center. -function COMMANDCENTER:AddGroups() - - local GroupSet = SET_GROUP:New() - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - GroupSet = Mission:AddGroups( GroupSet ) - end - - return GroupSet -end - - ---- Checks of the TaskGroup has a Task. --- @param #COMMANDCENTER self --- @return #boolean When true, the TaskGroup has a Task, otherwise the returned value will be false. -function COMMANDCENTER:IsGroupAssigned( TaskGroup ) - - local Assigned = false - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:IsGroupAssigned( TaskGroup ) then - Assigned = true - break - end - end - - return Assigned -end - - ---- Checks of the command center has the given MissionGroup. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP MissionGroup The group active within one of the missions governed by the command center. --- @return #boolean -function COMMANDCENTER:HasGroup( MissionGroup ) - - local Has = false - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:HasGroup( MissionGroup ) then - Has = true - break - end - end - - return Has -end - ---- Let the command center send a Message to all players. --- @param #COMMANDCENTER self --- @param #string Message The message text. -function COMMANDCENTER:MessageToAll( Message ) - - self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() ) - -end - ---- Let the command center send a message to the MessageGroup. --- @param #COMMANDCENTER self --- @param #string Message The message text. --- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. -function COMMANDCENTER:MessageToGroup( Message, MessageGroup ) - - self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() ) - -end - ---- Let the command center send a message to the MessageGroup. --- @param #COMMANDCENTER self --- @param #string Message The message text. --- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. --- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message. -function COMMANDCENTER:MessageTypeToGroup( Message, MessageGroup, MessageType ) - - self:GetPositionable():MessageTypeToGroup( Message, MessageType, MessageGroup, self:GetShortText() ) - -end - ---- Let the command center send a message to the coalition of the command center. --- @param #COMMANDCENTER self --- @param #string Message The message text. -function COMMANDCENTER:MessageToCoalition( Message ) - - local CCCoalition = self:GetPositionable():GetCoalition() - --TODO: Fix coalition bug! - - self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() ) - -end - - ---- Let the command center send a message of a specified type to the coalition of the command center. --- @param #COMMANDCENTER self --- @param #string Message The message text. --- @param Core.Message#MESSAGE.MessageType MessageType The type of the message, resulting in automatic time duration and prefix of the message. -function COMMANDCENTER:MessageTypeToCoalition( Message, MessageType ) - - local CCCoalition = self:GetPositionable():GetCoalition() - --TODO: Fix coalition bug! - - self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition, self:GetShortText() ) - -end - - ---- Let the command center send a report of the status of all missions to a group. --- Each Mission is listed, with an indication how many Tasks are still to be completed. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP ReportGroup The group to receive the report. -function COMMANDCENTER:ReportSummary( ReportGroup ) - self:F( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetName() - Report:Add( string.format( '%s - Report Summary Missions', Name ) ) - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportSummary( ReportGroup ) ) - end - - self:MessageToGroup( Report:Text(), ReportGroup ) -end - ---- Let the command center send a report of the players of all missions to a group. --- Each Mission is listed, with an indication how many Tasks are still to be completed. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP ReportGroup The group to receive the report. -function COMMANDCENTER:ReportMissionsPlayers( ReportGroup ) - self:F( ReportGroup ) - - local Report = REPORT:New() - - Report:Add( "Players active in all missions." ) - - for MissionID, MissionData in pairs( self.Missions ) do - local Mission = MissionData -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportPlayersPerTask(ReportGroup) ) - end - - self:MessageToGroup( Report:Text(), ReportGroup ) -end - ---- Let the command center send a report of the status of a task to a group. --- Report the details of a Mission, listing the Mission, and all the Task details. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP ReportGroup The group to receive the report. --- @param Tasking.Task#TASK Task The task to be reported. -function COMMANDCENTER:ReportDetails( ReportGroup, Task ) - self:F( ReportGroup ) - - local Report = REPORT:New() - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportDetails() ) - end - - self:MessageToGroup( Report:Text(), ReportGroup ) -end - - ---- Let the command center flash a report of the status of the subscribed task to a group. --- @param #COMMANDCENTER self --- @param Flash #boolean -function COMMANDCENTER:SetFlashStatus( Flash ) - self:F() - - self.FlashStatus = Flash and true -end - ---- Duration a command center message is shown. --- @param #COMMANDCENTER self --- @param seconds #number -function COMMANDCENTER:SetMessageDuration(seconds) - self:F() - - self.MessageDuration = 10 or seconds -end diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua deleted file mode 100644 index 81dd1f882..000000000 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ /dev/null @@ -1,402 +0,0 @@ ---- **Tasking** - This module contains the DETECTION_MANAGER class and derived classes. --- --- === --- --- The @{#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. --- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. --- --- 1.1) DETECTION_MANAGER constructor: --- ----------------------------------- --- * @{#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. --- --- 1.2) DETECTION_MANAGER reporting: --- --------------------------------- --- Derived DETECTION_MANAGER classes will reports detected units using the method @{#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. --- --- The time interval in seconds of the reporting can be changed using the methods @{#DETECTION_MANAGER.SetRefreshTimeInterval}(). --- To control how long a reporting message is displayed, use @{#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. --- --- Reporting can be started and stopped using the methods @{#DETECTION_MANAGER.StartReporting}() and @{#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{#DETECTION_MANAGER.ReportNow}(). --- --- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. --- --- === --- --- 2) @{#DETECTION_REPORTING} class, extends @{#DETECTION_MANAGER} --- === --- The @{#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Tasking.DetectionManager#DETECTION_MANAGER} class. --- --- 2.1) DETECTION_REPORTING constructor: --- ------------------------------- --- The @{#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. --- --- --- # 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 --- --- === --- --- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module Tasking.DetectionManager --- @image Task_Detection_Manager.JPG - -do -- DETECTION MANAGER - - -- @type DETECTION_MANAGER - -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Tasking.CommandCenter#COMMANDCENTER CC The command center that is used to communicate with the players. - -- @extends Core.Fsm#FSM - - --- DETECTION_MANAGER class. - -- @field #DETECTION_MANAGER - DETECTION_MANAGER = { - ClassName = "DETECTION_MANAGER", - SetGroup = nil, - Detection = nil, - } - - -- @field Tasking.CommandCenter#COMMANDCENTER - DETECTION_MANAGER.CC = nil - - --- FAC constructor. - -- @param #DETECTION_MANAGER self - -- @param Core.Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:New( SetGroup, Detection ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New() ) -- #DETECTION_MANAGER - - self.SetGroup = SetGroup - self.Detection = Detection - - self:SetStartState( "Stopped" ) - self:AddTransition( "Stopped", "Start", "Started" ) - - --- Start Handler OnBefore for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnBeforeStart - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Start Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterStart - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Start Trigger for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] Start - -- @param #DETECTION_MANAGER self - - --- Start Asynchronous Trigger for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] __Start - -- @param #DETECTION_MANAGER self - -- @param #number Delay - - - - self:AddTransition( "Started", "Stop", "Stopped" ) - - --- Stop Handler OnBefore for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnBeforeStop - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Stop Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterStop - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Stop Trigger for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] Stop - -- @param #DETECTION_MANAGER self - - --- Stop Asynchronous Trigger for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] __Stop - -- @param #DETECTION_MANAGER self - -- @param #number Delay - - self:AddTransition( "Started", "Success", "Started" ) - - --- Success Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterSuccess - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - - self:AddTransition( "Started", "Failed", "Started" ) - - --- Failed Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterFailed - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - - self:AddTransition( "Started", "Aborted", "Started" ) - - --- Aborted Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterAborted - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:AddTransition( "Started", "Cancelled", "Started" ) - - --- Cancelled Handler OnAfter for DETECTION_MANAGER - -- @function [parent=#DETECTION_MANAGER] OnAfterCancelled - -- @param #DETECTION_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - - self:AddTransition( "Started", "Report", "Started" ) - - self:SetRefreshTimeInterval( 30 ) - self:SetReportDisplayTime( 25 ) - - Detection:__Start( 3 ) - - return self - end - - function DETECTION_MANAGER:onafterStart( From, Event, To ) - self:Report() - end - - function DETECTION_MANAGER:onafterReport( From, Event, To ) - - self:__Report( -self._RefreshTimeInterval ) - - self:ProcessDetected( self.Detection ) - end - - --- Set the reporting time interval. - -- @param #DETECTION_MANAGER self - -- @param #number RefreshTimeInterval The interval in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetRefreshTimeInterval( RefreshTimeInterval ) - self:F2() - - self._RefreshTimeInterval = RefreshTimeInterval - end - - - --- Set the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime ) - self:F2() - - self._ReportDisplayTime = ReportDisplayTime - end - - --- Get the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. - function DETECTION_MANAGER:GetReportDisplayTime() - self:F2() - - return self._ReportDisplayTime - end - - - --- Set a command center to communicate actions to the players reporting to the command center. - -- @param #DETECTION_MANAGER self - -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:SetTacticalMenu( DispatcherMainMenuText, DispatcherMenuText ) - - local DispatcherMainMenu = MENU_MISSION:New( DispatcherMainMenuText, nil ) - local DispatcherMenu = MENU_MISSION_COMMAND:New( DispatcherMenuText, DispatcherMainMenu, - function() - self:ShowTacticalDisplay( self.Detection ) - end - ) - - return self - end - - - - - --- Set a command center to communicate actions to the players reporting to the command center. - -- @param #DETECTION_MANAGER self - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. - -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:SetCommandCenter( CommandCenter ) - - self.CC = CommandCenter - - return self - end - - - --- Get the command center to communicate actions to the players. - -- @param #DETECTION_MANAGER self - -- @return Tasking.CommandCenter#COMMANDCENTER The command center. - function DETECTION_MANAGER:GetCommandCenter() - - return self.CC - end - - - --- Send an information message to the players reporting to the command center. - -- @param #DETECTION_MANAGER self - -- @param #table Squadron The squadron table. - -- @param #string Message The message to be sent. - -- @param #string SoundFile The name of the sound file .wav or .ogg. - -- @param #number SoundDuration The duration of the sound. - -- @param #string SoundPath The path pointing to the folder in the mission file. - -- @param Wrapper.Group#GROUP DefenderGroup The defender group sending the message. - -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:MessageToPlayers( Squadron, Message, DefenderGroup ) - - self:F( { Message = Message } ) - --- if not self.PreviousMessage or self.PreviousMessage ~= Message then --- self.PreviousMessage = Message --- if self.CC then --- self.CC:MessageToCoalition( Message ) --- end --- end - - if self.CC then - self.CC:MessageToCoalition( Message ) - end - - Message = Message:gsub( "°", " degrees " ) - Message = Message:gsub( "(%d)%.(%d)", "%1 dot %2" ) - - -- Here we handle the transmission of the voice over. - -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. - local RadioQueue = Squadron.RadioQueue -- Core.RadioSpeech#RADIOSPEECH - if RadioQueue then - local DefenderUnit = DefenderGroup:GetUnit(1) - if DefenderUnit and DefenderUnit:IsAlive() then - RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) - end - RadioQueue:Speak( Message, Squadron.Language ) - end - - return self - end - - - - --- Reports the detected items to the @{Core.Set#SET_GROUP}. - -- @param #DETECTION_MANAGER self - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:ProcessDetected( Detection ) - - end - -end - - -do -- DETECTION_REPORTING - - --- DETECTION_REPORTING class. - -- @type DETECTION_REPORTING - -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @extends #DETECTION_MANAGER - DETECTION_REPORTING = { - ClassName = "DETECTION_REPORTING", - } - - - --- DETECTION_REPORTING constructor. - -- @param #DETECTION_REPORTING self - -- @param Core.Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_AREAS Detection - -- @return #DETECTION_REPORTING self - function DETECTION_REPORTING:New( SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING - - self:Schedule( 1, 30 ) - return self - end - - --- Creates a string of the detected items in a @{Functional.Detection} object. - -- @param #DETECTION_MANAGER self - -- @param Core.Set#SET_UNIT DetectedSet The detected Set created by the @{Functional.Detection#DETECTION_BASE} object. - -- @return #DETECTION_MANAGER self - function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - local UnitType = DetectedUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) - end - - - - --- Reports the detected items to the @{Core.Set#SET_GROUP}. - -- @param #DETECTION_REPORTING self - -- @param Wrapper.Group#GROUP Group The @{Wrapper.Group} object to where the report needs to go. - -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_BASE} object. - -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. - function DETECTION_REPORTING:ProcessDetected( Group, Detection ) - self:F2( Group ) - - local DetectedMsg = {} - for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea - DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) - end - local FACGroup = Detection:GetDetectionGroups() - FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group ) - - return true - end - -end - diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua deleted file mode 100644 index 6fa3da747..000000000 --- a/Moose Development/Moose/Tasking/Mission.lua +++ /dev/null @@ -1,1211 +0,0 @@ ---- **Tasking** - A mission models a goal to be achieved through the execution and completion of tasks by human players. --- --- **Features:** --- --- * A mission has a goal to be achieved, through the execution and completion of tasks of different categories by human players. --- * A mission manages these tasks. --- * A mission has a state, that indicates the fase of the mission. --- * A mission has a menu structure, that facilitates mission reports and tasking menus. --- * A mission can assign a task to a player. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Mission --- @image Task_Mission.JPG - ---- --- @type MISSION --- @field #MISSION.Clients _Clients --- @field Core.Menu#MENU_COALITION MissionMenu --- @field #string MissionBriefing --- @extends Core.Fsm#FSM - ---- Models goals to be achieved and can contain multiple tasks to be executed to achieve the goals. --- --- A mission contains multiple tasks and can be of different task types. --- These tasks need to be assigned to human players to be executed. --- --- A mission can have multiple states, which will evolve as the mission progresses during the DCS simulation. --- --- - **IDLE**: The mission is defined, but not started yet. No task has yet been joined by a human player as part of the mission. --- - **ENGAGED**: The mission is ongoing, players have joined tasks to be executed. --- - **COMPLETED**: The goals of the mission has been successfully reached, and the mission is flagged as completed. --- - **FAILED**: For a certain reason, the goals of the mission has not been reached, and the mission is flagged as failed. --- - **HOLD**: The mission was enaged, but for some reason it has been put on hold. --- --- Note that a mission goals need to be checked by a goal check trigger: @{#MISSION.OnBeforeMissionGoals}(), which may return false if the goal has not been reached. --- This goal is checked automatically by the mission object every x seconds. --- --- - @{#MISSION.Start}() or @{#MISSION.__Start}() will start the mission, and will bring it from **IDLE** state to **ENGAGED** state. --- - @{#MISSION.Stop}() or @{#MISSION.__Stop}() will stop the mission, and will bring it from **ENGAGED** state to **IDLE** state. --- - @{#MISSION.Complete}() or @{#MISSION.__Complete}() will complete the mission, and will bring the mission state to **COMPLETED**. --- Note that the mission must be in state **ENGAGED** to be able to complete the mission. --- - @{#MISSION.Fail}() or @{#MISSION.__Fail}() will fail the mission, and will bring the mission state to **FAILED**. --- Note that the mission must be in state **ENGAGED** to be able to fail the mission. --- - @{#MISSION.Hold}() or @{#MISSION.__Hold}() will hold the mission, and will bring the mission state to **HOLD**. --- Note that the mission must be in state **ENGAGED** to be able to hold the mission. --- Re-engage the mission using the engage trigger. --- --- The following sections provide an overview of the most important methods that can be used as part of a mission object. --- Note that the @{Tasking.CommandCenter} system is using most of these methods to manage the missions in its system. --- --- ## 1. Create a mission object. --- --- - @{#MISSION.New}(): Creates a new MISSION object. --- --- ## 2. Mission task management. --- --- Missions maintain tasks, which can be added or removed, or enquired. --- --- - @{#MISSION.AddTask}(): Adds a task to the mission. --- - @{#MISSION.RemoveTask}(): Removes a task from the mission. --- --- ## 3. Mission detailed methods. --- --- Various methods are added to manage missions. --- --- ### 3.1. Naming and description. --- --- There are several methods that can be used to retrieve the properties of a mission: --- --- - Use the method @{#MISSION.GetName}() to retrieve the name of the mission. --- This is the name given as part of the @{#MISSION.New}() constructor. --- --- A textual description can be retrieved that provides the mission name to be used within message communication: --- --- - @{#MISSION.GetShortText}() returns the mission name as `Mission "MissionName"`. --- - @{#MISSION.GetText}() returns the mission name as `Mission "MissionName (MissionPriority)"`. A longer version including the priority text of the mission. --- --- ### 3.2. Get task information. --- --- - @{#MISSION.GetTasks}(): Retrieves a list of the tasks controlled by the mission. --- - @{#MISSION.GetTask}(): Retrieves a specific task controlled by the mission. --- - @{#MISSION.GetTasksRemaining}(): Retrieve a list of the tasks that aren't finished or failed, and are governed by the mission. --- - @{#MISSION.GetGroupTasks}(): Retrieve a list of the tasks that can be assigned to a @{Wrapper.Group}. --- - @{#MISSION.GetTaskTypes}(): Retrieve a list of the different task types governed by the mission. --- --- ### 3.3. Get the command center. --- --- - @{#MISSION.GetCommandCenter}(): Retrieves the @{Tasking.CommandCenter} governing the mission. --- --- ### 3.4. Get the groups active in the mission as a @{Core.Set}. --- --- - @{#MISSION.GetGroups}(): Retrieves a @{Core.Set#SET_GROUP} of all the groups active in the mission (as part of the tasks). --- --- ### 3.5. Get the names of the players. --- --- - @{#MISSION.GetPlayerNames}(): Retrieves the list of the players that were active within th mission.. --- --- ## 4. Menu management. --- --- A mission object is able to manage its own menu structure. Use the @{#MISSION.GetMenu}() and @{#MISSION.SetMenu}() to manage the underlying submenu --- structure managing the tasks of the mission. --- --- ## 5. Reporting management. --- --- Several reports can be generated for a mission, and will return a text string that can be used to display using the @{Core.Message} system. --- --- - @{#MISSION.ReportBriefing}(): Generates the briefing for the mission. --- - @{#MISSION.ReportOverview}(): Generates an overview of the tasks and status of the mission. --- - @{#MISSION.ReportDetails}(): Generates a detailed report of the tasks of the mission. --- - @{#MISSION.ReportSummary}(): Generates a summary report of the tasks of the mission. --- - @{#MISSION.ReportPlayersPerTask}(): Generates a report showing the active players per task. --- - @{#MISSION.ReportPlayersProgress}(): Generates a report showing the task progress per player. --- --- --- # 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 #MISSION -MISSION = { - ClassName = "MISSION", - Name = "", - MissionStatus = "PENDING", - AssignedGroups = {}, -} - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param #MISSION self --- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter --- @param #string MissionName Name of the mission. This name will be used to reference the status of each mission by the players. --- @param #string MissionPriority String indicating the "priority" of the Mission. e.g. "Primary", "Secondary". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param #string MissionBriefing String indicating the mission briefing to be shown when a player joins a @{Wrapper.Client#CLIENT}. --- @param DCS#coalition.side MissionCoalition Side of the coalition, i.e. and enumerator @{#DCS.coalition.side} corresponding to RED, BLUE or NEUTRAL. --- @return #MISSION self -function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) - - local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM - - self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - - self.CommandCenter = CommandCenter - CommandCenter:AddMission( self ) - - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - - self.Tasks = {} - self.TaskNumber = 0 - self.PlayerNames = {} -- These are the players that achieved progress in the mission. - - self:SetStartState( "IDLE" ) - - self:AddTransition( "IDLE", "Start", "ENGAGED" ) - - --- OnLeave Transition Handler for State IDLE. - -- @function [parent=#MISSION] OnLeaveIDLE - -- @param #MISSION self - -- @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 IDLE. - -- @function [parent=#MISSION] OnEnterIDLE - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnLeave Transition Handler for State ENGAGED. - -- @function [parent=#MISSION] OnLeaveENGAGED - -- @param #MISSION self - -- @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 ENGAGED. - -- @function [parent=#MISSION] OnEnterENGAGED - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Start. - -- @function [parent=#MISSION] OnBeforeStart - -- @param #MISSION self - -- @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 Start. - -- @function [parent=#MISSION] OnAfterStart - -- @param #MISSION self - -- @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 Start. - -- @function [parent=#MISSION] Start - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Start. - -- @function [parent=#MISSION] __Start - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "ENGAGED", "Stop", "IDLE" ) - - --- OnLeave Transition Handler for State IDLE. - -- @function [parent=#MISSION] OnLeaveIDLE - -- @param #MISSION self - -- @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 IDLE. - -- @function [parent=#MISSION] OnEnterIDLE - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Stop. - -- @function [parent=#MISSION] OnBeforeStop - -- @param #MISSION self - -- @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 Stop. - -- @function [parent=#MISSION] OnAfterStop - -- @param #MISSION self - -- @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 Stop. - -- @function [parent=#MISSION] Stop - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Stop. - -- @function [parent=#MISSION] __Stop - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "ENGAGED", "Complete", "COMPLETED" ) - - --- OnLeave Transition Handler for State COMPLETED. - -- @function [parent=#MISSION] OnLeaveCOMPLETED - -- @param #MISSION self - -- @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 COMPLETED. - -- @function [parent=#MISSION] OnEnterCOMPLETED - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Complete. - -- @function [parent=#MISSION] OnBeforeComplete - -- @param #MISSION self - -- @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 Complete. - -- @function [parent=#MISSION] OnAfterComplete - -- @param #MISSION self - -- @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 Complete. - -- @function [parent=#MISSION] Complete - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Complete. - -- @function [parent=#MISSION] __Complete - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Fail", "FAILED" ) - - --- OnLeave Transition Handler for State FAILED. - -- @function [parent=#MISSION] OnLeaveFAILED - -- @param #MISSION self - -- @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 FAILED. - -- @function [parent=#MISSION] OnEnterFAILED - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Fail. - -- @function [parent=#MISSION] OnBeforeFail - -- @param #MISSION self - -- @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 Fail. - -- @function [parent=#MISSION] OnAfterFail - -- @param #MISSION self - -- @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 Fail. - -- @function [parent=#MISSION] Fail - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Fail. - -- @function [parent=#MISSION] __Fail - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "*", "MissionGoals", "*" ) - - --- MissionGoals Handler OnBefore for MISSION - -- @function [parent=#MISSION] OnBeforeMissionGoals - -- @param #MISSION self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- MissionGoals Handler OnAfter for MISSION - -- @function [parent=#MISSION] OnAfterMissionGoals - -- @param #MISSION self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- MissionGoals Trigger for MISSION - -- @function [parent=#MISSION] MissionGoals - -- @param #MISSION self - - --- MissionGoals Asynchronous Trigger for MISSION - -- @function [parent=#MISSION] __MissionGoals - -- @param #MISSION self - -- @param #number Delay - - -- Private implementations - - CommandCenter:SetMenu() - - return self -end - - - - ---- FSM function for a MISSION --- @param #MISSION self --- @param #string From --- @param #string Event --- @param #string To -function MISSION:onenterCOMPLETED( From, Event, To ) - - self:GetCommandCenter():MessageTypeToCoalition( self:GetText() .. " has been completed! Good job guys!", MESSAGE.Type.Information ) -end - ---- Gets the mission name. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetName() - return self.Name -end - - ---- Gets the mission text. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetText() - return string.format( 'Mission "%s (%s)"', self.Name, self.MissionPriority ) -end - - ---- Gets the short mission text. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetShortText() - return string.format( 'Mission "%s"', self.Name ) -end - - ---- Add a Unit to join the Mission. --- For each Task within the Mission, the Unit is joined with the Task. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. --- @return #boolean true if Unit is part of a Task in the Mission. -function MISSION:JoinUnit( PlayerUnit, PlayerGroup ) - self:T( { Mission = self:GetName(), PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) - - local PlayerUnitAdded = false - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:JoinUnit( PlayerUnit, PlayerGroup ) then - PlayerUnitAdded = true - end - end - - return PlayerUnitAdded -end - ---- Aborts a PlayerUnit from the Mission. --- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @return #MISSION -function MISSION:AbortUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local PlayerGroup = PlayerUnit:GetGroup() - Task:AbortGroup( PlayerGroup ) - end - - return self -end - ---- Handles a crash of a PlayerUnit from the Mission. --- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player crashing. --- @return #MISSION -function MISSION:CrashUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local PlayerGroup = PlayerUnit:GetGroup() - Task:CrashGroup( PlayerGroup ) - end - - return self -end - ---- Add a scoring to the mission. --- @param #MISSION self --- @return #MISSION self -function MISSION:AddScoring( Scoring ) - self.Scoring = Scoring - return self -end - ---- Get the scoring object of a mission. --- @param #MISSION self --- @return #SCORING Scoring -function MISSION:GetScoring() - return self.Scoring -end - ---- Gets the groups for which TASKS are given in the mission --- @param #MISSION self --- @param Core.Set#SET_GROUP GroupSet --- @return Core.Set#SET_GROUP -function MISSION:GetGroups() - - return self:AddGroups() - -end - ---- Adds the groups for which TASKS are given in the mission --- @param #MISSION self --- @param Core.Set#SET_GROUP GroupSet --- @return Core.Set#SET_GROUP -function MISSION:AddGroups( GroupSet ) - - GroupSet = GroupSet or SET_GROUP:New() - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - GroupSet = Task:AddGroups( GroupSet ) - end - - return GroupSet - -end - - ---- Sets the Planned Task menu. --- @param #MISSION self --- @param #number MenuTime -function MISSION:SetMenu( MenuTime ) - self:F( { self:GetName(), MenuTime } ) - - local MenuCount = {} - --for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local TaskType = Task:GetType() - MenuCount[TaskType] = MenuCount[TaskType] or 1 - if MenuCount[TaskType] <= 10 then - Task:SetMenu( MenuTime ) - MenuCount[TaskType] = MenuCount[TaskType] + 1 - end - end -end - ---- Removes the Planned Task menu. --- @param #MISSION self --- @param #number MenuTime -function MISSION:RemoveMenu( MenuTime ) - self:F( { self:GetName(), MenuTime } ) - - for _, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Task:RemoveMenu( MenuTime ) - end -end - - - -do -- Group Assignment - - --- Returns if the @{Tasking.Mission} is assigned to the Group. - -- @param #MISSION self - -- @param Wrapper.Group#GROUP MissionGroup - -- @return #boolean - function MISSION:IsGroupAssigned( MissionGroup ) - - local MissionGroupName = MissionGroup:GetName() - - if self.AssignedGroups[MissionGroupName] == MissionGroup then - self:T2( { "Mission is assigned to:", MissionGroup:GetName() } ) - return true - end - - self:T2( { "Mission is not assigned to:", MissionGroup:GetName() } ) - return false - end - - - --- Set @{Wrapper.Group} assigned to the @{Tasking.Mission}. - -- @param #MISSION self - -- @param Wrapper.Group#GROUP MissionGroup - -- @return #MISSION - function MISSION:SetGroupAssigned( MissionGroup ) - - local MissionName = self:GetName() - local MissionGroupName = MissionGroup:GetName() - - self.AssignedGroups[MissionGroupName] = MissionGroup - self:T( string.format( "Mission %s is assigned to %s", MissionName, MissionGroupName ) ) - - return self - end - - --- Clear the @{Wrapper.Group} assignment from the @{Tasking.Mission}. - -- @param #MISSION self - -- @param Wrapper.Group#GROUP MissionGroup - -- @return #MISSION - function MISSION:ClearGroupAssignment( MissionGroup ) - - local MissionName = self:GetName() - local MissionGroupName = MissionGroup:GetName() - - self.AssignedGroups[MissionGroupName] = nil - --self:E( string.format( "Mission %s is unassigned to %s", MissionName, MissionGroupName ) ) - - return self - end - -end - ---- Gets the COMMANDCENTER. --- @param #MISSION self --- @return Tasking.CommandCenter#COMMANDCENTER -function MISSION:GetCommandCenter() - return self.CommandCenter -end - - ---- Removes a Task menu. --- @param #MISSION self --- @param Tasking.Task#TASK Task --- @return #MISSION self -function MISSION:RemoveTaskMenu( Task ) - - Task:RemoveMenu() -end - - ---- Gets the root mission menu for the TaskGroup. Obsolete?! Originally no reference to TaskGroup parameter! --- @param #MISSION self --- @param Wrapper.Group#GROUP TaskGroup Task group. --- @return Core.Menu#MENU_COALITION self -function MISSION:GetRootMenu( TaskGroup ) -- R2.2 - - local CommandCenter = self:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu( TaskGroup ) - - local MissionName = self:GetText() - --local MissionMenu = CommandCenterMenu:GetMenu( MissionName ) - - self.MissionMenu = MENU_COALITION:New( self.MissionCoalition, MissionName, CommandCenterMenu ) - - return self.MissionMenu -end - ---- Gets the mission menu for the TaskGroup. --- @param #MISSION self --- @param Wrapper.Group#GROUP TaskGroup Task group. --- @return Core.Menu#MENU_COALITION self -function MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure - - local CommandCenter = self:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu( TaskGroup ) - - self.MissionGroupMenu = self.MissionGroupMenu or {} - self.MissionGroupMenu[TaskGroup] = self.MissionGroupMenu[TaskGroup] or {} - - local GroupMenu = self.MissionGroupMenu[TaskGroup] - - local MissionText = self:GetText() - self.MissionMenu = MENU_GROUP:New( TaskGroup, MissionText, CommandCenterMenu ) - - GroupMenu.BriefingMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Mission Briefing", self.MissionMenu, self.MenuReportBriefing, self, TaskGroup ) - - GroupMenu.MarkTasks = MENU_GROUP_COMMAND:New( TaskGroup, "Mark Task Locations on Map", self.MissionMenu, self.MarkTargetLocations, self, TaskGroup ) - GroupMenu.TaskReportsMenu = MENU_GROUP:New( TaskGroup, "Task Reports", self.MissionMenu ) - GroupMenu.ReportTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Tasks Summary", GroupMenu.TaskReportsMenu, self.MenuReportTasksSummary, self, TaskGroup ) - GroupMenu.ReportPlannedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Planned Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Planned" ) - GroupMenu.ReportAssignedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Assigned Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Assigned" ) - GroupMenu.ReportSuccessTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Successful Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Success" ) - GroupMenu.ReportFailedTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Failed Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Failed" ) - GroupMenu.ReportHeldTasksMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Held Tasks", GroupMenu.TaskReportsMenu, self.MenuReportTasksPerStatus, self, TaskGroup, "Hold" ) - - GroupMenu.PlayerReportsMenu = MENU_GROUP:New( TaskGroup, "Statistics Reports", self.MissionMenu ) - GroupMenu.ReportMissionHistory = MENU_GROUP_COMMAND:New( TaskGroup, "Report Mission Progress", GroupMenu.PlayerReportsMenu, self.MenuReportPlayersProgress, self, TaskGroup ) - GroupMenu.ReportPlayersPerTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Report Players per Task", GroupMenu.PlayerReportsMenu, self.MenuReportPlayersPerTask, self, TaskGroup ) - - return self.MissionMenu -end - - - - ---- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param #string TaskName The Name of the @{Tasking.Task} within the @{Tasking.Mission}. --- @return Tasking.Task#TASK The Task --- @return #nil Returns nil if no task was found. -function MISSION:GetTask( TaskName ) - self:F( { TaskName } ) - - return self.Tasks[TaskName] -end - - ---- Return the next @{Tasking.Task} ID to be completed within the @{Tasking.Mission}. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Tasking.Task} object. --- @return Tasking.Task#TASK The task added. -function MISSION:GetNextTaskID( Task ) - - self.TaskNumber = self.TaskNumber + 1 - - return self.TaskNumber -end - - ---- Register a @{Tasking.Task} to be completed within the @{Tasking.Mission}. --- Note that there can be multiple @{Tasking.Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Tasking.Task} object. --- @return Tasking.Task#TASK The task added. -function MISSION:AddTask( Task ) - - local TaskName = Task:GetTaskName() - self:T( { "==> Adding TASK ", MissionName = self:GetName(), TaskName = TaskName } ) - - self.Tasks[TaskName] = Task - - self:GetCommandCenter():SetMenu() - - return Task -end - - ---- Removes a @{Tasking.Task} to be completed within the @{Tasking.Mission}. --- Note that there can be multiple @{Tasking.Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Tasking.Task} object. --- @return #nil The cleaned Task reference. -function MISSION:RemoveTask( Task ) - - local TaskName = Task:GetTaskName() - self:T( { "<== Removing TASK ", MissionName = self:GetName(), TaskName = TaskName } ) - - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - -- Ensure everything gets garbarge collected. - self.Tasks[TaskName] = nil - Task = nil - - collectgarbage() - - self:GetCommandCenter():SetMenu() - - return nil -end - ---- Is the @{Tasking.Mission} **COMPLETED**. --- @param #MISSION self --- @return #boolean -function MISSION:IsCOMPLETED() - return self:Is( "COMPLETED" ) -end - ---- Is the @{Tasking.Mission} **IDLE**. --- @param #MISSION self --- @return #boolean -function MISSION:IsIDLE() - return self:Is( "IDLE" ) -end - ---- Is the @{Tasking.Mission} **ENGAGED**. --- @param #MISSION self --- @return #boolean -function MISSION:IsENGAGED() - return self:Is( "ENGAGED" ) -end - ---- Is the @{Tasking.Mission} **FAILED**. --- @param #MISSION self --- @return #boolean -function MISSION:IsFAILED() - return self:Is( "FAILED" ) -end - ---- Is the @{Tasking.Mission} **HOLD**. --- @param #MISSION self --- @return #boolean -function MISSION:IsHOLD() - return self:Is( "HOLD" ) -end - ---- Validates if the Mission has a Group --- @param #MISSION --- @return #boolean true if the Mission has a Group. -function MISSION:HasGroup( TaskGroup ) - local Has = false - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:HasGroup( TaskGroup ) then - Has = true - break - end - end - - return Has -end - --- @param #MISSION self --- @return #number -function MISSION:GetTasksRemaining() - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:IsStateSuccess() or Task:IsStateFailed() then - else - TasksRemaining = TasksRemaining + 1 - end - end - return TasksRemaining -end - --- @param #MISSION self --- @return #number -function MISSION:GetTaskTypes() - -- Determine how many tasks are remaining. - local TaskTypeList = {} - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local TaskType = Task:GetType() - TaskTypeList[TaskType] = TaskType - end - return TaskTypeList -end - - -function MISSION:AddPlayerName( PlayerName ) - self.PlayerNames = self.PlayerNames or {} - self.PlayerNames[PlayerName] = PlayerName - return self -end - -function MISSION:GetPlayerNames() - return self.PlayerNames -end - - ---- Create a briefing report of the Mission. --- @param #MISSION self --- @return #string -function MISSION:ReportBriefing() - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Mission Briefing Report', Name, Status ) ) - - Report:Add( self.MissionBriefing ) - - return Report:Text() -end - - ------ Create a status report of the Mission. ----- This reports provides a one liner of the mission status. It indicates how many players and how many Tasks. ----- ----- Mission "" - Status "" ----- - Task Types: , ----- - Planned Tasks (xp) ----- - Assigned Tasks(xp) ----- - Success Tasks (xp) ----- - Hold Tasks (xp) ----- - Cancelled Tasks (xp) ----- - Aborted Tasks (xp) ----- - Failed Tasks (xp) ----- --- @param #MISSION self ----- @return #string ---function MISSION:ReportSummary() --- --- local Report = REPORT:New() --- --- -- List the name of the mission. --- local Name = self:GetText() --- --- -- Determine the status of the mission. --- local Status = "<" .. self:GetState() .. ">" --- --- Report:Add( string.format( '%s - Status "%s"', Name, Status ) ) --- --- local TaskTypes = self:GetTaskTypes() --- --- Report:Add( string.format( " - Task Types: %s", table.concat(TaskTypes, ", " ) ) ) --- --- local TaskStatusList = { "Planned", "Assigned", "Success", "Hold", "Cancelled", "Aborted", "Failed" } --- --- for TaskStatusID, TaskStatus in pairs( TaskStatusList ) do --- local TaskCount = 0 --- local TaskPlayerCount = 0 --- -- Determine how many tasks are remaining. --- for TaskID, Task in pairs( self:GetTasks() ) do --- local Task = Task -- Tasking.Task#TASK --- if Task:Is( TaskStatus ) then --- TaskCount = TaskCount + 1 --- TaskPlayerCount = TaskPlayerCount + Task:GetPlayerCount() --- end --- end --- if TaskCount > 0 then --- Report:Add( string.format( " - %02d %s Tasks (%dp)", TaskCount, TaskStatus, TaskPlayerCount ) ) --- end --- end --- --- return Report:Text() ---end - - ---- Create an active player report of the Mission. --- This reports provides a one liner of the mission status. It indicates how many players and how many Tasks. --- --- Mission "" - - Active Players Report --- - Player ": Task , Task --- - Player : Task , Task --- - .. --- --- @param #MISSION self --- @return #string -function MISSION:ReportPlayersPerTask( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Players per Task Report', Name, Status ) ) - - local PlayerList = {} - - -- Determine how many tasks are remaining. - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local PlayerNames = Task:GetPlayerNames() - for PlayerName, PlayerGroup in pairs( PlayerNames ) do - PlayerList[PlayerName] = Task:GetName() - end - - end - - for PlayerName, TaskName in pairs( PlayerList ) do - Report:Add( string.format( ' - Player (%s): Task "%s"', PlayerName, TaskName ) ) - end - - return Report:Text() -end - ---- Create an Mission Progress report of the Mission. --- This reports provides a one liner per player of the mission achievements per task. --- --- Mission "" - - Active Players Report --- - Player : Task : --- - Player : Task : --- - .. --- --- @param #MISSION self --- @return #string -function MISSION:ReportPlayersProgress( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Players per Task Progress Report', Name, Status ) ) - - local PlayerList = {} - - -- Determine how many tasks are remaining. - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local TaskName = Task:GetName() - local Goal = Task:GetGoal() - PlayerList[TaskName] = PlayerList[TaskName] or {} - if Goal then - local TotalContributions = Goal:GetTotalContributions() - local PlayerContributions = Goal:GetPlayerContributions() - self:F( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } ) - for PlayerName, PlayerContribution in pairs( PlayerContributions ) do - PlayerList[TaskName][PlayerName] = string.format( 'Player (%s): Task "%s": %d%%', PlayerName, TaskName, PlayerContributions[PlayerName] * 100 / TotalContributions ) - end - else - PlayerList[TaskName]["_"] = string.format( 'Player (---): Task "%s": %d%%', TaskName, 0 ) - end - end - - for TaskName, TaskData in pairs( PlayerList ) do - for PlayerName, TaskText in pairs( TaskData ) do - Report:Add( string.format( ' - %s', TaskText ) ) - end - end - - return Report:Text() -end - - ---- Mark all the target locations on the Map. --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup --- @return #string -function MISSION:MarkTargetLocations( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - All Tasks are marked on the map. Select a Task from the Mission Menu and Join the Task!!!', Name, Status ) ) - - -- Determine how many tasks are remaining. - for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do - local Task = Task -- Tasking.Task#TASK - Task:MenuMarkToGroup( ReportGroup ) - end - - return Report:Text() -end - - ---- Create a summary report of the Mission (one line). --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup --- @return #string -function MISSION:ReportSummary( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Task Overview Report', Name, Status ) ) - - -- Determine how many tasks are remaining. - for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do - local Task = Task -- Tasking.Task#TASK - Report:Add( "- " .. Task:ReportSummary( ReportGroup ) ) - end - - return Report:Text() -end - ---- Create a overview report of the Mission (multiple lines). --- @param #MISSION self --- @return #string -function MISSION:ReportOverview( ReportGroup, TaskStatus ) - - self:F( { TaskStatus = TaskStatus } ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - %s Tasks Report', Name, Status, TaskStatus ) ) - - -- Determine how many tasks are remaining. - local Tasks = 0 - for TaskID, Task in UTILS.spairs( self:GetTasks(), function( t, a, b ) return t[a]:ReportOrder( ReportGroup ) < t[b]:ReportOrder( ReportGroup ) end ) do - local Task = Task -- Tasking.Task#TASK - if Task:Is( TaskStatus ) then - Report:Add( string.rep( "-", 140 ) ) - Report:Add( Task:ReportOverview( ReportGroup ) ) - end - Tasks = Tasks + 1 - if Tasks >= 8 then - break - end - end - - return Report:Text() -end - ---- Create a detailed report of the Mission, listing all the details of the Task. --- @param #MISSION self --- @return #string -function MISSION:ReportDetails( ReportGroup ) - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetText() - - -- Determine the status of the mission. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( string.format( '%s - %s - Task Detailed Report', Name, Status ) ) - - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Report:Add( string.rep( "-", 140 ) ) - Report:Add( Task:ReportDetails( ReportGroup ) ) - end - - return Report:Text() -end - ---- Get all the TASKs from the Mission. This function is useful in GoalFunctions. --- @return {TASK,...} Structure of TASKS with the @{Tasking.Task#TASK} number as the key. --- @usage --- -- Get Tasks from the Mission. --- Tasks = Mission:GetTasks() --- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) -function MISSION:GetTasks() - - return self.Tasks or {} -end - ---- Get the relevant tasks of a TaskGroup. --- @param #MISSION --- @param Wrapper.Group#GROUP TaskGroup --- @return #list -function MISSION:GetGroupTasks( TaskGroup ) - - local Tasks = {} - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:HasGroup( TaskGroup ) then - Tasks[#Tasks+1] = Task - end - end - - return Tasks -end - - ---- Reports the briefing. --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup The group to which the report needs to be sent. -function MISSION:MenuReportBriefing( ReportGroup ) - - local Report = self:ReportBriefing() - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Briefing ) -end - - ---- Mark all the targets of the Mission on the Map. --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuMarkTargetLocations( ReportGroup ) - - local Report = self:MarkTargetLocations( ReportGroup ) - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - - - ---- Report the task summary. --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuReportTasksSummary( ReportGroup ) - - local Report = self:ReportSummary( ReportGroup ) - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - - - - --- @param #MISSION self --- @param #string TaskStatus The status --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuReportTasksPerStatus( ReportGroup, TaskStatus ) - - local Report = self:ReportOverview( ReportGroup, TaskStatus ) - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - - --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuReportPlayersPerTask( ReportGroup ) - - local Report = self:ReportPlayersPerTask() - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - --- @param #MISSION self --- @param Wrapper.Group#GROUP ReportGroup -function MISSION:MenuReportPlayersProgress( ReportGroup ) - - local Report = self:ReportPlayersProgress() - - self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) -end - - - - - diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua deleted file mode 100644 index 452f88242..000000000 --- a/Moose Development/Moose/Tasking/Task.lua +++ /dev/null @@ -1,2058 +0,0 @@ ---- **Tasking** - A task object governs the main engine to administer human taskings. --- --- **Features:** --- --- * A base class for other task classes filling in the details and making a concrete task process. --- * Manage the overall task execution, following-up the progression made by the pilots and actors. --- * Provide a mechanism to set a task status, depending on the progress made within the task. --- * Manage a task briefing. --- * Manage the players executing the task. --- * Manage the task menu system. --- * Manage the task goal and scoring. --- --- === --- --- # 1) Tasking from a player perspective. --- --- Tasking can be controlled by using the "other" menu in the radio menu of the player group. --- --- ![Other Menu](../Tasking/Menu_Main.JPG) --- --- ## 1.1) Command Centers govern multiple Missions. --- --- Depending on the tactical situation, your coalition may have one (or multiple) command center(s). --- These command centers govern one (or multiple) mission(s). --- --- For each command center, there will be a separate **Command Center Menu** that focuses on the missions governed by that command center. --- --- ![Command Center](../Tasking/Menu_CommandCenter.JPG) --- --- In the above example menu structure, there is one command center with the name **`[Lima]`**. --- The command center has one @{Tasking.Mission}, named **`"Overlord"`** with **`High`** priority. --- --- ## 1.2) Missions govern multiple Tasks. --- --- A mission has a mission goal to be achieved by the players within the coalition. --- The mission goal is actually dependent on the tactical situation of the overall battlefield and the conditions set to achieve the goal. --- So a mission can be much more than just shoot stuff ... It can be a combination of different conditions or events to complete a mission goal. --- --- A mission can be in a specific state during the simulation run. For more information about these states, please check the @{Tasking.Mission} section. --- --- To achieve the mission goal, a mission administers @{#TASK}s that are set to achieve the mission goal by the human players. --- Each of these tasks can be **dynamically created** using a task dispatcher, or **coded** by the mission designer. --- Each mission has a separate **Mission Menu**, that focuses on the administration of these tasks. --- --- On top, a mission has a mission briefing, can help to allocate specific points of interest on the map, and provides various reports. --- --- ![Mission](../Tasking/Menu_Mission.JPG) --- --- The above shows a mission menu in detail of **`"Overlord"`**. --- --- The two other menus are related to task assignment. Which will be detailed later. --- --- ### 1.2.1) Mission briefing. --- --- The task briefing will show a message containing a description of the mission goal, and other tactical information. --- --- ![Mission](../Tasking/Report_Briefing.JPG) --- --- ### 1.2.2) Mission Map Locations. --- --- Various points of interest as part of the mission can be indicated on the map using the *Mark Task Locations on Map* menu. --- As a result, the map will contain various points of interest for the player (group). --- --- ![Mission](../Tasking/Report_Mark_Task_Location.JPG) --- --- ### 1.2.3) Mission Task Reports. --- --- Various reports can be generated on the status of each task governed within the mission. --- --- ![Mission](../Tasking/Report_Task_Summary.JPG) --- --- The Task Overview Report will show each task, with its task status and a short coordinate information. --- --- ![Mission](../Tasking/Report_Tasks_Planned.JPG) --- --- The other Task Menus will show for each task more details, for example here the planned tasks report. --- Note that the order of the tasks are shortest distance first to the unit position seated by the player. --- --- ### 1.2.4) Mission Statistics. --- --- Various statistics can be displayed regarding the mission. --- --- ![Mission](../Tasking/Report_Statistics_Progress.JPG) --- --- A statistic report on the progress of the mission. Each task achievement will increase the % to 100% as a goal to complete the task. --- --- ## 1.3) Join a Task. --- --- The mission menu contains a very important option, that is to join a task governed within the mission. --- In order to join a task, select the **Join Planned Task** menu, and a new menu will be given. --- --- ![Mission](../Tasking/Menu_Join_Planned_Tasks.JPG) --- --- A mission governs multiple tasks, as explained earlier. Each task is of a certain task type. --- This task type was introduced to have some sort of task classification system in place for the player. --- A short acronym is shown that indicates the task type. The meaning of each acronym can be found in the task types explanation. --- --- ![Mission](../Tasking/Menu_Join_Tasks.JPG) --- --- When the player selects a task type, a list of the available tasks of that type are listed... --- In this case the **`SEAD`** task type was selected and a list of available **`SEAD`** tasks can be selected. --- --- ![Mission](../Tasking/Menu_Join_Planned_Task.JPG) --- --- A new list of menu options are now displayed that allow to join the task selected, but also to obtain first some more information on the task. --- --- ### 1.3.1) Report Task Details. --- --- ![Mission](../Tasking/Report_Task_Detailed.JPG) --- --- When selected, a message is displayed that shows detailed information on the task, like the coordinate, enemy target information, threat level etc. --- --- ### 1.3.2) Mark Task Location on Map. --- --- ![Mission](../Tasking/Report_Task_Detailed.JPG) --- --- When selected, the target location on the map is indicated with specific information on the task. --- --- ### 1.3.3) Join Task. --- --- ![Mission](../Tasking/Report_Task_Detailed.JPG) --- --- By joining a task, the player will indicate that the task is assigned to him, and the task is started. --- The Command Center will communicate several task details to the player and the coalition of the player. --- --- ## 1.4) Task Control and Actions. --- --- ![Mission](../Tasking/Menu_Main_Task.JPG) --- --- When a player has joined a task, a **Task Action Menu** is available to be used by the player. --- --- ![Mission](../Tasking/Menu_Task.JPG) --- --- The task action menu contains now menu items specific to the task, but also one generic menu item, which is to control the task. --- This **Task Control Menu** allows to display again the task details and the task map location information. --- But it also allows to abort a task! --- --- Depending on the task type, the task action menu can contain more menu items which are specific to the task. --- For example, cargo transportation tasks will contain various additional menu items to select relevant cargo coordinates, --- or to load/unload cargo. --- --- ## 1.5) Automatic task assignment. --- --- ![Command Center](../Tasking/Menu_CommandCenter.JPG) --- --- When we take back the command center menu, you see two additional **Assign Task** menu items. --- The menu **Assign Task On** will automatically allocate a task to the player. --- After the selection of this menu, the menu will change into **Assign Task Off**, --- and will need to be selected again by the player to switch of the automatic task assignment. --- --- The other option is to select **Assign Task**, which will assign a new random task to the player. --- --- When a task is automatically assigned to a player, the task needs to be confirmed as accepted within 30 seconds. --- If this is not the case, the task will be cancelled automatically, and a new random task will be assigned to the player. --- This will continue to happen until the player accepts the task or switches off the automatic task assignment process. --- --- The player can accept the task using the menu **Confirm Task Acceptance** ... --- --- ## 1.6) Task states. --- --- A task has a state, reflecting the progress or completion status of the task: --- --- - **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet to a pilot. --- - **Assigned**: Expresses that the task is assigned to a group of pilots, and that the task is in execution mode. --- - **Success**: Expresses the successful execution and finalization of the task. --- - **Failed**: Expresses the failure of a task. --- - **Abort**: Expresses that the task is aborted by by the player using the abort menu. --- - **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- ### 1.6.1) Task progress. --- --- The task governor takes care of the **progress** and **completion** of the task **goal(s)**. --- Tasks are executed by **human pilots** and actors within a DCS simulation. --- Pilots can use a **menu system** to engage or abort a task, and provides means to --- understand the **task briefing** and goals, and the relevant **task locations** on the map and --- obtain **various reports** related to the task. --- --- ### 1.6.2) Task completion. --- --- As the task progresses, the **task status** will change over time, from Planned state to Completed state. --- **Multiple pilots** can execute the same task, as such, the tasking system provides a **co-operative model** for joint task execution. --- Depending on the task progress, a **scoring** can be allocated to award pilots of the achievements made. --- The scoring is fully flexible, and different levels of awarding can be provided depending on the task type and complexity. --- --- A normal flow of task status would evolve from the **Planned** state, to the **Assigned** state ending either in a **Success** or a **Failed** state. --- --- Planned -> Assigned -> Success --- -> Failed --- -> Cancelled --- --- The state completion is by default set to **Success**, if the goals of the task have been reached, but can be overruled by a goal method. --- --- Depending on the tactical situation, a task can be **Cancelled** by the mission governor. --- It is actually the mission designer who has the flexibility to decide at which conditions a task would be set to **Success**, **Failed** or **Cancelled**. --- This decision all depends on the task goals, and the phase/evolution of the task conditions that would accomplish the goals. --- --- For example, if the task goal is to merely destroy a target, and the target is mid-mission destroyed by another event than the pilot destroying the target, --- the task goal could be set to **Failed**, or .. **Cancelled** ... --- However, it could very well be also acceptable that the task would be flagged as **Success**. --- --- The tasking mechanism governs beside the progress also a scoring mechanism, and in case of goal completion without any active pilot involved --- in the execution of the task, could result in a **Success** task completion status, but no score would be awarded, as there were no players involved. --- --- These different completion states are important for the mission designer to reflect scoring to a player. --- A success could mean a positive score to be given, while a failure could mean a negative score or penalties to be awarded. --- --- # 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 --- --- === --- --- ### Author(s): **FlightControl** --- --- ### Contribution(s): --- --- === --- --- @module Tasking.Task --- @image MOOSE.JPG - ---- --- @type TASK --- @field Core.Scheduler#SCHEDULER TaskScheduler --- @field Tasking.Mission#MISSION Mission --- @field Core.Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @field Core.Fsm#FSM_PROCESS FsmTemplate --- @field Tasking.Mission#MISSION Mission --- @field Tasking.CommandCenter#COMMANDCENTER CommandCenter --- @field Tasking.TaskInfo#TASKINFO TaskInfo --- @extends Core.Fsm#FSM_TASK - ---- Governs the main engine to administer human taskings. --- --- A task is governed by a @{Tasking.Mission} object. Tasks are of different types. --- The @{#TASK} object is used or derived by more detailed tasking classes that will implement the task execution mechanisms --- and goals. --- --- # 1) Derived task classes. --- --- The following TASK_ classes are derived from @{#TASK}. --- --- TASK --- TASK_A2A --- TASK_A2A_ENGAGE --- TASK_A2A_INTERCEPT --- TASK_A2A_SWEEP --- TASK_A2G --- TASK_A2G_SEAD --- TASK_A2G_CAS --- TASK_A2G_BAI --- TASK_CARGO --- TASK_CARGO_TRANSPORT --- TASK_CARGO_CSAR --- --- ## 1.1) A2A Tasks --- --- - @{Tasking.Task_A2A#TASK_A2A_ENGAGE} - Models an A2A engage task of a target group of airborne intruders mid-air. --- - @{Tasking.Task_A2A#TASK_A2A_INTERCEPT} - Models an A2A ground intercept task of a target group of airborne intruders mid-air. --- - @{Tasking.Task_A2A#TASK_A2A_SWEEP} - Models an A2A sweep task to clean an area of previously detected intruders mid-air. --- --- ## 1.2) A2G Tasks --- --- - @{Tasking.Task_A2G#TASK_A2G_SEAD} - Models an A2G Suppression or Extermination of Air Defenses task to clean an area of air to ground defense threats. --- - @{Tasking.Task_A2G#TASK_A2G_CAS} - Models an A2G Close Air Support task to provide air support to nearby friendlies near the front-line. --- - @{Tasking.Task_A2G#TASK_A2G_BAI} - Models an A2G Battlefield Air Interdiction task to provide air support to nearby friendlies near the front-line. --- --- ## 1.3) Cargo Tasks --- --- - @{Tasking.Task_CARGO#TASK_CARGO_TRANSPORT} - Models the transportation of cargo to deployment zones. --- - @{Tasking.Task_CARGO#TASK_CARGO_CSAR} - Models the rescue of downed friendly pilots from behind enemy lines. --- --- --- # 2) Task status events. --- --- The task statuses can be set by using the following methods: --- --- - @{#TASK.Success}() - Set the task to **Success** state. --- - @{#TASK.Fail}() - Set the task to **Failed** state. --- - @{#TASK.Hold}() - Set the task to **Hold** state. --- - @{#TASK.Abort}() - Set the task to **Aborted** state, aborting the task. The task may be replanned. --- - @{#TASK.Cancel}() - Set the task to **Cancelled** state, cancelling the task. --- --- The mentioned derived TASK_ classes are implementing the task status transitions out of the box. --- So no extra logic needs to be written. --- --- # 3) Goal conditions for a task. --- --- Every 30 seconds, a @{#Task.Goal} trigger method is fired. --- You as a mission designer, can capture the **Goal** event trigger to check your own task goal conditions and take action! --- --- ## 3.1) Goal event handler `OnAfterGoal()`. --- --- And this is a really great feature! Imagine a task which has **several conditions to check** before the task can move into **Success** state. --- You can do this with the OnAfterGoal method. --- --- The following code provides an example of such a goal condition check implementation. --- --- function Task:OnAfterGoal() --- if condition == true then --- self:Success() -- This will flag the task to Success when the condition is true. --- else --- if condition2 == true and condition3 == true then --- self:Fail() -- This will flag the task to Failed, when condition2 and condition3 would be true. --- end --- end --- end --- --- So the @{#TASK.OnAfterGoal}() event handler would be called every 30 seconds automatically, --- and within this method, you can now check the conditions and take respective action. --- --- ## 3.2) Goal event trigger `Goal()`. --- --- If you would need to check a goal at your own defined event timing, then just call the @{#TASK.Goal}() method within your logic. --- The @{#TASK.OnAfterGoal}() event handler would then directly be called and would execute the logic. --- Note that you can also delay the goal check by using the delayed event trigger syntax `:__Goal( Delay )`. --- --- --- # 4) Score task completion. --- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK.AddScore}() to add scores when a status is reached. --- --- # 5) Task briefing. --- --- A task briefing is a text that is shown to the player when he is assigned to the task. --- The briefing is broadcasted by the command center owning the mission. --- --- The briefing is part of the parameters in the @{#TASK.New}() constructor, --- but can separately be modified later in your mission using the --- @{#TASK.SetBriefing}() method. --- --- --- @field #TASK TASK --- -TASK = { - ClassName = "TASK", - TaskScheduler = nil, - ProcessClasses = {}, -- The container of the Process classes that will be used to create and assign new processes for the task to ProcessUnits. - Processes = {}, -- The container of actual process objects instantiated and assigned to ProcessUnits. - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, - FsmTemplate = nil, - Mission = nil, - CommandCenter = nil, - TimeOut = 0, - AssignedGroups = {}, -} - ---- FSM PlayerAborted event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerAborted --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he went back to spectators or left the mission. --- @param #string PlayerName The name of the Player. - ---- FSM PlayerCrashed event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerCrashed --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he crashed in the mission. --- @param #string PlayerName The name of the Player. - ---- FSM PlayerDead event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerDead --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he died in the mission. --- @param #string PlayerName The name of the Player. - ---- FSM Fail synchronous event function for TASK. --- Use this event to Fail the Task. --- @function [parent=#TASK] Fail --- @param #TASK self - ---- FSM Fail asynchronous event function for TASK. --- Use this event to Fail the Task. --- @function [parent=#TASK] __Fail --- @param #TASK self - ---- FSM Abort synchronous event function for TASK. --- Use this event to Abort the Task. --- @function [parent=#TASK] Abort --- @param #TASK self - ---- FSM Abort asynchronous event function for TASK. --- Use this event to Abort the Task. --- @function [parent=#TASK] __Abort --- @param #TASK self - ---- FSM Success synchronous event function for TASK. --- Use this event to make the Task a Success. --- @function [parent=#TASK] Success --- @param #TASK self - ---- FSM Success asynchronous event function for TASK. --- Use this event to make the Task a Success. --- @function [parent=#TASK] __Success --- @param #TASK self - ---- FSM Cancel synchronous event function for TASK. --- Use this event to Cancel the Task. --- @function [parent=#TASK] Cancel --- @param #TASK self - ---- FSM Cancel asynchronous event function for TASK. --- Use this event to Cancel the Task. --- @function [parent=#TASK] __Cancel --- @param #TASK self - ---- FSM Replan synchronous event function for TASK. --- Use this event to Replan the Task. --- @function [parent=#TASK] Replan --- @param #TASK self - ---- FSM Replan asynchronous event function for TASK. --- Use this event to Replan the Task. --- @function [parent=#TASK] __Replan --- @param #TASK self - - ---- Instantiates a new TASK. Should never be used. Interface Class. --- @param #TASK self --- @param Tasking.Mission#MISSION Mission The mission wherein the Task is registered. --- @param Core.Set#SET_GROUP SetGroupAssign The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @return #TASK self -function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing ) - - local self = BASE:Inherit( self, FSM_TASK:New( TaskName ) ) -- Tasking.Task#TASK - - self:SetStartState( "Planned" ) - self:AddTransition( "Planned", "Assign", "Assigned" ) - self:AddTransition( "Assigned", "AssignUnit", "Assigned" ) - self:AddTransition( "Assigned", "Success", "Success" ) - self:AddTransition( "Assigned", "Hold", "Hold" ) - self:AddTransition( "Assigned", "Fail", "Failed" ) - self:AddTransition( { "Planned", "Assigned" }, "Abort", "Aborted" ) - self:AddTransition( "Assigned", "Cancel", "Cancelled" ) - self:AddTransition( "Assigned", "Goal", "*" ) - - self.Fsm = {} - - local Fsm = self:GetUnitProcess() - Fsm:SetStartState( "Planned" ) - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "Assigned", Rejected = "Reject" } ) - Fsm:AddTransition( "Assigned", "Assigned", "*" ) - - --- Goal Handler OnBefore for TASK - -- @function [parent=#TASK] OnBeforeGoal - -- @param #TASK self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. - -- @param #string PlayerName The name of the player. - -- @return #boolean - - --- Goal Handler OnAfter for TASK - -- @function [parent=#TASK] OnAfterGoal - -- @param #TASK self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. - -- @param #string PlayerName The name of the player. - - --- Goal Trigger for TASK - -- @function [parent=#TASK] Goal - -- @param #TASK self - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. - -- @param #string PlayerName The name of the player. - - --- Goal Asynchronous Trigger for TASK - -- @function [parent=#TASK] __Goal - -- @param #TASK self - -- @param #number Delay - -- @param Wrapper.Unit#UNIT PlayerUnit The @{Wrapper.Unit} of the player. - -- @param #string PlayerName The name of the player. - - - - self:AddTransition( "*", "PlayerCrashed", "*" ) - self:AddTransition( "*", "PlayerAborted", "*" ) - self:AddTransition( "*", "PlayerRejected", "*" ) - self:AddTransition( "*", "PlayerDead", "*" ) - self:AddTransition( { "Failed", "Aborted", "Cancelled" }, "Replan", "Planned" ) - self:AddTransition( "*", "TimeOut", "Cancelled" ) - - self:F( "New TASK " .. TaskName ) - - self.Processes = {} - - self.Mission = Mission - self.CommandCenter = Mission:GetCommandCenter() - - self.SetGroup = SetGroupAssign - - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self:SetBriefing( TaskBriefing ) - - - self.TaskInfo = TASKINFO:New( self ) - - self.TaskProgress = {} - - return self -end - ---- Get the Task FSM Process Template --- @param #TASK self --- @return Core.Fsm#FSM_PROCESS -function TASK:GetUnitProcess( TaskUnit ) - - if TaskUnit then - return self:GetStateMachine( TaskUnit ) - else - self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() - return self.FsmTemplate - end -end - ---- Sets the Task FSM Process Template --- @param #TASK self --- @param Core.Fsm#FSM_PROCESS -function TASK:SetUnitProcess( FsmTemplate ) - - self.FsmTemplate = FsmTemplate -end - ---- Add a PlayerUnit to join the Task. --- For each Group within the Task, the Unit is checked if it can join the Task. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. --- @return #boolean true if Unit is part of the Task. -function TASK:JoinUnit( PlayerUnit, PlayerGroup ) - self:F( { PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) - - local PlayerUnitAdded = false - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is added to the Task. - -- If the PlayerGroup is not assigned to the Task, the menu needs to be set. In that case, the PlayerUnit will become the GroupPlayer leader. - if self:IsStatePlanned() or self:IsStateReplanned() then - --self:SetMenuForGroup( PlayerGroup ) - --self:MessageToGroups( PlayerUnit:GetPlayerName() .. " is planning to join Task " .. self:GetName() ) - end - if self:IsStateAssigned() then - local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup ) - self:F( { IsGroupAssigned = IsGroupAssigned } ) - if IsGroupAssigned then - self:AssignToUnit( PlayerUnit ) - self:MessageToGroups( PlayerUnit:GetPlayerName() .. " joined Task " .. self:GetName() ) - end - end - end - - return PlayerUnitAdded -end - ---- A group rejecting a planned task. --- @param #TASK self --- @param Wrapper.Group#GROUP PlayerGroup The group rejecting the task. --- @return #TASK -function TASK:RejectGroup( PlayerGroup ) - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned or is planned to be assigned to the Task. - -- If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStatePlanned() then - - local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup ) - if IsGroupAssigned then - local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName() - self:GetMission():GetCommandCenter():MessageToGroup( "Task " .. self:GetName() .. " has been rejected! We will select another task.", PlayerGroup ) - self:UnAssignFromGroup( PlayerGroup ) - - self:PlayerRejected( PlayerGroup:GetUnit(1) ) - end - - end - end - - return self -end - - ---- A group aborting the task. --- @param #TASK self --- @param Wrapper.Group#GROUP PlayerGroup The group aborting the task. --- @return #TASK -function TASK:AbortGroup( PlayerGroup ) - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned or is planned to be assigned to the Task. - -- If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStateAssigned() then - - local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup ) - if IsGroupAssigned then - local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName() - self:UnAssignFromGroup( PlayerGroup ) - - -- Now check if the task needs to go to hold... - -- It will go to hold, if there are no players in the mission... - PlayerGroups:Flush( self ) - local IsRemaining = false - for GroupName, AssignedGroup in pairs( PlayerGroups:GetSet() or {} ) do - if self:IsGroupAssigned( AssignedGroup ) == true then - IsRemaining = true - self:F( { Task = self:GetName(), IsRemaining = IsRemaining } ) - break - end - end - - self:F( { Task = self:GetName(), IsRemaining = IsRemaining } ) - if IsRemaining == false then - self:Abort() - end - - self:PlayerAborted( PlayerGroup:GetUnit(1) ) - end - - end - end - - return self -end - - ---- A group crashing and thus aborting from the task. --- @param #TASK self --- @param Wrapper.Group#GROUP PlayerGroup The group aborting the task. --- @return #TASK -function TASK:CrashGroup( PlayerGroup ) - self:F( { PlayerGroup = PlayerGroup } ) - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStateAssigned() then - local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup ) - self:F( { IsGroupAssigned = IsGroupAssigned } ) - if IsGroupAssigned then - local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName() - self:MessageToGroups( PlayerName .. " crashed! " ) - self:UnAssignFromGroup( PlayerGroup ) - - -- Now check if the task needs to go to hold... - -- It will go to hold, if there are no players in the mission... - - PlayerGroups:Flush( self ) - local IsRemaining = false - for GroupName, AssignedGroup in pairs( PlayerGroups:GetSet() or {} ) do - if self:IsGroupAssigned( AssignedGroup ) == true then - IsRemaining = true - self:F( { Task = self:GetName(), IsRemaining = IsRemaining } ) - break - end - end - - self:F( { Task = self:GetName(), IsRemaining = IsRemaining } ) - if IsRemaining == false then - self:Abort() - end - - self:PlayerCrashed( PlayerGroup:GetUnit(1) ) - end - - end - end - - return self -end - - - ---- Gets the Mission to where the TASK belongs. --- @param #TASK self --- @return Tasking.Mission#MISSION -function TASK:GetMission() - - return self.Mission -end - - ---- Gets the SET_GROUP assigned to the TASK. --- @param #TASK self --- @return Core.Set#SET_GROUP -function TASK:GetGroups() - - return self.SetGroup -end - - ---- Gets the SET_GROUP assigned to the TASK. --- @param #TASK self --- @param Core.Set#SET_GROUP GroupSet --- @return Core.Set#SET_GROUP -function TASK:AddGroups( GroupSet ) - - GroupSet = GroupSet or SET_GROUP:New() - - self.SetGroup:ForEachGroup( - -- @param Wrapper.Group#GROUP GroupSet - function( GroupItem ) - GroupSet:Add( GroupItem:GetName(), GroupItem) - end - ) - - return GroupSet -end - -do -- Group Assignment - - --- Returns if the @{#TASK} is assigned to the Group. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - -- @return #boolean - function TASK:IsGroupAssigned( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self.AssignedGroups[TaskGroupName] then - --self:T( { "Task is assigned to:", TaskGroup:GetName() } ) - return true - end - - --self:T( { "Task is not assigned to:", TaskGroup:GetName() } ) - return false - end - - - --- Set @{Wrapper.Group} assigned to the @{#TASK}. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - -- @return #TASK - function TASK:SetGroupAssigned( TaskGroup ) - - local TaskName = self:GetName() - local TaskGroupName = TaskGroup:GetName() - - self.AssignedGroups[TaskGroupName] = TaskGroup - self:F( string.format( "Task %s is assigned to %s", TaskName, TaskGroupName ) ) - - -- Set the group to be assigned at mission level. This allows to decide the menu options on mission level for this group. - self:GetMission():SetGroupAssigned( TaskGroup ) - - local SetAssignedGroups = self:GetGroups() - --- SetAssignedGroups:ForEachGroup( --- function( AssignedGroup ) --- if self:IsGroupAssigned(AssignedGroup) then --- self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is assigned to group %s.", TaskName, TaskGroupName ), AssignedGroup ) --- else --- self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is assigned to your group.", TaskName ), AssignedGroup ) --- end --- end --- ) - - return self - end - - --- Clear the @{Wrapper.Group} assignment from the @{#TASK}. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - -- @return #TASK - function TASK:ClearGroupAssignment( TaskGroup ) - - local TaskName = self:GetName() - local TaskGroupName = TaskGroup:GetName() - - self.AssignedGroups[TaskGroupName] = nil - --self:F( string.format( "Task %s is unassigned to %s", TaskName, TaskGroupName ) ) - - -- Set the group to be assigned at mission level. This allows to decide the menu options on mission level for this group. - self:GetMission():ClearGroupAssignment( TaskGroup ) - - local SetAssignedGroups = self:GetGroups() - - SetAssignedGroups:ForEachGroup( - function( AssignedGroup ) - if self:IsGroupAssigned(AssignedGroup) then - --self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is unassigned from group %s.", TaskName, TaskGroupName ), AssignedGroup ) - else - --self:GetMission():GetCommandCenter():MessageToGroup( string.format( "Task %s is unassigned from your group.", TaskName ), AssignedGroup ) - end - end - ) - - return self - end - -end - -do -- Group Assignment - - -- @param #TASK self - -- @param Actions.Act_Assign#ACT_ASSIGN AcceptClass - function TASK:SetAssignMethod( AcceptClass ) - - local ProcessTemplate = self:GetUnitProcess() - - ProcessTemplate:SetProcess( "Planned", "Accept", AcceptClass ) -- Actions.Act_Assign#ACT_ASSIGN - end - - - --- Assign the @{#TASK} to a @{Wrapper.Group}. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - -- @return #TASK - function TASK:AssignToGroup( TaskGroup ) - self:F( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - local Mission = self:GetMission() - local CommandCenter = Mission:GetCommandCenter() - - self:SetGroupAssigned( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - self:F(PlayerName) - if PlayerName ~= nil and PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - CommandCenter:MessageToGroup( - string.format( 'Task "%s": Briefing for player (%s):\n%s', - self:GetName(), - PlayerName, - self:GetBriefing() - ), TaskGroup - ) - end - end - - CommandCenter:SetMenu() - - self:MenuFlashTaskStatus( TaskGroup, self:GetMission():GetCommandCenter().FlashStatus ) - - return self - end - - --- UnAssign the @{#TASK} from a @{Wrapper.Group}. - -- @param #TASK self - -- @param Wrapper.Group#GROUP TaskGroup - function TASK:UnAssignFromGroup( TaskGroup ) - self:F2( { TaskGroup = TaskGroup:GetName() } ) - - self:ClearGroupAssignment( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil and PlayerName ~= "" then -- Only remove units that have players! - self:UnAssignFromUnit( TaskUnit ) - end - end - - local Mission = self:GetMission() - local CommandCenter = Mission:GetCommandCenter() - CommandCenter:SetMenu() - - self:MenuFlashTaskStatus( TaskGroup, false ) -- stop message flashing, if any #1383 & #1312 - - end -end - - ---- --- @param #TASK self --- @param Wrapper.Group#GROUP FindGroup --- @return #boolean -function TASK:HasGroup( FindGroup ) - - local SetAttackGroup = self:GetGroups() - return SetAttackGroup:FindGroup( FindGroup:GetName() ) - -end - ---- Assign the @{#TASK} to an alive @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local FsmTemplate = self:GetUnitProcess() - - -- Assign a new FsmUnit to TaskUnit. - local FsmUnit = self:SetStateMachine( TaskUnit, FsmTemplate:Copy( TaskUnit, self ) ) -- Core.Fsm#FSM_PROCESS - - FsmUnit:SetStartState( "Planned" ) - - FsmUnit:Accept() -- Each Task needs to start with an Accept event to start the flow. - - return self -end - ---- UnAssign the @{#TASK} from an alive @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:UnAssignFromUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - self:RemoveStateMachine( TaskUnit ) - - -- If a Task Control Menu had been set, then this will be removed. - self:RemoveTaskControlMenu( TaskUnit ) - return self -end - ---- Sets the TimeOut for the @{#TASK}. If @{#TASK} stayed planned for longer than TimeOut, it gets into Cancelled status. --- @param #TASK self --- @param #integer Timer in seconds --- @return #TASK self -function TASK:SetTimeOut ( Timer ) - self:F( Timer ) - self.TimeOut = Timer - self:__TimeOut( self.TimeOut ) - return self -end - ---- Send a message of the @{#TASK} to the assigned @{Wrapper.Group}s. --- @param #TASK self -function TASK:MessageToGroups( Message ) - self:F( { Message = Message } ) - - local Mission = self:GetMission() - local CC = Mission:GetCommandCenter() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - TaskGroup = TaskGroup -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() == true then - CC:MessageToGroup( Message, TaskGroup, TaskGroup:GetName() ) - end - end -end - - ---- Send the briefing message of the @{#TASK} to the assigned @{Wrapper.Group}s. --- @param #TASK self -function TASK:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if TaskGroup:IsAlive() then - if self:IsGroupAssigned( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end - end -end - - ---- UnAssign the @{#TASK} from the @{Wrapper.Group}s. --- @param #TASK self -function TASK:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if TaskGroup:IsAlive() == true then - if self:IsGroupAssigned(TaskGroup) then - self:UnAssignFromGroup( TaskGroup ) - end - end - end -end - - - ---- Returns if the @{#TASK} has still alive and assigned Units. --- @param #TASK self --- @return #boolean -function TASK:HasAliveUnits() - self:F() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if TaskGroup:IsAlive() == true then - if self:IsStateAssigned() then - if self:IsGroupAssigned( TaskGroup ) then - for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do - if TaskUnit:IsAlive() then - self:T( { HasAliveUnits = true } ) - return true - end - end - end - end - end - end - - self:T( { HasAliveUnits = false } ) - return false -end - ---- Set the menu options of the @{#TASK} to all the groups in the SetGroup. --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:SetMenu( MenuTime ) --R2.1 Mission Reports and Task Reports added. Fixes issue #424. - self:F( { self:GetName(), MenuTime } ) - - --self.SetGroup:Flush() - --for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetAliveSet() ) do - for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetSet() ) do - local TaskGroup = TaskGroupData -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() == true and TaskGroup:GetPlayerNames() then - - -- Set Mission Menus - - local Mission = self:GetMission() - local MissionMenu = Mission:GetMenu( TaskGroup ) - if MissionMenu then - self:SetMenuForGroup( TaskGroup, MenuTime ) - end - end - end -end - - - ---- Set the Menu for a Group --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:SetMenuForGroup( TaskGroup, MenuTime ) - - if self:IsStatePlanned() or self:IsStateAssigned() then - self:SetPlannedMenuForGroup( TaskGroup, MenuTime ) - if self:IsGroupAssigned( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup, MenuTime ) - end - end -end - - ---- Set the planned menu option of the @{#TASK}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @param #number MenuTime --- @return #TASK self -function TASK:SetPlannedMenuForGroup( TaskGroup, MenuTime ) - self:F( TaskGroup:GetName() ) - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local MissionMenu = Mission:GetMenu( TaskGroup ) - - local TaskType = self:GetType() - local TaskPlayerCount = self:GetPlayerCount() - local TaskPlayerString = string.format( " (%dp)", TaskPlayerCount ) - local TaskText = string.format( "%s", self:GetName() ) - local TaskName = string.format( "%s", self:GetName() ) - - self.MenuPlanned = self.MenuPlanned or {} - self.MenuPlanned[TaskGroup] = MENU_GROUP_DELAYED:New( TaskGroup, "Join Planned Task", MissionMenu, Mission.MenuReportTasksPerStatus, Mission, TaskGroup, "Planned" ):SetTime( MenuTime ):SetTag( "Tasking" ) - local TaskTypeMenu = MENU_GROUP_DELAYED:New( TaskGroup, TaskType, self.MenuPlanned[TaskGroup] ):SetTime( MenuTime ):SetTag( "Tasking" ) - local TaskTypeMenu = MENU_GROUP_DELAYED:New( TaskGroup, TaskText, TaskTypeMenu ):SetTime( MenuTime ):SetTag( "Tasking" ) - - if not Mission:IsGroupAssigned( TaskGroup ) then - --self:F( { "Replacing Join Task menu" } ) - local JoinTaskMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Join Task" ), TaskTypeMenu, self.MenuAssignToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - local MarkTaskMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Mark Task Location on Map" ), TaskTypeMenu, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - end - - local ReportTaskMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Report Task Details" ), TaskTypeMenu, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - - return self -end - ---- Set the assigned menu options of the @{#TASK}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime ) - self:F( { TaskGroup:GetName(), MenuTime } ) - - local TaskType = self:GetType() - local TaskPlayerCount = self:GetPlayerCount() - local TaskPlayerString = string.format( " (%dp)", TaskPlayerCount ) - local TaskText = string.format( "%s%s", self:GetName(), TaskPlayerString ) --, TaskThreatLevelString ) - local TaskName = string.format( "%s", self:GetName() ) - - for UnitName, TaskUnit in pairs( TaskGroup:GetPlayerUnits() ) do - local TaskUnit = TaskUnit -- Wrapper.Unit#UNIT - if TaskUnit then - local MenuControl = self:GetTaskControlMenu( TaskUnit ) - local TaskControl = MENU_GROUP:New( TaskGroup, "Control Task", MenuControl ):SetTime( MenuTime ):SetTag( "Tasking" ) - if self:IsStateAssigned() then - local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Abort Task" ), TaskControl, self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - end - local MarkMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Mark Task Location on Map" ), TaskControl, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Details" ), TaskControl, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) - if not self.FlashTaskStatus then - local TaskFlashStatusMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Flash Task Details" ), TaskControl, self.MenuFlashTaskStatus, self, TaskGroup, true ):SetTime( MenuTime ):SetTag( "Tasking" ) - else - local TaskFlashStatusMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Stop Flash Task Details" ), TaskControl, self.MenuFlashTaskStatus, self, TaskGroup, nil ):SetTime( MenuTime ):SetTag( "Tasking" ) - end - end - end - - return self -end - ---- Remove the menu options of the @{#TASK} to all the groups in the SetGroup. --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:RemoveMenu( MenuTime ) - self:F( { self:GetName(), MenuTime } ) - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if TaskGroup:IsAlive() == true then - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() == true and TaskGroup:GetPlayerNames() then - self:RefreshMenus( TaskGroup, MenuTime ) - end - end - end -end - - ---- Remove the menu option of the @{#TASK} for a @{Wrapper.Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:RefreshMenus( TaskGroup, MenuTime ) - self:F( { TaskGroup:GetName(), MenuTime } ) - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local MissionMenu = Mission:GetMenu( TaskGroup ) - - local TaskName = self:GetName() - self.MenuPlanned = self.MenuPlanned or {} - local PlannedMenu = self.MenuPlanned[TaskGroup] - - self.MenuAssigned = self.MenuAssigned or {} - local AssignedMenu = self.MenuAssigned[TaskGroup] - - if PlannedMenu then - self.MenuPlanned[TaskGroup] = PlannedMenu:Remove( MenuTime , "Tasking" ) - PlannedMenu:Set() - end - - if AssignedMenu then - self.MenuAssigned[TaskGroup] = AssignedMenu:Remove( MenuTime, "Tasking" ) - AssignedMenu:Set() - end - -end - ---- Remove the assigned menu option of the @{#TASK} for a @{Wrapper.Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:RemoveAssignedMenuForGroup( TaskGroup ) - self:F() - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local MissionMenu = Mission:GetMenu( TaskGroup ) - - if MissionMenu then - MissionMenu:RemoveSubMenus() - end - -end - --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup -function TASK:MenuAssignToGroup( TaskGroup ) - - self:F( "Join Task menu selected") - - self:AssignToGroup( TaskGroup ) -end - --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup -function TASK:MenuMarkToGroup( TaskGroup ) - self:F() - - self:UpdateTaskInfo( self.DetectedItem ) - - local TargetCoordinates = self.TaskInfo:GetData( "Coordinates" ) -- Core.Point#COORDINATE - if TargetCoordinates then - for TargetCoordinateID, TargetCoordinate in pairs( TargetCoordinates ) do - local Report = REPORT:New():SetIndent( 0 ) - self.TaskInfo:Report( Report, "M", TaskGroup, self ) - local MarkText = Report:Text( ", " ) - self:F( { Coordinate = TargetCoordinate, MarkText = MarkText } ) - TargetCoordinate:MarkToGroup( MarkText, TaskGroup ) - --Coordinate:MarkToAll( Briefing ) - end - else - local TargetCoordinate = self.TaskInfo:GetData( "Coordinate" ) -- Core.Point#COORDINATE - if TargetCoordinate then - local Report = REPORT:New():SetIndent( 0 ) - self.TaskInfo:Report( Report, "M", TaskGroup, self ) - local MarkText = Report:Text( ", " ) - self:F( { Coordinate = TargetCoordinate, MarkText = MarkText } ) - TargetCoordinate:MarkToGroup( MarkText, TaskGroup ) - end - end - -end - ---- Report the task status. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup -function TASK:MenuTaskStatus( TaskGroup ) - - if TaskGroup:IsAlive() then - - local ReportText = self:ReportDetails( TaskGroup ) - - self:T( ReportText ) - self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed ) - end - -end - ---- Report the task status. --- @param #TASK self -function TASK:MenuFlashTaskStatus( TaskGroup, Flash ) - - self.FlashTaskStatus = Flash - - if self.FlashTaskStatus then - self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60) --Issue #1383 never ending flash messages - else - if self.FlashTaskScheduler then - self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID ) - self.FlashTaskScheduler = nil - self.FlashTaskScheduleID = nil - end - end - -end - ---- Report the task status. --- @param #TASK self -function TASK:MenuTaskAbort( TaskGroup ) - - self:AbortGroup( TaskGroup ) -end - - - ---- Returns the @{#TASK} name. --- @param #TASK self --- @return #string TaskName -function TASK:GetTaskName() - return self.TaskName -end - ---- Returns the @{#TASK} briefing. --- @param #TASK self --- @return #string Task briefing. -function TASK:GetTaskBriefing() - return self.TaskBriefing -end - - - - ---- Get the default or currently assigned @{Core.Fsm#FSM_PROCESS} template with key ProcessName. --- @param #TASK self --- @param #string ProcessName --- @return Core.Fsm#FSM_PROCESS -function TASK:GetProcessTemplate( ProcessName ) - - local ProcessTemplate = self.ProcessClasses[ProcessName] - - return ProcessTemplate -end - - - --- TODO: Obsolete? ---- Fail processes from @{#TASK} with key @{Wrapper.Unit}. --- @param #TASK self --- @param #string TaskUnitName --- @return #TASK self -function TASK:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{#TASK} with key @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @param Core.Fsm#FSM_PROCESS Fsm --- @return #TASK self -function TASK:SetStateMachine( TaskUnit, Fsm ) - self:F2( { TaskUnit, self.Fsm[TaskUnit] ~= nil, Fsm:GetClassNameAndID() } ) - - self.Fsm[TaskUnit] = Fsm - - return Fsm -end - ---- Gets the FiniteStateMachine of @{#TASK} with key @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return Core.Fsm#FSM_PROCESS -function TASK:GetStateMachine( TaskUnit ) - self:F2( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - return self.Fsm[TaskUnit] -end - ---- Remove FiniteStateMachines from @{#TASK} with key @{Wrapper.Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:RemoveStateMachine( TaskUnit ) - self:F( { TaskUnit = TaskUnit:GetName(), HasFsm = ( self.Fsm[TaskUnit] ~= nil ) } ) - - --self:F( self.Fsm ) - --for TaskUnitT, Fsm in pairs( self.Fsm ) do - --local Fsm = Fsm -- Core.Fsm#FSM_PROCESS - --self:F( TaskUnitT ) - --self.Fsm[TaskUnit] = nil - --end - - if self.Fsm[TaskUnit] then - self.Fsm[TaskUnit]:Remove() - self.Fsm[TaskUnit] = nil - end - - collectgarbage() - self:F( "Garbage Collected, Processes should be finalized now ...") -end - - ---- Checks if there is a FiniteStateMachine assigned to @{Wrapper.Unit} for @{#TASK}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:HasStateMachine( TaskUnit ) - self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - return ( self.Fsm[TaskUnit] ~= nil ) -end - - ---- Gets the Scoring of the task --- @param #TASK self --- @return Functional.Scoring#SCORING Scoring -function TASK:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task type, the Task name. --- @param #TASK self --- @return #string The Task ID -function TASK:GetTaskIndex() - - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK self --- @param #string TaskName -function TASK:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK self --- @return #string The Task Name -function TASK:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK self --- @param #string TaskType -function TASK:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK self --- @return #string TaskType -function TASK:GetType() - return self.TaskType -end - ---- Sets the ID of the Task --- @param #TASK self --- @param #string TaskID -function TASK:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK self --- @return #string TaskID -function TASK:GetID() - return self.TaskID -end - - ---- Sets a @{#TASK} to status **Success**. --- @param #TASK self -function TASK:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{#TASK} status **Success**. --- @param #TASK self -function TASK:IsStateSuccess() - return self:Is( "Success" ) -end - ---- Sets a @{#TASK} to status **Failed**. --- @param #TASK self -function TASK:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{#TASK} status **Failed**. --- @param #TASK self -function TASK:IsStateFailed() - return self:Is( "Failed" ) -end - ---- Sets a @{#TASK} to status **Planned**. --- @param #TASK self -function TASK:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{#TASK} status **Planned**. --- @param #TASK self -function TASK:IsStatePlanned() - return self:Is( "Planned" ) -end - ---- Sets a @{#TASK} to status **Aborted**. --- @param #TASK self -function TASK:StateAborted() - self:SetState( self, "State", "Aborted" ) - return self -end - ---- Is the @{#TASK} status **Aborted**. --- @param #TASK self -function TASK:IsStateAborted() - return self:Is( "Aborted" ) -end - ---- Sets a @{#TASK} to status **Cancelled**. --- @param #TASK self -function TASK:StateCancelled() - self:SetState( self, "State", "Cancelled" ) - return self -end - ---- Is the @{#TASK} status **Cancelled**. --- @param #TASK self -function TASK:IsStateCancelled() - return self:Is( "Cancelled" ) -end - ---- Sets a @{#TASK} to status **Assigned**. --- @param #TASK self -function TASK:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{#TASK} status **Assigned**. --- @param #TASK self -function TASK:IsStateAssigned() - return self:Is( "Assigned" ) -end - ---- Sets a @{#TASK} to status **Hold**. --- @param #TASK self -function TASK:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{#TASK} status **Hold**. --- @param #TASK self -function TASK:IsStateHold() - return self:Is( "Hold" ) -end - ---- Sets a @{#TASK} to status **Replanned**. --- @param #TASK self -function TASK:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{#TASK} status **Replanned**. --- @param #TASK self -function TASK:IsStateReplanned() - return self:Is( "Replanned" ) -end - ---- Gets the @{#TASK} status. --- @param #TASK self -function TASK:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{#TASK} briefing. --- @param #TASK self --- @param #string TaskBriefing --- @return #TASK self -function TASK:SetBriefing( TaskBriefing ) - self:F(TaskBriefing) - self.TaskBriefing = TaskBriefing - return self -end - ---- Gets the @{#TASK} briefing. --- @param #TASK self --- @return #string The briefing text. -function TASK:GetBriefing() - return self.TaskBriefing -end - - - - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterAssigned( From, Event, To, PlayerUnit, PlayerName ) - - --- This test is required, because the state transition will be fired also when the state does not change in case of an event. - if From ~= "Assigned" then - - local PlayerNames = self:GetPlayerNames() - local PlayerText = REPORT:New() - for PlayerName, TaskName in pairs( PlayerNames ) do - PlayerText:Add( PlayerName ) - end - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " is assigned to players " .. PlayerText:Text(",") .. ". Good Luck!" ) - - -- Set the total Progress to be achieved. - self:SetGoalTotal() -- Polymorphic to set the initial goal total! - - if self.Dispatcher then - self:F( "Firing Assign event " ) - self.Dispatcher:Assign( self, PlayerUnit, PlayerName ) - end - - self:GetMission():__Start( 1 ) - - -- When the task is assigned, the task goal needs to be checked of the derived classes. - self:__Goal( -10, PlayerUnit, PlayerName ) -- Polymorphic - - self:SetMenu() - - self:F( { "--> Task Assigned", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "--> Task Player Names", PlayerNames = PlayerNames } ) - - end -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterSuccess( From, Event, To ) - - self:F( { "<-> Task Replanned", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "<-> Task Player Names", PlayerNames = self:GetPlayerNames() } ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " is successful! Good job!" ) - self:UnAssignFromGroups() - - self:GetMission():__MissionGoals( 1 ) - -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterAborted( From, Event, To ) - - self:F( { "<-- Task Aborted", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "<-- Task Player Names", PlayerNames = self:GetPlayerNames() } ) - - if From ~= "Aborted" then - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been aborted! Task may be replanned." ) - self:__Replan( 5 ) - self:SetMenu() - end - -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterCancelled( From, Event, To ) - - self:F( { "<-- Task Cancelled", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "<-- Player Names", PlayerNames = self:GetPlayerNames() } ) - - if From ~= "Cancelled" then - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been cancelled! The tactical situation has changed." ) - self:UnAssignFromGroups() - self:SetMenu() - end - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onafterReplan( From, Event, To ) - - self:F( { "Task Replanned", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "Task Player Names", PlayerNames = self:GetPlayerNames() } ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Replanning Task " .. self:GetName() .. "." ) - - self:SetMenu() - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterFailed( From, Event, To ) - - self:F( { "Task Failed", TaskName = self:GetName(), Mission = self:GetMission():GetName() } ) - self:F( { "Task Player Names", PlayerNames = self:GetPlayerNames() } ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has failed!" ) - - self:UnAssignFromGroups() -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onstatechange( From, Event, To ) - - if self:IsTrace() then - --MESSAGE:New( "@ Task " .. self.TaskName .. " : " .. From .. " changed to " .. To .. " by " .. Event, 2 ):ToAll() - end - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - self:F( { self.Scores[To].ScoreText, self.Scores[To].Score } ) - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterPlanned( From, Event, To) - if not self.TimeOut == 0 then - self.__TimeOut( self.TimeOut ) - end -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onbeforeTimeOut( From, Event, To ) - if From == "Planned" then - self:RemoveMenu() - return true - end - return false -end - -do -- Links - - --- Set goal of a task - -- @param #TASK self - -- @param Core.Goal#GOAL Goal - -- @return #TASK - function TASK:SetGoal( Goal ) - self.Goal = Goal - end - - - --- Get goal of a task - -- @param #TASK self - -- @return Core.Goal#GOAL The Goal - function TASK:GetGoal() - return self.Goal - end - - - --- Set dispatcher of a task - -- @param #TASK self - -- @param Tasking.DetectionManager#DETECTION_MANAGER Dispatcher - -- @return #TASK - function TASK:SetDispatcher( Dispatcher ) - self.Dispatcher = Dispatcher - end - - --- Set detection of a task - -- @param #TASK self - -- @param Functional.Detection#DETECTION_BASE Detection - -- @param DetectedItem - -- @return #TASK - function TASK:SetDetection( Detection, DetectedItem ) - - self:F( { DetectedItem, Detection } ) - - self.Detection = Detection - self.DetectedItem = DetectedItem - end - -end - -do -- Reporting - ---- Create a summary report of the Task. --- List the Task Name and Status --- @param #TASK self --- @param Wrapper.Group#GROUP ReportGroup --- @return #string -function TASK:ReportSummary( ReportGroup ) - - self:UpdateTaskInfo( self.DetectedItem ) - - local Report = REPORT:New() - - -- List the name of the Task. - Report:Add( "Task " .. self:GetName() ) - - -- Determine the status of the Task. - Report:Add( "State: <" .. self:GetState() .. ">" ) - - self.TaskInfo:Report( Report, "S", ReportGroup, self ) - - return Report:Text( ', ' ) -end - ---- Create an overiew report of the Task. --- List the Task Name and Status --- @param #TASK self --- @return #string -function TASK:ReportOverview( ReportGroup ) - - self:UpdateTaskInfo( self.DetectedItem ) - - -- List the name of the Task. - local TaskName = self:GetName() - local Report = REPORT:New() - - self.TaskInfo:Report( Report, "O", ReportGroup, self ) - - return Report:Text() -end - ---- Create a count of the players in the Task. --- @param #TASK self --- @return #number The total number of players in the task. -function TASK:GetPlayerCount() --R2.1 Get a count of the players. - - local PlayerCount = 0 - - -- Loop each Unit active in the Task, and find Player Names. - for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do - local PlayerGroup = PlayerGroup -- Wrapper.Group#GROUP - if PlayerGroup:IsAlive() == true then - if self:IsGroupAssigned( PlayerGroup ) then - local PlayerNames = PlayerGroup:GetPlayerNames() - PlayerCount = PlayerCount + ((PlayerNames) and #PlayerNames or 0) -- PlayerNames can be nil when there are no players. - end - end - end - - return PlayerCount -end - - ---- Create a list of the players in the Task. --- @param #TASK self --- @return #map<#string,Wrapper.Group#GROUP> A map of the players -function TASK:GetPlayerNames() --R2.1 Get a map of the players. - - local PlayerNameMap = {} - - -- Loop each Unit active in the Task, and find Player Names. - for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do - local PlayerGroup = PlayerGroup -- Wrapper.Group#GROUP - if PlayerGroup:IsAlive() == true then - if self:IsGroupAssigned( PlayerGroup ) then - local PlayerNames = PlayerGroup:GetPlayerNames() - for PlayerNameID, PlayerName in pairs( PlayerNames or {} ) do - PlayerNameMap[PlayerName] = PlayerGroup - end - end - end - end - - return PlayerNameMap -end - - ---- Create a detailed report of the Task. --- List the Task Status, and the Players assigned to the Task. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @return #string -function TASK:ReportDetails( ReportGroup ) - - self:UpdateTaskInfo( self.DetectedItem ) - - local Report = REPORT:New():SetIndent( 3 ) - - -- List the name of the Task. - local Name = self:GetName() - - -- Determine the status of the Task. - local Status = "<" .. self:GetState() .. ">" - - Report:Add( "Task " .. Name .. " - " .. Status .. " - Detailed Report" ) - - -- Loop each Unit active in the Task, and find Player Names. - local PlayerNames = self:GetPlayerNames() - - local PlayerReport = REPORT:New() - for PlayerName, PlayerGroup in pairs( PlayerNames ) do - PlayerReport:Add( "Players group " .. PlayerGroup:GetCallsign() .. ": " .. PlayerName ) - end - local Players = PlayerReport:Text() - - if Players ~= "" then - Report:AddIndent( "Players assigned:", "-" ) - Report:AddIndent( Players ) - end - - self.TaskInfo:Report( Report, "D", ReportGroup, self ) - - return Report:Text() -end - - -end -- Reporting - - -do -- Additional Task Scoring and Task Progress - - --- Add Task Progress for a Player Name - -- @param #TASK self - -- @param #string PlayerName The name of the player. - -- @param #string ProgressText The text that explains the Progress achieved. - -- @param #number ProgressTime The time the progress was achieved. - -- @oaram #number ProgressPoints The amount of points of magnitude granted. This will determine the shared Mission Success scoring. - -- @return #TASK - function TASK:AddProgress( PlayerName, ProgressText, ProgressTime, ProgressPoints ) - self.TaskProgress = self.TaskProgress or {} - self.TaskProgress[ProgressTime] = self.TaskProgress[ProgressTime] or {} - self.TaskProgress[ProgressTime].PlayerName = PlayerName - self.TaskProgress[ProgressTime].ProgressText = ProgressText - self.TaskProgress[ProgressTime].ProgressPoints = ProgressPoints - self:GetMission():AddPlayerName( PlayerName ) - return self - end - - function TASK:GetPlayerProgress( PlayerName ) - local ProgressPlayer = 0 - for ProgressTime, ProgressData in pairs( self.TaskProgress ) do - if PlayerName == ProgressData.PlayerName then - ProgressPlayer = ProgressPlayer + ProgressData.ProgressPoints - end - end - return ProgressPlayer - end - - --- Set a score when progress has been made by the player. - -- @param #TASK self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK - function TASK:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountPlayer", "Player " .. PlayerName .. " has achieved progress.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- @param #TASK self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK - function TASK:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "The task is a success!", Score ) - - return self - end - - --- Set a penalty when the A2A attack has failed. - -- @param #TASK self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK - function TASK:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The task is a failure!", Penalty ) - - return self - end - -end - -do -- Task Control Menu - - -- The Task Control Menu is a menu attached to the task at the main menu to quickly be able to do actions in the task. - -- The Task Control Menu can only be shown when the task is assigned to the player. - -- The Task Control Menu is linked to the process executing the task, so no task menu can be set to the main static task definition. - - --- Init Task Control Menu - -- @param #TASK self - -- @param Wrapper.Unit#UNIT TaskUnit The @{Wrapper.Unit} that contains a player. - -- @return Task Control Menu Refresh ID - function TASK:InitTaskControlMenu( TaskUnit ) - - self.TaskControlMenuTime = timer.getTime() - - return self.TaskControlMenuTime - end - - --- Get Task Control Menu - -- @param #TASK self - -- @param Wrapper.Unit#UNIT TaskUnit The @{Wrapper.Unit} that contains a player. - -- @return Core.Menu#MENU_GROUP TaskControlMenu The Task Control Menu - function TASK:GetTaskControlMenu( TaskUnit, TaskName ) - - TaskName = TaskName or "" - - local TaskGroup = TaskUnit:GetGroup() - local TaskPlayerCount = TaskGroup:GetPlayerCount() - - if TaskPlayerCount <= 1 then - self.TaskControlMenu = MENU_GROUP:New( TaskUnit:GetGroup(), "Task " .. self:GetName() .. " control" ):SetTime( self.TaskControlMenuTime ) - else - self.TaskControlMenu = MENU_GROUP:New( TaskUnit:GetGroup(), "Task " .. self:GetName() .. " control for " .. TaskUnit:GetPlayerName() ):SetTime( self.TaskControlMenuTime ) - end - - return self.TaskControlMenu - end - - --- Remove Task Control Menu - -- @param #TASK self - -- @param Wrapper.Unit#UNIT TaskUnit The @{Wrapper.Unit} that contains a player. - function TASK:RemoveTaskControlMenu( TaskUnit ) - - if self.TaskControlMenu then - self.TaskControlMenu:Remove() - self.TaskControlMenu = nil - end - end - - --- Refresh Task Control Menu - -- @param #TASK self - -- @param Wrapper.Unit#UNIT TaskUnit The @{Wrapper.Unit} that contains a player. - -- @param MenuTime The refresh time that was used to refresh the Task Control Menu items. - -- @param MenuTag The tag. - function TASK:RefreshTaskControlMenu( TaskUnit, MenuTime, MenuTag ) - - if self.TaskControlMenu then - self.TaskControlMenu:Remove( MenuTime, MenuTag ) - end - end - -end diff --git a/Moose Development/Moose/Tasking/TaskInfo.lua b/Moose Development/Moose/Tasking/TaskInfo.lua deleted file mode 100644 index 428e629c9..000000000 --- a/Moose Development/Moose/Tasking/TaskInfo.lua +++ /dev/null @@ -1,377 +0,0 @@ ---- **Tasking** - Controls the information of a Task. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.TaskInfo --- @image MOOSE.JPG - ---- --- @type TASKINFO --- @extends Core.Base#BASE - ---- --- # TASKINFO class, extends @{Core.Base#BASE} --- --- ## The TASKINFO class implements the methods to contain information and display information of a task. --- --- # 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 #TASKINFO -TASKINFO = { - ClassName = "TASKINFO", -} - ---- --- @type TASKINFO.Detail #string A string that flags to document which level of detail needs to be shown in the report. --- --- - "M" for Markings on the Map (F10). --- - "S" for Summary Reports. --- - "O" for Overview Reports. --- - "D" for Detailed Reports. -TASKINFO.Detail = "" - ---- Instantiates a new TASKINFO. --- @param #TASKINFO self --- @param Tasking.Task#TASK Task The task owning the information. --- @return #TASKINFO self -function TASKINFO:New( Task ) - - local self = BASE:Inherit( self, BASE:New() ) -- Core.Base#BASE - - self.Task = Task - self.VolatileInfo = SET_BASE:New() - self.PersistentInfo = SET_BASE:New() - - self.Info = self.VolatileInfo - - return self -end - - ---- Add taskinfo. --- @param #TASKINFO self --- @param #string Key The info key. --- @param Data The data of the info. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddInfo( Key, Data, Order, Detail, Keep, ShowKey, Type ) - self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } ) - if Keep == true then - self.PersistentInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } ) - end - return self -end - - ---- Get taskinfo. --- @param #TASKINFO self --- @param #string The info key. --- @return Data The data of the info. --- @return #number Order The display order, which is a number from 0 to 100. --- @return #TASKINFO.Detail Detail The detail Level. -function TASKINFO:GetInfo( Key ) - local Object = self:Get( Key ) - return Object.Data, Object.Order, Object.Detail -end - - ---- Get data. --- @param #TASKINFO self --- @param #string The info key. --- @return Data The data of the info. -function TASKINFO:GetData( Key ) - local Object = self.Info:Get( Key ) - return Object and Object.Data -end - - ---- Add Text. --- @param #TASKINFO self --- @param #string Key The key. --- @param #string Text The text. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddText( Key, Text, Order, Detail, Keep ) - self:AddInfo( Key, Text, Order, Detail, Keep ) - return self -end - - ---- Add the task name. --- @param #TASKINFO self --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddTaskName( Order, Detail, Keep ) - self:AddInfo( "TaskName", self.Task:GetName(), Order, Detail, Keep ) - return self -end - - - - ---- Add a Coordinate. --- @param #TASKINFO self --- @param Core.Point#COORDINATE Coordinate --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddCoordinate( Coordinate, Order, Detail, Keep, ShowKey, Name ) - self:AddInfo( Name or "Coordinate", Coordinate, Order, Detail, Keep, ShowKey, "Coordinate" ) - return self -end - - ---- Get the Coordinate. --- @param #TASKINFO self --- @return Core.Point#COORDINATE Coordinate -function TASKINFO:GetCoordinate( Name ) - return self:GetData( Name or "Coordinate" ) -end - - - ---- Add Coordinates. --- @param #TASKINFO self --- @param #list Coordinates --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddCoordinates( Coordinates, Order, Detail, Keep ) - self:AddInfo( "Coordinates", Coordinates, Order, Detail, Keep ) - return self -end - - - ---- Add Threat. --- @param #TASKINFO self --- @param #string ThreatText The text of the Threat. --- @param #string ThreatLevel The level of the Threat. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddThreat( ThreatText, ThreatLevel, Order, Detail, Keep ) - self:AddInfo( "Threat", " [" .. string.rep( "■", ThreatLevel ) .. string.rep( "□", 10 - ThreatLevel ) .. "]:" .. ThreatText, Order, Detail, Keep ) - return self -end - - ---- Get Threat. --- @param #TASKINFO self --- @return #string The threat -function TASKINFO:GetThreat() - self:GetInfo( "Threat" ) - return self -end - - - ---- Add the Target count. --- @param #TASKINFO self --- @param #number TargetCount The amount of targets. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddTargetCount( TargetCount, Order, Detail, Keep ) - self:AddInfo( "Counting", string.format( "%d", TargetCount ), Order, Detail, Keep ) - return self -end - ---- Add the Targets. --- @param #TASKINFO self --- @param #number TargetCount The amount of targets. --- @param #string TargetTypes The text containing the target types. --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddTargets( TargetCount, TargetTypes, Order, Detail, Keep ) - self:AddInfo( "Targets", string.format( "%d of %s", TargetCount, TargetTypes ), Order, Detail, Keep ) - return self -end - ---- Get Targets. --- @param #TASKINFO self --- @return #string The targets -function TASKINFO:GetTargets() - self:GetInfo( "Targets" ) - return self -end - - - - ---- Add the QFE at a Coordinate. --- @param #TASKINFO self --- @param Core.Point#COORDINATE Coordinate --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddQFEAtCoordinate( Coordinate, Order, Detail, Keep ) - self:AddInfo( "QFE", Coordinate, Order, Detail, Keep ) - return self -end - ---- Add the Temperature at a Coordinate. --- @param #TASKINFO self --- @param Core.Point#COORDINATE Coordinate --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddTemperatureAtCoordinate( Coordinate, Order, Detail, Keep ) - self:AddInfo( "Temperature", Coordinate, Order, Detail, Keep ) - return self -end - ---- Add the Wind at a Coordinate. --- @param #TASKINFO self --- @param Core.Point#COORDINATE Coordinate --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddWindAtCoordinate( Coordinate, Order, Detail, Keep ) - self:AddInfo( "Wind", Coordinate, Order, Detail, Keep ) - return self -end - ---- Add Cargo. --- @param #TASKINFO self --- @param Cargo.Cargo#CARGO Cargo --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddCargo( Cargo, Order, Detail, Keep ) - self:AddInfo( "Cargo", Cargo, Order, Detail, Keep ) - return self -end - - ---- Add Cargo set. --- @param #TASKINFO self --- @param Core.Set#SET_CARGO SetCargo --- @param #number Order The display order, which is a number from 0 to 100. --- @param #TASKINFO.Detail Detail The detail Level. --- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. --- @return #TASKINFO self -function TASKINFO:AddCargoSet( SetCargo, Order, Detail, Keep ) - - local CargoReport = REPORT:New() - CargoReport:Add( "" ) - SetCargo:ForEachCargo( - -- @param Cargo.Cargo#CARGO Cargo - function( Cargo ) - CargoReport:Add( string.format( ' - %s (%s) %s - status %s ', Cargo:GetName(), Cargo:GetType(), Cargo:GetTransportationMethod(), Cargo:GetCurrentState() ) ) - end - ) - - self:AddInfo( "Cargo", CargoReport:Text(), Order, Detail, Keep ) - - - return self -end - - - ---- Create the taskinfo Report --- @param #TASKINFO self --- @param Core.Report#REPORT Report --- @param #TASKINFO.Detail Detail The detail Level. --- @param Wrapper.Group#GROUP ReportGroup --- @param Tasking.Task#TASK Task --- @return #TASKINFO self -function TASKINFO:Report( Report, Detail, ReportGroup, Task ) - - local Line = 0 - local LineReport = REPORT:New() - - if not self.Task:IsStatePlanned() and not self.Task:IsStateAssigned() then - self.Info = self.PersistentInfo - end - - for Key, Data in UTILS.spairs( self.Info.Set, function( t, a, b ) return t[a].Order < t[b].Order end ) do - - if Data.Detail:find( Detail ) then - local Text = "" - local ShowKey = ( Data.ShowKey == nil or Data.ShowKey == true ) - if Key == "TaskName" then - Key = nil - Text = Data.Data - elseif Data.Type and Data.Type == "Coordinate" then - local Coordinate = Data.Data -- Core.Point#COORDINATE - Text = Coordinate:ToString( ReportGroup:GetUnit(1), nil, Task ) - elseif Key == "Threat" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "Counting" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "Targets" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "QFE" then - local Coordinate = Data.Data -- Core.Point#COORDINATE - Text = Coordinate:ToStringPressure( ReportGroup:GetUnit(1), nil, Task ) - elseif Key == "Temperature" then - local Coordinate = Data.Data -- Core.Point#COORDINATE - Text = Coordinate:ToStringTemperature( ReportGroup:GetUnit(1), nil, Task ) - elseif Key == "Wind" then - local Coordinate = Data.Data -- Core.Point#COORDINATE - Text = Coordinate:ToStringWind( ReportGroup:GetUnit(1), nil, Task ) - elseif Key == "Cargo" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "Friendlies" then - local DataText = Data.Data -- #string - Text = DataText - elseif Key == "Players" then - local DataText = Data.Data -- #string - Text = DataText - else - local DataText = Data.Data -- #string - if type(DataText) == "string" then --Issue #1388 - don't just assume this is a string - Text = DataText - end - end - - if Line < math.floor( Data.Order / 10 ) then - if Line == 0 then - Report:AddIndent( LineReport:Text( ", " ), "-" ) - else - Report:AddIndent( LineReport:Text( ", " ) ) - end - LineReport = REPORT:New() - Line = math.floor( Data.Order / 10 ) - end - - if Text ~= "" then - LineReport:Add( ( ( Key and ShowKey == true ) and ( Key .. ": " ) or "" ) .. Text ) - end - - end - end - - Report:AddIndent( LineReport:Text( ", " ) ) -end diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua deleted file mode 100644 index 9309526bb..000000000 --- a/Moose Development/Moose/Tasking/Task_A2A.lua +++ /dev/null @@ -1,654 +0,0 @@ ---- **Tasking** - The TASK_A2A models tasks for players in Air to Air engagements. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_A2A --- @image MOOSE.JPG - -do -- TASK_A2A - - --- The TASK_A2A class - -- @type TASK_A2A - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines Air To Air tasks for a @{Core.Set} of Target Units, - -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. - -- The TASK_A2A is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: - -- - -- * **None**: Start of the process - -- * **Planned**: The A2A task is planned. - -- * **Assigned**: The A2A task is assigned to a @{Wrapper.Group#GROUP}. - -- * **Success**: The A2A task is successfully completed. - -- * **Failed**: The A2A task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- - -- # 1) Set the scoring of achievements in an A2A attack. - -- - -- Scoring or penalties can be given in the following circumstances: - -- - -- * @{#TASK_A2A.SetScoreOnDestroy}(): Set a score when a target in scope of the A2A attack, has been destroyed. - -- * @{#TASK_A2A.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- * @{#TASK_A2A.SetPenaltyOnFailed}(): Set a penalty when the A2A attack has failed. - -- - -- # 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 #TASK_A2A - TASK_A2A = { - ClassName = "TASK_A2A" - } - - --- Instantiates a new TASK_A2A. - -- @param #TASK_A2A self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetAttack The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_A2A self - function TASK_A2A:New( Mission, SetAttack, TaskName, TargetSetUnit, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New( Mission, SetAttack, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2A - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TaskType = TaskType - - local Fsm = self:GetUnitProcess() - - Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" ) - Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } ) - Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } ) - - Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" ) - - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" ) - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" ) - - Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" ) - Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} ) - Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" ) - - -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) - -- Fsm:AddTransition( "Accounted", "Success", "Success" ) - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #TASK_CARGO Task - function Fsm:OnLeaveAssigned( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - self:SelectAction() - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2A#TASK_A2A Task - function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.RendezVousSetUnit - - if Task:GetRendezVousZone( TaskUnit ) then - self:__RouteToRendezVousZone( 0.1 ) - else - if Task:GetRendezVousCoordinate( TaskUnit ) then - self:__RouteToRendezVousPoint( 0.1 ) - else - self:__ArriveAtRendezVous( 0.1 ) - end - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2A Task - function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2A Task - function Fsm:onafterEngage( TaskUnit, Task ) - self:F( { self } ) - self:__Account( 0.1 ) - self:__RouteToTarget( 0.1 ) - self:__RouteToTargets( -10 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2A#TASK_A2A Task - function Fsm:onafterRouteToTarget( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - if Task:GetTargetZone( TaskUnit ) then - self:__RouteToTargetZone( 0.1 ) - else - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - local Coordinate = TargetUnit:GetPointVec3() - self:T( { TargetCoordinate = Coordinate, Coordinate:GetX(), Coordinate:GetAlt(), Coordinate:GetZ() } ) - Task:SetTargetCoordinate( Coordinate, TaskUnit ) - end - self:__RouteToTargetPoint( 0.1 ) - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2A#TASK_A2A Task - function Fsm:onafterRouteToTargets( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit ) - end - self:__RouteToTargets( -10 ) - end - - return self - - end - - -- @param #TASK_A2A self - -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. - function TASK_A2A:SetTargetSetUnit( TargetSetUnit ) - - self.TargetSetUnit = TargetSetUnit - end - - -- @param #TASK_A2A self - function TASK_A2A:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - -- @param #TASK_A2A self - -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. - -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) - ActRouteRendezVous:SetRange( RendezVousRange ) - end - - -- @param #TASK_A2A self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. - -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - function TASK_A2A:GetRendezVousCoordinate( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() - end - - -- @param #TASK_A2A self - -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteRendezVous:SetZone( RendezVousZone ) - end - - -- @param #TASK_A2A self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. - function TASK_A2A:GetRendezVousZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteRendezVous:GetZone() - end - - -- @param #TASK_A2A self - -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteTarget:SetCoordinate( TargetCoordinate ) - end - - -- @param #TASK_A2A self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map. - function TASK_A2A:GetTargetCoordinate( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteTarget:GetCoordinate() - end - - -- @param #TASK_A2A self - -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteTarget:SetZone( TargetZone, Altitude, Heading ) - end - - -- @param #TASK_A2A self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_A2A:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - - function TASK_A2A:SetGoalTotal() - - self.GoalTotal = self.TargetSetUnit:Count() - end - - function TASK_A2A:GetGoalTotal() - - return self.GoalTotal - end - - --- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. - -- @param #TASK_A2A self - function TASK_A2A:ReportOrder( ReportGroup ) - self:UpdateTaskInfo( self.DetectedItem ) - - local Coordinate = self.TaskInfo:GetData( "Coordinate" ) - local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - - return Distance - end - - --- This method checks every 10 seconds if the goal has been reached of the task. - -- @param #TASK_A2A self - function TASK_A2A:onafterGoal( TaskUnit, From, Event, To ) - local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - - if TargetSetUnit:Count() == 0 then - self:Success() - end - - self:__Goal( -10 ) - end - - -- @param #TASK_A2A self - function TASK_A2A:UpdateTaskInfo( DetectedItem ) - - if self:IsStatePlanned() or self:IsStateAssigned() then - local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() - self.TaskInfo:AddTaskName( 0, "MSOD" ) - self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) - - local ThreatLevel, ThreatText - if DetectedItem then - ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem ) - else - ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G() - end - self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true ) - - if self.Detection then - local DetectedItemsCount = self.TargetSetUnit:Count() - local ReportTypes = REPORT:New() - local TargetTypes = {} - for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do - local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit ) - if not TargetTypes[TargetType] then - TargetTypes[TargetType] = TargetType - ReportTypes:Add( TargetType ) - end - end - self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) - else - local DetectedItemsCount = self.TargetSetUnit:Count() - local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() - self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) - end - end - end - - --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. - -- @param #TASK_A2A self - -- @param #number AutoAssignMethod The method to be applied to the task. - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. - -- @param Wrapper.Group#GROUP TaskGroup The player group. - function TASK_A2A:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then - return math.random( 1, 9 ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then - local Coordinate = self.TaskInfo:GetData( "Coordinate" ) - local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() ) - return math.floor( Distance ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then - return 1 - end - - return 0 - end - -end - -do -- TASK_A2A_INTERCEPT - - --- The TASK_A2A_INTERCEPT class - -- @type TASK_A2A_INTERCEPT - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines an intercept task for a human player to be executed. - -- When enemy planes need to be intercepted by human players, use this task type to urge the players to get out there! - -- - -- The TASK_A2A_INTERCEPT is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create intercept tasks - -- based on detected airborne enemy targets intruding friendly airspace. - -- - -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is intercepting the targets. - -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- - -- @field #TASK_A2A_INTERCEPT - TASK_A2A_INTERCEPT = { - ClassName = "TASK_A2A_INTERCEPT" - } - - --- Instantiates a new TASK_A2A_INTERCEPT. - -- @param #TASK_A2A_INTERCEPT self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2A_INTERCEPT - function TASK_A2A_INTERCEPT:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "INTERCEPT", TaskBriefing ) ) -- #TASK_A2A_INTERCEPT - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Intercept incoming intruders.\n" ) - - return self - end - - --- Set a score when a target in scope of the A2A attack, has been destroyed. - -- @param #TASK_A2A_INTERCEPT self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_INTERCEPT - function TASK_A2A_INTERCEPT:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has intercepted a target.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- @param #TASK_A2A_INTERCEPT self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_INTERCEPT - function TASK_A2A_INTERCEPT:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully intercepted!", Score ) - - return self - end - - --- Set a penalty when the A2A attack has failed. - -- @param #TASK_A2A_INTERCEPT self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_INTERCEPT - function TASK_A2A_INTERCEPT:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The intercept has failed!", Penalty ) - - return self - end - -end - -do -- TASK_A2A_SWEEP - - --- The TASK_A2A_SWEEP class - -- @type TASK_A2A_SWEEP - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines a sweep task for a human player to be executed. - -- A sweep task needs to be given when targets were detected but somehow the detection was lost. - -- Most likely, these enemy planes are hidden in the mountains or are flying under radar. - -- These enemy planes need to be sweeped by human players, and use this task type to urge the players to get out there and find those enemy fighters. - -- - -- The TASK_A2A_SWEEP is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create sweep tasks - -- based on detected airborne enemy targets intruding friendly airspace, for which the detection has been lost for more than 60 seconds. - -- - -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is sweeping the targets. - -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- - -- @field #TASK_A2A_SWEEP - TASK_A2A_SWEEP = { - ClassName = "TASK_A2A_SWEEP" - } - - --- Instantiates a new TASK_A2A_SWEEP. - -- @param #TASK_A2A_SWEEP self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2A_SWEEP self - function TASK_A2A_SWEEP:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "SWEEP", TaskBriefing ) ) -- #TASK_A2A_SWEEP - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Perform a fighter sweep. Incoming intruders were detected and could be hiding at the location.\n" ) - - return self - end - - -- @param #TASK_A2A_SWEEP self - function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To ) - local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - - if TargetSetUnit:Count() == 0 then - self:Success() - end - - self:__Goal( -10 ) - end - - --- Set a score when a target in scope of the A2A attack, has been destroyed. - -- @param #TASK_A2A_SWEEP self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_SWEEP - function TASK_A2A_SWEEP:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has sweeped a target.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- @param #TASK_A2A_SWEEP self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_SWEEP - function TASK_A2A_SWEEP:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully sweeped!", Score ) - - return self - end - - --- Set a penalty when the A2A attack has failed. - -- @param #TASK_A2A_SWEEP self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_SWEEP - function TASK_A2A_SWEEP:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The sweep has failed!", Penalty ) - - return self - end - -end - -do -- TASK_A2A_ENGAGE - - --- The TASK_A2A_ENGAGE class - -- @type TASK_A2A_ENGAGE - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines an engage task for a human player to be executed. - -- When enemy planes are close to human players, use this task type is used urge the players to get out there! - -- - -- The TASK_A2A_ENGAGE is used by the @{Tasking.Task_A2A_Dispatcher#TASK_A2A_DISPATCHER} to automatically create engage tasks - -- based on detected airborne enemy targets intruding friendly airspace. - -- - -- The task is defined for a @{Tasking.Mission#MISSION}, where a friendly @{Core.Set#SET_GROUP} consisting of GROUPs with one human players each, is engaging the targets. - -- The task is given a name and a briefing, that is used in the menu structure and in the reporting. - -- - -- @field #TASK_A2A_ENGAGE - TASK_A2A_ENGAGE = { - ClassName = "TASK_A2A_ENGAGE" - } - - --- Instantiates a new TASK_A2A_ENGAGE. - -- @param #TASK_A2A_ENGAGE self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2A_ENGAGE self - function TASK_A2A_ENGAGE:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2A:New( Mission, SetGroup, TaskName, TargetSetUnit, "ENGAGE", TaskBriefing ) ) -- #TASK_A2A_ENGAGE - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Bogeys are nearby! Players close by are ordered to ENGAGE the intruders!\n" ) - - return self - end - - --- Set a score when a target in scope of the A2A attack, has been destroyed . - -- @param #TASK_A2A_ENGAGE self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_ENGAGE - function TASK_A2A_ENGAGE:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has engaged and destroyed a target.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2A attack, have been destroyed. - -- @param #TASK_A2A_ENGAGE self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_ENGAGE - function TASK_A2A_ENGAGE:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully engaged!", Score ) - - return self - end - - --- Set a penalty when the A2A attack has failed. - -- @param #TASK_A2A_ENGAGE self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2A_ENGAGE - function TASK_A2A_ENGAGE:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The target engagement has failed!", Penalty ) - - return self - end - -end - diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua deleted file mode 100644 index d592ad4a6..000000000 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ /dev/null @@ -1,620 +0,0 @@ ---- **Tasking** - Dynamically allocates A2A tasks to human players, based on detected airborne targets through an EWR network. --- --- **Features:** --- --- * Dynamically assign tasks to human players based on detected targets. --- * Dynamically change the tasks as the tactical situation evolves during the mission. --- * Dynamically assign (CAP) Control Air Patrols tasks for human players to perform CAP. --- * Dynamically assign (GCI) Ground Control Intercept tasks for human players to perform GCI. --- * Dynamically assign Engage tasks for human players to engage on close-by airborne bogeys. --- * Define and use an EWR (Early Warning Radar) network. --- * Define different ranges to engage upon intruders. --- * Keep task achievements. --- * Score task achievements. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_A2A_Dispatcher --- @image Task_A2A_Dispatcher.JPG - -do -- TASK_A2A_DISPATCHER - - --- TASK_A2A_DISPATCHER class. - -- @type TASK_A2A_DISPATCHER - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Core.Set} of EWR installation groups. - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG) - -- - -- The EWR will detect units, will group them, and will dispatch @{Tasking.Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. - -- Find a summary below describing for which situation a task type is created: - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia9.JPG) - -- - -- * **INTERCEPT Task**: Is created when the target is known, is detected and within a danger zone, and there is no friendly airborne in range. - -- * **SWEEP Task**: Is created when the target is unknown, was detected and the last position is only known, and within a danger zone, and there is no friendly airborne in range. - -- * **ENGAGE Task**: Is created when the target is known, is detected and within a danger zone, and there is a friendly airborne in range, that will receive this task. - -- - -- ## 1. TASK\_A2A\_DISPATCHER constructor: - -- - -- The @{#TASK_A2A_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance. - -- - -- ### 1.1. Define or set the **Mission**: - -- - -- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter. - -- - -- local HQ = GROUP:FindByName( "HQ", "Bravo" ) - -- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" ) - -- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED ) - -- - -- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission. - -- Create the MISSION object, and hook it under the command center. - -- - -- ### 1.2. Build a set of the groups seated by human players: - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia6.JPG) - -- - -- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into. - -- - -- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart() - -- - -- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission. - -- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available. - -- - -- ### 1.3. Define the **EWR network**: - -- - -- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third parameter. - -- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy. - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia5.JPG) - -- - -- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units. - -- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US). - -- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar. - -- The position of these units is very important as they need to provide enough coverage - -- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them. - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia7.JPG) - -- - -- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates. - -- For example if they are a long way forward and can detect enemy planes on the ground and taking off - -- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition. - -- Having the radars further back will mean a slower escalation because fewer targets will be detected and - -- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map. - -- It all depends on what the desired effect is. - -- - -- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class. - -- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network, - -- increasing or decreasing the radar coverage of the Early Warning System. - -- - -- See the following example to setup an EWR network containing EWR stations and AWACS. - -- - -- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart() - -- - -- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 ) - -- EWRDetection:SetFriendliesRange( 10000 ) - -- EWRDetection:SetRefreshTimeInterval(30) - -- - -- -- Setup the A2A dispatcher, and initialize it. - -- A2ADispatcher = TASK_A2A_DISPATCHER:New( Mission, AttackGroups, EWRDetection ) - -- - -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**. - -- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set. - -- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set. - -- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is chosen, which is 6 km. - -- The **EWRDetection** object is then passed to the @{#TASK_A2A_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism. - -- - -- ### 2. Define the detected **target grouping radius**: - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia8.JPG) - -- - -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. - -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. - -- Fast planes like in the 80s, need a larger radius than WWII planes. - -- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft. - -- - -- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate - -- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small! - -- - -- ## 3. Set the **Engage radius**: - -- - -- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission. - -- - -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia11.JPG) - -- - -- So, if there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, - -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, - -- will be considered to receive the command to engage that target area. - -- You need to evaluate the value of this parameter carefully. - -- If too small, more intercept missions may be triggered upon detected target areas. - -- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. - -- - -- ## 4. Set **Scoring** and **Messages**: - -- - -- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Tasking.Task} dispatched by the TASK\_A2A\_DISPATCHER. - -- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_, - -- when the player reaches certain achievements in the task. - -- - -- The prototype to handle the **Assign** event needs to be developed as follows: - -- - -- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... ) - -- - -- -- @param #TaskDispatcher self - -- -- @param #string From Contains the name of the state from where the Event was triggered. - -- -- @param #string Event Contains the name of the event that was triggered. In this case Assign. - -- -- @param #string To Contains the name of the state that will be transitioned to. - -- -- @param Tasking.Task_A2A#TASK_A2A Task The Task object, which is any derived object from TASK_A2A. - -- -- @param Wrapper.Unit#UNIT TaskUnit The Unit or Client that contains the Player. - -- -- @param #string PlayerName The name of the Player that joined the TaskUnit. - -- function TaskDispatcher:OnAfterAssign( From, Event, To, Task, TaskUnit, PlayerName ) - -- Task:SetScoreOnProgress( PlayerName, 20, TaskUnit ) - -- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit ) - -- Task:SetScoreOnFail( PlayerName, -100, TaskUnit ) - -- end - -- - -- The **OnAfterAssign** method (function) is added to the TaskDispatcher object. - -- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher. - -- So, this method will be called only **ONCE** when a player joins a unit in scope of the task. - -- - -- The TASK class implements various methods to additional **set scoring** for player achievements: - -- - -- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task. - -- Examples of **task progress** can be destroying units, arriving at zones etc. - -- - -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state. - -- This means the **task has been successfully completed**. - -- - -- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state. - -- This means the **task has not been successfully completed**, and the scores must be given with a negative value! - -- - -- # 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 #TASK_A2A_DISPATCHER - TASK_A2A_DISPATCHER = { - ClassName = "TASK_A2A_DISPATCHER", - Mission = nil, - Detection = nil, - Tasks = {}, - SweepZones = {}, - } - - --- TASK_A2A_DISPATCHER constructor. - -- @param #TASK_A2A_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. - -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. - -- @return #TASK_A2A_DISPATCHER self - function TASK_A2A_DISPATCHER:New( Mission, SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2A_DISPATCHER - - self.Detection = Detection - self.Mission = Mission - self.FlashNewTask = false - - -- TODO: Check detection through radar. - self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER ) - self.Detection:InitDetectRadar( true ) - self.Detection:SetRefreshTimeInterval( 30 ) - - self:AddTransition( "Started", "Assign", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#TASK_A2A_DISPATCHER] OnAfterAssign - -- @param #TASK_A2A_DISPATCHER self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Tasking.Task_A2A#TASK_A2A Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:__Start( 5 ) - - return self - end - - --- Define the radius to when an ENGAGE task will be generated for any nearby by airborne friendlies, which are executing cap or returning from an intercept mission. - -- So, if there is a target area detected and reported, - -- then any friendlies that are airborne near this target area, - -- will be commanded to (re-)engage that target when available (if no other tasks were commanded). - -- An ENGAGE task will be created for those pilots. - -- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target, - -- will be considered to receive the command to engage that target area. - -- You need to evaluate the value of this parameter carefully. - -- If too small, more intercept missions may be triggered upon detected target areas. - -- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far. - -- @param #TASK_A2A_DISPATCHER self - -- @param #number EngageRadius (Optional, Default = 100000) The radius to report friendlies near the target. - -- @return #TASK_A2A_DISPATCHER - -- @usage - -- - -- -- Set 50km as the radius to engage any target by airborne friendlies. - -- TaskA2ADispatcher:SetEngageRadius( 50000 ) - -- - -- -- Set 100km as the radius to engage any target by airborne friendlies. - -- TaskA2ADispatcher:SetEngageRadius() -- 100000 is the default value. - -- - function TASK_A2A_DISPATCHER:SetEngageRadius( EngageRadius ) - - self.Detection:SetFriendliesRange( EngageRadius or 100000 ) - - return self - end - - --- Set flashing player messages on or off - -- @param #TASK_A2A_DISPATCHER self - -- @param #boolean onoff Set messages on (true) or off (false) - function TASK_A2A_DISPATCHER:SetSendMessages( onoff ) - self.FlashNewTask = onoff - end - - --- Creates an INTERCEPT task when there are targets for it. - -- @param #TASK_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2A_DISPATCHER:EvaluateINTERCEPT( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Check if there is at least one UNIT in the DetectedSet is visible. - - if DetectedItem.IsDetected == true then - - -- Here we're doing something advanced... We're copying the DetectedSet. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates an SWEEP task when there are targets for it. - -- @param #TASK_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove? - - if DetectedItem.IsDetected == false then - - -- Here we're doing something advanced... We're copying the DetectedSet. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates an ENGAGE task when there are human friendlies airborne near the targets. - -- @param #TASK_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone -- TODO: This seems unused, remove? - - local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) - - -- Only allow ENGAGE when there are Players near the zone, and when the Area has detected items since the last run in a 60 seconds time zone. - if PlayersCount > 0 and DetectedItem.IsDetected == true then - - -- Here we're doing something advanced... We're copying the DetectedSet. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Evaluates the removal of the Task from the Mission. - -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". - -- @param #TASK_A2A_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission - -- @param Tasking.Task#TASK Task - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - -- @param #boolean DetectedItemID - -- @param #boolean DetectedItemChange - -- @return Tasking.Task#TASK - function TASK_A2A_DISPATCHER:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, DetectedItemIndex, DetectedItemChanged ) - - if Task then - - if Task:IsStatePlanned() then - local TaskName = Task:GetName() - local TaskType = TaskName:match( "(%u+)%.%d+" ) - - self:T2( { TaskType = TaskType } ) - - local Remove = false - - local IsPlayers = Detection:IsPlayersNearBy( DetectedItem ) - if TaskType == "ENGAGE" then - if IsPlayers == false then - Remove = true - end - end - - if TaskType == "INTERCEPT" then - if IsPlayers == true then - Remove = true - end - if DetectedItem.IsDetected == false then - Remove = true - end - end - - if TaskType == "SWEEP" then - if DetectedItem.IsDetected == true then - Remove = true - end - end - - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - -- DetectedSet:Flush( self ) - -- self:F( { DetectedSetCount = DetectedSet:Count() } ) - if DetectedSet:Count() == 0 then - Remove = true - end - - if DetectedItemChanged == true or Remove then - Task = self:RemoveTask( DetectedItemIndex ) - end - end - end - - return Task - end - - --- Calculates which friendlies are nearby the area - -- @param #TASK_A2A_DISPATCHER self - -- @param DetectedItem - -- @return #number, Tasking.CommandCenter#REPORT - function TASK_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) - - local FriendlyTypes = {} - local FriendliesCount = 0 - - if FriendlyUnitsNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() - for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do - local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT - if FriendlyUnit:IsAirPlane() then - local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() - FriendliesCount = FriendliesCount + 1 - local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and (FriendlyTypes[FriendlyType] + 1) or 1 - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then - end - end - end - - end - - -- self:F( { FriendliesCount = FriendliesCount } ) - - local FriendlyTypesReport = REPORT:New() - - if FriendliesCount > 0 then - for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format( "%d of %s", FriendlyTypeCount, FriendlyType ) ) - end - else - FriendlyTypesReport:Add( "-" ) - end - - return FriendliesCount, FriendlyTypesReport - end - - --- Calculates which HUMAN friendlies are nearby the area - -- @param #TASK_A2A_DISPATCHER self - -- @param DetectedItem - -- @return #number, Tasking.CommandCenter#REPORT - function TASK_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) - - local DetectedSet = DetectedItem.Set - local PlayersNearBy = self.Detection:GetPlayersNearBy( DetectedItem ) - - local PlayerTypes = {} - local PlayersCount = 0 - - if PlayersNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() - for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do - local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT - local PlayerName = PlayerUnit:GetPlayerName() - -- self:F( { PlayerName = PlayerName, PlayerUnit = PlayerUnit } ) - if PlayerUnit:IsAirPlane() and PlayerName ~= nil then - local FriendlyUnitThreatLevel = PlayerUnit:GetThreatLevel() - PlayersCount = PlayersCount + 1 - local PlayerType = PlayerUnit:GetTypeName() - PlayerTypes[PlayerName] = PlayerType - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then - end - end - end - - end - - local PlayerTypesReport = REPORT:New() - - if PlayersCount > 0 then - for PlayerName, PlayerType in pairs( PlayerTypes ) do - PlayerTypesReport:Add( string.format( '"%s" in %s', PlayerName, PlayerType ) ) - end - else - PlayerTypesReport:Add( "-" ) - end - - return PlayersCount, PlayerTypesReport - end - - function TASK_A2A_DISPATCHER:RemoveTask( TaskIndex ) - self.Mission:RemoveTask( self.Tasks[TaskIndex] ) - self.Tasks[TaskIndex] = nil - end - - --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. - -- @param #TASK_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function TASK_A2A_DISPATCHER:ProcessDetected( Detection ) - self:F() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - if Mission:IsIDLE() or Mission:IsENGAGED() then - - local TaskReport = REPORT:New() - - -- Checking the task queue for the dispatcher, and removing any obsolete task! - for TaskIndex, TaskData in pairs( self.Tasks ) do - local Task = TaskData -- Tasking.Task#TASK - if Task:IsStatePlanned() then - local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex ) - if not DetectedItem then - local TaskText = Task:GetName() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) - end - Task = self:RemoveTask( TaskIndex ) - end - end - end - - -- Now that all obsolete tasks are removed, loop through the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - -- DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local TaskIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - local Task = self.Tasks[TaskIndex] - Task = self:EvaluateRemoveTask( Mission, Task, Detection, DetectedItem, TaskIndex, DetectedItemChanged ) -- Task will be removed if it is planned and changed. - - -- Evaluate INTERCEPT - if not Task and DetectedCount > 0 then - local TargetSetUnit = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed... - if TargetSetUnit then - Task = TASK_A2A_ENGAGE:New( Mission, self.SetGroup, string.format( "ENGAGE.%03d", DetectedID ), TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - local TargetSetUnit = self:EvaluateINTERCEPT( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed... - if TargetSetUnit then - Task = TASK_A2A_INTERCEPT:New( Mission, self.SetGroup, string.format( "INTERCEPT.%03d", DetectedID ), TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - local TargetSetUnit = self:EvaluateSWEEP( DetectedItem ) -- Returns a SetUnit - if TargetSetUnit then - Task = TASK_A2A_SWEEP:New( Mission, self.SetGroup, string.format( "SWEEP.%03d", DetectedID ), TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - end - end - end - - if Task then - self.Tasks[TaskIndex] = Task - Task:SetTargetZone( DetectedZone, DetectedItem.Coordinate.y, DetectedItem.Coordinate.Heading ) - Task:SetDispatcher( self ) - Mission:AddTask( Task ) - - function Task.OnEnterSuccess( Task, From, Event, To ) - self:Success( Task ) - end - - function Task.OnEnterCancelled( Task, From, Event, To ) - self:Cancelled( Task ) - end - - function Task.OnEnterFailed( Task, From, Event, To ) - self:Failed( Task ) - end - - function Task.OnEnterAborted( Task, From, Event, To ) - self:Aborted( Task ) - end - - TaskReport:Add( Task:GetName() ) - else - self:F( "This should not happen" ) - end - - end - - if Task then - local FriendliesCount, FriendliesReport = self:GetFriendliesNearBy( DetectedItem, Unit.Category.AIRPLANE ) - Task.TaskInfo:AddText( "Friendlies", string.format( "%d ( %s )", FriendliesCount, FriendliesReport:Text( "," ) ), 40, "MOD" ) - local PlayersCount, PlayersReport = self:GetPlayerFriendliesNearBy( DetectedItem ) - Task.TaskInfo:AddText( "Players", string.format( "%d ( %s )", PlayersCount, PlayersReport:Text( "," ) ), 40, "MOD" ) - end - - -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedItem ) - end - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text( ", " ) - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and (self.FlashNewTask) then - Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) - end - end - - end - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua deleted file mode 100644 index 84bdcf360..000000000 --- a/Moose Development/Moose/Tasking/Task_A2G.lua +++ /dev/null @@ -1,635 +0,0 @@ ---- **Tasking** - The TASK_A2G models tasks for players in Air to Ground engagements. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_A2G --- @image MOOSE.JPG - -do -- TASK_A2G - - --- The TASK_A2G class - -- @type TASK_A2G - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- The TASK_A2G class defines Air To Ground tasks for a @{Core.Set} of Target Units, - -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. - -- The TASK_A2G is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: - -- - -- * **None**: Start of the process - -- * **Planned**: The A2G task is planned. - -- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}. - -- * **Success**: The A2G task is successfully completed. - -- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- - -- ## 1) Set the scoring of achievements in an A2G attack. - -- - -- Scoring or penalties can be given in the following circumstances: - -- - -- * @{#TASK_A2G.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed. - -- * @{#TASK_A2G.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- * @{#TASK_A2G.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed. - -- - -- # 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 #TASK_A2G - TASK_A2G = { - ClassName = "TASK_A2G" - } - - --- Instantiates a new TASK_A2G. - -- @param #TASK_A2G self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_A2G self - function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- Tasking.Task#TASK_A2G - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TaskType = TaskType - - local Fsm = self:GetUnitProcess() - - Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" ) - Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } ) - Fsm:AddProcess( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } ) - - Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" ) - - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" ) - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" ) - - Fsm:AddProcess( "Engaging", "Account", ACT_ACCOUNT_DEADS:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" ) - Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} ) - Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" ) - - -- Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) - -- Fsm:AddTransition( "Accounted", "Success", "Success" ) - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterAssigned( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.RendezVousSetUnit - - self:RouteToRendezVous() - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.RendezVousSetUnit - - if Task:GetRendezVousZone( TaskUnit ) then - self:__RouteToRendezVousZone( 0.1 ) - else - if Task:GetRendezVousCoordinate( TaskUnit ) then - self:__RouteToRendezVousPoint( 0.1 ) - else - self:__ArriveAtRendezVous( 0.1 ) - end - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2G Task - function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2G Task - function Fsm:onafterEngage( TaskUnit, Task ) - self:F( { self } ) - self:__Account( 0.1 ) - self:__RouteToTarget( 0.1 ) - self:__RouteToTargets( -10 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToTarget( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - if Task:GetTargetZone( TaskUnit ) then - self:__RouteToTargetZone( 0.1 ) - else - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - local Coordinate = TargetUnit:GetPointVec3() - self:T( { TargetCoordinate = Coordinate, Coordinate:GetX(), Coordinate:GetY(), Coordinate:GetZ() } ) - Task:SetTargetCoordinate( Coordinate, TaskUnit ) - end - self:__RouteToTargetPoint( 0.1 ) - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToTargets( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - Task:SetTargetCoordinate( TargetUnit:GetCoordinate(), TaskUnit ) - end - self:__RouteToTargets( -10 ) - end - - return self - - end - - -- @param #TASK_A2G self - -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. - function TASK_A2G:SetTargetSetUnit( TargetSetUnit ) - - self.TargetSetUnit = TargetSetUnit - end - - -- @param #TASK_A2G self - function TASK_A2G:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - -- @param #TASK_A2G self - -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. - -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousCoordinate( RendezVousCoordinate, RendezVousRange, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteRendezVous:SetCoordinate( RendezVousCoordinate ) - ActRouteRendezVous:SetRange( RendezVousRange ) - end - - -- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. - -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - function TASK_A2G:GetRendezVousCoordinate( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() - end - - -- @param #TASK_A2G self - -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteRendezVous:SetZone( RendezVousZone ) - end - - -- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. - function TASK_A2G:GetRendezVousZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteRendezVous:GetZone() - end - - -- @param #TASK_A2G self - -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteTarget:SetCoordinate( TargetCoordinate ) - end - - -- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map. - function TASK_A2G:GetTargetCoordinate( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteTarget:GetCoordinate() - end - - -- @param #TASK_A2G self - -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetTargetZone( TargetZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteTarget:SetZone( TargetZone ) - end - - -- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_A2G:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - - function TASK_A2G:SetGoalTotal() - - self.GoalTotal = self.TargetSetUnit:CountAlive() - end - - function TASK_A2G:GetGoalTotal() - - return self.GoalTotal - end - - --- Return the relative distance to the target vicinity from the player, in order to sort the targets in the reports per distance from the threats. - -- @param #TASK_A2G self - function TASK_A2G:ReportOrder( ReportGroup ) - self:UpdateTaskInfo( self.DetectedItem ) - - local Coordinate = self.TaskInfo:GetData( "Coordinate" ) - local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - - return Distance - end - - --- This method checks every 10 seconds if the goal has been reached of the task. - -- @param #TASK_A2G self - function TASK_A2G:onafterGoal( TaskUnit, From, Event, To ) - local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT - - if TargetSetUnit:CountAlive() == 0 then - self:Success() - end - - self:__Goal( -10 ) - end - - -- @param #TASK_A2G self - function TASK_A2G:UpdateTaskInfo( DetectedItem ) - - if self:IsStatePlanned() or self:IsStateAssigned() then - local TargetCoordinate = DetectedItem and self.Detection:GetDetectedItemCoordinate( DetectedItem ) or self.TargetSetUnit:GetFirst():GetCoordinate() - self.TaskInfo:AddTaskName( 0, "MSOD" ) - self.TaskInfo:AddCoordinate( TargetCoordinate, 1, "SOD" ) - - local ThreatLevel, ThreatText - if DetectedItem then - ThreatLevel, ThreatText = self.Detection:GetDetectedItemThreatLevel( DetectedItem ) - else - ThreatLevel, ThreatText = self.TargetSetUnit:CalculateThreatLevelA2G() - end - self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true ) - - if self.Detection then - local DetectedItemsCount = self.TargetSetUnit:CountAlive() - local ReportTypes = REPORT:New() - local TargetTypes = {} - for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do - local TargetType = self.Detection:GetDetectedUnitTypeName( TargetUnit ) - if not TargetTypes[TargetType] then - TargetTypes[TargetType] = TargetType - ReportTypes:Add( TargetType ) - end - end - self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) - else - local DetectedItemsCount = self.TargetSetUnit:CountAlive() - local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() - self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) - self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) - end - self.TaskInfo:AddQFEAtCoordinate( TargetCoordinate, 30, "MOD" ) - self.TaskInfo:AddTemperatureAtCoordinate( TargetCoordinate, 31, "MD" ) - self.TaskInfo:AddWindAtCoordinate( TargetCoordinate, 32, "MD" ) - end - - end - - --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. - -- @param #TASK_A2G self - -- @param #number AutoAssignMethod The method to be applied to the task. - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. - -- @param Wrapper.Group#GROUP TaskGroup The player group. - function TASK_A2G:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then - return math.random( 1, 9 ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then - local Coordinate = self.TaskInfo:GetData( "Coordinate" ) - local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() ) - self:F( { Distance = Distance } ) - return math.floor( Distance ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then - return 1 - end - - return 0 - end - -end - -do -- TASK_A2G_SEAD - - --- The TASK_A2G_SEAD class - -- @type TASK_A2G_SEAD - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines an Suppression or Extermination of Air Defenses task for a human player to be executed. - -- These tasks are important to be executed as they will help to achieve air superiority at the vicinity. - -- - -- The TASK_A2G_SEAD is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks - -- based on detected enemy ground targets. - -- - -- @field #TASK_A2G_SEAD - TASK_A2G_SEAD = { - ClassName = "TASK_A2G_SEAD" - } - - --- Instantiates a new TASK_A2G_SEAD. - -- @param #TASK_A2G_SEAD self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2G_SEAD self - function TASK_A2G_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD", TaskBriefing ) ) -- #TASK_A2G_SEAD - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Execute a Suppression of Enemy Air Defenses." ) - - return self - end - - --- Set a score when a target in scope of the A2G attack, has been destroyed . - -- @param #TASK_A2G_SEAD self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_SEAD - function TASK_A2G_SEAD:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has SEADed a target.", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- @param #TASK_A2G_SEAD self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_SEAD - function TASK_A2G_SEAD:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All radar emitting targets have been successfully SEADed!", Score ) - - return self - end - - --- Set a penalty when the A2G attack has failed. - -- @param #TASK_A2G_SEAD self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_SEAD - function TASK_A2G_SEAD:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The SEADing has failed!", Penalty ) - - return self - end - -end - -do -- TASK_A2G_BAI - - --- The TASK_A2G_BAI class - -- @type TASK_A2G_BAI - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines a Battlefield Air Interdiction task for a human player to be executed. - -- These tasks are more strategic in nature and are most of the time further away from friendly forces. - -- BAI tasks can also be used to express the abscence of friendly forces near the vicinity. - -- - -- The TASK_A2G_BAI is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create BAI tasks - -- based on detected enemy ground targets. - -- - -- @field #TASK_A2G_BAI - TASK_A2G_BAI = { ClassName = "TASK_A2G_BAI" } - - --- Instantiates a new TASK_A2G_BAI. - -- @param #TASK_A2G_BAI self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2G_BAI self - function TASK_A2G_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "BAI", TaskBriefing ) ) -- #TASK_A2G_BAI - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or "Execute a Battlefield Air Interdiction of a group of enemy targets." ) - - return self - end - - --- Set a score when a target in scope of the A2G attack, has been destroyed . - -- @param #TASK_A2G_BAI self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_BAI - function TASK_A2G_BAI:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Battlefield Air Interdiction (BAI).", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- @param #TASK_A2G_BAI self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_BAI - function TASK_A2G_BAI:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Battlefield Air Interdiction (BAI) is a success!", Score ) - - return self - end - - --- Set a penalty when the A2G attack has failed. - -- @param #TASK_A2G_BAI self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_BAI - function TASK_A2G_BAI:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The Battlefield Air Interdiction (BAI) has failed!", Penalty ) - - return self - end - -end - -do -- TASK_A2G_CAS - - --- The TASK_A2G_CAS class - -- @type TASK_A2G_CAS - -- @field Core.Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - - --- Defines an Close Air Support task for a human player to be executed. - -- Friendly forces will be in the vicinity within 6km from the enemy. - -- - -- The TASK_A2G_CAS is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create CAS tasks - -- based on detected enemy ground targets. - -- - -- @field #TASK_A2G_CAS - TASK_A2G_CAS = { ClassName = "TASK_A2G_CAS" } - - --- Instantiates a new TASK_A2G_CAS. - -- @param #TASK_A2G_CAS self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_UNIT TargetSetUnit - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_A2G_CAS self - function TASK_A2G_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskBriefing ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "CAS", TaskBriefing ) ) -- #TASK_A2G_CAS - self:F() - - Mission:AddTask( self ) - - self:SetBriefing( TaskBriefing or ( "Execute a Close Air Support for a group of enemy targets. " .. "Beware of friendlies at the vicinity! " ) ) - - return self - end - - --- Set a score when a target in scope of the A2G attack, has been destroyed . - -- @param #TASK_A2G_CAS self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points to be granted when task process has been achieved. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_CAS - function TASK_A2G_CAS:SetScoreOnProgress( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "AccountForPlayer", "Player " .. PlayerName .. " has destroyed a target in Close Air Support (CAS).", Score ) - - return self - end - - --- Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- @param #TASK_A2G_CAS self - -- @param #string PlayerName The name of the player. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_CAS - function TASK_A2G_CAS:SetScoreOnSuccess( PlayerName, Score, TaskUnit ) - self:F( { PlayerName, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", "All targets have been successfully destroyed! The Close Air Support (CAS) was a success!", Score ) - - return self - end - - --- Set a penalty when the A2G attack has failed. - -- @param #TASK_A2G_CAS self - -- @param #string PlayerName The name of the player. - -- @param #number Penalty The penalty in points, must be a negative value! - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_A2G_CAS - function TASK_A2G_CAS:SetScoreOnFail( PlayerName, Penalty, TaskUnit ) - self:F( { PlayerName, Penalty, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", "The Close Air Support (CAS) has failed!", Penalty ) - - return self - end - -end diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua deleted file mode 100644 index a98477d86..000000000 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ /dev/null @@ -1,828 +0,0 @@ ---- **Tasking** - Dynamically allocates A2G tasks to human players, based on detected ground targets through reconnaissance. --- --- **Features:** --- --- * Dynamically assign tasks to human players based on detected targets. --- * Dynamically change the tasks as the tactical situation evolves during the mission. --- * Dynamically assign (CAS) Close Air Support tasks for human players. --- * Dynamically assign (BAI) Battlefield Air Interdiction tasks for human players. --- * Dynamically assign (SEAD) Suppression of Enemy Air Defense tasks for human players to eliminate G2A missile threats. --- * Define and use an EWR (Early Warning Radar) network. --- * Define different ranges to engage upon intruders. --- * Keep task achievements. --- * Score task achievements. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_A2G_Dispatcher --- @image Task_A2G_Dispatcher.JPG - -do -- TASK_A2G_DISPATCHER - - --- TASK\_A2G\_DISPATCHER class. - -- @type TASK_A2G_DISPATCHER - -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Tasking.Mission#MISSION Mission - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - - --- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Functional.Detection} object. - -- - -- It uses the Tasking System within the MOOSE framework, which is a multi-player Tasking Orchestration system. - -- It provides a truly dynamic battle environment for pilots and ground commanders to engage upon, - -- in a true co-operation environment wherein **Multiple Teams** will collaborate in Missions to **achieve a common Mission Goal**. - -- - -- The A2G dispatcher will dispatch the A2G Tasks to a defined @{Core.Set} of @{Wrapper.Group}s that will be manned by **Players**. - -- We call this the **AttackSet** of the A2G dispatcher. So, the Players are seated in the @{Wrapper.Client}s of the @{Wrapper.Group} @{Core.Set}. - -- - -- Depending on the actions of the enemy, preventive tasks are dispatched to the players to orchestrate the engagement in a true co-operation. - -- The detection object will group the detected targets by its grouping method, and integrates a @{Core.Set} of @{Wrapper.Group}s that are Recce vehicles or air units. - -- We call this the **RecceSet** of the A2G dispatcher. - -- - -- Depending on the current detected tactical situation, different task types will be dispatched to the Players seated in the AttackSet.. - -- There are currently 3 **Task Types** implemented in the TASK\_A2G\_DISPATCHER: - -- - -- - **SEAD Task**: Dispatched when there are ground based Radar Emitters detected within an area. - -- - **CAS Task**: Dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. - -- - **BAI Task**: Dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. - -- - -- # 0. Tactical Situations - -- - -- This chapters provides some insights in the tactical situations when certain Task Types are created. - -- The Task Types are depending on the enemy positions that were detected, and the current location of friendly units. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia3.JPG) - -- - -- In the demonstration mission [TAD-A2G-000 - AREAS - Detection test], - -- the tactical situation is a demonstration how the A2G detection works. - -- This example will be taken further in the explanation in the following chapters. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia4.JPG) - -- - -- The red coalition are the players, the blue coalition is the enemy. - -- - -- Red reconnaissance vehicles and airborne units are detecting the targets. - -- We call this the RecceSet as explained above, which is a Set of Groups that - -- have a group name starting with `Recce` (configured in the mission script). - -- - -- Red attack units are responsible for executing the mission for the command center. - -- We call this the AttackSet, which is a Set of Groups with a group name starting with `Attack` (configured in the mission script). - -- These units are setup in this demonstration mission to be ground vehicles and airplanes. - -- For demonstration purposes, the attack airplane is stationed on the ground to explain - -- the messages and the menus properly. - -- Further test missions demonstrate the A2G task dispatcher from within air. - -- - -- Depending upon the detection results, the A2G dispatcher will create different tasks. - -- - -- # 0.1. SEAD Task - -- - -- A SEAD Task is dispatched when there are ground based Radar Emitters detected within an area. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia9.JPG) - -- - -- - Once all Radar Emitting Units have been destroyed, the Task will convert into a BAI or CAS task! - -- - A CAS and BAI task may be converted into a SEAD task, once a radar has been detected within the area! - -- - -- # 0.2. CAS Task - -- - -- A CAS Task is dispatched when there are no ground based Radar Emitters within the area, but there are friendly ground Units within 6 km from the enemy. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia10.JPG) - -- - -- - After the detection of the CAS task, if the friendly Units are destroyed, the CAS task will convert into a BAI task! - -- - Only ground Units are taken into account. Airborne units are ships are not considered friendlies that require Close Air Support. - -- - -- # 0.3. BAI Task - -- - -- A BAI Task is dispatched when there are no ground based Radar Emitters within the area, and there aren't friendly ground Units within 6 km from the enemy. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia11.JPG) - -- - -- - A BAI task may be converted into a CAS task if friendly Ground Units approach within 6 km range! - -- - -- # 1. Player Experience - -- - -- The A2G dispatcher is residing under a @{Tasking.CommandCenter}, which is orchestrating a @{Tasking.Mission}. - -- As a result, you'll find for DCS World missions that implement the A2G dispatcher a **Command Center Menu** and under this one or more **Mission Menus**. - -- - -- For example, if there are 2 Command Centers (CC). - -- Each CC is controlling a couple of Missions, the Radio Menu Structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha (Primary)" - -- F2. Mission "Beta (Secondary)" - -- F3. Mission "Gamma (Tactical)" - -- F1. Command Center [Lima] - -- F1. Mission "Overlord (High)" - -- - -- Command Center [Gori] is controlling Mission "Alpha", "Beta", "Gamma". Alpha is the Primary mission, Beta the Secondary and there is a Tactical mission Gamma. - -- Command Center [Lima] is controlling Missions "Overlord", which needs to be executed with High priority. - -- - -- ## 1.1. Mission Menu (Under the Command Center Menu) - -- - -- The Mission Menu controls the information of the mission, including the: - -- - -- - **Mission Briefing**: A briefing of the Mission in text, which will be shown as a message. - -- - **Mark Task Locations**: A summary of each Task will be shown on the map as a marker. - -- - **Create Task Reports**: A menu to create various reports of the current tasks dispatched by the A2G dispatcher. - -- - **Create Mission Reports**: A menu to create various reports on the current mission. - -- - -- For CC [Lima], Mission "Overlord", the menu structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Lima] - -- F1. Mission "Overlord" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia5.JPG) - -- - -- ### 1.1.1. Mission Briefing Menu - -- - -- The Mission Briefing Menu will show in text a summary description of the overall mission objectives and expectations. - -- Note that the Mission Briefing is not the briefing of a specific task, but rather provides an overall strategy and tactical situation, - -- and explains the mission goals. - -- - -- - -- ### 1.1.2. Mark Task Locations Menu - -- - -- The Mark Task Locations Menu will mark the location indications of the Tasks on the map, if this intelligence is known by the Command Center. - -- For A2G tasks this information will always be know, but it can be that for other tasks a location intelligence will be less relevant. - -- Note that each Planned task and each Engaged task will be marked. Completed, Failed and Cancelled tasks are not marked. - -- Depending on the task type, a summary information is shown to bring to the player the relevant information for situational awareness. - -- - -- ### 1.1.3. Task Reports Menu - -- - -- The Task Reports Menu is a sub menu, that allows to create various reports: - -- - -- - **Tasks Summary**: This report will list all the Tasks that are or were active within the mission, indicating its status. - -- - **Planned Tasks**: This report will list all the Tasks that are in status Planned, which are Tasks not assigned to any player, and are ready to be executed. - -- - **Assigned Tasks**: This report will list all the Tasks that are in status Assigned, which are Tasks assigned to (a) player(s) and are currently executed. - -- - **Successful Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and are completed successfully. - -- - **Failed Tasks**: This report will list all the Tasks that are in status Success, which are Tasks executed by (a) player(s) and that have failed. - -- - -- The information shown of the tasks will vary according the underlying task type, but are self explanatory. - -- - -- For CC [Gori], Mission "Alpha", the Task Reports menu structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F1. Tasks Summary - -- F2. Planned Tasks - -- F3. Assigned Tasks - -- F4. Successful Tasks - -- F5. Failed Tasks - -- F4. Mission Reports - -- - -- Note that these reports provide an "overview" of the tasks. Detailed information of the task can be retrieved using the Detailed Report on the Task Menu. - -- (See later). - -- - -- ### 1.1.4. Mission Reports Menu - -- - -- The Mission Reports Menu is a sub menu, that provides options to retrieve further information on the current Mission: - -- - -- - **Report Mission Progress**: Shows the progress of the current Mission. Each Task has a % of completion. - -- - **Report Players per Task**: Show which players are engaged on which Task within the Mission. - -- - -- For CC |Gori|, Mission "Alpha", the Mission Reports menu structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F1. Report Mission Progress - -- F2. Report Players per Task - -- - -- - -- ## 1.2. Task Management Menus - -- - -- Very important to remember is: **Multiple Players can be assigned to the same Task, but from the player perspective, the Player can only be assigned to one Task per Mission at the same time!** - -- Consider this like the two major modes in which a player can be in. He can be free of tasks or he can be assigned to a Task. - -- Depending on whether a Task has been Planned or Assigned to a Player (Group), - -- **the Mission Menu will contain extra Menus to control specific Tasks.** - -- - -- #### 1.2.1. Join a Planned Task - -- - -- If the Player has not yet been assigned to a Task within the Mission, the Mission Menu will contain additionally a: - -- - -- - Join Planned Task Menu: This menu structure allows the player to join a planned task (a Task with status Planned). - -- - -- For CC |Gori|, Mission "Alpha", the menu structure could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Join Planned Task - -- - -- **The F5. Join Planned Task allows the player to join a Planned Task and take an engagement in the running Mission.** - -- - -- #### 1.2.2. Manage an Assigned Task - -- - -- If the Player has been assigned to one Task within the Mission, the Mission Menu will contain an extra: - -- - -- - Assigned Task __TaskName__ Menu: This menu structure allows the player to take actions on the currently engaged task. - -- - -- In this example, the Group currently seated by the player is not assigned yet to a Task. - -- The Player has the option to assign itself to a Planned Task using menu option F5 under the Mission Menu "Alpha". - -- - -- This would be an example menu structure, - -- for CC |Gori|, Mission "Alpha", when a player would have joined Task CAS.001: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Assigned Task CAS.001 - -- - -- **The F5. Assigned Task __TaskName__ allows the player to control the current Assigned Task and take further actions.** - -- - -- ## 1.3. Join Planned Task Menu - -- - -- The Join Planned Task Menu contains the different Planned A2G Tasks **in a structured Menu Hierarchy**. - -- The Menu Hierarchy is structuring the Tasks per **Task Type**, and then by **Task Name (ID)**. - -- - -- For example, for CC [Gori], Mission "Alpha", - -- if a Mission "ALpha" contains 5 Planned Tasks, which would be: - -- - -- - 2 CAS Tasks - -- - 1 BAI Task - -- - 2 SEAD Tasks - -- - -- the Join Planned Task Menu Hierarchy could look like this: - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center [Gori] - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Join Planned Task - -- F2. BAI - -- F1. BAI.001 - -- F1. CAS - -- F1. CAS.002 - -- F3. SEAD - -- F1. SEAD.003 - -- F2. SEAD.004 - -- F3. SEAD.005 - -- - -- An example from within a running simulation: - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia6.JPG) - -- - -- Each Task Type Menu would have a list of the Task Menus underneath. - -- Each Task Menu (eg. `CAS.001`) has a **detailed Task Menu structure to control the specific task**! - -- - -- ### 1.3.1. Planned Task Menu - -- - -- Each Planned Task Menu will allow for the following actions: - -- - -- - Report Task Details: Provides a detailed report on the Planned Task. - -- - Mark Task Location on Map: Mark the approximate location of the Task on the Map, if relevant. - -- - Join Task: Join the Task. This is THE menu option to let a Player join the Task, and to engage within the Mission. - -- - -- The Join Planned Task Menu could look like this for for CC |Gori|, Mission "Alpha": - -- - -- Radio MENU Structure (F10. Other) - -- - -- F1. Command Center |Gori| - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Join Planned Task - -- F1. CAS - -- F1. CAS.001 - -- F1. Report Task Details - -- F2. Mark Task Location on Map - -- F3. Join Task - -- - -- **The Join Task is THE menu option to let a Player join the Task, and to engage within the Mission.** - -- - -- - -- ## 1.4. Assigned Task Menu - -- - -- The Assigned Task Menu allows to control the **current assigned task** within the Mission. - -- - -- Depending on the Type of Task, the following menu options will be available: - -- - -- - **Report Task Details**: Provides a detailed report on the Planned Task. - -- - **Mark Task Location on Map**: Mark the approximate location of the Task on the Map, if relevant. - -- - **Abort Task: Abort the current assigned Task:** This menu option lets the player abort the Task. - -- - -- For example, for CC |Gori|, Mission "Alpha", the Assigned Menu could be: - -- - -- F1. Command Center |Gori| - -- F1. Mission "Alpha" - -- F1. Mission Briefing - -- F2. Mark Task Locations on Map - -- F3. Task Reports - -- F4. Mission Reports - -- F5. Assigned Task - -- F1. Report Task Details - -- F2. Mark Task Location on Map - -- F3. Abort Task - -- - -- Task abortion will result in the Task to be Cancelled, and the Task **may** be **Replanned**. - -- However, this will depend on the setup of each Mission. - -- - -- ## 1.5. Messages - -- - -- During game play, different messages are displayed. - -- These messages provide an update of the achievements made, and the state wherein the task is. - -- - -- The various reports can be used also to retrieve the current status of the mission and its tasks. - -- - -- ![](..\Presentations\TASK_A2G_DISPATCHER\Dia7.JPG) - -- - -- The @{Core.Settings} menu provides additional options to control the timing of the messages. - -- There are: - -- - -- - Status messages, which are quick status updates. The settings menu allows to switch off these messages. - -- - Information messages, which are shown a bit longer, as they contain important information. - -- - Summary reports, which are quick reports showing a high level summary. - -- - Overview reports, which are providing the essential information. It provides an overview of a greater thing, and may take a bit of time to read. - -- - Detailed reports, which provide with very detailed information. It takes a bit longer to read those reports, so the display of those could be a bit longer. - -- - -- # 2. TASK\_A2G\_DISPATCHER constructor - -- - -- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK\_A2G\_DISPATCHER instance. - -- - -- # 3. Usage - -- - -- To use the TASK\_A2G\_DISPATCHER class, you need: - -- - -- - A @{Tasking.CommandCenter} object. The master communication channel. - -- - A @{Tasking.Mission} object. Each task belongs to a Mission. - -- - A @{Functional.Detection} object. There are several detection grouping methods to choose from. - -- - A @{Tasking.Task_A2G_Dispatcher} object. The master A2G task dispatcher. - -- - A @{Core.Set} of @{Wrapper.Group} objects that will detect the enemy, the RecceSet. This is attached to the @{Functional.Detection} object. - -- - A @{Core.Set} of @{Wrapper.Group} objects that will attack the enemy, the AttackSet. This is attached to the @{Tasking.Task_A2G_Dispatcher} object. - -- - -- Below an example mission declaration that is defines a Task A2G Dispatcher object. - -- - -- -- Declare the Command Center - -- local HQ = GROUP - -- :FindByName( "HQ", "Bravo HQ" ) - -- - -- local CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED ) - -- - -- -- Define the RecceSet that will detect the enemy. - -- local RecceSet = SET_GROUP - -- :New() - -- :FilterPrefixes( "FAC" ) - -- :FilterCoalitions("red") - -- :FilterStart() - -- - -- -- Setup the detection. We use DETECTION_AREAS to detect and group the enemies within areas of 3 km radius. - -- local DetectionAreas = DETECTION_AREAS - -- :New( RecceSet, 3000 ) -- The RecceSet will detect the enemies. - -- - -- -- Setup the AttackSet, which is a SET_GROUP. - -- -- The SET_GROUP is a dynamic collection of GROUP objects. - -- local AttackSet = SET_GROUP - -- :New() -- Create the SET_GROUP object. - -- :FilterCoalitions( "red" ) -- Only incorporate the RED coalitions. - -- :FilterPrefixes( "Attack" ) -- Only incorporate groups that start with the name Attack. - -- :FilterStart() -- Enable the dynamic filtering. From this moment the AttackSet will contain all groups that are red and start with the name Attack. - -- - -- -- Now we have everything to setup the main A2G TaskDispatcher. - -- TaskDispatcher = TASK_A2G_DISPATCHER - -- :New( Mission, AttackSet, DetectionAreas ) -- We assign the TaskDispatcher under Mission. The AttackSet will engage the enemy and will receive the dispatched Tasks. The DetectionAreas will report any detected enemies to the TaskDispatcher. - -- - -- - -- # 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 #TASK_A2G_DISPATCHER - TASK_A2G_DISPATCHER = { - ClassName = "TASK_A2G_DISPATCHER", - Mission = nil, - Detection = nil, - Tasks = {} - } - - --- TASK_A2G_DISPATCHER constructor. - -- @param #TASK_A2G_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. - -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. - -- @return #TASK_A2G_DISPATCHER self - function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER - - self.Detection = Detection - self.Mission = Mission - self.FlashNewTask = true -- set to false to suppress flash messages - - self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } ) - - self:AddTransition( "Started", "Assign", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#TASK_A2G_DISPATCHER] OnAfterAssign - -- @param #TASK_A2G_DISPATCHER self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Tasking.Task_A2G#TASK_A2G Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:__Start( 5 ) - - return self - end - - --- Set flashing player messages on or off - -- @param #TASK_A2G_DISPATCHER self - -- @param #boolean onoff Set messages on (true) or off (false) - function TASK_A2G_DISPATCHER:SetSendMessages( onoff ) - self.FlashNewTask = onoff - end - - --- Creates a SEAD task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount > 0 then - - -- Here we're doing something advanced... We're copying the DetectedSet, but making a new Set only with SEADable Radar units in it. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterHasSEAD() - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a CAS task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Determine if the set has ground units. - -- There should be ground unit friendlies nearby. Airborne units are valid friendlies types. - -- And there shouldn't be any radar. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) -- Are there friendlies nearby of type GROUND_UNIT? - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount == 0 and GroundUnitCount > 0 and FriendliesNearBy == true then - - -- Copy the Set - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a BAI task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Determine if the set has ground units. - -- There shouldn't be any ground unit friendlies nearby. - -- And there shouldn't be any radar. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) -- Are there friendlies nearby of type GROUND_UNIT? - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount == 0 and GroundUnitCount > 0 and FriendliesNearBy == false then - - -- Copy the Set - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - function TASK_A2G_DISPATCHER:RemoveTask( TaskIndex ) - self.Mission:RemoveTask( self.Tasks[TaskIndex] ) - self.Tasks[TaskIndex] = nil - end - - --- Evaluates the removal of the Task from the Mission. - -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". - -- @param #TASK_A2G_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission - -- @param Tasking.Task#TASK Task - -- @param #boolean DetectedItemID - -- @param #boolean DetectedItemChange - -- @return Tasking.Task#TASK - function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, TaskIndex, DetectedItemChanged ) - - if Task then - if (Task:IsStatePlanned() and DetectedItemChanged == true) or Task:IsStateCancelled() then - -- self:F( "Removing Tasking: " .. Task:GetTaskName() ) - self:RemoveTask( TaskIndex ) - end - end - - return Task - end - - --- Assigns tasks in relation to the detected items to the @{Core.Set#SET_GROUP}. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function TASK_A2G_DISPATCHER:ProcessDetected( Detection ) - self:F() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - if Mission:IsIDLE() or Mission:IsENGAGED() then - - local TaskReport = REPORT:New() - - -- Checking the task queue for the dispatcher, and removing any obsolete task! - for TaskIndex, TaskData in pairs( self.Tasks ) do - local Task = TaskData -- Tasking.Task#TASK - if Task:IsStatePlanned() then - local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex ) - if not DetectedItem then - local TaskText = Task:GetName() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self.FlashNewTask then - Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) - end - end - Task = self:RemoveTask( TaskIndex ) - end - end - end - - --- First we need to the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedZone = DetectedItem.Zone - -- self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - -- DetectedSet:Flush( self ) - - local DetectedItemID = DetectedItem.ID - local TaskIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - self:F( { DetectedItemChanged = DetectedItemChanged, DetectedItemID = DetectedItemID, TaskIndex = TaskIndex } ) - - local Task = self.Tasks[TaskIndex] -- Tasking.Task_A2G#TASK_A2G - - if Task then - -- If there is a Task and the task was assigned, then we check if the task was changed ... If it was, we need to reevaluate the targets. - if Task:IsStateAssigned() then - if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set - local TargetsReport = REPORT:New() - local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - if Task:IsInstanceOf( TASK_A2G_SEAD ) then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) - else - Task:Cancel() - end - else - local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... - if TargetSetUnit then - if Task:IsInstanceOf( TASK_A2G_CAS ) then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - else - local TargetSetUnit = self:EvaluateBAI( DetectedItem ) -- Returns a SetUnit if there are targets to be BAIed... - if TargetSetUnit then - if Task:IsInstanceOf( TASK_A2G_BAI ) then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - TargetsReport:Add( Detection:GetChangeText( DetectedItem ) ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - end - end - end - - -- Now we send to each group the changes, if any. - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - local TargetsText = TargetsReport:Text( ", " ) - if (Mission:IsGroupAssigned( TaskGroup )) and TargetsText ~= "" and self.FlashNewTask then - Mission:GetCommandCenter():MessageToGroup( string.format( "Task %s has change of targets:\n %s", Task:GetName(), TargetsText ), TaskGroup ) - end - end - end - end - end - - if Task then - if Task:IsStatePlanned() then - if DetectedItemChanged == true then -- The detection has changed, thus a new TargetSet is to be evaluated and set - if Task:IsInstanceOf( TASK_A2G_SEAD ) then - local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - else - if Task:IsInstanceOf( TASK_A2G_CAS ) then - local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... - if TargetSetUnit then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - else - if Task:IsInstanceOf( TASK_A2G_BAI ) then - local TargetSetUnit = self:EvaluateBAI( DetectedItem ) -- Returns a SetUnit if there are targets to be BAIed... - if TargetSetUnit then - Task:SetTargetSetUnit( TargetSetUnit ) - Task:SetDetection( Detection, DetectedItem ) - Task:UpdateTaskInfo( DetectedItem ) - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - else - Task:Cancel() - Task = self:RemoveTask( TaskIndex ) - end - end - end - end - end - end - - -- Evaluate SEAD - if not Task then - local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object - Task:SetDetection( Detection, DetectedItem ) - end - - -- Evaluate CAS - if not Task then - local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... - if TargetSetUnit then - Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object - Task:SetDetection( Detection, DetectedItem ) - end - - -- Evaluate BAI - if not Task then - local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed... - if TargetSetUnit then - Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit ) - DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) -- inject a name for DESIGNATE, if using same DETECTION object - Task:SetDetection( Detection, DetectedItem ) - end - end - end - - if Task then - self.Tasks[TaskIndex] = Task - Task:SetTargetZone( DetectedZone ) - Task:SetDispatcher( self ) - Task:UpdateTaskInfo( DetectedItem ) - Mission:AddTask( Task ) - - function Task.OnEnterSuccess( Task, From, Event, To ) - self:Success( Task ) - end - - function Task.OnEnterCancelled( Task, From, Event, To ) - self:Cancelled( Task ) - end - - function Task.OnEnterFailed( Task, From, Event, To ) - self:Failed( Task ) - end - - function Task.OnEnterAborted( Task, From, Event, To ) - self:Aborted( Task ) - end - - TaskReport:Add( Task:GetName() ) - else - self:F( "This should not happen" ) - end - end - - -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedItem ) - end - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text( ", " ) - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if (not Mission:IsGroupAssigned( TaskGroup )) and TaskText ~= "" and self.FlashNewTask then - Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) - end - end - - end - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/Task_CARGO.lua b/Moose Development/Moose/Tasking/Task_CARGO.lua deleted file mode 100644 index be894d805..000000000 --- a/Moose Development/Moose/Tasking/Task_CARGO.lua +++ /dev/null @@ -1,1410 +0,0 @@ ---- **Tasking** - Base class to model tasks for players to transport cargo. --- --- ## Features: --- --- * TASK_CARGO is the **base class** for: --- --- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} --- * @{Tasking.Task_Cargo_CSAR#TASK_CARGO_CSAR} --- --- --- === --- --- ## Test Missions: --- --- Test missions can be located on the main GITHUB site. --- --- [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Tasking/Task_Cargo_Dispatcher) --- --- === --- --- ## Tasking system. --- --- #### If you are not yet aware what the MOOSE tasking system is about, read FIRST the explanation on the @{Tasking.Task} module. --- --- === --- --- ## Context of cargo tasking. --- --- The Moose framework provides various CARGO classes that allow DCS physical or logical objects to be transported or sling loaded by Carriers. --- The CARGO_ classes, as part of the MOOSE core, are able to Board, Load, UnBoard and UnLoad cargo between Carrier units. --- --- The TASK_CARGO class is not meant to use within your missions as a mission designer. It is a base class, and other classes are derived from it. --- --- The following TASK_CARGO_ classes are important, as they implement the CONCRETE tasks: --- --- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT}: Defines a task for a human player to transport a set of cargo between various zones. --- * @{Tasking.Task_Cargo_CSAR#TASK_CARGO_CSAR}: Defines a task for a human player to Search and Rescue wounded pilots. --- --- However! The menu system and basic usage of the TASK_CARGO classes is explained in the @{#TASK_CARGO} class description. --- So please browse further below to understand how to use it from a player perspective! --- --- === --- --- ## Cargo tasking from a player perspective. --- --- A human player can join the battle field in a client airborne slot or a ground vehicle within the CA module (ALT-J). --- The player needs to accept the task from the task overview list within the mission, using the menus. --- --- Once the task is assigned to the player and accepted by the player, the player will obtain --- an extra **Cargo (Radio) Menu** that contains the CARGO objects that need to be transported. --- --- Each @{Tasking.Task_CARGO#TASK_CARGO} object has a certain state: --- --- * **UnLoaded**: The cargo is located within the battlefield. It may still need to be transported. --- * **Loaded**: The cargo is loaded within a Carrier. This can be your air unit, or another air unit, or even a vehicle. --- * **Boarding**: The cargo is running or moving towards your Carrier for loading. --- * **UnBoarding**: The cargo is driving or jumping out of your Carrier and moves to a location in the Deployment Zone. --- --- Cargo must be transported towards different Deployment @{Core.Zone}s. --- --- The Cargo Menu system allows to execute **various actions** to transport the cargo. --- In the menu, you'll find for each CARGO, that is part of the scope of the task, various actions that can be completed. --- Depending on the location of your Carrier unit, the menu options will vary. --- --- ### Joining a Cargo Transport Task --- --- Once you've joined a task, using the **Join Planned Task Menu**, --- you can Pickup cargo from a pickup location and Deploy cargo in deployment zones, using the **Task Action Menu**. --- --- ### Task Action Menu. --- --- When a player has joined a **`CARGO`** task (type), for that player only, --- it's **Task Action Menu** will show an additional menu options. --- --- From within this menu, you will be able to route to a cargo location, deploy zone, and load/unload cargo. --- --- ### Pickup cargo by Boarding, Loading and Sling Loading. --- --- There are three different ways how cargo can be picked up: --- --- - **Boarding**: Moveable cargo (like infantry or vehicles), can be boarded, that means, the cargo will move towards your carrier to board. --- However, it can only execute the boarding actions if it is within the foreseen **Reporting Range**. --- Therefore, it is important that you steer your Carrier within the Reporting Range around the cargo, --- so that boarding actions can be executed on the cargo. The reporting range is set by the mission designer. --- Fortunately, the cargo is reporting to you when it is within reporting range. --- --- - **Loading**: Stationary cargo (like crates), which are heavy, can only be loaded or sling loaded, meaning, --- your carrier must be close enough to the cargo to be able to load the cargo within the carrier bays. --- Moose provides you with an additional menu system to load stationary cargo into your carrier bays using the menu. --- These menu options will become available, when the carrier is within loading range. --- The Moose cargo will report to the carrier when the range is close enough. The load range is set by the mission designer. --- --- - **Sling Loading**: Stationary cargo (like crates), which are heavy, can only be loaded or sling loaded, meaning, --- your carrier must be close enough to the cargo to be able to load the cargo within the carrier bays. --- Sling loading cargo is done using the default DCS menu system. However, Moose cargo will report to the carrier that --- it is within sling loading range. --- --- In order to be able to pickup cargo, you'll need to know where the cargo is located, right? --- --- Fortunately, if your Carrier is not within the reporting range of the cargo, --- **the HQ can help to route you to the locations of cargo**. --- --- ![Task_Types](../Tasking/Task_Cargo_Main_Menu.JPG) --- --- Use the task action menu to receive HQ help for this. --- --- ![Task_Types](../Tasking/Task_Cargo_Action_Menu.JPG) --- --- Depending on the location within the battlefield, the task action menu will contain **Route options** that can be selected --- to start the HQ sending you routing messages. --- The **route options will vary**, depending on the position of your carrier, and the location of the cargo and the deploy zones. --- Note that the route options will **only be created** for cargo that is **in scope of your cargo transportation task**, --- so there may be other cargo objects within the DCS simulation, but if those belong to other cargo transportations tasks, --- then no routing options will be shown for these cargo. --- This is done to ensure that **different teams** have a **defined scope** for defined cargo, and that **multiple teams** can join --- **multiple tasks**, transporting cargo **simultaneously** in a **cooperation**. --- --- In this example, there is a menu option to **Route to pickup cargo...**. --- Use this menu to route towards cargo locations for pickup into your carrier. --- --- ![Task_Types](../Tasking/Task_Cargo_Types_Menu.JPG) --- --- When you select this menu, you'll see a new menu listing the different cargo types that are out there in the dcs simulator. --- These cargo types are symbolic names that are assigned by the mission designer, like oil, liquid, engineers, food, workers etc. --- MOOSE has introduced this concept to allow mission designers to make different cargo types for different purposes. --- Only the creativity of the mission designer limits now the things that can be done with cargo ... --- Okay, let's continue ..., and let's select Oil ... --- --- When selected, the HQ will send you routing messages. --- --- ![Task_Types](../Tasking/Task_Cargo_Routing_BR.JPG) --- --- An example of routing in BR mode. --- --- Note that the coordinate display format in the message can be switched between LL DMS, LL DDM, MGRS and BR. --- --- ![Task_Types](../Tasking/Main_Settings.JPG) --- --- Use the @{Core.Settings} menu to change your display format preferences. --- --- ![Task_Types](../Tasking/Settings_A2G_Coordinate.JPG) --- --- There you can change the display format to another format that suits your need. --- Because cargo transportation is Air 2 Ground oriented, you need to select the A2G coordinate format display options. --- Note that the main settings menu contains much more --- options to control your display formats, like switch to metric and imperial, or change the duration of the display messages. --- --- ![Task_Types](../Tasking/Task_Cargo_Routing_LL.JPG) --- --- Here I changed the routing display format to LL DMS. --- --- One important thing to know, is that the routing messages will flash at regular time intervals. --- When using BR coordinate display format, the **distance and angle will change accordingly** from your carrier position and the location of the cargo. --- --- Another important note is the routing towards deploy zones. --- These routing options will only be shown, when your carrier bays have cargo loaded. --- So, only when there is something to be deployed from your carrier, the deploy options will be shown. --- --- #### Pickup Cargo. --- --- In order to pickup cargo, use the **task action menu** to **route to a specific cargo**. --- When a cargo route is selected, the HQ will send you routing messages indicating the location of the cargo. --- --- Upon arrival at the cargo, and when the cargo is within **reporting range**, the cargo will contact you and **further instructions will be given**. --- --- - When your Carrier is airborne, you will receive instructions to land your Carrier. --- The action will not be completed until you've landed your Carrier. --- --- - For ground carriers, you can just drive to the optimal cargo board or load position. --- --- It takes a bit of skill to land a helicopter near a cargo to be loaded, but that is part of the game, isn't it? --- Expecially when you are landing in a "hot" zone, so when cargo is under immediate threat of fire. --- --- #### Board Cargo (infantry). --- --- ![](../Tasking/Boarding_Ready.png) --- --- If your Carrier is within the **Reporting Range of the cargo**, and the cargo is **moveable**, the **cargo can be boarded**! --- This type of cargo will be most of the time be infantry. --- --- ![](../Tasking/Boarding_Menu.png) --- --- A **Board cargo...** sub menu has appeared, because your carrier is in boarding range of the cargo (infantry). --- Select the **Board cargo...** menu. --- --- ![](../Tasking/Boarding_Menu_Engineers.png) --- --- Any cargo that can be boarded (thus movable cargo), within boarding range of the carrier, will be listed here! --- In this example, the cargo **Engineers** can be boarded, by selecting the menu option. --- --- ![](../Tasking/Boarding_Started.png) --- --- After the menu option to board the cargo has been selected, the boarding process is started. --- A message from the cargo is communicated to the pilot, that boarding is started. --- --- ![](../Tasking/Boarding_Ongoing.png) --- --- **The pilot must wait at the exact position until all cargo has been boarded!** --- --- The moveable cargo will run in formation to your carrier, and will board one by one, depending on the near range set by the mission designer. --- The near range as added because carriers can be large or small, depending on the object size of the carrier. --- --- ![](../Tasking/Boarding_In_Progress.png) --- --- ![](../Tasking/Boarding_Almost_Done.png) --- --- Note that multiple units may need to board your Carrier, so it is required to await the full boarding process. --- --- ![](../Tasking/Boarding_Done.png) --- --- Once the cargo is fully boarded within your Carrier, you will be notified of this. --- --- **Remarks:** --- --- * For airborne Carriers, it is required to land first before the Boarding process can be initiated. --- If during boarding the Carrier gets airborne, the boarding process will be cancelled. --- * The carrier must remain stationary when the boarding sequence has started until further notified. --- --- #### Load Cargo. --- --- Cargo can be loaded into vehicles or helicopters or airplanes, as long as the carrier is sufficiently near to the cargo object. --- --- ![](../Tasking/Loading_Ready.png) --- --- If your Carrier is within the **Loading Range of the cargo**, thus, sufficiently near to the cargo, and the cargo is **stationary**, the **cargo can be loaded**, but not boarded! --- --- ![](../Tasking/Loading_Menu.png) --- --- Select the task action menu and now a **Load cargo...** sub menu will be listed. --- Select the **Load cargo...** sub menu, and a further detailed menu will be shown. --- --- ![](../Tasking/Loading_Menu_Crate.png) --- --- For each non-moveable cargo object (crates etc), **within loading range of the carrier**, the cargo will be listed and can be loaded into the carrier! --- --- ![](../Tasking/Loading_Cargo_Loaded.png) --- --- Once the cargo is loaded within your Carrier, you will be notified of this. --- --- **Remarks:** --- --- * For airborne Carriers, it is required to **land first right near the cargo**, before the loading process can be initiated. --- As stated, this requires some pilot skills :-) --- --- #### Sling Load Cargo (helicopters only). --- --- If your Carrier is within the **Loading Range of the cargo**, and the cargo is **stationary**, the **cargo can also be sling loaded**! --- Note that this is only possible for helicopters. --- --- To sling load cargo, there is no task action menu required. Just follow the normal sling loading procedure and the cargo will report. --- Use the normal DCS sling loading menu system to hook the cargo you the cable attached on your helicopter. --- --- Again note that you may land firstly right next to the cargo, before the loading process can be initiated. --- As stated, this requires some pilot skills :-) --- --- --- ### Deploy cargo by Unboarding, Unloading and Sling Deploying. --- --- #### **Deploying the relevant cargo within deploy zones, will make you achieve cargo transportation tasks!!!** --- --- There are two different ways how cargo can be deployed: --- --- - **Unboarding**: Moveable cargo (like infantry or vehicles), can be unboarded, that means, --- the cargo will step out of the carrier and will run to a group location. --- Moose provides you with an additional menu system to unload stationary cargo from the carrier bays, --- using the menu. These menu options will become available, when the carrier is within the deploy zone. --- --- - **Unloading**: Stationary cargo (like crates), which are heavy, can only be unloaded or sling loaded. --- Moose provides you with an additional menu system to unload stationary cargo from the carrier bays, --- using the menu. These menu options will become available, when the carrier is within the deploy zone. --- --- - **Sling Deploying**: Stationary cargo (like crates), which are heavy, can also be sling deployed. --- Once the cargo is within the deploy zone, the cargo can be deployed from the sling onto the ground. --- --- In order to be able to deploy cargo, you'll need to know where the deploy zone is located, right? --- Fortunately, the HQ can help to route you to the locations of deploy zone. --- Use the task action menu to receive HQ help for this. --- --- ![](../Tasking/Routing_Deploy_Zone_Menu.png) --- --- Depending on the location within the battlefield, the task action menu will contain **Route options** that can be selected --- to start the HQ sending you routing messages. Also, if the carrier cargo bays contain cargo, --- then beside **Route options** there will also be **Deploy options** listed. --- These **Deploy options** are meant to route you to the deploy zone locations. --- --- ![](../Tasking/Routing_Deploy_Zone_Menu_Workplace.png) --- --- Depending on the task that you have selected, the deploy zones will be listed. --- **There may be multiple deploy zones within the mission, but only the deploy zones relevant for your task will be available in the menu!** --- --- ![](../Tasking/Routing_Deploy_Zone_Message.png) --- --- When a routing option is selected, you are sent routing messages in a selected coordinate format. --- Possible routing coordinate formats are: Bearing Range (BR), Lattitude Longitude (LL) or Military Grid System (MGRS). --- Note that for LL, there are two sub formats. (See pickup). --- --- ![](../Tasking/Routing_Deploy_Zone_Arrived.png) --- --- When you are within the range of the deploy zone (can be also a polygon!), a message is communicated by HQ that you have arrived within the zone! --- --- The routing messages are formulated in the coordinate format that is currently active as configured in your settings profile. --- Use the **Settings Menu** to select the coordinate format that you would like to use for location determination. --- --- #### Unboard Cargo. --- --- If your carrier contains cargo, and the cargo is **moveable**, the **cargo can be unboarded**! --- You can only unload cargo if there is cargo within your cargo bays within the carrier. --- --- ![](../Tasking/Unboarding_Menu.png) --- --- Select the task action menu and now an **Unboard cargo...** sub menu will be listed! --- Again, this option will only be listed if there is a non moveable cargo within your cargo bays. --- --- ![](../Tasking/Unboarding_Menu_Engineers.png) --- --- Now you will see a menu option to unload the non-moveable cargo. --- In this example, you can unload the **Engineers** that was loaded within your carrier cargo bays. --- Depending on the cargo loaded within your cargo bays, you will see other options here! --- Select the relevant menu option from the cargo unload menu, and the cargo will unloaded from your carrier. --- --- ![](../Tasking/Unboarding_Started.png) --- --- **The cargo will step out of your carrier and will move towards a grouping point.** --- When the unboarding process has started, you will be notified by a message to your carrier. --- --- ![](../Tasking/Unboarding_In_Progress.png) --- --- The moveable cargo will unboard one by one, so note that multiple units may need to unboard your Carrier, --- so it is required to await the full completion of the unboarding process. --- --- ![](../Tasking/Unboarding_Done.png) --- --- Once the cargo is fully unboarded from your carrier, you will be notified of this. --- --- **Remarks:** --- --- * For airborne carriers, it is required to land first before the unboarding process can be initiated. --- If during unboarding the Carrier gets airborne, the unboarding process will be cancelled. --- * Once the moveable cargo is unboarded, they will start moving towards a specified gathering point. --- * The moveable cargo will send a message to your carrier with unboarding status updates. --- --- **Deploying a cargo within a deployment zone, may complete a deployment task! So ensure that you deploy the right cargo at the right deployment zone!** --- --- #### Unload Cargo. --- --- If your carrier contains cargo, and the cargo is **stationary**, the **cargo can be unloaded**, but not unboarded! --- You can only unload cargo if there is cargo within your cargo bays within the carrier. --- --- ![](../Tasking/Unloading_Menu.png) --- --- Select the task action menu and now an **Unload cargo...** sub menu will be listed! --- Again, this option will only be listed if there is a non moveable cargo within your cargo bays. --- --- ![](../Tasking/Unloading_Menu_Crate.png) --- --- Now you will see a menu option to unload the non-moveable cargo. --- In this example, you can unload the **Crate** that was loaded within your carrier cargo bays. --- Depending on the cargo loaded within your cargo bays, you will see other options here! --- Select the relevant menu option from the cargo unload menu, and the cargo will unloaded from your carrier. --- --- ![](../Tasking/Unloading_Done.png) --- --- Once the cargo is unloaded fom your Carrier, you may be notified of this, when there is a truck near to the cargo. --- If there is no truck near to the unload area, no message will be sent to your carrier! --- --- **Remarks:** --- --- * For airborne Carriers, it is required to land first, before the unloading process can be initiated. --- * A truck must be near the unload area to get messages to your carrier of the unload event! --- * Unloading is only for non-moveable cargo. --- * The non-moveable cargo must be within your cargo bays, or no unload option will be available. --- --- **Deploying a cargo within a deployment zone, may complete a deployment task! So ensure that you deploy the right cargo at the right deployment zone!** --- --- --- #### Sling Deploy Cargo (helicopters only). --- --- If your Carrier is within the **deploy zone**, and the cargo is **stationary**, the **cargo can also be sling deploying**! --- Note that this is only possible for helicopters. --- --- To sling deploy cargo, there is no task action menu required. Just follow the normal sling deploying procedure. --- --- **Deploying a cargo within a deployment zone, may complete a deployment task! So ensure that you deploy the right cargo at the right deployment zone!** --- --- ## Cargo tasking from a mission designer perspective. --- --- Please consult the documentation how to implement the derived classes of SET_CARGO in: --- --- - @{Tasking.Task_CARGO#TASK_CARGO}: Documents the main methods how to handle the cargo tasking from a mission designer perspective. --- - @{Tasking.Task_CARGO#TASK_CARGO_TRANSPORT}: Documents the specific methods how to handle the cargo transportation tasking from a mission designer perspective. --- - @{Tasking.Task_CARGO#TASK_CARGO_CSAR}: Documents the specific methods how to handle the cargo CSAR tasking from a mission designer perspective. --- --- --- # 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 --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_CARGO --- @image MOOSE.JPG - -do -- TASK_CARGO - - -- @type TASK_CARGO - -- @extends Tasking.Task#TASK - - --- Model tasks for players to transport Cargo. - -- - -- This models the process of a flexible transporation tasking system of cargo. - -- - -- # 1) A flexible tasking system. - -- - -- The TASK_CARGO classes provide you with a flexible tasking sytem, - -- that allows you to transport cargo of various types between various locations - -- and various dedicated deployment zones. - -- - -- The cargo in scope of the TASK\_CARGO classes must be explicitly given, and is of type SET\_CARGO. - -- The SET_CARGO contains a collection of CARGO objects that must be handled by the players in the mission. - -- - -- # 2) Cargo Tasking from a mission designer perspective. - -- - -- A cargo task is governed by a @{Tasking.Mission} object. Tasks are of different types. - -- The @{#TASK} object is used or derived by more detailed tasking classes that will implement the task execution mechanisms - -- and goals. - -- - -- ## 2.1) Derived cargo task classes. - -- - -- The following TASK_CARGO classes are derived from @{#TASK}. - -- - -- TASK - -- TASK_CARGO - -- TASK_CARGO_TRANSPORT - -- TASK_CARGO_CSAR - -- - -- ### 2.1.1) Cargo Tasks - -- - -- - @{Tasking.Task_CARGO#TASK_CARGO_TRANSPORT} - Models the transportation of cargo to deployment zones. - -- - @{Tasking.Task_CARGO#TASK_CARGO_CSAR} - Models the rescue of downed friendly pilots from behind enemy lines. - -- - -- ## 2.2) Handle TASK_CARGO Events ... - -- - -- The TASK_CARGO classes define Cargo transport tasks, - -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. - -- - -- ### 2.2.1) Boarding events. - -- - -- Specific Cargo event can be captured, that allow to trigger specific actions! - -- - -- * **Boarded**: Triggered when the Cargo has been Boarded into your Carrier. - -- * **UnBoarded**: Triggered when the cargo has been Unboarded from your Carrier and has arrived at the Deployment Zone. - -- - -- ### 2.2.2) Loading events. - -- - -- Specific Cargo event can be captured, that allow to trigger specific actions! - -- - -- * **Loaded**: Triggered when the Cargo has been Loaded into your Carrier. - -- * **UnLoaded**: Triggered when the cargo has been Unloaded from your Carrier and has arrived at the Deployment Zone. - -- - -- ### 2.2.2) Standard TASK_CARGO Events - -- - -- The TASK_CARGO is implemented using a @{Core.Fsm#FSM_TASK}, and has the following standard statuses: - -- - -- * **None**: Start of the process. - -- * **Planned**: The cargo task is planned. - -- * **Assigned**: The cargo task is assigned to a @{Wrapper.Group#GROUP}. - -- * **Success**: The cargo task is successfully completed. - -- * **Failed**: The cargo task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- - -- - -- - -- === - -- - -- @field #TASK_CARGO - TASK_CARGO = { - ClassName = "TASK_CARGO", - } - - --- Instantiates a new TASK_CARGO. - -- @param #TASK_CARGO self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported. - -- @param #string TaskType The type of Cargo task. - -- @param #string TaskBriefing The Cargo Task briefing. - -- @return #TASK_CARGO self - function TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- #TASK_CARGO - self:F( {Mission, SetGroup, TaskName, SetCargo, TaskType}) - - self.SetCargo = SetCargo - self.TaskType = TaskType - self.SmokeColor = SMOKECOLOR.Red - - self.CargoItemCount = {} -- Map of Carriers having a cargo item count to check the cargo loading limits. - self.CargoLimit = 10 - - self.DeployZones = {} -- setmetatable( {}, { __mode = "v" } ) -- weak table on value - - self:AddTransition( "*", "CargoDeployed", "*" ) - - --- CargoDeployed Handler OnBefore for TASK_CARGO - -- @function [parent=#TASK_CARGO] OnBeforeCargoDeployed - -- @param #TASK_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded. - -- @return #boolean - - --- CargoDeployed Handler OnAfter for TASK_CARGO - -- @function [parent=#TASK_CARGO] OnAfterCargoDeployed - -- @param #TASK_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded. - -- @usage - -- - -- -- Add a Transport task to transport cargo of different types to a Transport Deployment Zone. - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups ) - -- - -- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() - -- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) - -- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) - -- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) - -- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) - -- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) - -- - -- -- Here we add the task. We name the task "Build a Workplace". - -- -- We provide the CargoSetWorkmaterials, and a briefing as the 2nd and 3rd parameter. - -- -- The :AddTransportTask() returns a Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT object, which we keep as a reference for further actions. - -- -- The WorkplaceTask holds the created and returned Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT object. - -- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." ) - -- - -- -- Here we set a TransportDeployZone. We use the WorkplaceTask as the reference, and provide a ZONE object. - -- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) ) - -- - -- Helos = { SPAWN:New( "Helicopters 1" ), SPAWN:New( "Helicopters 2" ), SPAWN:New( "Helicopters 3" ), SPAWN:New( "Helicopters 4" ), SPAWN:New( "Helicopters 5" ) } - -- EnemyHelos = { SPAWN:New( "Enemy Helicopters 1" ), SPAWN:New( "Enemy Helicopters 2" ), SPAWN:New( "Enemy Helicopters 3" ) } - -- - -- -- This is our worker method! So when a cargo is deployed within a deployment zone, this method will be called. - -- -- By example we are spawning here a random friendly helicopter and a random enemy helicopter. - -- function WorkplaceTask:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- Helos[ math.random(1,#Helos) ]:Spawn() - -- EnemyHelos[ math.random(1,#EnemyHelos) ]:Spawn() - -- end - - self:AddTransition( "*", "CargoPickedUp", "*" ) - - --- CargoPickedUp Handler OnBefore for TASK_CARGO - -- @function [parent=#TASK_CARGO] OnBeforeCargoPickedUp - -- @param #TASK_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @return #boolean - - --- CargoPickedUp Handler OnAfter for TASK_CARGO - -- @function [parent=#TASK_CARGO] OnAfterCargoPickedUp - -- @param #TASK_CARGO self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - - - local Fsm = self:GetUnitProcess() - --- Fsm:SetStartState( "Planned" ) --- --- Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "SelectAction", Rejected = "Reject" } ) - - Fsm:AddTransition( { "Planned", "Assigned", "Cancelled", "WaitingForCommand", "ArrivedAtPickup", "ArrivedAtDeploy", "Boarded", "UnBoarded", "Loaded", "UnLoaded", "Landed", "Boarding" }, "SelectAction", "*" ) - - Fsm:AddTransition( "*", "RouteToPickup", "RoutingToPickup" ) - Fsm:AddProcess ( "RoutingToPickup", "RouteToPickupPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtPickup", Cancelled = "CancelRouteToPickup" } ) - Fsm:AddTransition( "Arrived", "ArriveAtPickup", "ArrivedAtPickup" ) - Fsm:AddTransition( "Cancelled", "CancelRouteToPickup", "Cancelled" ) - - Fsm:AddTransition( "*", "RouteToDeploy", "RoutingToDeploy" ) - Fsm:AddProcess ( "RoutingToDeploy", "RouteToDeployZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtDeploy", Cancelled = "CancelRouteToDeploy" } ) - Fsm:AddTransition( "Arrived", "ArriveAtDeploy", "ArrivedAtDeploy" ) - Fsm:AddTransition( "Cancelled", "CancelRouteToDeploy", "Cancelled" ) - - Fsm:AddTransition( { "ArrivedAtPickup", "ArrivedAtDeploy", "Landing" }, "Land", "Landing" ) - Fsm:AddTransition( "Landing", "Landed", "Landed" ) - - Fsm:AddTransition( "*", "PrepareBoarding", "AwaitBoarding" ) - Fsm:AddTransition( "AwaitBoarding", "Board", "Boarding" ) - Fsm:AddTransition( "Boarding", "Boarded", "Boarded" ) - - Fsm:AddTransition( "*", "Load", "Loaded" ) - - Fsm:AddTransition( "*", "PrepareUnBoarding", "AwaitUnBoarding" ) - Fsm:AddTransition( "AwaitUnBoarding", "UnBoard", "UnBoarding" ) - Fsm:AddTransition( "UnBoarding", "UnBoarded", "UnBoarded" ) - - Fsm:AddTransition( "*", "Unload", "Unloaded" ) - - Fsm:AddTransition( "*", "Planned", "Planned" ) - - - Fsm:AddTransition( "Deployed", "Success", "Success" ) - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #TASK_CARGO Task - function Fsm:OnAfterAssigned( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - self:SelectAction() - end - - - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #TASK_CARGO Task - function Fsm:onafterSelectAction( TaskUnit, Task ) - - local TaskUnitName = TaskUnit:GetName() - local MenuTime = Task:InitTaskControlMenu( TaskUnit ) - local MenuControl = Task:GetTaskControlMenu( TaskUnit ) - - Task.SetCargo:ForEachCargo( - - -- @param Cargo.Cargo#CARGO Cargo - function( Cargo ) - - if Cargo:IsAlive() then - --- if Task:is( "RoutingToPickup" ) then --- MENU_GROUP_COMMAND:New( --- TaskUnit:GetGroup(), --- "Cancel Route " .. Cargo.Name, --- MenuControl, --- self.MenuRouteToPickupCancel, --- self, --- Cargo --- ):SetTime(MenuTime) --- end - - --self:F( { CargoUnloaded = Cargo:IsUnLoaded(), CargoLoaded = Cargo:IsLoaded(), CargoItemCount = CargoItemCount } ) - - local TaskGroup = TaskUnit:GetGroup() - - if Cargo:IsUnLoaded() then - local CargoBayFreeWeight = TaskUnit:GetCargoBayFreeWeight() - local CargoWeight = Cargo:GetWeight() - - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - - -- Only when there is space within the bay to load the next cargo item! - if CargoBayFreeWeight > CargoWeight then - if Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then - local NotInDeployZones = true - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if Cargo:IsInZone( DeployZone ) then - NotInDeployZones = false - end - end - if NotInDeployZones then - if not TaskUnit:InAir() then - if Cargo:CanBoard() == true then - if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - Cargo:Report( "Ready for boarding.", "board", TaskUnit:GetGroup() ) - local BoardMenu = MENU_GROUP:New( TaskGroup, "Board cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, BoardMenu, self.MenuBoardCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - else - Cargo:Report( "Board at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() .. "." ), "reporting", TaskUnit:GetGroup() ) - end - else - if Cargo:CanLoad() == true then - if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - Cargo:Report( "Ready for loading.", "load", TaskUnit:GetGroup() ) - local LoadMenu = MENU_GROUP:New( TaskGroup, "Load cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, LoadMenu, self.MenuLoadCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - else - Cargo:Report( "Load at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() ) .. " within " .. Cargo.NearRadius .. ".", "reporting", TaskUnit:GetGroup() ) - end - else - --local Cargo = Cargo -- Cargo.CargoSlingload#CARGO_SLINGLOAD - if Cargo:CanSlingload() == true then - if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - Cargo:Report( "Ready for sling loading.", "slingload", TaskUnit:GetGroup() ) - local SlingloadMenu = MENU_GROUP:New( TaskGroup, "Slingload cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, SlingloadMenu, self.MenuLoadCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - else - Cargo:Report( "Slingload at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() ) .. ".", "reporting", TaskUnit:GetGroup() ) - end - end - end - end - else - Cargo:ReportResetAll( TaskUnit:GetGroup() ) - end - end - else - if not Cargo:IsDeployed() == true then - local RouteToPickupMenu = MENU_GROUP:New( TaskGroup, "Route to pickup cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - --MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, RouteToPickupMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - Cargo:ReportResetAll( TaskUnit:GetGroup() ) - if Cargo:CanBoard() == true then - if not Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - local BoardMenu = MENU_GROUP:New( TaskGroup, "Board cargo", RouteToPickupMenu ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, BoardMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - else - if Cargo:CanLoad() == true then - if not Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - local LoadMenu = MENU_GROUP:New( TaskGroup, "Load cargo", RouteToPickupMenu ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, LoadMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - else - --local Cargo = Cargo -- Cargo.CargoSlingload#CARGO_SLINGLOAD - if Cargo:CanSlingload() == true then - if not Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - local SlingloadMenu = MENU_GROUP:New( TaskGroup, "Slingload cargo", RouteToPickupMenu ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, SlingloadMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - end - end - end - end - end - end - - -- Cargo in deployzones are flagged as deployed. - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if Cargo:IsInZone( DeployZone ) then - Task:I( { CargoIsDeployed = Task.CargoDeployed and "true" or "false" } ) - if Cargo:IsDeployed() == false then - Cargo:SetDeployed( true ) - -- Now we call a callback method to handle the CargoDeployed event. - Task:I( { CargoIsAlive = Cargo:IsAlive() and "true" or "false" } ) - if Cargo:IsAlive() then - Task:CargoDeployed( TaskUnit, Cargo, DeployZone ) - end - end - end - end - - end - - if Cargo:IsLoaded() == true and Cargo:IsLoadedInCarrier( TaskUnit ) == true then - if not TaskUnit:InAir() then - if Cargo:CanUnboard() == true then - local UnboardMenu = MENU_GROUP:New( TaskGroup, "Unboard cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, UnboardMenu, self.MenuUnboardCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - else - if Cargo:CanUnload() == true then - local UnloadMenu = MENU_GROUP:New( TaskGroup, "Unload cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, UnloadMenu, self.MenuUnloadCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - end - end - end - - -- Deployzones are optional zones that can be selected to request routing information. - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if not Cargo:IsInZone( DeployZone ) then - local RouteToDeployMenu = MENU_GROUP:New( TaskGroup, "Route to deploy cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" ) - MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Zone " .. DeployZoneName, RouteToDeployMenu, self.MenuRouteToDeploy, self, DeployZone ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent() - end - end - end - - end - ) - - Task:RefreshTaskControlMenu( TaskUnit, MenuTime, "Cargo" ) - - self:__SelectAction( -1 ) - - end - - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #TASK_CARGO Task - function Fsm:OnLeaveWaitingForCommand( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - --local MenuControl = Task:GetTaskControlMenu( TaskUnit ) - - --MenuControl:Remove() - end - - function Fsm:MenuBoardCargo( Cargo ) - self:__PrepareBoarding( 1.0, Cargo ) - end - - function Fsm:MenuLoadCargo( Cargo ) - self:__Load( 1.0, Cargo ) - end - - function Fsm:MenuUnboardCargo( Cargo, DeployZone ) - self:__PrepareUnBoarding( 1.0, Cargo, DeployZone ) - end - - function Fsm:MenuUnloadCargo( Cargo, DeployZone ) - self:__Unload( 1.0, Cargo, DeployZone ) - end - - function Fsm:MenuRouteToPickup( Cargo ) - self:__RouteToPickup( 1.0, Cargo ) - end - - function Fsm:MenuRouteToDeploy( DeployZone ) - self:__RouteToDeploy( 1.0, DeployZone ) - end - - - - --- - --#TASK_CAROG_TRANSPORT self - --#Wrapper.Unit#UNIT - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - -- @param From - -- @param Event - -- @param To - -- @param Cargo.Cargo#CARGO Cargo - function Fsm:onafterRouteToPickup( TaskUnit, Task, From, Event, To, Cargo ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if Cargo:IsAlive() then - self.Cargo = Cargo -- Cargo.Cargo#CARGO - Task:SetCargoPickup( self.Cargo, TaskUnit ) - self:__RouteToPickupPoint( -0.1 ) - end - - end - - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterArriveAtPickup( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - if self.Cargo:IsAlive() then - if TaskUnit:IsAir() then - Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() ) - self:__Land( -0.1, "Pickup" ) - else - self:__SelectAction( -0.1 ) - end - end - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterCancelRouteToPickup( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - Task:GetMission():GetCommandCenter():MessageToGroup( "Cancelled routing to Cargo " .. self.Cargo:GetName(), TaskUnit:GetGroup() ) - self:__SelectAction( -0.1 ) - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - function Fsm:onafterRouteToDeploy( TaskUnit, Task, From, Event, To, DeployZone ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - self:F( DeployZone ) - self.DeployZone = DeployZone - Task:SetDeployZone( self.DeployZone, TaskUnit ) - self:__RouteToDeployZone( -0.1 ) - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterArriveAtDeploy( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if TaskUnit:IsAir() then - Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() ) - self:__Land( -0.1, "Deploy" ) - else - self:__SelectAction( -0.1 ) - end - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterCancelRouteToDeploy( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - Task:GetMission():GetCommandCenter():MessageToGroup( "Cancelled routing to deploy zone " .. self.DeployZone:GetName(), TaskUnit:GetGroup() ) - self:__SelectAction( -0.1 ) - end - - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterLand( TaskUnit, Task, From, Event, To, Action ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if Action == "Pickup" then - if self.Cargo:IsAlive() then - if self.Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then - if TaskUnit:InAir() then - self:__Land( -10, Action ) - else - Task:GetMission():GetCommandCenter():MessageToGroup( "Landed at pickup location...", TaskUnit:GetGroup() ) - self:__Landed( -0.1, Action ) - end - else - self:__RouteToPickup( -0.1, self.Cargo ) - end - end - else - if TaskUnit:IsAlive() then - if TaskUnit:IsInZone( self.DeployZone ) then - if TaskUnit:InAir() then - self:__Land( -10, Action ) - else - Task:GetMission():GetCommandCenter():MessageToGroup( "Landed at deploy zone " .. self.DeployZone:GetName(), TaskUnit:GetGroup() ) - self:__Landed( -0.1, Action ) - end - else - self:__RouteToDeploy( -0.1, self.Cargo ) - end - end - end - end - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterLanded( TaskUnit, Task, From, Event, To, Action ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if Action == "Pickup" then - if self.Cargo:IsAlive() then - if self.Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then - if TaskUnit:InAir() then - self:__Land( -0.1, Action ) - else - self:__SelectAction( -0.1 ) - end - else - self:__RouteToPickup( -0.1, self.Cargo ) - end - end - else - if TaskUnit:IsAlive() then - if TaskUnit:IsInZone( self.DeployZone ) then - if TaskUnit:InAir() then - self:__Land( -10, Action ) - else - self:__SelectAction( -0.1 ) - end - else - self:__RouteToDeploy( -0.1, self.Cargo ) - end - end - end - end - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterPrepareBoarding( TaskUnit, Task, From, Event, To, Cargo ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - if Cargo and Cargo:IsAlive() then - self:__Board( -0.1, Cargo ) - end - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterBoard( TaskUnit, Task, From, Event, To, Cargo ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - function Cargo:OnEnterLoaded( From, Event, To, TaskUnit, TaskProcess ) - self:F({From, Event, To, TaskUnit, TaskProcess }) - TaskProcess:__Boarded( 0.1, self ) - end - - if Cargo:IsAlive() then - if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then - if TaskUnit:InAir() then - --- ABORT the boarding. Split group if any and go back to select action. - else - Cargo:MessageToGroup( "Boarding ...", TaskUnit:GetGroup() ) - if not Cargo:IsBoarding() then - Cargo:Board( TaskUnit, nil, self ) - end - end - else - --self:__ArriveAtCargo( -0.1 ) - end - end - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterBoarded( TaskUnit, Task, From, Event, To, Cargo ) - - local TaskUnitName = TaskUnit:GetName() - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - Cargo:MessageToGroup( "Boarded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() ) - - self:__Load( -0.1, Cargo ) - - end - - - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterLoad( TaskUnit, Task, From, Event, To, Cargo ) - - local TaskUnitName = TaskUnit:GetName() - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - if not Cargo:IsLoaded() then - Cargo:Load( TaskUnit ) - end - - Cargo:MessageToGroup( "Loaded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() ) - TaskUnit:AddCargo( Cargo ) - - Task:CargoPickedUp( TaskUnit, Cargo ) - - self:SelectAction( -1 ) - - end - - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - -- @param From - -- @param Event - -- @param To - -- @param Cargo - -- @param Core.Zone#ZONE_BASE DeployZone - function Fsm:onafterPrepareUnBoarding( TaskUnit, Task, From, Event, To, Cargo ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID(), From, Event, To, Cargo } ) - - self.Cargo = Cargo - self.DeployZone = nil - - -- Check if the Cargo is at a deployzone... If it is, provide it as a parameter! - if Cargo:IsAlive() then - for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do - if Cargo:IsInZone( DeployZone ) then - self.DeployZone = DeployZone -- Core.Zone#ZONE_BASE - break - end - end - self:__UnBoard( -0.1, Cargo, self.DeployZone ) - end - end - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - -- @param From - -- @param Event - -- @param To - -- @param Cargo - -- @param Core.Zone#ZONE_BASE DeployZone - function Fsm:onafterUnBoard( TaskUnit, Task, From, Event, To, Cargo, DeployZone ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID(), From, Event, To, Cargo, DeployZone } ) - - function self.Cargo:OnEnterUnLoaded( From, Event, To, DeployZone, TaskProcess ) - self:F({From, Event, To, DeployZone, TaskProcess }) - TaskProcess:__UnBoarded( -0.1 ) - end - - if self.Cargo:IsAlive() then - self.Cargo:MessageToGroup( "UnBoarding ...", TaskUnit:GetGroup() ) - if DeployZone then - self.Cargo:UnBoard( DeployZone:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) - else - self.Cargo:UnBoard( TaskUnit:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) - end - end - end - - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterUnBoarded( TaskUnit, Task ) - - local TaskUnitName = TaskUnit:GetName() - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - self.Cargo:MessageToGroup( "UnBoarded cargo " .. self.Cargo:GetName(), TaskUnit:GetGroup() ) - - self:Unload( self.Cargo ) - end - - --- - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_Cargo#TASK_CARGO Task - function Fsm:onafterUnload( TaskUnit, Task, From, Event, To, Cargo, DeployZone ) - - local TaskUnitName = TaskUnit:GetName() - self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } ) - - if not Cargo:IsUnLoaded() then - if DeployZone then - Cargo:UnLoad( DeployZone:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) - else - Cargo:UnLoad( TaskUnit:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self ) - end - end - TaskUnit:RemoveCargo( Cargo ) - - Cargo:MessageToGroup( "Unloaded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() ) - - self:Planned() - self:__SelectAction( 1 ) - end - - return self - - end - - - --- Set a limit on the amount of cargo items that can be loaded into the Carriers. - -- @param #TASK_CARGO self - -- @param CargoLimit Specifies a number of cargo items that can be loaded in the helicopter. - -- @return #TASK_CARGO - function TASK_CARGO:SetCargoLimit( CargoLimit ) - self.CargoLimit = CargoLimit - return self - end - - - ---@param Color Might be SMOKECOLOR.Blue, SMOKECOLOR.Red SMOKECOLOR.Orange, SMOKECOLOR.White or SMOKECOLOR.Green - function TASK_CARGO:SetSmokeColor(SmokeColor) - -- Makes sure Coloe is set - if SmokeColor == nil then - self.SmokeColor = SMOKECOLOR.Red -- Make sure a default color is exist - - elseif type(SmokeColor) == "number" then - self:F2(SmokeColor) - if SmokeColor > 0 and SmokeColor <=5 then -- Make sure number is within ragne, assuming first enum is one - self.SmokeColor = SMOKECOLOR.SmokeColor - end - end - end - - --@return SmokeColor - function TASK_CARGO:GetSmokeColor() - return self.SmokeColor - end - - -- @param #TASK_CARGO self - function TASK_CARGO:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - -- @param #TASK_CARGO self - -- @return Core.Set#SET_CARGO The Cargo Set. - function TASK_CARGO:GetCargoSet() - - return self.SetCargo - end - - -- @param #TASK_CARGO self - -- @return #list The Deployment Zones. - function TASK_CARGO:GetDeployZones() - - return self.DeployZones - end - - -- @param #TASK_CARGO self - -- @param AI.AI_Cargo#AI_CARGO Cargo The cargo. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetCargoPickup( Cargo, TaskUnit ) - - self:F({Cargo, TaskUnit}) - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local MenuTime = self:InitTaskControlMenu( TaskUnit ) - local MenuControl = self:GetTaskControlMenu( TaskUnit ) - - local ActRouteCargo = ProcessUnit:GetProcess( "RoutingToPickup", "RouteToPickupPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteCargo:Reset() - ActRouteCargo:SetCoordinate( Cargo:GetCoordinate() ) - ActRouteCargo:SetRange( Cargo:GetLoadRadius() ) - ActRouteCargo:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Cargo " .. Cargo:GetName(), MenuControl, MenuTime, "Cargo" ) - ActRouteCargo:Start() - - return self - end - - - -- @param #TASK_CARGO self - -- @param Core.Zone#ZONE DeployZone - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetDeployZone( DeployZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local MenuTime = self:InitTaskControlMenu( TaskUnit ) - local MenuControl = self:GetTaskControlMenu( TaskUnit ) - - local ActRouteDeployZone = ProcessUnit:GetProcess( "RoutingToDeploy", "RouteToDeployZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteDeployZone:Reset() - ActRouteDeployZone:SetZone( DeployZone ) - ActRouteDeployZone:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Deploy Zone" .. DeployZone:GetName(), MenuControl, MenuTime, "Cargo" ) - ActRouteDeployZone:Start() - - return self - end - - - -- @param #TASK_CARGO self - -- @param Core.Zone#ZONE DeployZone - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:AddDeployZone( DeployZone, TaskUnit ) - - self.DeployZones[DeployZone:GetName()] = DeployZone - - return self - end - - -- @param #TASK_CARGO self - -- @param Core.Zone#ZONE DeployZone - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:RemoveDeployZone( DeployZone, TaskUnit ) - - self.DeployZones[DeployZone:GetName()] = nil - - return self - end - - -- @param #TASK_CARGO self - -- @param #list DeployZones - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetDeployZones( DeployZones, TaskUnit ) - - for DeployZoneID, DeployZone in pairs( DeployZones or {} ) do - self.DeployZones[DeployZone:GetName()] = DeployZone - end - - return self - end - - - - -- @param #TASK_CARGO self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_CARGO:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - - --- Set a score when progress is made. - -- @param #TASK_CARGO self - -- @param #string Text The text to display to the player, when there is progress on the task goals. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetScoreOnProgress( Text, Score, TaskUnit ) - self:F( { Text, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScoreProcess( "Engaging", "Account", "Account", Text, Score ) - - return self - end - - --- Set a score when success is achieved. - -- @param #TASK_CARGO self - -- @param #string Text The text to display to the player, when the task goals have been achieved. - -- @param #number Score The score in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetScoreOnSuccess( Text, Score, TaskUnit ) - self:F( { Text, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Success", Text, Score ) - - return self - end - - --- Set a penalty when the task goals have failed.. - -- @param #TASK_CARGO self - -- @param #string Text The text to display to the player, when the task goals has failed. - -- @param #number Penalty The penalty in points. - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #TASK_CARGO - function TASK_CARGO:SetScoreOnFail( Text, Penalty, TaskUnit ) - self:F( { Text, Score, TaskUnit } ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - ProcessUnit:AddScore( "Failed", Text, Penalty ) - - return self - end - - function TASK_CARGO:SetGoalTotal() - - self.GoalTotal = self.SetCargo:Count() - end - - function TASK_CARGO:GetGoalTotal() - - return self.GoalTotal - end - - -- @param #TASK_CARGO self - function TASK_CARGO:UpdateTaskInfo() - - if self:IsStatePlanned() or self:IsStateAssigned() then - self.TaskInfo:AddTaskName( 0, "MSOD" ) - self.TaskInfo:AddCargoSet( self.SetCargo, 10, "SOD", true ) - local Coordinates = {} - for CargoName, Cargo in pairs( self.SetCargo:GetSet() ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - if not Cargo:IsLoaded() then - Coordinates[#Coordinates+1] = Cargo:GetCoordinate() - end - end - self.TaskInfo:AddCoordinates( Coordinates, 1, "M" ) - end - end - - function TASK_CARGO:ReportOrder( ReportGroup ) - - return 0 - end - - --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. - -- @param #TASK_CARGO self - -- @param #number AutoAssignMethod The method to be applied to the task. - -- @param Wrapper.Group#GROUP TaskGroup The player group. - function TASK_CARGO:GetAutoAssignPriority( AutoAssignMethod, TaskGroup ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then - return math.random( 1, 9 ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then - return 0 - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then - return 1 - end - - return 0 - end - - - -end - - diff --git a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua deleted file mode 100644 index c9c476efa..000000000 --- a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua +++ /dev/null @@ -1,402 +0,0 @@ ---- **Tasking** - Creates and manages player TASK_ZONE_CAPTURE tasks. --- --- The **TASK_CAPTURE_DISPATCHER** allows you to setup various tasks for let human --- players capture zones in a co-operation effort. --- --- The dispatcher will implement for you mechanisms to create capture zone tasks: --- --- * As setup by the mission designer. --- * Dynamically capture zone tasks. --- --- --- --- **Specific features:** --- --- * Creates a task to capture zones and achieve mission goals. --- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled. --- * Co-operation tasking, so a player joins a group of players executing the same task. --- --- --- **A complete task menu system to allow players to:** --- --- * Join the task, abort the task. --- * Mark the location of the zones to capture on the map. --- * Provide details of the zones. --- * Route to the zones. --- * Display the task briefing. --- --- --- **A complete mission menu system to allow players to:** --- --- * Join a task, abort the task. --- * Display task reports. --- * Display mission statistics. --- * Mark the task locations on the map. --- * Provide details of the zones. --- * Display the mission briefing. --- * Provide status updates as retrieved from the command center. --- * Automatically assign a random task as part of a mission. --- * Manually assign a specific task as part of a mission. --- --- --- **A settings system, using the settings menu:** --- --- * Tweak the duration of the display of messages. --- * Switch between metric and imperial measurement system. --- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS. --- * Various other options. --- --- # 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 --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_Capture_Dispatcher --- @image MOOSE.JPG - -do -- TASK_CAPTURE_DISPATCHER - - --- TASK_CAPTURE_DISPATCHER class. - -- @type TASK_CAPTURE_DISPATCHER - -- @extends Tasking.Task_Manager#TASK_MANAGER - -- @field TASK_CAPTURE_DISPATCHER.ZONE ZONE - - -- @type TASK_CAPTURE_DISPATCHER.CSAR - -- @field Wrapper.Unit#UNIT PilotUnit - -- @field Tasking.Task#TASK Task - - - --- Implements the dynamic dispatching of capture zone tasks. - -- - -- The **TASK_CAPTURE_DISPATCHER** allows you to setup various tasks for let human - -- players capture zones in a co-operation effort. - -- - -- Let's explore **step by step** how to setup the task capture zone dispatcher. - -- - -- # 1. Setup a mission environment. - -- - -- It is easy, as it works just like any other task setup, so setup a command center and a mission. - -- - -- ## 1.1. Create a command center. - -- - -- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. - -- The command assumes that you´ve setup a group in the mission editor with the name HQ. - -- This group will act as the command center object. - -- It is a good practice to mark this group as invisible and invulnerable. - -- - -- local CommandCenter = COMMANDCENTER - -- :New( GROUP:FindByName( "HQ" ), "HQ" ) -- Create the CommandCenter. - -- - -- ## 1.2. Create a mission. - -- - -- Tasks work in a **mission**, which groups these tasks to achieve a joint **mission goal**. A command center can **govern multiple missions**. - -- - -- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor. - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, - -- "Overlord", - -- "High", - -- "Capture the blue zones.", - -- coalition.side.RED - -- ) - -- - -- - -- # 2. Dispatch a **capture zone** task. - -- - -- So, now that we have a command center and a mission, we now create the capture zone task. - -- We create the capture zone task using the @{#TASK_CAPTURE_DISPATCHER.AddCaptureZoneTask}() constructor. - -- - -- ## 2.1. Create the capture zones. - -- - -- Because a capture zone task will not generate the capture zones, you'll need to create them first. - -- - -- - -- -- We define here a capture zone; of the type ZONE_CAPTURE_COALITION. - -- -- The zone to be captured has the name Alpha, and was defined in the mission editor as a trigger zone. - -- CaptureZone = ZONE:New( "Alpha" ) - -- CaptureZoneCoalitionApha = ZONE_CAPTURE_COALITION:New( CaptureZone, coalition.side.RED ) - -- - -- ## 2.2. Create a set of player groups. - -- - -- What is also needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the player slots, which must be aircraft (airplanes or helicopters), that can be manned by players. - -- -- We use the method FilterPrefixes to filter those player groups that have client slots, as defined in the mission editor. - -- -- In this example, we filter the groups where the name starts with "Blue Player", which captures the blue player slots. - -- local PlayerGroupSet = SET_GROUP:New():FilterPrefixes( "Blue Player" ):FilterStart() - -- - -- ## 2.3. Setup the capture zone task. - -- - -- First, we need to create a TASK_CAPTURE_DISPATCHER object. - -- - -- TaskCaptureZoneDispatcher = TASK_CAPTURE_DISPATCHER:New( Mission, PilotGroupSet ) - -- - -- So, the variable `TaskCaptureZoneDispatcher` will contain the object of class TASK_CAPTURE_DISPATCHER, - -- which will allow you to dispatch capture zone tasks: - -- - -- * for mission `Mission`, as was defined in section 1.2. - -- * for the group set `PilotGroupSet`, as was defined in section 2.2. - -- - -- Now that we have `TaskDispatcher` object, we can now **create the TaskCaptureZone**, using the @{#TASK_CAPTURE_DISPATCHER.AddCaptureZoneTask}() method! - -- - -- local TaskCaptureZone = TaskCaptureZoneDispatcher:AddCaptureZoneTask( - -- "Capture zone Alpha", - -- CaptureZoneCoalitionAlpha, - -- "Fly to zone Alpha and eliminate all enemy forces to capture it." ) - -- - -- As a result of this code, the `TaskCaptureZone` (returned) variable will contain an object of @{#TASK_CAPTURE_ZONE}! - -- We pass to the method the title of the task, and the `CaptureZoneCoalitionAlpha`, which is the zone to be captured, as defined in section 2.1! - -- This returned `TaskCaptureZone` object can now be used to setup additional task configurations, or to control this specific task with special events. - -- - -- And you're done! As you can see, it is a small bit of work, but the reward is great. - -- And, because all this is done using program interfaces, you can easily build a mission to capture zones yourself! - -- Based on various events happening within your mission, you can use the above methods to create new capture zones, - -- and setup a new capture zone task and assign it to a group of players, while your mission is running! - -- - -- - -- - -- @field #TASK_CAPTURE_DISPATCHER - TASK_CAPTURE_DISPATCHER = { - ClassName = "TASK_CAPTURE_DISPATCHER", - Mission = nil, - Tasks = {}, - Zones = {}, - ZoneCount = 0, - } - - - - TASK_CAPTURE_DISPATCHER.AI_A2G_Dispatcher = nil -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER - - --- TASK_CAPTURE_DISPATCHER constructor. - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. - -- @return #TASK_CAPTURE_DISPATCHER self - function TASK_CAPTURE_DISPATCHER:New( Mission, SetGroup ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CAPTURE_DISPATCHER - - self.Mission = Mission - self.FlashNewTask = false - - self:AddTransition( "Started", "Assign", "Started" ) - self:AddTransition( "Started", "ZoneCaptured", "Started" ) - - self:__StartTasks( 5 ) - - return self - end - - - --- Link a task capture dispatcher from the other coalition to understand its plan for defenses. - -- This is used for the tactical overview, so the players also know the zones attacked by the other coalition! - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param #TASK_CAPTURE_DISPATCHER DefenseTaskCaptureDispatcher - function TASK_CAPTURE_DISPATCHER:SetDefenseTaskCaptureDispatcher( DefenseTaskCaptureDispatcher ) - - self.DefenseTaskCaptureDispatcher = DefenseTaskCaptureDispatcher - end - - - --- Get the linked task capture dispatcher from the other coalition to understand its plan for defenses. - -- This is used for the tactical overview, so the players also know the zones attacked by the other coalition! - -- @param #TASK_CAPTURE_DISPATCHER self - -- @return #TASK_CAPTURE_DISPATCHER - function TASK_CAPTURE_DISPATCHER:GetDefenseTaskCaptureDispatcher() - - return self.DefenseTaskCaptureDispatcher - end - - - --- Link an AI A2G dispatcher from the other coalition to understand its plan for defenses. - -- This is used for the tactical overview, so the players also know the zones attacked by the other AI A2G dispatcher! - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER DefenseAIA2GDispatcher - function TASK_CAPTURE_DISPATCHER:SetDefenseAIA2GDispatcher( DefenseAIA2GDispatcher ) - - self.DefenseAIA2GDispatcher = DefenseAIA2GDispatcher - end - - - --- Get the linked AI A2G dispatcher from the other coalition to understand its plan for defenses. - -- This is used for the tactical overview, so the players also know the zones attacked by the AI A2G dispatcher! - -- @param #TASK_CAPTURE_DISPATCHER self - -- @return AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER - function TASK_CAPTURE_DISPATCHER:GetDefenseAIA2GDispatcher() - - return self.DefenseAIA2GDispatcher - end - - - --- Add a capture zone task. - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param #string TaskPrefix (optional) The prefix of the capture zone task. - -- If no TaskPrefix is given, then "Capture" will be used as the TaskPrefix. - -- The TaskPrefix will be appended with a . + a number of 3 digits, if the TaskPrefix already exists in the task collection. - -- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION CaptureZone The zone of the coalition to be captured as the task goal. - -- @param #string Briefing The briefing of the task to be shown to the player. - -- @return Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE - -- @usage - -- - -- - function TASK_CAPTURE_DISPATCHER:AddCaptureZoneTask( TaskPrefix, CaptureZone, Briefing ) - - local TaskName = TaskPrefix or "Capture" - if self.Zones[TaskName] then - self.ZoneCount = self.ZoneCount + 1 - TaskName = string.format( "%s.%03d", TaskName, self.ZoneCount ) - end - - self.Zones[TaskName] = {} - self.Zones[TaskName].CaptureZone = CaptureZone - self.Zones[TaskName].Briefing = Briefing - self.Zones[TaskName].Task = nil - self.Zones[TaskName].TaskPrefix = TaskPrefix - - self:ManageTasks() - - return self.Zones[TaskName] and self.Zones[TaskName].Task - end - - - --- Link an AI_A2G_DISPATCHER to the TASK_CAPTURE_DISPATCHER. - -- @param #TASK_CAPTURE_DISPATCHER self - -- @param AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER AI_A2G_Dispatcher The AI Dispatcher to be linked to the tasking. - -- @return Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE - function TASK_CAPTURE_DISPATCHER:Link_AI_A2G_Dispatcher( AI_A2G_Dispatcher ) - - self.AI_A2G_Dispatcher = AI_A2G_Dispatcher -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER - AI_A2G_Dispatcher.Detection:LockDetectedItems() - - return self - end - - - --- Assigns tasks to the @{Core.Set#SET_GROUP}. - -- @param #TASK_CAPTURE_DISPATCHER self - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function TASK_CAPTURE_DISPATCHER:ManageTasks() - self:F() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - if Mission:IsIDLE() or Mission:IsENGAGED() then - - local TaskReport = REPORT:New() - - -- Checking the task queue for the dispatcher, and removing any obsolete task! - for TaskIndex, TaskData in pairs( self.Tasks ) do - local Task = TaskData -- Tasking.Task#TASK - if Task:IsStatePlanned() then - -- Here we need to check if the pilot is still existing. --- Task = self:RemoveTask( TaskIndex ) - end - - end - - -- Now that all obsolete tasks are removed, loop through the Zone tasks. - for TaskName, CaptureZone in pairs( self.Zones ) do - - if not CaptureZone.Task then - -- New Transport Task - CaptureZone.Task = TASK_CAPTURE_ZONE:New( Mission, self.SetGroup, TaskName, CaptureZone.CaptureZone, CaptureZone.Briefing ) - CaptureZone.Task.TaskPrefix = CaptureZone.TaskPrefix -- We keep the TaskPrefix for further reference! - Mission:AddTask( CaptureZone.Task ) - TaskReport:Add( TaskName ) - - -- Link the Task Dispatcher to the capture zone task, because it is used on the UpdateTaskInfo. - CaptureZone.Task:SetDispatcher( self ) - CaptureZone.Task:UpdateTaskInfo() - - function CaptureZone.Task.OnEnterAssigned( Task, From, Event, To ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Unlock( Task.TaskZoneName ) -- This will unlock the zone to be defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = true - end - - function CaptureZone.Task.OnEnterSuccess( Task, From, Event, To ) - --self:Success( Task ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - function CaptureZone.Task.OnEnterCancelled( Task, From, Event, To ) - self:Cancelled( Task ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - function CaptureZone.Task.OnEnterFailed( Task, From, Event, To ) - self:Failed( Task ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - function CaptureZone.Task.OnEnterAborted( Task, From, Event, To ) - self:Aborted( Task ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - -- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher. - function CaptureZone.Task.OnAfterCaptured( Task, From, Event, To, TaskUnit ) - self:Captured( Task, Task.TaskPrefix, TaskUnit ) - if self.AI_A2G_Dispatcher then - self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. - end - CaptureZone.Task:UpdateTaskInfo() - CaptureZone.Task.ZoneGoal.Attacked = false - end - - end - - end - - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text(", ") - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and ( not self.FlashNewTask ) then - Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) - end - end - - end - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua b/Moose Development/Moose/Tasking/Task_Capture_Zone.lua deleted file mode 100644 index 33b464a35..000000000 --- a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua +++ /dev/null @@ -1,334 +0,0 @@ ---- **Tasking** - The TASK_Protect models tasks for players to protect or capture specific zones. --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: MillerTime --- --- === --- --- @module Tasking.Task_Capture_Zone --- @image MOOSE.JPG - -do -- TASK_ZONE_GOAL - - --- The TASK_ZONE_GOAL class - -- @type TASK_ZONE_GOAL - -- @field Functional.ZoneGoal#ZONE_GOAL ZoneGoal - -- @extends Tasking.Task#TASK - - --- # TASK_ZONE_GOAL class, extends @{Tasking.Task#TASK} - -- - -- The TASK_ZONE_GOAL class defines the task to protect or capture a protection zone. - -- The TASK_ZONE_GOAL is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: - -- - -- * **None**: Start of the process - -- * **Planned**: The A2G task is planned. - -- * **Assigned**: The A2G task is assigned to a @{Wrapper.Group#GROUP}. - -- * **Success**: The A2G task is successfully completed. - -- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. - -- - -- ## Set the scoring of achievements in an A2G attack. - -- - -- Scoring or penalties can be given in the following circumstances: - -- - -- * @{#TASK_ZONE_GOAL.SetScoreOnDestroy}(): Set a score when a target in scope of the A2G attack, has been destroyed. - -- * @{#TASK_ZONE_GOAL.SetScoreOnSuccess}(): Set a score when all the targets in scope of the A2G attack, have been destroyed. - -- * @{#TASK_ZONE_GOAL.SetPenaltyOnFailed}(): Set a penalty when the A2G attack has failed. - -- - -- # 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 #TASK_ZONE_GOAL - TASK_ZONE_GOAL = { - ClassName = "TASK_ZONE_GOAL", - } - - --- Instantiates a new TASK_ZONE_GOAL. - -- @param #TASK_ZONE_GOAL self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal - -- @return #TASK_ZONE_GOAL self - function TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoal, TaskType, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType, TaskBriefing ) ) -- #TASK_ZONE_GOAL - self:F() - - self.ZoneGoal = ZoneGoal - self.TaskType = TaskType - - local Fsm = self:GetUnitProcess() - - - Fsm:AddTransition( "Assigned", "StartMonitoring", "Monitoring" ) - Fsm:AddTransition( "Monitoring", "Monitor", "Monitoring", {} ) - Fsm:AddProcess( "Monitoring", "RouteToZone", ACT_ROUTE_ZONE:New(), {} ) - - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - self:SetTargetZone( self.ZoneGoal:GetZone() ) - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK Task - function Fsm:OnAfterAssigned( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - self:__StartMonitoring( 0.1 ) - self:__RouteToZone( 0.1 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_ZONE_GOAL Task - function Fsm:onafterStartMonitoring( TaskUnit, Task ) - self:F( { self } ) - self:__Monitor( 0.1 ) - end - - --- Monitor Loop - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_ZONE_GOAL Task - function Fsm:onafterMonitor( TaskUnit, Task ) - self:F( { self } ) - self:__Monitor( 15 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_ZONE_GOAL Task - function Fsm:onafterRouteTo( TaskUnit, Task ) - self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - if Task:GetTargetZone( TaskUnit ) then - self:__RouteToZone( 0.1 ) - end - end - - return self - - end - - -- @param #TASK_ZONE_GOAL self - -- @param Functional.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal Engine. - function TASK_ZONE_GOAL:SetProtect( ZoneGoal ) - - self.ZoneGoal = ZoneGoal -- Functional.ZoneGoal#ZONE_GOAL - end - - - - -- @param #TASK_ZONE_GOAL self - function TASK_ZONE_GOAL:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.ZoneGoal:GetZoneName() .. " )" - end - - - -- @param #TASK_ZONE_GOAL self - -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_ZONE_GOAL:SetTargetZone( TargetZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteZone:SetZone( TargetZone ) - end - - - -- @param #TASK_ZONE_GOAL self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_ZONE_GOAL:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteZone = ProcessUnit:GetProcess( "Monitoring", "RouteToZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteZone:GetZone() - end - - function TASK_ZONE_GOAL:SetGoalTotal( GoalTotal ) - - self.GoalTotal = GoalTotal - end - - function TASK_ZONE_GOAL:GetGoalTotal() - - return self.GoalTotal - end - -end - - -do -- TASK_CAPTURE_ZONE - - --- The TASK_CAPTURE_ZONE class - -- @type TASK_CAPTURE_ZONE - -- @field Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoal - -- @extends #TASK_ZONE_GOAL - - --- # TASK_CAPTURE_ZONE class, extends @{Tasking.Task_Capture_Zone#TASK_ZONE_GOAL} - -- - -- The TASK_CAPTURE_ZONE class defines an Suppression or Extermination of Air Defenses task for a human player to be executed. - -- These tasks are important to be executed as they will help to achieve air superiority at the vicinity. - -- - -- The TASK_CAPTURE_ZONE is used by the @{Tasking.Task_A2G_Dispatcher#TASK_A2G_DISPATCHER} to automatically create SEAD tasks - -- based on detected enemy ground targets. - -- - -- @field #TASK_CAPTURE_ZONE - TASK_CAPTURE_ZONE = { - ClassName = "TASK_CAPTURE_ZONE", - } - - - --- Instantiates a new TASK_CAPTURE_ZONE. - -- @param #TASK_CAPTURE_ZONE self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Functional.ZoneGoalCoalition#ZONE_GOAL_COALITION ZoneGoalCoalition - -- @param #string TaskBriefing The briefing of the task. - -- @return #TASK_CAPTURE_ZONE self - function TASK_CAPTURE_ZONE:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, TaskBriefing) - local self = BASE:Inherit( self, TASK_ZONE_GOAL:New( Mission, SetGroup, TaskName, ZoneGoalCoalition, "CAPTURE", TaskBriefing ) ) -- #TASK_CAPTURE_ZONE - self:F() - - Mission:AddTask( self ) - - self.TaskCoalition = ZoneGoalCoalition:GetCoalition() - self.TaskCoalitionName = ZoneGoalCoalition:GetCoalitionName() - self.TaskZoneName = ZoneGoalCoalition:GetZoneName() - - ZoneGoalCoalition:MonitorDestroyedUnits() - - self:SetBriefing( - TaskBriefing or - "Capture Zone " .. self.TaskZoneName - ) - - self:UpdateTaskInfo( true ) - - self:SetGoal( self.ZoneGoal.Goal ) - - return self - end - - - --- Instantiates a new TASK_CAPTURE_ZONE. - -- @param #TASK_CAPTURE_ZONE self - function TASK_CAPTURE_ZONE:UpdateTaskInfo( Persist ) - - Persist = Persist or false - - local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate() - self.TaskInfo:AddTaskName( 0, "MSOD", Persist ) - self.TaskInfo:AddCoordinate( ZoneCoordinate, 1, "SOD", Persist ) --- self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD", Persist ) --- self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD", Persist ) - local SetUnit = self.ZoneGoal:GetScannedSetUnit() - local ThreatLevel, ThreatText = SetUnit:CalculateThreatLevelA2G() - local ThreatCount = SetUnit:Count() - self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 20, "MOD", Persist ) - self.TaskInfo:AddInfo( "Remaining Units", ThreatCount, 21, "MOD", Persist, true) - - if self.Dispatcher then - local DefenseTaskCaptureDispatcher = self.Dispatcher:GetDefenseTaskCaptureDispatcher() -- Tasking.Task_Capture_Dispatcher#TASK_CAPTURE_DISPATCHER - - if DefenseTaskCaptureDispatcher then - -- Loop through all zones of the player Defenses, and check which zone has an assigned task! - -- The Zones collection contains a Task. This Task is checked if it is assigned. - -- If Assigned, then this task will be the task that is the closest to the defense zone. - for TaskName, CaptureZone in pairs( DefenseTaskCaptureDispatcher.Zones or {} ) do - local Task = CaptureZone.Task -- Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE - if Task and Task:IsStateAssigned() then -- We also check assigned. - -- Now we register the defense player zone information to the task report. - self.TaskInfo:AddInfo( "Defense Player Zone", Task.ZoneGoal:GetName(), 30, "MOD", Persist ) - self.TaskInfo:AddCoordinate( Task.ZoneGoal:GetZone():GetCoordinate(), 31, "MOD", Persist, false, "Defense Player Coordinate" ) - end - end - end - local DefenseAIA2GDispatcher = self.Dispatcher:GetDefenseAIA2GDispatcher() -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER - - if DefenseAIA2GDispatcher then - -- Loop through all the tasks of the AI Defenses, and check which zone is involved in the defenses and is active! - for Defender, Task in pairs( DefenseAIA2GDispatcher:GetDefenderTasks() or {} ) do - local DetectedItem = DefenseAIA2GDispatcher:GetDefenderTaskTarget( Defender ) - if DetectedItem then - local DetectedZone = DefenseAIA2GDispatcher.Detection:GetDetectedItemZone( DetectedItem ) - if DetectedZone then - self.TaskInfo:AddInfo( "Defense AI Zone", DetectedZone:GetName(), 40, "MOD", Persist ) - self.TaskInfo:AddCoordinate( DetectedZone:GetCoordinate(), 41, "MOD", Persist, false, "Defense AI Coordinate" ) - end - end - end - end - end - - end - - - function TASK_CAPTURE_ZONE:ReportOrder( ReportGroup ) - - local Coordinate = self.TaskInfo:GetCoordinate() - local Distance = ReportGroup:GetCoordinate():Get2DDistance( Coordinate ) - - return Distance - end - - - -- @param #TASK_CAPTURE_ZONE self - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_CAPTURE_ZONE:OnAfterGoal( From, Event, To, PlayerUnit, PlayerName ) - - self:F( { PlayerUnit = PlayerUnit, Achieved = self.ZoneGoal.Goal:IsAchieved() } ) - - if self.ZoneGoal then - if self.ZoneGoal.Goal:IsAchieved() then - local TotalContributions = self.ZoneGoal.Goal:GetTotalContributions() - local PlayerContributions = self.ZoneGoal.Goal:GetPlayerContributions() - self:F( { TotalContributions = TotalContributions, PlayerContributions = PlayerContributions } ) - for PlayerName, PlayerContribution in pairs( PlayerContributions ) do - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionGoalScore( self.Mission, PlayerName, "Zone " .. self.ZoneGoal:GetZoneName() .." captured", PlayerContribution * 200 / TotalContributions ) - end - end - self:Success() - end - end - - self:__Goal( -10, PlayerUnit, PlayerName ) - end - - --- This function is called from the @{Tasking.CommandCenter#COMMANDCENTER} to determine the method of automatic task selection. - -- @param #TASK_CAPTURE_ZONE self - -- @param #number AutoAssignMethod The method to be applied to the task. - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. - -- @param Wrapper.Group#GROUP TaskGroup The player group. - function TASK_CAPTURE_ZONE:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup, AutoAssignReference ) - - if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then - return math.random( 1, 9 ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Distance then - local Coordinate = self.TaskInfo:GetCoordinate() - local Distance = Coordinate:Get2DDistance( CommandCenter:GetPositionable():GetCoordinate() ) - return math.floor( Distance ) - elseif AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Priority then - return 1 - end - - return 0 - end - -end - diff --git a/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua b/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua deleted file mode 100644 index e27a225c0..000000000 --- a/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua +++ /dev/null @@ -1,398 +0,0 @@ ---- **Tasking** - Orchestrates the task for players to execute CSAR for downed pilots. --- --- **Specific features:** --- --- * Creates a task to retrieve a pilot @{Cargo.Cargo} from behind enemy lines. --- * Derived from the TASK_CARGO class, which is derived from the TASK class. --- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled. --- * Co-operation tasking, so a player joins a group of players executing the same task. --- --- --- **A complete task menu system to allow players to:** --- --- * Join the task, abort the task. --- * Mark the task location on the map. --- * Provide details of the target. --- * Route to the cargo. --- * Route to the deploy zones. --- * Load/Unload cargo. --- * Board/Unboard cargo. --- * Slingload cargo. --- * Display the task briefing. --- --- --- **A complete mission menu system to allow players to:** --- --- * Join a task, abort the task. --- * Display task reports. --- * Display mission statistics. --- * Mark the task locations on the map. --- * Provide details of the targets. --- * Display the mission briefing. --- * Provide status updates as retrieved from the command center. --- * Automatically assign a random task as part of a mission. --- * Manually assign a specific task as part of a mission. --- --- --- **A settings system, using the settings menu:** --- --- * Tweak the duration of the display of messages. --- * Switch between metric and imperial measurement system. --- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS. --- * Different settings modes for A2G and A2A operations. --- * Various other options. --- --- === --- --- Please read through the @{Tasking.Task_CARGO} process to understand the mechanisms of tasking and cargo tasking and handling. --- --- The cargo will be a downed pilot, which is located somwhere on the battlefield. Use the menus system and facilities to --- join the CSAR task, and retrieve the pilot from behind enemy lines. The menu system is generic, there is nothing --- specific on a CSAR task that requires further explanation, than reading the generic TASK_CARGO explanations. --- --- Enjoy! --- FC --- --- # 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 --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_Cargo_CSAR --- @image Task_Cargo_CSAR.JPG - - -do -- TASK_CARGO_CSAR - - -- @type TASK_CARGO_CSAR - -- @extends Tasking.Task_Cargo#TASK_CARGO - - --- Orchestrates the task for players to execute CSAR for downed pilots. - -- - -- CSAR tasks are suited to govern the process of return downed pilots behind enemy lines back to safetly. - -- Typically, this task is executed by helicopter pilots, but it can also be executed by ground forces! - -- - -- === - -- - -- A CSAR task can be created manually, but actually, it is better to **GENERATE** these tasks using the - -- @{Tasking.Task_Cargo_Dispatcher} module. - -- - -- Using the dispatcher, CSAR tasks will be created **automatically** when a pilot ejects from a damaged AI aircraft. - -- When this happens, the pilot actually will survive, but needs to be retrieved from behind enemy lines. - -- - -- # 1) Create a CSAR task manually (code it). - -- - -- Although it is recommended to use the dispatcher, you can create a CSAR task yourself as a mission designer. - -- It is easy, as it works just like any other task setup. - -- - -- ## 1.1) Create a command center. - -- - -- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. - -- - -- local CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) -- Create the CommandCenter. - -- - -- ## 1.2) Create a mission. - -- - -- Tasks work in a mission, which groups these tasks to achieve a joint mission goal. - -- A command center can govern multiple missions. - -- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor. - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, - -- "Overlord", - -- "High", - -- "Retrieve the downed pilots.", - -- coalition.side.RED - -- ) - -- - -- ## 1.3) Create the CSAR cargo task. - -- - -- So, now that we have a command center and a mission, we now create the CSAR task. - -- We create the CSAR task using the @{#TASK_CARGO_CSAR.New}() constructor. - -- - -- Because a CSAR task will not generate the cargo itself, you'll need to create it first. - -- The cargo in this case will be the downed pilot! - -- - -- -- Here we define the "cargo set", which is a collection of cargo objects. - -- -- The cargo set will be the input for the cargo transportation task. - -- -- So a transportation object is handling a cargo set, which is automatically refreshed when new cargo is added/deleted. - -- local CargoSet = SET_CARGO:New():FilterTypes( "Pilots" ):FilterStart() - -- - -- -- Now we add cargo into the battle scene. - -- local PilotGroup = GROUP:FindByName( "Pilot" ) - -- - -- -- CARGO_GROUP can be used to setup cargo with a GROUP object underneath. - -- -- We name this group Engineers. - -- -- Note that the name of the cargo is "Engineers". - -- -- The cargoset "CargoSet" will embed all defined cargo of type "Pilots" (prefix) into its set. - -- local CargoGroup = CARGO_GROUP:New( PilotGroup, "Pilots", "Downed Pilot", 500 ) - -- - -- What is also needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the Transport, which are the helicopter to retrieve the pilot, that can be manned by players. - -- local GroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart() - -- - -- Now that we have a CargoSet and a GroupSet, we can now create the CSARTask manually. - -- - -- -- Declare the CSAR task. - -- local CSARTask = TASK_CARGO_CSAR - -- :New( Mission, - -- GroupSet, - -- "CSAR Pilot", - -- CargoSet, - -- "Fly behind enemy lines, and retrieve the downed pilot." - -- ) - -- - -- So you can see, setting up a CSAR task manually is a lot of work. - -- It is better you use the cargo dispatcher to generate CSAR tasks and it will work as it is intended. - -- By doing this, CSAR tasking will become a dynamic experience. - -- - -- # 2) Create a task using the @{Tasking.Task_Cargo_Dispatcher} module. - -- - -- Actually, it is better to **GENERATE** these tasks using the @{Tasking.Task_Cargo_Dispatcher} module. - -- Using the dispatcher module, transport tasks can be created much more easy. - -- - -- Find below an example how to use the TASK_CARGO_DISPATCHER class: - -- - -- - -- -- Find the HQ group. - -- HQ = GROUP:FindByName( "HQ", "Bravo" ) - -- - -- -- Create the command center with the name "Lima". - -- CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) - -- - -- -- Create the mission, for the command center, with the name "CSAR Mission", a "Tactical" mission, with the mission briefing "Rescue downed pilots.", for the RED coalition. - -- Mission = MISSION - -- :New( CommandCenter, "CSAR Mission", "Tactical", "Rescue downed pilots.", coalition.side.RED ) - -- - -- -- Create the SET of GROUPs containing clients (players) that will transport the cargo. - -- -- These are have a name that start with "Rescue" and are of the "red" coalition. - -- AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Rescue" ):FilterStart() - -- - -- - -- -- Here we create the TASK_CARGO_DISPATCHER object! This is where we assign the dispatcher to generate tasks in the Mission for the AttackGroups. - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, AttackGroups ) - -- - -- - -- -- Here the task dispatcher will generate automatically CSAR tasks once a pilot ejects. - -- TaskDispatcher:StartCSARTasks( - -- "CSAR", - -- { ZONE_UNIT:New( "Hospital", STATIC:FindByName( "Hospital" ), 100 ) }, - -- "One of our pilots has ejected. Go out to Search and Rescue our pilot!\n" .. - -- "Use the radio menu to let the command center assist you with the CSAR tasking." - -- ) - -- - -- # 3) Handle cargo task events. - -- - -- When a player is picking up and deploying cargo using his carrier, events are generated by the tasks. These events can be captured and tailored with your own code. - -- - -- In order to properly capture the events and avoid mistakes using the documentation, it is advised that you execute the following actions: - -- - -- * **Copy / Paste** the code section into your script. - -- * **Change** the CLASS literal to the task object name you have in your script. - -- * Within the function, you can now **write your own code**! - -- * **IntelliSense** will recognize the type of the variables provided by the function. Note: the From, Event and To variables can be safely ignored, - -- but you need to declare them as they are automatically provided by the event handling system of MOOSE. - -- - -- You can send messages or fire off any other events within the code section. The sky is the limit! - -- - -- NOTE: CSAR tasks are actually automatically created by the TASK_CARGO_DISPATCHER. So the underlying is not really applicable for mission designers as they will use the dispatcher instead - -- of capturing these events from manually created CSAR tasks! - -- - -- ## 3.1) Handle the **CargoPickedUp** event. - -- - -- Find below an example how to tailor the **CargoPickedUp** event, generated by the CSARTask: - -- - -- function CSARTask:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has picked up cargo.", MESSAGE.Type.Information ):ToAll() - -- - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has picked up a cargo object in the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- --- CargoPickedUp event handler OnAfter for CLASS. - -- -- @param #CLASS self - -- -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has picked up the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been picked up. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- function CLASS:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- ## 3.2) Handle the **CargoDeployed** event. - -- - -- Find below an example how to tailor the **CargoDeployed** event, generated by the CSARTask: - -- - -- function CSARTask:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has deployed cargo at zone " .. DeployZone:GetName(), MESSAGE.Type.Information ):ToAll() - -- - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has deployed a cargo object from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- - -- --- CargoDeployed event handler OnAfter for CLASS. - -- -- @param #CLASS self - -- -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has deployed the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been deployed. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - -- function CLASS:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- === - -- - -- @field #TASK_CARGO_CSAR - TASK_CARGO_CSAR = { - ClassName = "TASK_CARGO_CSAR", - } - - --- Instantiates a new TASK_CARGO_CSAR. - -- @param #TASK_CARGO_CSAR self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported. - -- @param #string TaskBriefing The Cargo Task briefing. - -- @return #TASK_CARGO_CSAR self - function TASK_CARGO_CSAR:New( Mission, SetGroup, TaskName, SetCargo, TaskBriefing ) - local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "CSAR", TaskBriefing ) ) -- #TASK_CARGO_CSAR - self:F() - - Mission:AddTask( self ) - - - -- Events - - self:AddTransition( "*", "CargoPickedUp", "*" ) - self:AddTransition( "*", "CargoDeployed", "*" ) - - self:F( { CargoDeployed = self.CargoDeployed ~= nil and "true" or "false" } ) - - --- OnAfter Transition Handler for Event CargoPickedUp. - -- @function [parent=#TASK_CARGO_CSAR] OnAfterCargoPickedUp - -- @param #TASK_CARGO_CSAR self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - - --- OnAfter Transition Handler for Event CargoDeployed. - -- @function [parent=#TASK_CARGO_CSAR] OnAfterCargoDeployed - -- @param #TASK_CARGO_CSAR self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc. - -- @param Cargo.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status. - -- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded. - - local Fsm = self:GetUnitProcess() - - local CargoReport = REPORT:New( "Rescue a downed pilot from the following position:") - - SetCargo:ForEachCargo( - --- @param Cargo.Cargo#CARGO Cargo - function( Cargo ) - local CargoType = Cargo:GetType() - local CargoName = Cargo:GetName() - local CargoCoordinate = Cargo:GetCoordinate() - CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) ) - end - ) - - self:SetBriefing( - TaskBriefing or - CargoReport:Text() - ) - - - return self - end - - - - function TASK_CARGO_CSAR:ReportOrder( ReportGroup ) - - return 0 - end - - - --- - -- @param #TASK_CARGO_CSAR self - -- @return #boolean - function TASK_CARGO_CSAR:IsAllCargoTransported() - - local CargoSet = self:GetCargoSet() - local Set = CargoSet:GetSet() - - local DeployZones = self:GetDeployZones() - - local CargoDeployed = true - - -- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ). - for CargoID, CargoData in pairs( Set ) do - local Cargo = CargoData -- Cargo.Cargo#CARGO - - self:F( { Cargo = Cargo:GetName(), CargoDeployed = Cargo:IsDeployed() } ) - - if Cargo:IsDeployed() then - --- -- Loop the DeployZones set for the TASK_CARGO_CSAR. --- for DeployZoneID, DeployZone in pairs( DeployZones ) do --- --- -- If all cargo is in one of the deploy zones, then all is good. --- self:T( { Cargo.CargoObject } ) --- if Cargo:IsInZone( DeployZone ) == false then --- CargoDeployed = false --- end --- end - else - CargoDeployed = false - end - end - - self:F( { CargoDeployed = CargoDeployed } ) - - return CargoDeployed - end - - --- @param #TASK_CARGO_CSAR self - function TASK_CARGO_CSAR:onafterGoal( TaskUnit, From, Event, To ) - local CargoSet = self.CargoSet - - if self:IsAllCargoTransported() then - self:Success() - end - - self:__Goal( -10 ) - end - -end - diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua deleted file mode 100644 index f78b2d5cd..000000000 --- a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua +++ /dev/null @@ -1,919 +0,0 @@ ---- **Tasking** - Creates and manages player TASK_CARGO tasks. --- --- The **TASK_CARGO_DISPATCHER** allows you to setup various tasks for let human --- players transport cargo as part of a task. --- --- The cargo dispatcher will implement for you mechanisms to create cargo transportation tasks: --- --- * As setup by the mission designer. --- * Dynamically create CSAR missions (when a pilot is downed as part of a downed plane). --- * Dynamically spawn new cargo and create cargo taskings! --- --- --- --- **Specific features:** --- --- * Creates a task to transport @{Cargo.Cargo} to and between deployment zones. --- * Derived from the TASK_CARGO class, which is derived from the TASK class. --- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled. --- * Co-operation tasking, so a player joins a group of players executing the same task. --- --- --- **A complete task menu system to allow players to:** --- --- * Join the task, abort the task. --- * Mark the task location on the map. --- * Provide details of the target. --- * Route to the cargo. --- * Route to the deploy zones. --- * Load/Unload cargo. --- * Board/Unboard cargo. --- * Slingload cargo. --- * Display the task briefing. --- --- --- **A complete mission menu system to allow players to:** --- --- * Join a task, abort the task. --- * Display task reports. --- * Display mission statistics. --- * Mark the task locations on the map. --- * Provide details of the targets. --- * Display the mission briefing. --- * Provide status updates as retrieved from the command center. --- * Automatically assign a random task as part of a mission. --- * Manually assign a specific task as part of a mission. --- --- --- **A settings system, using the settings menu:** --- --- * Tweak the duration of the display of messages. --- * Switch between metric and imperial measurement system. --- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS. --- * Different settings modes for A2G and A2A operations. --- * Various other options. --- --- # 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 --- --- === --- --- ### Author: **FlightControl** --- --- ### Contributions: --- --- === --- --- @module Tasking.Task_Cargo_Dispatcher --- @image Task_Cargo_Dispatcher.JPG - -do -- TASK_CARGO_DISPATCHER - - --- TASK_CARGO_DISPATCHER class. - -- @type TASK_CARGO_DISPATCHER - -- @extends Tasking.Task_Manager#TASK_MANAGER - -- @field TASK_CARGO_DISPATCHER.CSAR CSAR - -- @field Core.Set#SET_ZONE SetZonesCSAR - - -- @type TASK_CARGO_DISPATCHER.CSAR - -- @field Wrapper.Unit#UNIT PilotUnit - -- @field Tasking.Task#TASK Task - - - --- Implements the dynamic dispatching of cargo tasks. - -- - -- The **TASK_CARGO_DISPATCHER** allows you to setup various tasks for let human - -- players transport cargo as part of a task. - -- - -- There are currently **two types of tasks** that can be constructed: - -- - -- * A **normal cargo transport** task, which tasks humans to transport cargo from a location towards a deploy zone. - -- * A **CSAR** cargo transport task. CSAR tasks are **automatically generated** when a friendly (AI) plane is downed and the friendly pilot ejects... - -- You as a player (the helo pilot) can go out in the battlefield, fly behind enemy lines, and rescue the pilot (back to a deploy zone). - -- - -- Let's explore **step by step** how to setup the task cargo dispatcher. - -- - -- # 1. Setup a mission environment. - -- - -- It is easy, as it works just like any other task setup, so setup a command center and a mission. - -- - -- ## 1.1. Create a command center. - -- - -- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor. - -- - -- local CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) -- Create the CommandCenter. - -- - -- ## 1.2. Create a mission. - -- - -- Tasks work in a mission, which groups these tasks to achieve a joint mission goal. - -- A command center can govern multiple missions. - -- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor. - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, - -- "Overlord", - -- "High", - -- "Transport the cargo.", - -- coalition.side.RED - -- ) - -- - -- - -- # 2. Dispatch a **transport cargo** task. - -- - -- So, now that we have a command center and a mission, we now create the transport task. - -- We create the transport task using the @{#TASK_CARGO_DISPATCHER.AddTransportTask}() constructor. - -- - -- ## 2.1. Create the cargo in the mission. - -- - -- Because a transport task will not generate the cargo itself, you'll need to create it first. - -- - -- -- Here we define the "cargo set", which is a collection of cargo objects. - -- -- The cargo set will be the input for the cargo transportation task. - -- -- So a transportation object is handling a cargo set, which is automatically updated when new cargo is added/deleted. - -- local WorkmaterialsCargoSet = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() - -- - -- -- Now we add cargo into the battle scene. - -- local PilotGroup = GROUP:FindByName( "Engineers" ) - -- - -- -- CARGO_GROUP can be used to setup cargo with a GROUP object underneath. - -- -- We name the type of this group "Workmaterials", so that this cargo group will be included within the WorkmaterialsCargoSet. - -- -- Note that the name of the cargo is "Engineer Team 1". - -- local CargoGroup = CARGO_GROUP:New( PilotGroup, "Workmaterials", "Engineer Team 1", 500 ) - -- - -- What is also needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the Transport, which are the helicopters to retrieve the pilot, that can be manned by players. - -- -- The name of these helicopter groups containing one client begins with "Transport", as modelled within the mission editor. - -- local PilotGroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart() - -- - -- ## 2.2. Setup the cargo transport task. - -- - -- First, we need to create a TASK_CARGO_DISPATCHER object. - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, PilotGroupSet ) - -- - -- So, the variable `TaskDispatcher` will contain the object of class TASK_CARGO_DISPATCHER, which will allow you to dispatch cargo transport tasks: - -- - -- * for mission `Mission`. - -- * for the group set `PilotGroupSet`. - -- - -- Now that we have `TaskDispatcher` object, we can now **create the TransportTask**, using the @{#TASK_CARGO_DISPATCHER.AddTransportTask}() method! - -- - -- local TransportTask = TaskDispatcher:AddTransportTask( - -- "Transport workmaterials", - -- WorkmaterialsCargoSet, - -- "Transport the workers, engineers and the equipment near the Workplace." ) - -- - -- As a result of this code, the `TransportTask` (returned) variable will contain an object of @{#TASK_CARGO_TRANSPORT}! - -- We pass to the method the title of the task, and the `WorkmaterialsCargoSet`, which is the set of cargo groups to be transported! - -- This object can also be used to setup additional things, or to control this specific task with special actions. - -- - -- And you're done! As you can see, it is a bit of work, but the reward is great. - -- And, because all this is done using program interfaces, you can build a mission with a **dynamic cargo transport task mechanism** yourself! - -- Based on events happening within your mission, you can use the above methods to create new cargo, and setup a new task for cargo transportation to a group of players! - -- - -- - -- # 3. Dispatch CSAR tasks. - -- - -- CSAR tasks can be dynamically created when a friendly pilot ejects, or can be created manually. - -- We'll explore both options. - -- - -- ## 3.1. CSAR task dynamic creation. - -- - -- Because there is an "event" in a running simulation that creates CSAR tasks, the method @{#TASK_CARGO_DISPATCHER.StartCSARTasks}() will create automatically: - -- - -- 1. a new downed pilot at the location where the plane was shot - -- 2. declare that pilot as cargo - -- 3. creates a CSAR task automatically to retrieve that pilot - -- 4. requires deploy zones to be specified where to transport the downed pilot to, in order to complete that task. - -- - -- You create a CSAR task dynamically in a very easy way: - -- - -- TaskDispatcher:StartCSARTasks( - -- "CSAR", - -- { ZONE_UNIT:New( "Hospital", STATIC:FindByName( "Hospital" ), 100 ) }, - -- "One of our pilots has ejected. Go out to Search and Rescue our pilot!\n" .. - -- "Use the radio menu to let the command center assist you with the CSAR tasking." - -- ) - -- - -- The method @{#TASK_CARGO_DISPATCHER.StopCSARTasks}() will automatically stop with the creation of CSAR tasks when friendly pilots eject. - -- - -- **Remarks:** - -- - -- * the ZONE_UNIT can also be a ZONE, or a ZONE_POLYGON object, or any other ZONE_ object! - -- * you can declare the array of zones in another variable, or course! - -- - -- - -- ## 3.2. CSAR task manual creation. - -- - -- We create the CSAR task using the @{#TASK_CARGO_DISPATCHER.AddCSARTask}() constructor. - -- - -- The method will create a new CSAR task, and will generate the pilots cargo itself, at the specified coordinate. - -- - -- What is first needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the Transport, which are the helicopter to retrieve the pilot, that can be manned by players. - -- local GroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart() - -- - -- We need to create a TASK_CARGO_DISPATCHER object. - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, GroupSet ) - -- - -- So, the variable `TaskDispatcher` will contain the object of class TASK_CARGO_DISPATCHER, which will allow you to dispatch cargo CSAR tasks: - -- - -- * for mission `Mission`. - -- * for the group of players (pilots) captured within the `GroupSet` (those groups with a name starting with `"Transport"`). - -- - -- Now that we have a PilotsCargoSet and a GroupSet, we can now create the CSAR task manually. - -- - -- -- Declare the CSAR task. - -- local CSARTask = TaskDispatcher:AddCSARTask( - -- "CSAR Task", - -- Coordinate, - -- 270, - -- "Bring the pilot back!" - -- ) - -- - -- As a result of this code, the `CSARTask` (returned) variable will contain an object of @{#TASK_CARGO_CSAR}! - -- We pass to the method the title of the task, and the `WorkmaterialsCargoSet`, which is the set of cargo groups to be transported! - -- This object can also be used to setup additional things, or to control this specific task with special actions. - -- Note that when you declare a CSAR task manually, you'll still need to specify a deployment zone! - -- - -- # 4. Setup the deploy zone(s). - -- - -- The task cargo dispatcher also foresees methods to setup the deployment zones to where the cargo needs to be transported! - -- - -- There are two levels on which deployment zones can be configured: - -- - -- * Default deploy zones: The TASK_CARGO_DISPATCHER object can have default deployment zones, which will apply over all tasks active in the task dispatcher. - -- * Task specific deploy zones: The TASK_CARGO_DISPATCHER object can have specific deployment zones which apply to a specific task only! - -- - -- Note that for Task specific deployment zones, there are separate deployment zone creation methods per task type! - -- - -- ## 4.1. Setup default deploy zones. - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetDefaultDeployZone}() to setup one deployment zone, and @{#TASK_CARGO_DISPATCHER.SetDefaultDeployZones}() to setup multiple default deployment zones in one call. - -- - -- ## 4.2. Setup task specific deploy zones for a **transport task**. - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetTransportDeployZone}() to setup one deployment zone, and @{#TASK_CARGO_DISPATCHER.SetTransportDeployZones}() to setup multiple default deployment zones in one call. - -- - -- ## 4.3. Setup task specific deploy zones for a **CSAR task**. - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetCSARDeployZone}() to setup one deployment zone, and @{#TASK_CARGO_DISPATCHER.SetCSARDeployZones}() to setup multiple default deployment zones in one call. - -- - -- ## 4.4. **CSAR ejection zones**. - -- - -- Setup a set of zones where the pilots will only eject and a task is created for CSAR. When such a set of zones is given, any ejection outside those zones will not result in a pilot created for CSAR! - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetCSARZones}() to setup the set of zones. - -- - -- ## 4.5. **CSAR ejection maximum**. - -- - -- Setup how many pilots will eject the maximum. This to avoid an overload of CSAR tasks being created :-) The default is endless CSAR tasks. - -- - -- Use the @{#TASK_CARGO_DISPATCHER.SetMaxCSAR}() to setup the maximum of pilots that will eject for CSAR. - -- - -- - -- # 5) Handle cargo task events. - -- - -- When a player is picking up and deploying cargo using his carrier, events are generated by the dispatcher. These events can be captured and tailored with your own code. - -- - -- In order to properly capture the events and avoid mistakes using the documentation, it is advised that you execute the following actions: - -- - -- * **Copy / Paste** the code section into your script. - -- * **Change** the CLASS literal to the task object name you have in your script. - -- * Within the function, you can now **write your own code**! - -- * **IntelliSense** will recognize the type of the variables provided by the function. Note: the From, Event and To variables can be safely ignored, - -- but you need to declare them as they are automatically provided by the event handling system of MOOSE. - -- - -- You can send messages or fire off any other events within the code section. The sky is the limit! - -- - -- First, we need to create a TASK_CARGO_DISPATCHER object. - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, PilotGroupSet ) - -- - -- Second, we create a new cargo transport task for the transportation of workmaterials. - -- - -- TaskDispatcher:AddTransportTask( - -- "Transport workmaterials", - -- WorkmaterialsCargoSet, - -- "Transport the workers, engineers and the equipment near the Workplace." ) - -- - -- Note that we don't really need to keep the resulting task, it is kept internally also in the dispatcher. - -- - -- Using the `TaskDispatcher` object, we can now cpature the CargoPickedUp and CargoDeployed events. - -- - -- ## 5.1) Handle the **CargoPickedUp** event. - -- - -- Find below an example how to tailor the **CargoPickedUp** event, generated by the `TaskDispatcher`: - -- - -- function TaskDispatcher:OnAfterCargoPickedUp( From, Event, To, Task, TaskPrefix, TaskUnit, Cargo ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has picked up cargo for task " .. Task:GetName() .. ".", MESSAGE.Type.Information ):ToAll() - -- - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has picked up a cargo object in the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- --- CargoPickedUp event handler OnAfter for CLASS. - -- -- @param #CLASS self - -- -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- -- @param Tasking.Task_Cargo#TASK_CARGO Task The cargo task for which the cargo has been picked up. Note that this will be a derived TAKS_CARGO object! - -- -- @param #string TaskPrefix The prefix of the task that was provided when the task was created. - -- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has picked up the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been picked up. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- function CLASS:OnAfterCargoPickedUp( From, Event, To, Task, TaskPrefix, TaskUnit, Cargo ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- ## 5.2) Handle the **CargoDeployed** event. - -- - -- Find below an example how to tailor the **CargoDeployed** event, generated by the `TaskDispatcher`: - -- - -- function WorkplaceTask:OnAfterCargoDeployed( From, Event, To, Task, TaskPrefix, TaskUnit, Cargo, DeployZone ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has deployed cargo at zone " .. DeployZone:GetName() .. " for task " .. Task:GetName() .. ".", MESSAGE.Type.Information ):ToAll() - -- - -- Helos[ math.random(1,#Helos) ]:Spawn() - -- EnemyHelos[ math.random(1,#EnemyHelos) ]:Spawn() - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has deployed a cargo object from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- - -- --- CargoDeployed event handler OnAfter for CLASS. - -- -- @param #CLASS self - -- -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- -- @param Tasking.Task_Cargo#TASK_CARGO Task The cargo task for which the cargo has been deployed. Note that this will be a derived TAKS_CARGO object! - -- -- @param #string TaskPrefix The prefix of the task that was provided when the task was created. - -- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has deployed the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been deployed. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - -- function CLASS:OnAfterCargoDeployed( From, Event, To, Task, TaskPrefix, TaskUnit, Cargo, DeployZone ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- - -- @field #TASK_CARGO_DISPATCHER - TASK_CARGO_DISPATCHER = { - ClassName = "TASK_CARGO_DISPATCHER", - Mission = nil, - Tasks = {}, - CSAR = {}, - CSARSpawned = 0, - - Transport = {}, - TransportCount = 0, - } - - - --- TASK_CARGO_DISPATCHER constructor. - -- @param #TASK_CARGO_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done. - -- @param Core.Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. - -- @return #TASK_CARGO_DISPATCHER self - function TASK_CARGO_DISPATCHER:New( Mission, SetGroup ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CARGO_DISPATCHER - - self.Mission = Mission - - self:AddTransition( "Started", "Assign", "Started" ) - self:AddTransition( "Started", "CargoPickedUp", "Started" ) - self:AddTransition( "Started", "CargoDeployed", "Started" ) - - --- OnAfter Transition Handler for Event Assign. - -- @function [parent=#TASK_CARGO_DISPATCHER] OnAfterAssign - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Tasking.Task_A2A#TASK_A2A Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param #string PlayerName - - self:SetCSARRadius() - self:__StartTasks( 5 ) - - self.MaxCSAR = nil - self.CountCSAR = 0 - - -- For CSAR missions, we process the event when a pilot ejects. - - self:HandleEvent( EVENTS.Ejection ) - - return self - end - - - --- Sets the set of zones were pilots will only be spawned (eject) when the planes crash. - -- Note that because this is a set of zones, the MD can create the zones dynamically within his mission! - -- Just provide a set of zones, see usage, but find the tactical situation here: - -- - -- ![CSAR Zones](../Tasking/CSAR_Zones.JPG) - -- - -- @param #TASK_CARGO_DISPATCHER self - -- @param Core.Set#SET_ZONE SetZonesCSAR The set of zones where pilots will only be spawned for CSAR when they eject. - -- @usage - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, AttackGroups ) - -- - -- -- Use this call to pass the set of zones. - -- -- Note that you can create the set of zones inline, because the FilterOnce method (and other SET_ZONE methods return self). - -- -- So here the zones can be created as normal trigger zones (MOOSE creates a collection of ZONE objects when teh mission starts of all trigger zones). - -- -- Just name them as CSAR zones here. - -- TaskDispatcher:SetCSARZones( SET_ZONE:New():FilterPrefixes("CSAR"):FilterOnce() ) - -- - function TASK_CARGO_DISPATCHER:SetCSARZones( SetZonesCSAR ) - - self.SetZonesCSAR = SetZonesCSAR - - end - - - --- Sets the maximum of pilots that will be spawned (eject) when the planes crash. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #number MaxCSAR The maximum of pilots that will eject for CSAR. - -- @usage - -- - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, AttackGroups ) - -- - -- -- Use this call to the maximum of CSAR to 10. - -- TaskDispatcher:SetMaxCSAR( 10 ) - -- - function TASK_CARGO_DISPATCHER:SetMaxCSAR( MaxCSAR ) - - self.MaxCSAR = MaxCSAR - - end - - - - --- Handle the event when a pilot ejects. - -- @param #TASK_CARGO_DISPATCHER self - -- @param Core.Event#EVENTDATA EventData - function TASK_CARGO_DISPATCHER:OnEventEjection( EventData ) - self:F( { EventData = EventData } ) - - if self.CSARTasks == true then - - local CSARCoordinate = EventData.IniUnit:GetCoordinate() - local CSARCoalition = EventData.IniUnit:GetCoalition() - local CSARCountry = EventData.IniUnit:GetCountry() - local CSARHeading = EventData.IniUnit:GetHeading() - - -- Only add a CSAR task if the coalition of the mission is equal to the coalition of the ejected unit. - if CSARCoalition == self.Mission:GetCommandCenter():GetCoalition() then - -- And only add if the eject is in one of the zones, if defined. - if not self.SetZonesCSAR or ( self.SetZonesCSAR and self.SetZonesCSAR:IsCoordinateInZone( CSARCoordinate ) ) then - -- And only if the maximum of pilots is not reached that ejected! - if not self.MaxCSAR or ( self.MaxCSAR and self.CountCSAR < self.MaxCSAR ) then - local CSARTaskName = self:AddCSARTask( self.CSARTaskName, CSARCoordinate, CSARHeading, CSARCountry, self.CSARBriefing ) - self:SetCSARDeployZones( CSARTaskName, self.CSARDeployZones ) - self.CountCSAR = self.CountCSAR + 1 - end - end - end - end - - return self - end - - - --- Define one default deploy zone for all the cargo tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param DefaultDeployZone A default deploy zone. - -- @return #TASK_CARGO_DISPATCHER - function TASK_CARGO_DISPATCHER:SetDefaultDeployZone( DefaultDeployZone ) - - self.DefaultDeployZones = { DefaultDeployZone } - - return self - end - - - --- Define the deploy zones for all the cargo tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param DefaultDeployZones A list of the deploy zones. - -- @return #TASK_CARGO_DISPATCHER - -- - function TASK_CARGO_DISPATCHER:SetDefaultDeployZones( DefaultDeployZones ) - - self.DefaultDeployZones = DefaultDeployZones - - return self - end - - - --- Start the generation of CSAR tasks to retrieve a downed pilots. - -- You need to specify a task briefing, a task name, default deployment zone(s). - -- This method can only be used once! - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string CSARTaskName The CSAR task name. - -- @param #string CSARDeployZones The zones to where the CSAR deployment should be directed. - -- @param #string CSARBriefing The briefing of the CSAR tasks. - -- @return #TASK_CARGO_DISPATCHER - function TASK_CARGO_DISPATCHER:StartCSARTasks( CSARTaskName, CSARDeployZones, CSARBriefing) - - if not self.CSARTasks then - self.CSARTasks = true - self.CSARTaskName = CSARTaskName - self.CSARDeployZones = CSARDeployZones - self.CSARBriefing = CSARBriefing - else - error( "TASK_CARGO_DISPATCHER: The generation of CSAR tasks has already started." ) - end - - return self - end - - - --- Stop the generation of CSAR tasks to retrieve a downed pilots. - -- @param #TASK_CARGO_DISPATCHER self - -- @return #TASK_CARGO_DISPATCHER - function TASK_CARGO_DISPATCHER:StopCSARTasks() - - if self.CSARTasks then - self.CSARTasks = nil - self.CSARTaskName = nil - self.CSARDeployZones = nil - self.CSARBriefing = nil - else - error( "TASK_CARGO_DISPATCHER: The generation of CSAR tasks was not yet started." ) - end - - return self - end - - - --- Add a CSAR task to retrieve a downed pilot. - -- You need to specify a coordinate from where the pilot will be spawned to be rescued. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string CSARTaskPrefix (optional) The prefix of the CSAR task. - -- @param Core.Point#COORDINATE CSARCoordinate The coordinate where a downed pilot will be spawned. - -- @param #number CSARHeading The heading of the pilot in degrees. - -- @param #DCSCountry CSARCountry The country ID of the pilot that will be spawned. - -- @param #string CSARBriefing The briefing of the CSAR task. - -- @return #string The CSAR Task Name as a string. The Task Name is the main key and is shown in the task list of the Mission Tasking menu. - -- @usage - -- - -- -- Add a CSAR task to rescue a downed pilot from within a coordinate. - -- local Coordinate = PlaneUnit:GetPointVec2() - -- TaskA2ADispatcher:AddCSARTask( "CSAR Task", Coordinate ) - -- - -- -- Add a CSAR task to rescue a downed pilot from within a coordinate of country RUSSIA, which is pointing to the west (270°). - -- local Coordinate = PlaneUnit:GetPointVec2() - -- TaskA2ADispatcher:AddCSARTask( "CSAR Task", Coordinate, 270, Country.RUSSIA ) - -- - function TASK_CARGO_DISPATCHER:AddCSARTask( CSARTaskPrefix, CSARCoordinate, CSARHeading, CSARCountry, CSARBriefing ) - - local CSARCoalition = self.Mission:GetCommandCenter():GetCoalition() - - CSARHeading = CSARHeading or 0 - CSARCountry = CSARCountry or self.Mission:GetCommandCenter():GetCountry() - - self.CSARSpawned = self.CSARSpawned + 1 - - local CSARTaskName = string.format( ( CSARTaskPrefix or "CSAR" ) .. ".%03d", self.CSARSpawned ) - - -- Create the CSAR Pilot SPAWN object. - -- Let us create the Template for the replacement Pilot :-) - local Template = { - ["visible"] = false, - ["hidden"] = false, - ["task"] = "Ground Nothing", - ["name"] = string.format( "CSAR Pilot#%03d", self.CSARSpawned ), - ["x"] = CSARCoordinate.x, - ["y"] = CSARCoordinate.z, - ["units"] = - { - [1] = - { - ["type"] = ( CSARCoalition == coalition.side.BLUE ) and "Soldier M4" or "Infantry AK", - ["name"] = string.format( "CSAR Pilot#%03d-01", self.CSARSpawned ), - ["skill"] = "Excellent", - ["playerCanDrive"] = false, - ["x"] = CSARCoordinate.x, - ["y"] = CSARCoordinate.z, - ["heading"] = CSARHeading, - }, -- end of [1] - }, -- end of ["units"] - } - - local CSARGroup = GROUP:NewTemplate( Template, CSARCoalition, Group.Category.GROUND, CSARCountry ) - - self.CSAR[CSARTaskName] = {} - self.CSAR[CSARTaskName].PilotGroup = CSARGroup - self.CSAR[CSARTaskName].Briefing = CSARBriefing - self.CSAR[CSARTaskName].Task = nil - self.CSAR[CSARTaskName].TaskPrefix = CSARTaskPrefix - - return CSARTaskName - end - - - --- Define the radius to when a CSAR task will be generated for any downed pilot within range of the nearest CSAR airbase. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #number CSARRadius (Optional, Default = 50000) The radius in meters to decide whether a CSAR needs to be created. - -- @return #TASK_CARGO_DISPATCHER - -- @usage - -- - -- -- Set 20km as the radius to CSAR any downed pilot within range of the nearest CSAR airbase. - -- TaskA2ADispatcher:SetEngageRadius( 20000 ) - -- - -- -- Set 50km as the radius to to CSAR any downed pilot within range of the nearest CSAR airbase. - -- TaskA2ADispatcher:SetEngageRadius() -- 50000 is the default value. - -- - function TASK_CARGO_DISPATCHER:SetCSARRadius( CSARRadius ) - - self.CSARRadius = CSARRadius or 50000 - - return self - end - - - --- Define one deploy zone for the CSAR tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string CSARTaskName (optional) The name of the CSAR task. - -- @param CSARDeployZone A CSAR deploy zone. - -- @return #TASK_CARGO_DISPATCHER - function TASK_CARGO_DISPATCHER:SetCSARDeployZone( CSARTaskName, CSARDeployZone ) - - if CSARTaskName then - self.CSAR[CSARTaskName].DeployZones = { CSARDeployZone } - end - - return self - end - - - --- Define the deploy zones for the CSAR tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string CSARTaskName (optional) The name of the CSAR task. - -- @param CSARDeployZones A list of the CSAR deploy zones. - -- @return #TASK_CARGO_DISPATCHER - -- - function TASK_CARGO_DISPATCHER:SetCSARDeployZones( CSARTaskName, CSARDeployZones ) - - if CSARTaskName and self.CSAR[CSARTaskName] then - self.CSAR[CSARTaskName].DeployZones = CSARDeployZones - end - - return self - end - - - --- Add a Transport task to transport cargo from fixed locations to a deployment zone. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #string TaskPrefix (optional) The prefix of the transport task. - -- This prefix will be appended with a . + a number of 3 digits. - -- If no TaskPrefix is given, then "Transport" will be used as the prefix. - -- @param Core.Set#SET_CARGO SetCargo The SetCargo to be transported. - -- @param #string Briefing The briefing of the task transport to be shown to the player. - -- @param #boolean Silent If true don't send a message that a new task is available. - -- @return Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT - -- @usage - -- - -- -- Add a Transport task to transport cargo of different types to a Transport Deployment Zone. - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups ) - -- - -- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() - -- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) - -- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) - -- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) - -- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) - -- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) - -- - -- -- Here we add the task. We name the task "Build a Workplace". - -- -- We provide the CargoSetWorkmaterials, and a briefing as the 2nd and 3rd parameter. - -- -- The :AddTransportTask() returns a Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT object, which we keep as a reference for further actions. - -- -- The WorkplaceTask holds the created and returned Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT object. - -- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." ) - -- - -- -- Here we set a TransportDeployZone. We use the WorkplaceTask as the reference, and provide a ZONE object. - -- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) ) - -- - function TASK_CARGO_DISPATCHER:AddTransportTask( TaskPrefix, SetCargo, Briefing, Silent ) - - self.TransportCount = self.TransportCount + 1 - - local verbose = Silent or false - - local TaskName = string.format( ( TaskPrefix or "Transport" ) .. ".%03d", self.TransportCount ) - - self.Transport[TaskName] = {} - self.Transport[TaskName].SetCargo = SetCargo - self.Transport[TaskName].Briefing = Briefing - self.Transport[TaskName].Task = nil - self.Transport[TaskName].TaskPrefix = TaskPrefix - - self:ManageTasks(verbose) - - return self.Transport[TaskName] and self.Transport[TaskName].Task - end - - - --- Define one deploy zone for the Transport tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT Task The name of the Transport task. - -- @param TransportDeployZone A Transport deploy zone. - -- @return #TASK_CARGO_DISPATCHER - -- @usage - -- - -- - function TASK_CARGO_DISPATCHER:SetTransportDeployZone( Task, TransportDeployZone ) - - if self.Transport[Task.TaskName] then - self.Transport[Task.TaskName].DeployZones = { TransportDeployZone } - else - error( "Task does not exist" ) - end - - self:ManageTasks() - - return self - end - - - --- Define the deploy zones for the Transport tasks. - -- @param #TASK_CARGO_DISPATCHER self - -- @param Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT Task The name of the Transport task. - -- @param TransportDeployZones A list of the Transport deploy zones. - -- @return #TASK_CARGO_DISPATCHER - -- - function TASK_CARGO_DISPATCHER:SetTransportDeployZones( Task, TransportDeployZones ) - - if self.Transport[Task.TaskName] then - self.Transport[Task.TaskName].DeployZones = TransportDeployZones - else - error( "Task does not exist" ) - end - - self:ManageTasks() - - return self - end - - --- Evaluates of a CSAR task needs to be started. - -- @param #TASK_CARGO_DISPATCHER self - -- @return Core.Set#SET_CARGO The SetCargo to be rescued. - -- @return #nil If there is no CSAR task required. - function TASK_CARGO_DISPATCHER:EvaluateCSAR( CSARUnit ) - - local CSARCargo = CARGO_GROUP:New( CSARUnit, "Pilot", CSARUnit:GetName(), 80, 1500, 10 ) - - local SetCargo = SET_CARGO:New() - SetCargo:AddCargosByName( CSARUnit:GetName() ) - - SetCargo:Flush(self) - - return SetCargo - - end - - - - --- Assigns tasks to the @{Core.Set#SET_GROUP}. - -- @param #TASK_CARGO_DISPATCHER self - -- @param #boolean Silent Announce new task (nil/false) or not (true). - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function TASK_CARGO_DISPATCHER:ManageTasks(Silent) - self:F() - local verbose = Silent and true - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - if Mission:IsIDLE() or Mission:IsENGAGED() then - - local TaskReport = REPORT:New() - - -- Checking the task queue for the dispatcher, and removing any obsolete task! - for TaskIndex, TaskData in pairs( self.Tasks ) do - local Task = TaskData -- Tasking.Task#TASK - if Task:IsStatePlanned() then - -- Here we need to check if the pilot is still existing. --- local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex ) --- if not DetectedItem then --- local TaskText = Task:GetName() --- for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do --- Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) --- end --- Task = self:RemoveTask( TaskIndex ) --- end - end - end - - -- Now that all obsolete tasks are removed, loop through the CSAR pilots. - for CSARName, CSAR in pairs( self.CSAR ) do - - if not CSAR.Task then - -- New CSAR Task - local SetCargo = self:EvaluateCSAR( CSAR.PilotGroup ) - CSAR.Task = TASK_CARGO_CSAR:New( Mission, self.SetGroup, CSARName, SetCargo, CSAR.Briefing ) - CSAR.Task.TaskPrefix = CSAR.TaskPrefix -- We keep the TaskPrefix for further reference! - Mission:AddTask( CSAR.Task ) - TaskReport:Add( CSARName ) - if CSAR.DeployZones then - CSAR.Task:SetDeployZones( CSAR.DeployZones or {} ) - else - CSAR.Task:SetDeployZones( self.DefaultDeployZones or {} ) - end - - -- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher. - function CSAR.Task.OnAfterCargoPickedUp( Task, From, Event, To, TaskUnit, Cargo ) - self:CargoPickedUp( Task, Task.TaskPrefix, TaskUnit, Cargo ) - end - - -- Now broadcast the onafterCargoDeployed event to the Task Cargo Dispatcher. - function CSAR.Task.OnAfterCargoDeployed( Task, From, Event, To, TaskUnit, Cargo, DeployZone ) - self:CargoDeployed( Task, Task.TaskPrefix, TaskUnit, Cargo, DeployZone ) - end - - end - end - - - -- Now that all obsolete tasks are removed, loop through the Transport tasks. - for TransportName, Transport in pairs( self.Transport ) do - - if not Transport.Task then - -- New Transport Task - Transport.Task = TASK_CARGO_TRANSPORT:New( Mission, self.SetGroup, TransportName, Transport.SetCargo, Transport.Briefing ) - Transport.Task.TaskPrefix = Transport.TaskPrefix -- We keep the TaskPrefix for further reference! - Mission:AddTask( Transport.Task ) - TaskReport:Add( TransportName ) - function Transport.Task.OnEnterSuccess( Task, From, Event, To ) - self:Success( Task ) - end - - function Transport.Task.OnEnterCancelled( Task, From, Event, To ) - self:Cancelled( Task ) - end - - function Transport.Task.OnEnterFailed( Task, From, Event, To ) - self:Failed( Task ) - end - - function Transport.Task.OnEnterAborted( Task, From, Event, To ) - self:Aborted( Task ) - end - - -- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher. - function Transport.Task.OnAfterCargoPickedUp( Task, From, Event, To, TaskUnit, Cargo ) - self:CargoPickedUp( Task, Task.TaskPrefix, TaskUnit, Cargo ) - end - - -- Now broadcast the onafterCargoDeployed event to the Task Cargo Dispatcher. - function Transport.Task.OnAfterCargoDeployed( Task, From, Event, To, TaskUnit, Cargo, DeployZone ) - self:CargoDeployed( Task, Task.TaskPrefix, TaskUnit, Cargo, DeployZone ) - end - - end - - if Transport.DeployZones then - Transport.Task:SetDeployZones( Transport.DeployZones or {} ) - else - Transport.Task:SetDeployZones( self.DefaultDeployZones or {} ) - end - - end - - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - local TaskText = TaskReport:Text(", ") - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and not verbose then - Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) - end - end - - end - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua b/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua deleted file mode 100644 index 6293f03fa..000000000 --- a/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua +++ /dev/null @@ -1,363 +0,0 @@ ---- **Tasking** - Models tasks for players to transport cargo. --- --- **Specific features:** --- --- * Creates a task to transport #Cargo.Cargo to and between deployment zones. --- * Derived from the TASK_CARGO class, which is derived from the TASK class. --- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled. --- * Co-operation tasking, so a player joins a group of players executing the same task. --- --- --- **A complete task menu system to allow players to:** --- --- * Join the task, abort the task. --- * Mark the task location on the map. --- * Provide details of the target. --- * Route to the cargo. --- * Route to the deploy zones. --- * Load/Unload cargo. --- * Board/Unboard cargo. --- * Slingload cargo. --- * Display the task briefing. --- --- --- **A complete mission menu system to allow players to:** --- --- * Join a task, abort the task. --- * Display task reports. --- * Display mission statistics. --- * Mark the task locations on the map. --- * Provide details of the targets. --- * Display the mission briefing. --- * Provide status updates as retrieved from the command center. --- * Automatically assign a random task as part of a mission. --- * Manually assign a specific task as part of a mission. --- --- --- **A settings system, using the settings menu:** --- --- * Tweak the duration of the display of messages. --- * Switch between metric and imperial measurement system. --- * Switch between coordinate formats used in messages: BR, BRA, LL DMS, LL DDM, MGRS. --- * Different settings modes for A2G and A2A operations. --- * Various other options. --- --- # 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 --- --- === --- --- Please read through the #Tasking.Task_Cargo process to understand the mechanisms of tasking and cargo tasking and handling. --- --- Enjoy! --- FC --- --- === --- --- @module Tasking.Task_Cargo_Transport --- @image Task_Cargo_Transport.JPG - - -do -- TASK_CARGO_TRANSPORT - - -- @type TASK_CARGO_TRANSPORT - -- @extends Tasking.Task_CARGO#TASK_CARGO - - --- Orchestrates the task for players to transport cargo to or between deployment zones. - -- - -- Transport tasks are suited to govern the process of transporting cargo to specific deployment zones. - -- Typically, this task is executed by helicopter pilots, but it can also be executed by ground forces! - -- - -- === - -- - -- A transport task can be created manually. - -- - -- # 1) Create a transport task manually (code it). - -- - -- Although it is recommended to use the dispatcher, you can create a transport task yourself as a mission designer. - -- It is easy, as it works just like any other task setup. - -- - -- ## 1.1) Create a command center. - -- - -- First you need to create a command center using the Tasking.CommandCenter#COMMANDCENTER.New constructor. - -- - -- local CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) -- Create the CommandCenter. - -- - -- ## 1.2) Create a mission. - -- - -- Tasks work in a mission, which groups these tasks to achieve a joint mission goal. - -- A command center can govern multiple missions. - -- Create a new mission, using the Tasking.Mission#MISSION.New constructor. - -- - -- -- Declare the Mission for the Command Center. - -- local Mission = MISSION - -- :New( CommandCenter, - -- "Overlord", - -- "High", - -- "Transport the cargo to the deploy zones.", - -- coalition.side.RED - -- ) - -- - -- ## 1.3) Create the transport cargo task. - -- - -- So, now that we have a command center and a mission, we now create the transport task. - -- We create the transport task using the #TASK_CARGO_TRANSPORT.New constructor. - -- - -- Because a transport task will not generate the cargo itself, you'll need to create it first. - -- The cargo in this case will be the downed pilot! - -- - -- -- Here we define the "cargo set", which is a collection of cargo objects. - -- -- The cargo set will be the input for the cargo transportation task. - -- -- So a transportation object is handling a cargo set, which is automatically refreshed when new cargo is added/deleted. - -- local CargoSet = SET_CARGO:New():FilterTypes( "Cargo" ):FilterStart() - -- - -- -- Now we add cargo into the battle scene. - -- local PilotGroup = GROUP:FindByName( "Engineers" ) - -- - -- -- CARGO_GROUP can be used to setup cargo with a GROUP object underneath. - -- -- We name this group Engineers. - -- -- Note that the name of the cargo is "Engineers". - -- -- The cargoset "CargoSet" will embed all defined cargo of type "Pilots" (prefix) into its set. - -- local CargoGroup = CARGO_GROUP:New( PilotGroup, "Cargo", "Engineer Team 1", 500 ) - -- - -- What is also needed, is to have a set of @{Wrapper.Group}s defined that contains the clients of the players. - -- - -- -- Allocate the Transport, which are the helicopter to retrieve the pilot, that can be manned by players. - -- local GroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart() - -- - -- Now that we have a CargoSet and a GroupSet, we can now create the TransportTask manually. - -- - -- -- Declare the transport task. - -- local TransportTask = TASK_CARGO_TRANSPORT - -- :New( Mission, - -- GroupSet, - -- "Transport Engineers", - -- CargoSet, - -- "Fly behind enemy lines, and retrieve the downed pilot." - -- ) - -- - -- So you can see, setting up a transport task manually is a lot of work. - -- It is better you use the cargo dispatcher to create transport tasks and it will work as it is intended. - -- By doing this, cargo transport tasking will become a dynamic experience. - -- - -- - -- # 2) Create a task using the Tasking.Task_Cargo_Dispatcher module. - -- - -- Actually, it is better to **GENERATE** these tasks using the Tasking.Task_Cargo_Dispatcher module. - -- Using the dispatcher module, transport tasks can be created easier. - -- - -- Find below an example how to use the TASK_CARGO_DISPATCHER class: - -- - -- - -- -- Find the HQ group. - -- HQ = GROUP:FindByName( "HQ", "Bravo" ) - -- - -- -- Create the command center with the name "Lima". - -- CommandCenter = COMMANDCENTER - -- :New( HQ, "Lima" ) - -- - -- -- Create the mission, for the command center, with the name "Operation Cargo Fun", a "Tactical" mission, with the mission briefing "Transport Cargo", for the BLUE coalition. - -- Mission = MISSION - -- :New( CommandCenter, "Operation Cargo Fun", "Tactical", "Transport Cargo", coalition.side.BLUE ) - -- - -- -- Create the SET of GROUPs containing clients (players) that will transport the cargo. - -- -- These are have a name that start with "Transport" and are of the "blue" coalition. - -- TransportGroups = SET_GROUP:New():FilterCoalitions( "blue" ):FilterPrefixes( "Transport" ):FilterStart() - -- - -- - -- -- Here we create the TASK_CARGO_DISPATCHER object! This is where we assign the dispatcher to generate tasks in the Mission for the TransportGroups. - -- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups ) - -- - -- - -- -- Here we declare the SET of CARGOs called "Workmaterials". - -- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart() - -- - -- -- Here we declare (add) CARGO_GROUP objects of various types, that are filtered and added in the CargoSetworkmaterials cargo set. - -- -- These cargo objects have the type "Workmaterials" which is exactly the type of cargo the CargoSetworkmaterials is filtering on. - -- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 ) - -- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 ) - -- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 ) - -- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 ) - -- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 ) - -- - -- -- And here we create a new WorkplaceTask, using the :AddTransportTask method of the TaskDispatcher. - -- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." ) - -- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) ) - -- - -- # 3) Handle cargo task events. - -- - -- When a player is picking up and deploying cargo using his carrier, events are generated by the tasks. These events can be captured and tailored with your own code. - -- - -- In order to properly capture the events and avoid mistakes using the documentation, it is advised that you execute the following actions: - -- - -- * **Copy / Paste** the code section into your script. - -- * **Change** the "myclass" literal to the task object name you have in your script. - -- * Within the function, you can now **write your own code**! - -- * **IntelliSense** will recognize the type of the variables provided by the function. Note: the From, Event and To variables can be safely ignored, - -- but you need to declare them as they are automatically provided by the event handling system of MOOSE. - -- - -- You can send messages or fire off any other events within the code section. The sky is the limit! - -- - -- - -- ## 3.1) Handle the CargoPickedUp event. - -- - -- Find below an example how to tailor the **CargoPickedUp** event, generated by the WorkplaceTask: - -- - -- function WorkplaceTask:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has picked up cargo.", MESSAGE.Type.Information ):ToAll() - -- - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has picked up a cargo object in the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- --- CargoPickedUp event handler OnAfter for "myclass". - -- -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has picked up the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been picked up. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- function myclass:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- ## 3.2) Handle the CargoDeployed event. - -- - -- Find below an example how to tailor the **CargoDeployed** event, generated by the WorkplaceTask: - -- - -- function WorkplaceTask:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- - -- MESSAGE:NewType( "Unit " .. TaskUnit:GetName().. " has deployed cargo at zone " .. DeployZone:GetName(), MESSAGE.Type.Information ):ToAll() - -- - -- Helos[ math.random(1,#Helos) ]:Spawn() - -- EnemyHelos[ math.random(1,#EnemyHelos) ]:Spawn() - -- end - -- - -- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has deployed a cargo object from the CarrierGroup. - -- You can use this event handler to post messages to players, or provide status updates etc. - -- - -- - -- --- CargoDeployed event handler OnAfter foR "myclass". - -- -- @param #string From A string that contains the "*from state name*" when the event was triggered. - -- -- @param #string Event A string that contains the "*event name*" when the event was triggered. - -- -- @param #string To A string that contains the "*to state name*" when the event was triggered. - -- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has deployed the cargo. - -- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been deployed. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object! - -- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - -- function myclass:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone ) - -- - -- -- Write here your own code. - -- - -- end - -- - -- - -- - -- === - -- - -- @field #TASK_CARGO_TRANSPORT - TASK_CARGO_TRANSPORT = { - ClassName = "TASK_CARGO_TRANSPORT", - } - - --- Instantiates a new TASK_CARGO_TRANSPORT. - -- @param #TASK_CARGO_TRANSPORT self - -- @param Tasking.Mission#MISSION Mission - -- @param Core.Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported. - -- @param #string TaskBriefing The Cargo Task briefing. - -- @return #TASK_CARGO_TRANSPORT self - function TASK_CARGO_TRANSPORT:New( Mission, SetGroup, TaskName, SetCargo, TaskBriefing ) - local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "Transport", TaskBriefing ) ) -- #TASK_CARGO_TRANSPORT - self:F() - - Mission:AddTask( self ) - - local Fsm = self:GetUnitProcess() - - local CargoReport = REPORT:New( "Transport Cargo. The following cargo needs to be transported including initial positions:") - - SetCargo:ForEachCargo( - -- @param Core.Cargo#CARGO Cargo - function( Cargo ) - local CargoType = Cargo:GetType() - local CargoName = Cargo:GetName() - local CargoCoordinate = Cargo:GetCoordinate() - CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) ) - end - ) - - self:SetBriefing( - TaskBriefing or - CargoReport:Text() - ) - - - return self - end - - function TASK_CARGO_TRANSPORT:ReportOrder( ReportGroup ) - - return 0 - end - - - --- - -- @param #TASK_CARGO_TRANSPORT self - -- @return #boolean - function TASK_CARGO_TRANSPORT:IsAllCargoTransported() - - local CargoSet = self:GetCargoSet() - local Set = CargoSet:GetSet() - - local DeployZones = self:GetDeployZones() - - local CargoDeployed = true - - -- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ). - for CargoID, CargoData in pairs( Set ) do - local Cargo = CargoData -- Core.Cargo#CARGO - - self:F( { Cargo = Cargo:GetName(), CargoDeployed = Cargo:IsDeployed() } ) - - if Cargo:IsDeployed() then - --- -- Loop the DeployZones set for the TASK_CARGO_TRANSPORT. --- for DeployZoneID, DeployZone in pairs( DeployZones ) do --- --- -- If all cargo is in one of the deploy zones, then all is good. --- self:T( { Cargo.CargoObject } ) --- if Cargo:IsInZone( DeployZone ) == false then --- CargoDeployed = false --- end --- end - else - CargoDeployed = false - end - end - - self:F( { CargoDeployed = CargoDeployed } ) - - return CargoDeployed - end - - -- @param #TASK_CARGO_TRANSPORT self - function TASK_CARGO_TRANSPORT:onafterGoal( TaskUnit, From, Event, To ) - local CargoSet = self.CargoSet - - if self:IsAllCargoTransported() then - self:Success() - end - - self:__Goal( -10 ) - end - -end - diff --git a/Moose Development/Moose/Tasking/Task_Manager.lua b/Moose Development/Moose/Tasking/Task_Manager.lua deleted file mode 100644 index 127b455ad..000000000 --- a/Moose Development/Moose/Tasking/Task_Manager.lua +++ /dev/null @@ -1,192 +0,0 @@ ---- **Tasking** - This module contains the TASK_MANAGER class and derived classes. --- --- === --- --- 1) @{Tasking.Task_Manager#TASK_MANAGER} class, extends @{Core.Fsm#FSM} --- === --- The @{Tasking.Task_Manager#TASK_MANAGER} class defines the core functions to report tasks to groups. --- Reportings can be done in several manners, and it is up to the derived classes if TASK_MANAGER to model the reporting behaviour. --- --- 1.1) TASK_MANAGER constructor: --- ----------------------------------- --- * @{Tasking.Task_Manager#TASK_MANAGER.New}(): Create a new TASK_MANAGER instance. --- --- 1.2) TASK_MANAGER reporting: --- --------------------------------- --- Derived TASK_MANAGER classes will manage tasks using the method @{Tasking.Task_Manager#TASK_MANAGER.ManageTasks}(). This method implements polymorphic behaviour. --- --- The time interval in seconds of the task management can be changed using the methods @{Tasking.Task_Manager#TASK_MANAGER.SetRefreshTimeInterval}(). --- To control how long a reporting message is displayed, use @{Tasking.Task_Manager#TASK_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{Tasking.Task_Manager#TASK_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. --- --- Task management can be started and stopped using the methods @{Tasking.Task_Manager#TASK_MANAGER.StartTasks}() and @{Tasking.Task_Manager#TASK_MANAGER.StopTasks}() respectively. --- If an ad-hoc report is requested, use the method @{Tasking.Task_Manager#TASK_MANAGER#ManageTasks}(). --- --- The default task management interval is every 60 seconds. --- --- # 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 --- --- === --- --- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module Tasking.Task_Manager --- @image MOOSE.JPG - -do -- TASK_MANAGER - - --- TASK_MANAGER class. - -- @type TASK_MANAGER - -- @field Core.Set#SET_GROUP SetGroup The set of group objects containing players for which tasks are managed. - -- @extends Core.Fsm#FSM - TASK_MANAGER = { - ClassName = "TASK_MANAGER", - SetGroup = nil, - } - - --- TASK\_MANAGER constructor. - -- @param #TASK_MANAGER self - -- @param Core.Set#SET_GROUP SetGroup The set of group objects containing players for which tasks are managed. - -- @return #TASK_MANAGER self - function TASK_MANAGER:New( SetGroup ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New() ) -- #TASK_MANAGER - - self.SetGroup = SetGroup - - self:SetStartState( "Stopped" ) - self:AddTransition( "Stopped", "StartTasks", "Started" ) - - --- StartTasks Handler OnBefore for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnBeforeStartTasks - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- StartTasks Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterStartTasks - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- StartTasks Trigger for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] StartTasks - -- @param #TASK_MANAGER self - - --- StartTasks Asynchronous Trigger for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] __StartTasks - -- @param #TASK_MANAGER self - -- @param #number Delay - - self:AddTransition( "Started", "StopTasks", "Stopped" ) - - --- StopTasks Handler OnBefore for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnBeforeStopTasks - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- StopTasks Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterStopTasks - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- StopTasks Trigger for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] StopTasks - -- @param #TASK_MANAGER self - - --- StopTasks Asynchronous Trigger for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] __StopTasks - -- @param #TASK_MANAGER self - -- @param #number Delay - - self:AddTransition( "Started", "Manage", "Started" ) - - self:AddTransition( "Started", "Success", "Started" ) - - --- Success Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterSuccess - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:AddTransition( "Started", "Failed", "Started" ) - - --- Failed Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterFailed - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:AddTransition( "Started", "Aborted", "Started" ) - - --- Aborted Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterAborted - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:AddTransition( "Started", "Cancelled", "Started" ) - - --- Cancelled Handler OnAfter for TASK_MANAGER - -- @function [parent=#TASK_MANAGER] OnAfterCancelled - -- @param #TASK_MANAGER self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Tasking.Task#TASK Task - - self:SetRefreshTimeInterval( 30 ) - - return self - end - - function TASK_MANAGER:onafterStartTasks( From, Event, To ) - self:Manage() - end - - function TASK_MANAGER:onafterManage( From, Event, To ) - - self:__Manage( -self._RefreshTimeInterval ) - - self:ManageTasks() - end - - --- Set the refresh time interval in seconds when a new task management action needs to be done. - -- @param #TASK_MANAGER self - -- @param #number RefreshTimeInterval The refresh time interval in seconds when a new task management action needs to be done. - -- @return #TASK_MANAGER self - function TASK_MANAGER:SetRefreshTimeInterval( RefreshTimeInterval ) - self:F2() - - self._RefreshTimeInterval = RefreshTimeInterval - end - - - --- Manages the tasks for the @{Core.Set#SET_GROUP}. - -- @param #TASK_MANAGER self - -- @return #TASK_MANAGER self - function TASK_MANAGER:ManageTasks() - - end - -end - diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua deleted file mode 100644 index 1a27696f1..000000000 --- a/Moose Development/Moose/Utilities/STTS.lua +++ /dev/null @@ -1,259 +0,0 @@ ---- **Utilities** - DCS Simple Text-To-Speech (STTS). --- --- --- @module Utilities.STTS --- @image MOOSE.JPG - ---- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world) --- @type STTS --- @field #string DIRECTORY Path of the SRS directory. - ---- Simple Text-To-Speech --- --- Version 0.4 - Compatible with SRS version 1.9.6.0+ --- --- # DCS Modification Required --- --- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitization. --- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)" --- Do this without DCS running to allow mission scripts to use os functions. --- --- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE* --- --- # USAGE: --- --- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialize it --- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission. --- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts. --- --- Example calls: --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2) --- --- Arguments in order are: --- --- * Message to say, make sure not to use a newline (\n) ! --- * Frequency in MHz --- * Modulation - AM/FM --- * Volume - 1.0 max, 0.5 half --- * Name of the transmitter - ATC, RockFM etc --- * Coalition - 0 spectator, 1 red 2 blue --- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed --- * OPTIONAL - Speed -10 to +10 --- * OPTIONAL - Gender male, female or neuter --- * OPTIONAL - Culture - en-US, en-GB etc --- * OPTIONAL - Voice - a specific voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line --- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly --- --- --- ## Example --- --- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB") --- --- ## Example --- --- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB") --- --- Arguments in order are: --- --- * FULL path to the MP3 OR OGG to play --- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations --- * Modulation - AM/FM - to use multiple --- * Volume - 1.0 max, 0.5 half --- * Name of the transmitter - ATC, RockFM etc --- * Coalition - 0 spectator, 1 red 2 blue --- --- ## Example --- --- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only --- --- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0) --- --- @field #STTS -STTS = { - ClassName = "STTS", - DIRECTORY = "", - SRS_PORT = 5002, - GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json", - EXECUTABLE = "DCS-SR-ExternalAudio.exe" -} - ---- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER -STTS.DIRECTORY = "D:/DCS/_SRS" - ---- LOCAL SRS PORT - DEFAULT IS 5002 -STTS.SRS_PORT = 5002 - ---- Google credentials file -STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" - ---- DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING -STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" - ---- Function for UUID. -function STTS.uuid() - local random = math.random - local template = 'yxxx-xxxxxxxxxxxx' - return string.gsub( template, '[xy]', function( c ) - local v = (c == 'x') and random( 0, 0xf ) or random( 8, 0xb ) - return string.format( '%x', v ) - end ) -end - ---- Round a number. --- @param #number x Number. --- @param #number n Precision. -function STTS.round( x, n ) - n = math.pow( 10, n or 0 ) - x = x * n - if x >= 0 then - x = math.floor( x + 0.5 ) - else - x = math.ceil( x - 0.5 ) - end - return x / n -end - ---- Function returns estimated speech time in seconds. --- Assumptions for time calc: 100 Words per min, average of 5 letters for english word so --- --- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second --- --- So length of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: --- --- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min --- --- @param #number length can also be passed as #string --- @param #number speed Defaults to 1.0 --- @param #boolean isGoogle We're using Google TTS -function STTS.getSpeechTime(length,speed,isGoogle) - - local maxRateRatio = 3 - - speed = speed or 1.0 - isGoogle = isGoogle or false - - local speedFactor = 1.0 - if isGoogle then - speedFactor = speed - else - if speed ~= 0 then - speedFactor = math.abs( speed ) * (maxRateRatio - 1) / 10 + 1 - end - if speed < 0 then - speedFactor = 1 / speedFactor - end - end - - local wpm = math.ceil( 100 * speedFactor ) - local cps = math.floor( (wpm * 5) / 60 ) - - if type( length ) == "string" then - length = string.len( length ) - end - - return length/cps --math.ceil(length/cps) -end - ---- Text to speech function. -function STTS.TextToSpeech( message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS ) - if os == nil or io == nil then - env.info( "[DCS-STTS] LUA modules os or io are sanitized. skipping. " ) - return - end - - speed = speed or 1 - gender = gender or "female" - culture = culture or "" - voice = voice or "" - coalition = coalition or "0" - name = name or "ROBOT" - volume = 1 - speed = 1 - - message = message:gsub( "\"", "\\\"" ) - - local cmd = string.format( "start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name ) - - if voice ~= "" then - cmd = cmd .. string.format( " -V \"%s\"", voice ) - else - - if culture ~= "" then - cmd = cmd .. string.format( " -l %s", culture ) - end - - if gender ~= "" then - cmd = cmd .. string.format( " -g %s", gender ) - end - end - - if googleTTS == true then - cmd = cmd .. string.format( " -G \"%s\"", STTS.GOOGLE_CREDENTIALS ) - end - - if speed ~= 1 then - cmd = cmd .. string.format( " -s %s", speed ) - end - - if volume ~= 1.0 then - cmd = cmd .. string.format( " -v %s", volume ) - end - - if point and type( point ) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL( point ) - - lat = STTS.round( lat, 4 ) - lon = STTS.round( lon, 4 ) - alt = math.floor( alt ) - - cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) - end - - cmd = cmd .. string.format( " -t \"%s\"", message ) - - if string.len( cmd ) > 255 then - local filename = os.getenv( 'TMP' ) .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" - local script = io.open( filename, "w+" ) - script:write( cmd .. " && exit" ) - script:close() - cmd = string.format( "\"%s\"", filename ) - timer.scheduleFunction( os.remove, filename, timer.getTime() + 1 ) - end - - if string.len( cmd ) > 255 then - env.info( "[DCS-STTS] - cmd string too long" ) - env.info( "[DCS-STTS] TextToSpeech Command :\n" .. cmd .. "\n" ) - end - os.execute( cmd ) - - return STTS.getSpeechTime( message, speed, googleTTS ) -end - ---- Play mp3 function. --- @param #string pathToMP3 Path to the sound file. --- @param #string freqs Frequencies, e.g. "305, 256". --- @param #string modulations Modulations, e.g. "AM, FM". --- @param #string volume Volume, e.g. "0.5". -function STTS.PlayMP3( pathToMP3, freqs, modulations, volume, name, coalition, point ) - - local cmd = string.format( "start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1" ) - - if point and type( point ) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL( point ) - - lat = STTS.round( lat, 4 ) - lon = STTS.round( lon, 4 ) - alt = math.floor( alt ) - - cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) - end - - env.info( "[DCS-STTS] MP3/OGG Command :\n" .. cmd .. "\n" ) - os.execute( cmd ) - -end diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index ab22084a2..5b8cd0b3a 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -2,7 +2,6 @@ Utilities/Enums.lua Utilities/Utils.lua Utilities/Enums.lua Utilities/Profiler.lua -Utilities/STTS.lua Utilities/FiFo.lua Utilities/Socket.lua From a0f58a95db5f50f83539d6a96f3fd29af9620289 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 30 Nov 2024 12:55:50 +0100 Subject: [PATCH 078/349] xx --- Moose Development/Moose/Modules.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 91cfd2830..7bc8903a2 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -50,12 +50,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Net.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Storage.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/DynamicCargo.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/Cargo.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoUnit.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoSlingload.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoCrate.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoGroup.lua' ) - __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Scoring.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/CleanUp.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Movement.lua' ) From 116527abfecf6f0eba6f0a135f77b1f9eba2ee53 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 30 Nov 2024 13:09:58 +0100 Subject: [PATCH 079/349] xx --- Moose Development/Moose/Core/Fsm.lua | 18 ++++-------------- Moose Development/Moose/Core/Point.lua | 8 ++++---- .../Moose/Wrapper/Controllable.lua | 2 -- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index e3fbd4f6c..d85f7c4c2 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -45,12 +45,6 @@ -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. --- Each of these FSM implementation classes start either with: --- --- * an acronym **AI\_**, which indicates a FSM implementation directing **AI controlled** @{Wrapper.Group#GROUP} and/or @{Wrapper.Unit#UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. --- * an acronym **TASK\_**, which indicates a FSM implementation executing a @{Tasking.Task#TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. --- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{Tasking.Task#TASK}, seated in a @{Wrapper.Client#CLIENT} (slot) or a @{Wrapper.Unit#UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. --- -- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections. -- -- ##__Disclaimer:__ @@ -61,7 +55,6 @@ -- -- The following derived classes are available in the MOOSE framework, that implement a specialized form of a FSM: -- --- * @{#FSM_TASK}: Models Finite State Machines for @{Tasking.Task}s. -- * @{#FSM_PROCESS}: Models Finite State Machines for @{Tasking.Task} actions, which control @{Wrapper.Client}s. -- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Wrapper.Client}s. -- * @{#FSM_SET}: Models Finite State Machines for @{Core.Set}s. Note that these FSMs control multiple objects!!! So State concerns here @@ -78,7 +71,8 @@ -- @image Core_Finite_State_Machine.JPG do -- FSM - + + --- -- @type FSM -- @field #string ClassName Name of the class. -- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler. @@ -117,11 +111,6 @@ do -- FSM -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. - -- Each of these FSM implementation classes start either with: - -- - -- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{Wrapper.Group#GROUP} and/or @{Wrapper.Unit#UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. - -- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{Tasking.Task#TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. - -- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{Tasking.Task#TASK}, seated in a @{Wrapper.Client#CLIENT} (slot) or a @{Wrapper.Unit#UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. -- -- ![Transition Rules and Transition Handlers and Event Triggers](..\Presentations\FSM\Dia3.JPG) -- @@ -1081,7 +1070,8 @@ do -- FSM_CONTROLLABLE end do -- FSM_PROCESS - + + --- -- @type FSM_PROCESS -- @field Tasking.Task#TASK Task -- @extends Core.Fsm#FSM_CONTROLLABLE diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 169140b4d..052b3987d 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -3313,16 +3313,16 @@ do -- COORDINATE -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable to retrieve the settings from, otherwise the default settings will be chosen. -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. - -- @param Tasking.Task#TASK Task The task for which coordinates need to be calculated. -- @return #string The coordinate Text in the configured coordinate system. - function COORDINATE:ToString( Controllable, Settings, Task ) + function COORDINATE:ToString( Controllable, Settings ) -- self:E( { Controllable = Controllable and Controllable:GetName() } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS local ModeA2A = nil - + + --[[ if Task then if Task:IsInstanceOf( TASK_A2A ) then ModeA2A = true @@ -3339,7 +3339,7 @@ do -- COORDINATE end end end - + --]] if ModeA2A == nil then local IsAir = Controllable and ( Controllable:IsAirPlane() or Controllable:IsHelicopter() ) or false diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 362f6eb03..fbe6a31e2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1777,8 +1777,6 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, return DCSTask end --- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES - --- (AIR) Engaging targets of defined types. -- @param #CONTROLLABLE self -- @param DCS#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. From bc606484585b6cb9bfeed702f29ffa09d045df87 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 30 Nov 2024 16:19:21 +0100 Subject: [PATCH 080/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 747c4bda2..289c287f6 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1396,7 +1396,7 @@ do -- @return #string type Long, medium or short range -- @return #number blind "blind" spot function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma,chm) - self:T(self.lid.."_GetSAMRangeFromUnits") + self:T(self.lid.."_GetSAMDataFromUnits") local found = false local range = self.checkradius local height = 3000 @@ -1449,7 +1449,7 @@ do -- @return #string type Long, medium or short range -- @return #number blind "blind" spot function MANTIS:_GetSAMRange(grpname) - self:T(self.lid.."_GetSAMRange") + self:I(self.lid.."_GetSAMRange for "..tostring(grpname)) local range = self.checkradius local height = 3000 local type = MANTIS.SamType.MEDIUM @@ -1466,9 +1466,9 @@ do elseif string.find(grpname,"CHM",1,true) then CHMod = true end - if self.automode then + --if self.automode then for idx,entry in pairs(self.SamData) do - --self:I("ID = " .. idx) + self:T("ID = " .. idx) if string.find(grpname,idx,1,true) then local _entry = entry -- #MANTIS.SamData type = _entry.Type @@ -1476,14 +1476,14 @@ do range = _entry.Range * 1000 * radiusscale -- max firing range height = _entry.Height * 1000 -- max firing height blind = _entry.Blindspot - --self:I("Matching Groupname = " .. grpname .. " Range= " .. range) + self:T("Matching Groupname = " .. grpname .. " Range= " .. range) found = true break end end - end + --end -- secondary filter if not found - if (not found and self.automode) or HDSmod or SMAMod or CHMod then + if (not found) or HDSmod or SMAMod or CHMod then range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod) elseif not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) From 7c06c615d0ea98e7b0bdc03c4cbb588e7a5c158f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 Dec 2024 10:56:54 +0100 Subject: [PATCH 081/349] xx --- Moose Development/Moose/Modules_local.lua | 8 -------- Moose Development/Moose/Ops/Auftrag.lua | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index b6e3d1b74..a080a6942 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -48,12 +48,6 @@ __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' ) @@ -122,6 +116,4 @@ __Moose.Include( 'Sound\\RadioQueue.lua' ) __Moose.Include( 'Sound\\RadioSpeech.lua' ) __Moose.Include( 'Sound\\SRS.lua' ) -__Moose.Include( 'Tasking\\Task_Capture_Dispatcher.lua' ) - __Moose.Include( 'Globals.lua' ) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 1b3b11a74..cbc36878b 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -3481,7 +3481,7 @@ end --- Set Rules of Engagement (ROE) for this mission. -- @param #AUFTRAG self --- @param #string roe Mission ROE. +-- @param #number roe Mission ROE, e.g. `ENUMS.ROE.ReturnFire` (whiche equals 3) -- @return #AUFTRAG self function AUFTRAG:SetROE(roe) @@ -3493,7 +3493,7 @@ end --- Set Reaction on Threat (ROT) for this mission. -- @param #AUFTRAG self --- @param #string rot Mission ROT. +-- @param #number rot Mission ROT, e.g. `ENUMS.ROT.NoReaction` (whiche equals 0) -- @return #AUFTRAG self function AUFTRAG:SetROT(rot) From 855ea764b1d2b4f2d8ace9d7728487cbb8184243 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 6 Dec 2024 14:06:56 +0100 Subject: [PATCH 082/349] #SET_OPSGROUP - fix for teleport/respawn --- Moose Development/Moose/Core/Set.lua | 31 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index eeb7baf0e..d9abf666a 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -7936,22 +7936,31 @@ do -- SET_OPSGROUP --- Handles the OnBirth event for the Set. -- @param #SET_OPSGROUP self -- @param Core.Event#EVENTDATA Event Event data. - function SET_OPSGROUP:_EventOnBirth( Event ) +function SET_OPSGROUP:_EventOnBirth(Event) --self:F3( { Event } ) if Event.IniDCSUnit and Event.IniDCSGroup then - local DCSgroup=Event.IniDCSGroup --DCS#Group + local DCSgroup = Event.IniDCSGroup --DCS#Group - if DCSgroup:getInitialSize() == DCSgroup:getSize() then -- This seems to be not a good check as even for the first birth event, getSize returns the total number of units in the group. - - local groupname, group = self:AddInDatabase( Event ) - - if group and group:CountAliveUnits()==DCSgroup:getInitialSize() then - if group and self:IsIncludeObject( group ) then - self:Add( groupname, group ) - end + -- group:CountAliveUnits() alternative as this fails for Respawn/Teleport + local CountAliveActive = 0 + for index, data in pairs(DCSgroup:getUnits()) do + if data:isExist() and data:isActive() then + CountAliveActive = CountAliveActive + 1 + end + end + + if DCSgroup:getInitialSize() == DCSgroup:getSize() then + + local groupname, group = self:AddInDatabase(Event) + + -- group:CountAliveUnits() alternative + if group and CountAliveActive == DCSgroup:getInitialSize() then + if group and self:IsIncludeObject(group) then + self:Add(groupname, group) + end + end end - end end end From 426297812ed4eeed35747d3126e316102d3c0221 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 8 Dec 2024 18:56:48 +0100 Subject: [PATCH 083/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 8f130c255..dc402157d 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -252,7 +252,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.15" +EASYGCICAP.version="0.1.16" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -269,7 +269,7 @@ EASYGCICAP.version="0.1.15" -- @param #string Alias A Name for this GCICAP -- @param #string AirbaseName Name of the Home Airbase -- @param #string Coalition Coalition, e.g. "blue" or "red" --- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR" +-- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR", can be handed in as table of names, e.g.{"EWR","Radar","SAM"} -- @return #EASYGCICAP self function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) -- Inherit everything from FSM class. @@ -280,7 +280,8 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.coalitionname = string.lower(Coalition) or "blue" self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED self.wings = {} - self.EWRName = EWRName or self.coalitionname.." EWR" + if type(EWRName) == "string" then EWRName = {EWRName} end + self.EWRName = EWRName --or self.coalitionname.." EWR" --self.CapZoneName = CapZoneName self.airbasename = AirbaseName self.airbase = AIRBASE:FindByName(self.airbasename) @@ -1291,11 +1292,11 @@ function EASYGCICAP:_StartIntel() self:T(self.lid.."_StartIntel") -- Border GCI Detection local BlueAir_DetectionSetGroup = SET_GROUP:New() - BlueAir_DetectionSetGroup:FilterPrefixes( { self.EWRName } ) + BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName ) BlueAir_DetectionSetGroup:FilterStart() -- Intel type detection - local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.EWRName) + local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias) BlueIntel:SetClusterAnalysis(true,false,false) BlueIntel:SetForgetTime(300) BlueIntel:SetAcceptZones(self.GoZoneSet) From eca130b3af00d31be9d01f977221a88c3150dfd6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 10 Dec 2024 12:12:56 +0100 Subject: [PATCH 084/349] #AUFTRAG/OPS allow extra ingress coord for air groups --- Moose Development/Moose/Ops/Auftrag.lua | 36 ++++++++++++++++++++++++ Moose Development/Moose/Ops/OpsGroup.lua | 9 +++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index d08bb1eab..996e76df2 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -162,6 +162,7 @@ -- @field #number missionRange Mission range in meters. Used by LEGION classes (AIRWING, BRIGADE, ...). -- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate. -- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate. +-- @field Core.Point#COORDINATE missionIngressCoord Mission Ingress waypoint coordinate. -- @field #number missionWaypointRadius Random radius in meters. -- @field #boolean legionReturn If `true`, assets return to their legion (default). If `false`, they will stay alive. -- @@ -4657,6 +4658,15 @@ function AUFTRAG:SetGroupWaypointCoordinate(opsgroup, coordinate) return self end +--- [Air] Set mission (ingress) waypoint coordinate for FLIGHT group. +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE coordinate Waypoint Coordinate. +-- @return #AUFTRAG self +function AUFTRAG:SetIngressCoordinate(coordinate) + self.missionIngressCoord = coordinate + return self +end + --- Get mission (ingress) waypoint coordinate of OPS group -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group. @@ -5755,6 +5765,25 @@ function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) end end +--- [Air] Set the mission ingress coordinate. This is the coordinate where the assigned group will fly before the actual mission coordinate. +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE Coordinate Egrees coordinate. +-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @return #AUFTRAG self +function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude) + + -- Obviously a zone was passed. We get the coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + + self.missionIngressCoordgressCoord=Coordinate + + if Altitude then + self.missionIngressCoordgressCoord.y=UTILS.FeetToMeters(Altitude) + end +end + --- Get the mission egress coordinate if this was defined. -- @param #AUFTRAG self -- @return Core.Point#COORDINATE Coordinate Coordinate or nil. @@ -5762,6 +5791,13 @@ function AUFTRAG:GetMissionEgressCoord() return self.missionEgressCoord end +--- Get the mission ingress coordinate if this was defined. +-- @param #AUFTRAG self +-- @return Core.Point#COORDINATE Coordinate Coordinate or nil. +function AUFTRAG:GetMissionIngressCoord() + return self.missionIngressCoord +end + --- Get coordinate which was set as mission waypoint coordinate. -- @param #AUFTRAG self -- @return Core.Point#COORDINATE Coordinate where the mission is executed or `#nil`. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 344a2f975..9f5a4ce41 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6118,7 +6118,14 @@ function OPSGROUP:RouteToMission(mission, delay) -- Add mission execution (ingress) waypoint. local waypoint=nil --#OPSGROUP.Waypoint if self:IsFlightgroup() then - + + local ingresscoord = mission:GetMissionIngressCoord() + + if ingresscoord then + waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, SpeedToMission, uid, UTILS.MetersToFeet(ingresscoord.y or self.altitudeCruise), false) + uid=waypoint.uid + end + waypoint=FLIGHTGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) elseif self:IsArmygroup() then From 270eeac08441b6b9e2f4aea46e05d6b259b1b772 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:30:59 +0100 Subject: [PATCH 085/349] Update Auftrag.lua Ingress small typo fic --- Moose Development/Moose/Ops/Auftrag.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 996e76df2..c51c2cd3c 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -5777,10 +5777,10 @@ function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude) Coordinate=Coordinate:GetCoordinate() end - self.missionIngressCoordgressCoord=Coordinate + self.missionIngressCoord=Coordinate if Altitude then - self.missionIngressCoordgressCoord.y=UTILS.FeetToMeters(Altitude) + self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude) end end From 4ae5e4ddd0486f41d9400a4823a717eebec880b1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 11 Dec 2024 13:41:44 +0100 Subject: [PATCH 086/349] xx --- Moose Development/Moose/Ops/Auftrag.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 996e76df2..d09d9c95f 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -5767,7 +5767,7 @@ end --- [Air] Set the mission ingress coordinate. This is the coordinate where the assigned group will fly before the actual mission coordinate. -- @param #AUFTRAG self --- @param Core.Point#COORDINATE Coordinate Egrees coordinate. +-- @param Core.Point#COORDINATE Coordinate Ingrees coordinate. -- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. -- @return #AUFTRAG self function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude) From 3a3a3baf934a745f919f7f10b32adf40136da78e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 11 Dec 2024 13:53:30 +0100 Subject: [PATCH 087/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 595aa7041..860e2fe2e 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -439,27 +439,50 @@ MANTIS.SamDataSMA = { -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) MANTIS.SamDataCH = { - -- units from CH (Military Assets by Currenthill) - -- https://www.currenthill.com/ - -- group name MUST contain CHM to ID launcher type correctly! - ["2S38 CH"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" }, - ["PantsirS1 CH"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, - ["PantsirS2 CH"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, - ["PGL-625 CH"] = { Range=10, Blindspot=0.5, Height=5, Type="Short", Radar="PGL_625" }, - ["HQ-17A CH"] = { Range=20, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, - ["M903PAC2 CH"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, - ["M903PAC3 CH"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, - ["TorM2 CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, - ["TorM2K CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" }, - ["TorM2M CH"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, - ["NASAMS3-AMRAAMER CH"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, - ["NASAMS3-AIM9X2 CH"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, - ["C-RAM CH"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_Centurion_C_RAM" }, - ["PGZ-09 CH"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" }, - ["S350-9M100 CH"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, - ["S350-9M96D CH"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" }, - ["LAV-AD CH"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_LAVAD" }, - ["HQ-22 CH"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" }, + -- units from CH (Military Assets by Currenthill) + -- https://www.currenthill.com/ + -- group name MUST contain CHM to ID launcher type correctly! + ["2S38 CHM"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" }, + ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, + ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, + ["PGL-625 CHM"] = { Range=10, Blindspot=0.5, Height=5, Type="Short", Radar="PGL_625" }, + ["HQ-17A CHM"] = { Range=20, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, + ["M903PAC2 CHM"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, + ["M903PAC3 CHM"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, + ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, + ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" }, + ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, + ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, + ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, + ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_Centurion_C_RAM" }, + ["PGZ-09 CHM"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" }, + ["S350-9M100 CHM"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, + ["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" }, + ["LAV-AD CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_LAVAD" }, + ["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" }, + ["PGZ-95 CHM"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_PGZ95" }, + ["LD-3000 CHM"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="CH_LD3000_stationary" }, + ["LD-3000M CHM"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="CH_LD3000" }, + ["FlaRakRad CHM"] = { Range=8, Blindspot=1.5, Height=6, Type="Short", Radar="HQ17A" }, + ["IRIS-T SLM CHM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, + ["M903PAC2KAT1 CHM"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="CH_MIM104_M903_PAC2_KAT1" }, + ["Skynex CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Short", Radar="CH_SkynexHX" }, + ["Skyshield CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Short", Radar="CH_Skyshield_Gun" }, + ["WieselOzelot CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" }, + ["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" }, + ["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" }, + ["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" }, + ["Stormer CHM"] = { Range=7.5, Blindspot=0.3, Height=7, Type="Short", Radar="CH_StormerHVM" }, + ["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" }, + ["USInfantryFIM92K CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_USInfantry_FIM92" }, + ["RBS98M CHM"] = { Range=20, Blindspot=0, Height=8, Type="Short", Radar="RBS-98" }, + ["RBS70 CHM"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-70" }, + ["RBS90 CHM"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-90" }, + ["RBS103A CHM"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103A" }, + ["RBS103B CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" }, + ["RBS103AM CHM"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, + ["RBS103BM CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" }, + ["Lvkv9040M CHM"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" }, } ----------------------------------------------------------------------- @@ -640,7 +663,7 @@ do -- TODO Version -- @field #string version - self.version="0.8.18" + self.version="0.8.19" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- From c80f128b0f18f76598d62256cf862d793cf3ec09 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 11 Dec 2024 14:17:14 +0100 Subject: [PATCH 088/349] #CTLD added --- Moose Development/Moose/Ops/CTLD.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 151325c9c..1bb03c121 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Oct 2024 +-- Last Update Dec 2024 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -838,6 +838,7 @@ do -- my_ctld.TroopUnloadDistHover = 1.5 -- If grounded, spawn dropped troops this far away in meters from the helo -- my_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules -- my_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook +-- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook -- -- ## 2.1 CH-47 Chinook support -- @@ -1237,6 +1238,7 @@ CTLD = { TroopUnloadDistGround = 5, TroopUnloadDistGroundHerc = 25, TroopUnloadDistGroundHook = 15, + TroopUnloadDistHoverHook = 5, TroopUnloadDistHover = 1.5, UserSetGroup = nil, } @@ -1343,7 +1345,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.18" +CTLD.version="1.1.19" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3504,7 +3506,12 @@ function CTLD:_UnloadTroops(Group, Unit) if IsHerc or IsHook then Angle = (heading+180)%360 end local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround if IsHerc then offset = self.TroopUnloadDistGroundHerc or 25 end - if IsHook then offset = self.TroopUnloadDistGroundHook or 15 end + if IsHook then + offset = self.TroopUnloadDistGroundHook or 15 + if self.TroopUnloadDistHoverHook then + offset = self.TroopUnloadDistHoverHook or 5 + end + end randomcoord:Translate(offset,Angle,nil,true) end local tempcount = 0 From f1445cdbdd6bc05f28f12e95efcf66331657a614 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 11 Dec 2024 14:36:54 +0100 Subject: [PATCH 089/349] # --- Moose Development/Moose/Ops/OpsGroup.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2f6310a8a..87f56e3ff 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4169,7 +4169,7 @@ function OPSGROUP:onbeforeTaskExecute(From, Event, To, Task) if self:IsWaiting() then -- Group is already waiting else - -- Wait indefinately. + -- Wait indefinitely. local alt=Mission.missionAltitude and UTILS.MetersToFeet(Mission.missionAltitude) or nil self:Wait(nil, alt) end @@ -6121,7 +6121,7 @@ function OPSGROUP:RouteToMission(mission, delay) local ingresscoord = mission:GetMissionIngressCoord() - if ingresscoord then + if ingresscoord and not self:IsWaiting() then waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, SpeedToMission, uid, UTILS.MetersToFeet(ingresscoord.y or self.altitudeCruise), false) uid=waypoint.uid end From 3805ab226b33620075d869b8cd75dc930c561b8e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 11 Dec 2024 16:41:22 +0100 Subject: [PATCH 090/349] #AUFTRAG Better use of Ingress Coordinate --- Moose Development/Moose/Ops/Auftrag.lua | 8 +++++++- Moose Development/Moose/Ops/OpsGroup.lua | 9 +++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f49ec5824..c53eaf0db 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1820,7 +1820,7 @@ end --- **[AIR]** Create a BOMBRUNWAY mission. -- @param #AUFTRAG self --- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these to not have a runway. +-- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these do not have a runway. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. -- @return #AUFTRAG self function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) @@ -5832,6 +5832,12 @@ function AUFTRAG:GetMissionWaypointCoord(group, randomradius, surfacetypes) end return coord end + + -- Check if a coord has been explicitly set. + if self.missionIngressCoord then + local coord=self.missionIngressCoord + return coord + end -- Create waypoint coordinate half way between us and the target. local waypointcoord=COORDINATE:New(0,0,0) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 87f56e3ff..3fd37f26d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1041,7 +1041,7 @@ function OPSGROUP:SetReturnToLegion(Switch) else self.legionReturn=true end - self:T(self.lid..string.format("Setting ReturnToLetion=%s", tostring(self.legionReturn))) + self:T(self.lid..string.format("Setting ReturnToLegion=%s", tostring(self.legionReturn))) return self end @@ -6119,13 +6119,14 @@ function OPSGROUP:RouteToMission(mission, delay) local waypoint=nil --#OPSGROUP.Waypoint if self:IsFlightgroup() then + local ingresscoord = mission:GetMissionIngressCoord() - if ingresscoord and not self:IsWaiting() then - waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, SpeedToMission, uid, UTILS.MetersToFeet(ingresscoord.y or self.altitudeCruise), false) + if ingresscoord and mission:IsReadyToPush() then + waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, SpeedToMission, uid, UTILS.MetersToFeet(self.altitudeCruise), false) uid=waypoint.uid end - + waypoint=FLIGHTGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) elseif self:IsArmygroup() then From bb43a0e03ce4824d3fb146ae15de69122b4bd8c9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 12 Dec 2024 13:07:50 +0100 Subject: [PATCH 091/349] #AUFTRAG #OPSGROUP - Flightgroup handling for ingress and holding coord --- Moose Development/Moose/Ops/Auftrag.lua | 53 +++++++++++++++++++++--- Moose Development/Moose/Ops/OpsGroup.lua | 22 ++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index c53eaf0db..93b9b27ef 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -4664,6 +4664,7 @@ end -- @return #AUFTRAG self function AUFTRAG:SetIngressCoordinate(coordinate) self.missionIngressCoord = coordinate + self.missionIngressCoordAlt = UTILS.MetersToFeet(coordinate.y) or 10000 return self end @@ -5722,7 +5723,7 @@ function AUFTRAG:GetMissionTypesText(MissionTypes) return text end ---- Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`. +--- [NON-AIR] Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed. -- @return #AUFTRAG self @@ -5762,6 +5763,7 @@ function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) if Altitude then self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) + self.missionEgressCoordAlt = UTILS.FeetToMeters(Altitude) end end @@ -5781,6 +5783,29 @@ function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude) if Altitude then self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude) + self.missionIngressCoordAlt = UTILS.FeetToMeters(Altitude or 10000) + end +end + +--- [Air] Set the mission holding coordinate. This is the coordinate where the assigned group will fly before the actual mission execution starts. Do not forget to add a push condition, too! +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE Coordinate Holding coordinate. +-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @param #number Duration (Optional) Duration in seconds on how long to hold, defaults to 15 minutes. Mission continues if either a push condition is met or the time is up. +-- @return #AUFTRAG self +function AUFTRAG:SetMissionHoldingCoord(Coordinate, Altitude, Duration) + + -- Obviously a zone was passed. We get the coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + + self.missionHoldingCoord=Coordinate + self.missionHoldingDuration=Duration or 900 + + if Altitude then + self.missionHoldingCoord.y=UTILS.FeetToMeters(Altitude) + self.missionHoldingCoordAlt = UTILS.FeetToMeters(Altitude or 10000) end end @@ -5798,6 +5823,13 @@ function AUFTRAG:GetMissionIngressCoord() return self.missionIngressCoord end +--- Get the mission holding coordinate if this was defined. +-- @param #AUFTRAG self +-- @return Core.Point#COORDINATE Coordinate Coordinate or nil. +function AUFTRAG:GetMissionHoldingCoord() + return self.missionHoldingCoord +end + --- Get coordinate which was set as mission waypoint coordinate. -- @param #AUFTRAG self -- @return Core.Point#COORDINATE Coordinate where the mission is executed or `#nil`. @@ -5833,15 +5865,26 @@ function AUFTRAG:GetMissionWaypointCoord(group, randomradius, surfacetypes) return coord end - -- Check if a coord has been explicitly set. + local coord=group:GetCoordinate() + + -- Check if an ingress or holding coord has been explicitly set. + if self.missionHoldingCoord then + coord=self.missionHoldingCoord + if self.missionHoldingCoorddAlt then + coord:SetAltitude(self.missionHoldingCoordAlt, true) + end + end + if self.missionIngressCoord then - local coord=self.missionIngressCoord - return coord + coord=self.missionIngressCoord + if self.missionIngressCoordAlt then + coord:SetAltitude(self.missionIngressCoordAlt, true) + end end -- Create waypoint coordinate half way between us and the target. local waypointcoord=COORDINATE:New(0,0,0) - local coord=group:GetCoordinate() + if coord then waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction) else diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3fd37f26d..96adc32e7 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6121,9 +6121,25 @@ function OPSGROUP:RouteToMission(mission, delay) local ingresscoord = mission:GetMissionIngressCoord() + local holdingcoord = mission:GetMissionHoldingCoord() - if ingresscoord and mission:IsReadyToPush() then - waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, SpeedToMission, uid, UTILS.MetersToFeet(self.altitudeCruise), false) + if holdingcoord then + waypoint=FLIGHTGROUP.AddWaypoint(self, holdingcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionHoldingCoordAlt or self.altitudeCruise), false) + uid=waypoint.uid + -- Orbit until flaghold=1 (true) but max 5 min + self.flaghold:Set(0) + local TaskOrbit = self.group:TaskOrbit(holdingcoord, mission.missionHoldingCoordAlt) + local TaskStop = self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, mission.missionHoldingDuration or 900) + local TaskCntr = self.group:TaskControlled(TaskOrbit, TaskStop) + local TaskOver = self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) + local DCSTasks=self.group:TaskCombo({TaskCntr, TaskOver}) + -- Add waypoint task. UpdateRoute is called inside. + local waypointtask=self:AddTaskWaypoint(DCSTasks, waypoint, "Holding") + waypointtask.ismission=false + end + + if ingresscoord then + waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionIngressCoordAlt or self.altitudeCruise), false) uid=waypoint.uid end @@ -6165,7 +6181,7 @@ function OPSGROUP:RouteToMission(mission, delay) if egresscoord then local Ewaypoint=nil --#OPSGROUP.Waypoint if self:IsFlightgroup() then - Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) + Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionEgressCoordAlt or self.altitudeCruise), false) elseif self:IsArmygroup() then Ewaypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, mission.optionFormation, false) elseif self:IsNavygroup() then From 465f8a5cc96ff39a9a575be065901be62f415f74 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 13 Dec 2024 12:05:18 +0100 Subject: [PATCH 092/349] xx --- Moose Development/Moose/Ops/FlightGroup.lua | 19 +++++++++++++++++++ Moose Development/Moose/Ops/OpsGroup.lua | 1 + Moose Development/Moose/Utilities/Utils.lua | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c265e11d2..1d77397e0 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1277,6 +1277,25 @@ function FLIGHTGROUP:Status() end end + --- check if we need to end holding + --self:T(self.lid.."Checking if we are holding at a holding point...") + if mission and mission.missionHoldingCoord and self.isHoldingAtHoldingPoint == true then + self:T(self.lid.."...yes") + if mission:IsReadyToPush() then + --self:T(self.lid.."Ready to push -> YES") + -- move flag to 1 + self.flaghold:Set(1) + -- Not waiting any more. + self.Twaiting=nil + self.dTwait=nil + self.isHoldingAtHoldingPoint = false + --else + --self:T(self.lid.."Ready to push -> NO!") + end + --else + --self:T(self.lid.."...no") + end + -- If mission, check if DCS task needs to be updated. if mission and mission.updateDCSTask then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 6b6425306..ec4bf82b9 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6136,6 +6136,7 @@ function OPSGROUP:RouteToMission(mission, delay) -- Add waypoint task. UpdateRoute is called inside. local waypointtask=self:AddTaskWaypoint(DCSTasks, waypoint, "Holding") waypointtask.ismission=false + self.isHoldingAtHoldingPoint = true end if ingresscoord then diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index fbcb6ba60..343609c9e 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2528,7 +2528,7 @@ end --- Function to save an object to a file -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. Existing file will be overwritten. --- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line. +-- @param #string Data The data structure to save. This will be e.g. a string of text lines with an \\n at the end of each line. -- @return #boolean outcome True if saving is possible, else false. function UTILS.SaveToFile(Path,Filename,Data) -- Thanks to @FunkyFranky From 87a94a72a67cadd0cc281c5f510abc8212ae0069 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 15 Dec 2024 13:33:18 +0100 Subject: [PATCH 093/349] #GROUP added `Teleport(Coordinate)` function leveraging `Respawn()`. Now also translate routes if there are any. --- Moose Development/Moose/Wrapper/Group.lua | 60 +++++++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 7be201af0..3760a84fc 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1989,15 +1989,11 @@ end -- - @{#GROUP.InitHeight}: Set the height for the units in meters for the respawned group. (This is applicable for air units). -- - @{#GROUP.InitRandomizeHeading}: Randomize the headings for the units within the respawned group. -- - @{#GROUP.InitZone}: Set the respawn @{Core.Zone} for the respawned group. --- - @{#GROUP.InitRandomizeZones}: Randomize the respawn @{Core.Zone} between one of the @{Core.Zone}s given for the respawned group. -- - @{#GROUP.InitRandomizePositionZone}: Randomize the positions of the units of the respawned group within the @{Core.Zone}. -- - @{#GROUP.InitRandomizePositionRadius}: Randomize the positions of the units of the respawned group in a circle band. --- - @{#GROUP.InitRandomizeTemplates}: Randomize the Template for the respawned group. --- -- -- Notes: -- --- - When InitZone or InitRandomizeZones is not used, the position of the respawned group will be its current position. -- - The current alive group will always be destroyed and respawned using the template definition. -- -- @param Wrapper.Group#GROUP self @@ -2019,10 +2015,24 @@ function GROUP:Respawn( Template, Reset ) end return h end + + local function TransFormRoute(Template,OldPos,NewPos) + if Template.route and Template.route.points then + for _,_point in ipairs(Template.route.points) do + --self:I(string.format("Point x = %f Point y = %f",_point.x,_point.y)) + _point.x = _point.x - OldPos.x + NewPos.x + _point.y = _point.y - OldPos.y + NewPos.y + --self:I(string.format("Point x = %f Point y = %f",_point.x,_point.y)) + end + end + return Template + end -- First check if group is alive. if self:IsAlive() then - + + local OldPos = self:GetVec2() + -- Respawn zone. local Zone = self.InitRespawnZone -- Core.Zone#ZONE @@ -2035,6 +2045,8 @@ function GROUP:Respawn( Template, Reset ) -- X, Y Template.x = Vec3.x Template.y = Vec3.z + + local NewPos = { x = Vec3.x, y = Vec3.z } --Template.x = nil --Template.y = nil @@ -2089,11 +2101,13 @@ function GROUP:Respawn( Template, Reset ) -- Set heading. Template.units[UnitID].heading = _Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading()) Template.units[UnitID].psi = -Template.units[UnitID].heading - + -- Debug. --self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) end end + + Template = TransFormRoute(Template,OldPos,NewPos) elseif Reset==false then -- Reset=false or nil @@ -2132,11 +2146,13 @@ function GROUP:Respawn( Template, Reset ) -- Heading Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading - + -- Debug. --self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) end - + + Template = TransFormRoute(Template,OldPos,NewPos) + else local units=self:GetUnits() @@ -2185,10 +2201,11 @@ function GROUP:Respawn( Template, Reset ) -- Destroy old group. Dont trigger any dead/crash events since this is a respawn. self:Destroy(false) - --self:T({Template=Template}) + --UTILS.PrintTableToLog(Template) -- Spawn new group. - _DATABASE:Spawn(Template) + self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template) + --_DATABASE:Spawn(Template) -- Reset events. self:ResetEvents() @@ -2196,6 +2213,29 @@ function GROUP:Respawn( Template, Reset ) return self end +--- Respawn the @{Wrapper.Group} at a @{Core.Point#COORDINATE}. +-- The method will setup the new group template according the Init(Respawn) settings provided for the group. +-- These settings can be provided by calling the relevant Init...() methods of the Group prior. +-- +-- - @{#GROUP.InitHeading}: Set the heading for the units in degrees within the respawned group. +-- - @{#GROUP.InitHeight}: Set the height for the units in meters for the respawned group. (This is applicable for air units). +-- - @{#GROUP.InitRandomizeHeading}: Randomize the headings for the units within the respawned group. +-- - @{#GROUP.InitRandomizePositionZone}: Randomize the positions of the units of the respawned group within the @{Core.Zone}. +-- - @{#GROUP.InitRandomizePositionRadius}: Randomize the positions of the units of the respawned group in a circle band. +-- +-- Notes: +-- +-- - When no coordinate is given, the position of the respawned group will be its current position. +-- - The current alive group will always be destroyed first. +-- - The new group will have all of its original units and health restored. +-- +-- @param Wrapper.Group#GROUP self +-- @param Core.Point#COORDINATE Coordinate Where to respawn the group. Can be handed as a @{Core.Zone#ZONE_BASE} object. +-- @return Wrapper.Group#GROUP self +function GROUP:Teleport(Coordinate) + self:InitZone(Coordinate) + return self:Respawn(nil,false) +end --- Respawn a group at an airbase. -- Note that the group has to be on parking spots at the airbase already in order for this to work. From 3b02e153ba0f7ebdb4c006b9d7f148ed62a86170 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 17 Dec 2024 12:38:57 +0100 Subject: [PATCH 094/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 860e2fe2e..a62fbe63f 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1511,6 +1511,9 @@ do elseif not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end + if string.find(grpname,"SHORAD",1,true) then + type = MANTIS.SamType.SHORT -- force short on match + end return range, height, type, blind end @@ -1674,6 +1677,10 @@ do function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates") local switchedon = 0 + local statusreport = REPORT:New("\nMANTIS Status") + local instatusred = 0 + local instatusgreen = 0 + local SEADactive = 0 for _,_data in pairs (samset) do local samcoordinate = _data[2] local name = _data[1] @@ -1696,7 +1703,7 @@ do elseif (not self.UseEmOnOff) and switchedon < limit then samgroup:OptionAlarmStateRed() switchedon = switchedon + 1 - switch = true + switch = true end if self.SamStateTracker[name] ~= "RED" and switch then self:__RedState(1,samgroup) @@ -1714,7 +1721,7 @@ do -- debug output if (self.debug or self.verbose) and switch then local text = string.format("SAM %s in alarm state RED!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + --local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug if self.verbose then self:I(self.lid..text) end end end --end alive @@ -1732,12 +1739,26 @@ do end if self.debug or self.verbose then local text = string.format("SAM %s in alarm state GREEN!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + --local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end end end --end alive - end --end check - end --for for loop + end --end check + end --for loop + if self.debug then + for _,_status in pairs(self.SamStateTracker) do + if _status == "GREEN" then + instatusgreen=instatusgreen+1 + elseif _status == "RED" then + instatusred=instatusred+1 + end + end + statusreport:Add("+-----------------------+") + statusreport:Add(string.format("+ SAM in RED State: %0d",instatusred)) + statusreport:Add(string.format("+ SAM in GREEN State: %0d",instatusgreen)) + statusreport:Add("+-----------------------+") + MESSAGE:New(statusreport:Text(),10,nil,true):ToAll() + end return self end @@ -1851,7 +1872,7 @@ do end --]] if self.autoshorad then - self.Shorad = SHORAD:New(self.name.."-SHORAD",self.name.."-SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff) + self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff) self.Shorad:SetDefenseLimits(80,95) self.ShoradLink = true self.Shorad.Groupset=self.ShoradGroupSet From f8808e0231a1d29d5a02cc364a86bf67187813fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 17 Dec 2024 12:43:09 +0100 Subject: [PATCH 095/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index a62fbe63f..a48828973 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -663,7 +663,7 @@ do -- TODO Version -- @field #string version - self.version="0.8.19" + self.version="0.8.20" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -1753,11 +1753,11 @@ do instatusred=instatusred+1 end end - statusreport:Add("+-----------------------+") - statusreport:Add(string.format("+ SAM in RED State: %0d",instatusred)) - statusreport:Add(string.format("+ SAM in GREEN State: %0d",instatusgreen)) - statusreport:Add("+-----------------------+") - MESSAGE:New(statusreport:Text(),10,nil,true):ToAll() + statusreport:Add("+-----------------------------+") + statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) + statusreport:Add(string.format("+ SAM in GREEN State: %2d",instatusgreen)) + statusreport:Add("+-----------------------------+") + MESSAGE:New(statusreport:Text(),10,nil,true):ToAll():ToLog() end return self end From 3efe02fe37044db57f32b1127992a1c3688ee58b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Dec 2024 11:36:36 +0100 Subject: [PATCH 096/349] xx --- Moose Development/Moose/Core/Set.lua | 18 +++++++++--------- Moose Development/Moose/Ops/CTLD.lua | 6 ++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index d9abf666a..96b629660 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1709,7 +1709,7 @@ do --- Iterate the SET_GROUP and return true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE} -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE}, false otherwise -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1756,7 +1756,7 @@ do --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1781,7 +1781,7 @@ do --- Iterate the SET_GROUP and return true if at least one @{#UNIT} of one @{Wrapper.Group#GROUP} of the @{#SET_GROUP} is in @{Core.Zone} -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1807,7 +1807,7 @@ do --- Iterate the SET_GROUP and return true if at least one @{Wrapper.Group#GROUP} of the @{#SET_GROUP} is partly in @{Core.Zone}. -- Will return false if a @{Wrapper.Group#GROUP} is fully in the @{Core.Zone} -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1842,7 +1842,7 @@ do -- This could also be achieved with `not SET_GROUP:AnyPartlyInZone(Zone)`, but it's easier for the -- mission designer to add a dedicated method -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if no @{Wrapper.Group#GROUP} is inside the @{Core.Zone#ZONE} in any way, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1869,7 +1869,7 @@ do -- That could easily be done with SET_GROUP:ForEachGroupCompletelyInZone(), but this function -- provides an easy to use shortcut... -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #number the number of GROUPs completely in the Zone -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1891,7 +1891,7 @@ do --- Iterate the SET_GROUP and count how many UNITs are completely in the Zone -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #number the number of GROUPs completely in the Zone -- @usage -- local MyZone = ZONE:New("Zone1") @@ -2706,7 +2706,7 @@ do -- SET_UNIT --- Check if no element of the SET_UNIT is in the Zone. -- @param #SET_UNIT self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean function SET_UNIT:IsNotInZone( Zone ) @@ -3746,7 +3746,7 @@ do -- SET_STATIC --- Check if no element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean function SET_STATIC:IsNotInZone( Zone ) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 1bb03c121..234512673 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1278,6 +1278,12 @@ CTLD.RadioModulation = { FM = 1, } +--- Loaded Cargo +-- @type CTLD.LoadedCargo +-- @field #number Troopsloaded +-- @field #number Cratesloaded +-- @field #table Cargo Table of #CTLD_CARGO objects + --- Zone Info. -- @type CTLD.CargoZone -- @field #string name Name of Zone. From 925754f29012a105b4b62b8fdd490ecf9dfa1333 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Dec 2024 12:34:00 +0100 Subject: [PATCH 097/349] xx --- Moose Development/Moose/Ops/Auftrag.lua | 23 +++++++++++++++++++---- Moose Development/Moose/Ops/CTLD.lua | 23 ++++++++++++++++++++++- Moose Development/Moose/Ops/OpsGroup.lua | 8 ++++---- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 93b9b27ef..0bdfcaa58 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -5723,7 +5723,7 @@ function AUFTRAG:GetMissionTypesText(MissionTypes) return text end ---- [NON-AIR] Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`. +--- [NON-AIR] Set the mission waypoint coordinate from where the mission is executed. Note that altitude is set via `:SetMissionAltitude`. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed. -- @return #AUFTRAG self @@ -5751,8 +5751,9 @@ end -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Egrees coordinate. -- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @param #number Speed (Optional) Speed in knots to reach this waypoint. Defaults to mission speed. -- @return #AUFTRAG self -function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) +function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude, Speed) -- Obviously a zone was passed. We get the coordinate. if Coordinate:IsInstanceOf("ZONE_BASE") then @@ -5765,14 +5766,19 @@ function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) self.missionEgressCoordAlt = UTILS.FeetToMeters(Altitude) end + + self.missionEgressCoordSpeed=Speed and Speed or nil + + return self end --- [Air] Set the mission ingress coordinate. This is the coordinate where the assigned group will fly before the actual mission coordinate. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Ingrees coordinate. -- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @param #number Speed (Optional) Speed in knots to reach this waypoint. Defaults to mission speed. -- @return #AUFTRAG self -function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude) +function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude, Speed) -- Obviously a zone was passed. We get the coordinate. if Coordinate:IsInstanceOf("ZONE_BASE") then @@ -5785,15 +5791,20 @@ function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude) self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude) self.missionIngressCoordAlt = UTILS.FeetToMeters(Altitude or 10000) end + + self.missionIngressCoordSpeed=Speed and Speed or nil + + return self end --- [Air] Set the mission holding coordinate. This is the coordinate where the assigned group will fly before the actual mission execution starts. Do not forget to add a push condition, too! -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Holding coordinate. -- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @param #number Speed (Optional) Speed in knots to reach this waypoint and hold there. Defaults to mission speed. -- @param #number Duration (Optional) Duration in seconds on how long to hold, defaults to 15 minutes. Mission continues if either a push condition is met or the time is up. -- @return #AUFTRAG self -function AUFTRAG:SetMissionHoldingCoord(Coordinate, Altitude, Duration) +function AUFTRAG:SetMissionHoldingCoord(Coordinate, Altitude, Speed, Duration) -- Obviously a zone was passed. We get the coordinate. if Coordinate:IsInstanceOf("ZONE_BASE") then @@ -5807,6 +5818,10 @@ function AUFTRAG:SetMissionHoldingCoord(Coordinate, Altitude, Duration) self.missionHoldingCoord.y=UTILS.FeetToMeters(Altitude) self.missionHoldingCoordAlt = UTILS.FeetToMeters(Altitude or 10000) end + + self.missionHoldingCoordSpeed=Speed and Speed or nil + + return self end --- Get the mission egress coordinate if this was defined. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 234512673..9bac1a3c2 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1351,7 +1351,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.19" +CTLD.version="1.1.20" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5389,6 +5389,27 @@ end return Stock end + --- User - Query the cargo loaded from a specific unit + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit The (client) unit to query. + -- @return #number Troopsloaded + -- @return #number Cratesloaded + -- @return #table Cargo Table of #CTLD_CARGO objects + function CTLD:GetLoadedCargo(Unit) + local Troops = 0 + local Crates = 0 + local Cargo = {} + if Unit and Unit:IsAlive() then + local name = Unit:GetName() + if self.Loaded_Cargo[name] then + Troops = self.Loaded_Cargo[name].Troopsloaded or 0 + Crates = self.Loaded_Cargo[name].Cratesloaded or 0 + Cargo = self.Loaded_Cargo[name].Cargo or {} + end + end + return Troops, Crates, Cargo + end + --- User - function to get a table of statics cargo in stock -- @param #CTLD self -- @return #table Table Table of Stock, indexed by cargo type name diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ec4bf82b9..9f7a76c0b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6124,11 +6124,11 @@ function OPSGROUP:RouteToMission(mission, delay) local holdingcoord = mission:GetMissionHoldingCoord() if holdingcoord then - waypoint=FLIGHTGROUP.AddWaypoint(self, holdingcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionHoldingCoordAlt or self.altitudeCruise), false) + waypoint=FLIGHTGROUP.AddWaypoint(self, holdingcoord, mission.missionHoldingCoordSpeed or SpeedToMission, uid, UTILS.MetersToFeet(mission.missionHoldingCoordAlt or self.altitudeCruise), false) uid=waypoint.uid -- Orbit until flaghold=1 (true) but max 5 min self.flaghold:Set(0) - local TaskOrbit = self.group:TaskOrbit(holdingcoord, mission.missionHoldingCoordAlt) + local TaskOrbit = self.group:TaskOrbit(holdingcoord, mission.missionHoldingCoordAlt, UTILS.KnotsToMps(mission.missionHoldingCoordSpeed or SpeedToMission)) local TaskStop = self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, mission.missionHoldingDuration or 900) local TaskCntr = self.group:TaskControlled(TaskOrbit, TaskStop) local TaskOver = self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) @@ -6140,7 +6140,7 @@ function OPSGROUP:RouteToMission(mission, delay) end if ingresscoord then - waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionIngressCoordAlt or self.altitudeCruise), false) + waypoint=FLIGHTGROUP.AddWaypoint(self, ingresscoord, mission.missionIngressCoordSpeed or SpeedToMission, uid, UTILS.MetersToFeet(mission.missionIngressCoordAlt or self.altitudeCruise), false) uid=waypoint.uid end @@ -6182,7 +6182,7 @@ function OPSGROUP:RouteToMission(mission, delay) if egresscoord then local Ewaypoint=nil --#OPSGROUP.Waypoint if self:IsFlightgroup() then - Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionEgressCoordAlt or self.altitudeCruise), false) + Ewaypoint=FLIGHTGROUP.AddWaypoint(self, egresscoord, mission.missionEgressCoordSpeed or SpeedToMission, waypoint.uid, UTILS.MetersToFeet(mission.missionEgressCoordAlt or self.altitudeCruise), false) elseif self:IsArmygroup() then Ewaypoint=ARMYGROUP.AddWaypoint(self, egresscoord, SpeedToMission, waypoint.uid, mission.optionFormation, false) elseif self:IsNavygroup() then From d0aa039626fb0093599b8f858fcca9d969ed7540 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 19 Dec 2024 14:16:10 +0100 Subject: [PATCH 098/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index a48828973..547b989b1 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1753,9 +1753,18 @@ do instatusred=instatusred+1 end end + local activeshorads = 0 + if self.Shorad then + for _,_name in pairs(self.Shorad.ActiveGroups or {}) do + activeshorads=activeshorads+1 + end + end statusreport:Add("+-----------------------------+") statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) statusreport:Add(string.format("+ SAM in GREEN State: %2d",instatusgreen)) + if self.Shorad then + statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads)) + end statusreport:Add("+-----------------------------+") MESSAGE:New(statusreport:Text(),10,nil,true):ToAll():ToLog() end From f7583dbd56745c3df6cd93b183f5a0ea269c91db Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 21 Dec 2024 11:22:52 +0100 Subject: [PATCH 099/349] x --- Moose Development/Moose/Functional/Mantis.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 547b989b1..f6db00da2 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Sep 2024 +-- Last Update: Dec 2024 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -663,7 +663,7 @@ do -- TODO Version -- @field #string version - self.version="0.8.20" + self.version="0.8.21" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- From 678dc92a660fac5785cb16a4b70e6efa15725b2d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 22 Dec 2024 13:00:22 +0100 Subject: [PATCH 100/349] #AWACS - add a mission parameter to ensure selection of AWACS aircraft --- Moose Development/Moose/Ops/Awacs.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index b450bebc1..5c2c015a0 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -17,7 +17,7 @@ -- === -- -- ### Author: **applevangelist** --- @date Last Update Oct 2024 +-- @date Last Update Dec 2024 -- @module Ops.AWACS -- @image OPS_AWACS.jpg @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.67", -- #string + version = "0.2.68", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -5922,6 +5922,7 @@ function AWACS:onafterStart(From, Event, To) local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) mission:SetMissionRange(self.MaxMissionRange) + mission:SetRequiredAttribute({ GROUP.Attribute.AIR_AWACS }) -- prefered plane type, thanks to Heart8reaker local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 mission:SetTime(nil,timeonstation) self.CatchAllMissions[#self.CatchAllMissions+1] = mission From b3ed0681a85014e0beddc843f2d875bc628e0f9e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 26 Dec 2024 17:19:14 +0100 Subject: [PATCH 101/349] xx --- Moose Development/Moose/Core/Point.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 120 ++++++++++++++++++++ Moose Development/Moose/Wrapper/Unit.lua | 1 + 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 052b3987d..c77c174c2 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -110,7 +110,7 @@ do -- COORDINATE -- -- ## 4.4) Get the North correction of the current location. -- - -- * @{#COORDINATE.GetNorthCorrection}(): Obtains the north correction at the current 3D point. + -- * @{#COORDINATE.GetNorthCorrectionRadians}(): Obtains the north correction at the current 3D point. -- -- ## 4.5) Point Randomization -- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 343609c9e..edf00b2f7 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4204,3 +4204,123 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, return ReturnObjects, ADFName end + +--- Converts a Vec2 to a Vec3. +-- @param vec the 2D vector +-- @param y optional new y axis (altitude) value. If omitted it's 0. +function UTILS.Vec2toVec3(vec,y) + if not vec.z then + if vec.alt and not y then + y = vec.alt + elseif not y then + y = 0 + end + return {x = vec.x, y = y, z = vec.y} + else + return {x = vec.x, y = vec.y, z = vec.z} -- it was already Vec3, actually. + end +end + +--- Get the correction needed for true north in radians +-- @param gPoint The map point vec2 or vec3 +-- @return number correction +function UTILS.GetNorthCorrection(gPoint) + local point = UTILS.DeepCopy(gPoint) + if not point.z then --Vec2; convert to Vec3 + point.z = point.y + point.y = 0 + end + local lat, lon = coord.LOtoLL(point) + local north_posit = coord.LLtoLO(lat + 1, lon) + return math.atan2(north_posit.z - point.z, north_posit.x - point.x) +end + +--- Convert time in seconds to a DHMS table `{d = days, h = hours, m = minutes, s = seconds}` +-- @param timeInSec Time in Seconds +-- @return #table Table with DHMS data +function UTILS.GetDHMS(timeInSec) + if timeInSec and type(timeInSec) == 'number' then + local tbl = {d = 0, h = 0, m = 0, s = 0} + if timeInSec > 86400 then + while timeInSec > 86400 do + tbl.d = tbl.d + 1 + timeInSec = timeInSec - 86400 + end + end + if timeInSec > 3600 then + while timeInSec > 3600 do + tbl.h = tbl.h + 1 + timeInSec = timeInSec - 3600 + end + end + if timeInSec > 60 then + while timeInSec > 60 do + tbl.m = tbl.m + 1 + timeInSec = timeInSec - 60 + end + end + tbl.s = timeInSec + return tbl + else + BASE:E("No number handed!") + return + end +end + +--- Returns heading-error corrected direction in radians. +-- True-north corrected direction from point along vector vec. +-- @param vec Vec3 Starting point +-- @param point Vec2 Direction +-- @return direction corrected direction from point. +function UTILS.GetDirectionRadians(vec, point) + local dir = math.atan2(vec.z, vec.x) + if point then + dir = dir + UTILS.GetNorthCorrection(point) + end + if dir < 0 then + dir = dir + 2 * math.pi -- put dir in range of 0 to 2*pi + end + return dir +end + +--- Raycasting a point in polygon. Code from http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm +-- @param point Vec2 or Vec3 to test +-- @param #table poly Polygon Table of Vec2/3 point forming the Polygon +-- @param #number maxalt Altitude limit (optional) +-- @param #boolean outcome +function UTILS.IsPointInPolygon(point, poly, maxalt) + point = UTILS.Vec2toVec3(point) + local px = point.x + local pz = point.z + local cn = 0 + local newpoly = UTILS.DeepCopy(poly) + + if not maxalt or (point.y <= maxalt) then + local polysize = #newpoly + newpoly[#newpoly + 1] = newpoly[1] + + newpoly[1] = UTILS.Vec2toVec3(newpoly[1]) + + for k = 1, polysize do + newpoly[k+1] = UTILS.Vec2toVec3(newpoly[k+1]) + if ((newpoly[k].z <= pz) and (newpoly[k+1].z > pz)) or ((newpoly[k].z > pz) and (newpoly[k+1].z <= pz)) then + local vt = (pz - newpoly[k].z) / (newpoly[k+1].z - newpoly[k].z) + if (px < newpoly[k].x + vt*(newpoly[k+1].x - newpoly[k].x)) then + cn = cn + 1 + end + end + end + + return cn%2 == 1 + else + return false + end +end + +--- Vector scalar multiplication. +-- @param vec Vec3 vector to multiply +-- @param #number mult scalar multiplicator +-- @return Vec3 new vector multiplied with the given scalar +function UTILS.ScalarMult(vec, mult) + return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult} +end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index c0531e14c..9230eaeee 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -127,6 +127,7 @@ function UNIT:Register( UnitName ) local group = unit:getGroup() if group then self.GroupName=group:getName() + self.groupId = group:getID() end self.DCSUnit = unit end From ecdca9919e3932b6f11d2bed1bcf934c60eab1e5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Dec 2024 12:24:02 +0100 Subject: [PATCH 102/349] xx --- Moose Development/Moose/Core/Point.lua | 13 +++++++++++-- Moose Development/Moose/Functional/Autolase.lua | 10 +++++++--- Moose Development/Moose/Ops/CTLD.lua | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index c77c174c2..ae3763298 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1957,9 +1957,18 @@ do -- COORDINATE --- Smokes the point in a color. -- @param #COORDINATE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor - function COORDINATE:Smoke( SmokeColor ) + -- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins) + function COORDINATE:Smoke( SmokeColor, name ) self:F2( { SmokeColor } ) - trigger.action.smoke( self:GetVec3(), SmokeColor ) + self.firename = name or "Smoke-"..math.random(1,100000) + trigger.action.smoke( self:GetVec3(), SmokeColor, self.firename ) + end + + --- Stops smoking the point in a color. + -- @param #COORDINATE self + -- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins) + function COORDINATE:StopSmoke( name ) + self:StopBigSmokeAndFire( name ) end --- Smoke the COORDINATE Green. diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 612162389..423fc55fe 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -118,7 +118,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.25" +AUTOLASE.version = "0.1.26" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -757,9 +757,11 @@ function AUTOLASE:ShowStatus(Group,Unit) end local code = self:GetLaserCode(unit:GetName()) report:Add(string.format("Recce %s has code %d",name,code)) + report:Add("---------------") end end report:Add(string.format("Lasing min threat level %d",self.minthreatlevel)) + report:Add("---------------") local lines = 0 for _ind,_entry in pairs(self.CurrentLasing) do local entry = _entry -- #AUTOLASE.LaserSpot @@ -779,7 +781,7 @@ function AUTOLASE:ShowStatus(Group,Unit) if playername then local settings = _DATABASE:GetPlayerSettings(playername) if settings then - self:I("Get Settings ok!") + self:T("Get Settings ok!") if settings:IsA2G_MGRS() then locationstring = entry.coordinate:ToStringMGRS(settings) elseif settings:IsA2G_LL_DMS() then @@ -789,12 +791,14 @@ function AUTOLASE:ShowStatus(Group,Unit) end end end - local text = string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring) + local text = string.format("+ %s lasing %s code %d\nat %s",reccename,typename,code,locationstring) report:Add(text) + report:Add("---------------") lines = lines + 1 end if lines == 0 then report:Add("No targets!") + report:Add("---------------") end local reporttime = self.reporttimelong if lines == 0 then reporttime = self.reporttimeshort end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9bac1a3c2..9b6c0ead1 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1351,7 +1351,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.20" +CTLD.version="1.1.21" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3514,7 +3514,7 @@ function CTLD:_UnloadTroops(Group, Unit) if IsHerc then offset = self.TroopUnloadDistGroundHerc or 25 end if IsHook then offset = self.TroopUnloadDistGroundHook or 15 - if self.TroopUnloadDistHoverHook then + if hoverunload and self.TroopUnloadDistHoverHook then offset = self.TroopUnloadDistHoverHook or 5 end end From 8cd625b6f9d147b37fef59af6bedd2b90d56ba4d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Dec 2024 14:40:23 +0100 Subject: [PATCH 103/349] #MSRS Google 2025 voice catalog --- Moose Development/Moose/Sound/SRS.lua | 130 ++++++++++++++++---------- 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 261489e7b..3726f309f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -318,11 +318,14 @@ MSRS.Voices = { ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE ["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE - ["en_GB_Standard_A"] = 'en-GB-Standard-A', -- [9] FEMALE - ["en_GB_Standard_B"] = 'en-GB-Standard-B', -- [10] MALE - ["en_GB_Standard_C"] = 'en-GB-Standard-C', -- [11] FEMALE - ["en_GB_Standard_D"] = 'en-GB-Standard-D', -- [12] MALE - ["en_GB_Standard_F"] = 'en-GB-Standard-F', -- [13] FEMALE + -- 2025 changes + ["en_GB_Standard_A"] = 'en-GB-Standard-N', -- [9] FEMALE + ["en_GB_Standard_B"] = 'en-GB-Standard-O', -- [10] MALE + ["en_GB_Standard_C"] = 'en-GB-Standard-N', -- [11] FEMALE + ["en_GB_Standard_D"] = 'en-GB-Standard-O', -- [12] MALE + ["en_GB_Standard_F"] = 'en-GB-Standard-N', -- [13] FEMALE + ["en_GB_Standard_O"] = 'en-GB-Standard-O', -- [12] MALE + ["en_GB_Standard_N"] = 'en-GB-Standard-N', -- [13] FEMALE ["en_US_Standard_A"] = 'en-US-Standard-A', -- [14] MALE ["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE ["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE @@ -333,25 +336,36 @@ MSRS.Voices = { ["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE ["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE ["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE - ["fr_FR_Standard_A"] = "fr-FR-Standard-A", -- Female - ["fr_FR_Standard_B"] = "fr-FR-Standard-B", -- Male - ["fr_FR_Standard_C"] = "fr-FR-Standard-C", -- Female - ["fr_FR_Standard_D"] = "fr-FR-Standard-D", -- Male - ["fr_FR_Standard_E"] = "fr-FR-Standard-E", -- Female - ["de_DE_Standard_A"] = "de-DE-Standard-A", -- Female - ["de_DE_Standard_B"] = "de-DE-Standard-B", -- Male - ["de_DE_Standard_C"] = "de-DE-Standard-C", -- Female - ["de_DE_Standard_D"] = "de-DE-Standard-D", -- Male - ["de_DE_Standard_E"] = "de-DE-Standard-E", -- Male - ["de_DE_Standard_F"] = "de-DE-Standard-F", -- Female - ["es_ES_Standard_A"] = "es-ES-Standard-A", -- Female - ["es_ES_Standard_B"] = "es-ES-Standard-B", -- Male - ["es_ES_Standard_C"] = "es-ES-Standard-C", -- Female - ["es_ES_Standard_D"] = "es-ES-Standard-D", -- Female - ["it_IT_Standard_A"] = "it-IT-Standard-A", -- Female - ["it_IT_Standard_B"] = "it-IT-Standard-B", -- Female - ["it_IT_Standard_C"] = "it-IT-Standard-C", -- Male - ["it_IT_Standard_D"] = "it-IT-Standard-D", -- Male + -- 2025 catalog changes + ["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female + ["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male + ["fr_FR_Standard_C"] = "fr-FR-Standard-F", -- Female + ["fr_FR_Standard_D"] = "fr-FR-Standard-G", -- Male + ["fr_FR_Standard_E"] = "fr-FR-Standard-F", -- Female + ["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male + ["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female + -- 2025 catalog changes + ["de_DE_Standard_A"] = "de-DE-Standard-G", -- Female + ["de_DE_Standard_B"] = "de-DE-Standard-H", -- Male + ["de_DE_Standard_C"] = "de-DE-Standard-G", -- Female + ["de_DE_Standard_D"] = "de-DE-Standard-H", -- Male + ["de_DE_Standard_E"] = "de-DE-Standard-H", -- Male + ["de_DE_Standard_F"] = "de-DE-Standard-G", -- Female + ["de_DE_Standard_H"] = "de-DE-Standard-H", -- Male + ["de_DE_Standard_G"] = "de-DE-Standard-G", -- Female + ["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female + ["es_ES_Standard_B"] = "es-ES-Standard-F", -- Male + ["es_ES_Standard_C"] = "es-ES-Standard-E", -- Female + ["es_ES_Standard_D"] = "es-ES-Standard-F", -- Male + ["es_ES_Standard_E"] = "es-ES-Standard-E", -- Female + ["es_ES_Standard_F"] = "es-ES-Standard-F", -- Male + -- 2025 catalog changes + ["it_IT_Standard_A"] = "it-IT-Standard-E", -- Female + ["it_IT_Standard_B"] = "it-IT-Standard-E", -- Female + ["it_IT_Standard_C"] = "it-IT-Standard-F", -- Male + ["it_IT_Standard_D"] = "it-IT-Standard-F", -- Male + ["it_IT_Standard_E"] = "it-IT-Standard-E", -- Female + ["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male }, Wavenet = { ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE @@ -362,12 +376,15 @@ MSRS.Voices = { ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE ["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE - ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE - ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE - ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE - ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE - ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE - ["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- [14] MALE + -- 2025 changes + ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-N', -- [9] FEMALE + ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-O', -- [10] MALE + ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-N', -- [11] FEMALE + ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-O', -- [12] MALE + ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-N', -- [13] FEMALE + ["en_GB_Wavenet_O"] = 'en-GB-Wavenet-O', -- [12] MALE + ["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE + ["en_US_Wavenet_A"] = 'en-US-Wavenet-N', -- [14] MALE ["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- [15] MALE ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE @@ -377,24 +394,35 @@ MSRS.Voices = { ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE ["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE - ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-A", -- Female - ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-B", -- Male - ["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-C", -- Female - ["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-D", -- Male - ["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-E", -- Female - ["de_DE_Wavenet_A"] = "de-DE-Wavenet-A", -- Female - ["de_DE_Wavenet_B"] = "de-DE-Wavenet-B", -- Male - ["de_DE_Wavenet_C"] = "de-DE-Wavenet-C", -- Female - ["de_DE_Wavenet_D"] = "de-DE-Wavenet-D", -- Male - ["de_DE_Wavenet_E"] = "de-DE-Wavenet-E", -- Male - ["de_DE_Wavenet_F"] = "de-DE-Wavenet-F", -- Female - ["es_ES_Wavenet_B"] = "es-ES-Wavenet-B", -- Male - ["es_ES_Wavenet_C"] = "es-ES-Wavenet-C", -- Female - ["es_ES_Wavenet_D"] = "es-ES-Wavenet-D", -- Female - ["it_IT_Wavenet_A"] = "it-IT-Wavenet-A", -- Female - ["it_IT_Wavenet_B"] = "it-IT-Wavenet-B", -- Female - ["it_IT_Wavenet_C"] = "it-IT-Wavenet-C", -- Male - ["it_IT_Wavenet_D"] = "it-IT-Wavenet-D", -- Male + -- 2025 catalog changes + ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female + ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male + ["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-F", -- Female + ["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-G", -- Male + ["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-F", -- Female + ["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male + ["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female + -- 2025 catalog changes + ["de_DE_Wavenet_A"] = "de-DE-Wavenet-G", -- Female + ["de_DE_Wavenet_B"] = "de-DE-Wavenet-H", -- Male + ["de_DE_Wavenet_C"] = "de-DE-Wavenet-G", -- Female + ["de_DE_Wavenet_D"] = "de-DE-Wavenet-H", -- Male + ["de_DE_Wavenet_E"] = "de-DE-Wavenet-H", -- Male + ["de_DE_Wavenet_F"] = "de-DE-Wavenet-G", -- Female + ["de_DE_Wavenet_H"] = "de-DE-Wavenet-H", -- Male + ["de_DE_Wavenet_G"] = "de-DE-Wavenet-G", -- Female + ["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male + ["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female + ["es_ES_Wavenet_D"] = "es-ES-Wavenet-E", -- Female + ["es_ES_Wavenet_E"] = "es-ES-Wavenet-E", -- Male + ["es_ES_Wavenet_F"] = "es-ES-Wavenet-F", -- Female + -- 2025 catalog changes + ["it_IT_Wavenet_A"] = "it-IT-Wavenet-E", -- Female + ["it_IT_Wavenet_B"] = "it-IT-Wavenet-E", -- Female + ["it_IT_Wavenet_C"] = "it-IT-Wavenet-F", -- Male + ["it_IT_Wavenet_D"] = "it-IT-Wavenet-F", -- Male + ["it_IT_Wavenet_E"] = "it-IT-Wavenet-E", -- Female + ["it_IT_Wavenet_F"] = "it-IT-Wavenet-F", -- Male } , }, } @@ -1634,9 +1662,9 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab end for _,freq in pairs(Frequencies) do - self:F("Calling GRPC.tts with the following parameter:") - self:F({ssml=ssml, freq=freq, options=options}) - self:F(options.provider[provider]) + self:T("Calling GRPC.tts with the following parameter:") + self:T({ssml=ssml, freq=freq, options=options}) + self:T(options.provider[provider]) GRPC.tts(ssml, freq*1e6, options) end From 80e20e59ec04f6f10ff90517ad3b6911c8bfa0a4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 29 Dec 2024 12:52:21 +0100 Subject: [PATCH 104/349] #STORAGE - Added persistence options --- Moose Development/Moose/Wrapper/Storage.lua | 150 +++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 4ad6bf6fa..4ed35c196 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -499,7 +499,7 @@ function STORAGE:IsUnlimited(Type) end -- Debug info. - self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n)) + self:T(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n)) end return unlimited @@ -595,6 +595,154 @@ function STORAGE:GetInventory(Item) return inventory.aircraft, inventory.liquids, inventory.weapon end +--- Save the contents of a STORAGE to files in CSV format. Filenames created are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Requires io and lfs to be desanitized to be working. +-- @param #STORAGE self +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The base name of the files. Existing files will be overwritten. +-- @return #STORAGE self +function STORAGE:SaveToFile(Path,Filename) + + if not io then + BASE:E("ERROR: io not desanitized. Can't save the files.") + return false + end + + -- Check default path. + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your given path.") + end + + local ac, lq, wp = self:GetInventory() + local DataAircraft = "" + local DataLiquids = "" + local DataWeapons = "" + + if #lq > 0 then + DataLiquids = DataLiquids .."Liquids in Storage:\n" + for key,amount in pairs(lq) do + DataLiquids = DataLiquids..tostring(key).."="..tostring(amount).."\n" + end + --self:I(DataLiquids) + UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids) + end + + if UTILS.TableLength(ac) > 0 then + DataAircraft = DataAircraft .."Aircraft in Storage:\n" + for key,amount in pairs(ac) do + DataAircraft = DataAircraft..tostring(key).."="..tostring(amount).."\n" + end + --self:I(DataAircraft) + UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft) + end + + if UTILS.TableLength(wp) > 0 then + DataWeapons = DataWeapons .."Weapons and Materiel in Storage:\n" + for key,amount in pairs(wp) do + DataWeapons = DataWeapons..tostring(key).."="..tostring(amount).."\n" + end + -- Gazelle table keys + for key,amount in pairs(ENUMS.Storage.weapons.Gazelle) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.Gazelle."..tostring(key).."="..tostring(amount).."\n" + end + -- CH47 + for key,amount in pairs(ENUMS.Storage.weapons.CH47) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.CH47[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.CH47."..tostring(key).."="..tostring(amount).."\n" + end + -- UH1H + for key,amount in pairs(ENUMS.Storage.weapons.UH1H) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.UH1H[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.UH1H."..tostring(key).."="..tostring(amount).."\n" + end + -- OH58D + for key,amount in pairs(ENUMS.Storage.weapons.OH58) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.OH58[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.OH58."..tostring(key).."="..tostring(amount).."\n" + end + -- AH64D + for key,amount in pairs(ENUMS.Storage.weapons.AH64D) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n" + end + --self:I(DataAircraft) + UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons) + end + + return self +end + +--- Load the contents of a STORAGE from files. Filenames searched for are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Requires io and lfs to be desanitized to be working. +-- @param #STORAGE self +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return #STORAGE self +function STORAGE:LoadFromFile(Path,Filename) + + if not io then + BASE:E("ERROR: io not desanitized. Can't read the files.") + return false + end + + -- Check default path. + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. File will be read from DCS installation root directory rather than your give path.") + end + + --Liquids + if self:IsLimitedLiquids() then + local Ok,Liquids = UTILS.LoadFromFile(Path,Filename.."_Liquids.csv") + if Ok then + for _id,_line in pairs(Liquids) do + if string.find(_line,"Storage") == nil then + local tbl=UTILS.Split(_line,"=") + local lqno = tonumber(tbl[1]) + local lqam = tonumber(tbl[2]) + self:SetLiquid(lqno,lqam) + end + end + else + self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) + end + end + + --Aircraft + if self:IsLimitedAircraft() then + local Ok,Aircraft = UTILS.LoadFromFile(Path,Filename.."_Aircraft.csv") + if Ok then + for _id,_line in pairs(Aircraft) do + if string.find(_line,"Storage") == nil then + local tbl=UTILS.Split(_line,"=") + local acname = tbl[1] + local acnumber = tonumber(tbl[2]) + self:SetAmount(acname,acnumber) + end + end + else + self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) + end + end + + --Weapons + if self:IsLimitedWeapons()() then + local Ok,Weapons = UTILS.LoadFromFile(Path,Filename.."_Weapons.csv") + if Ok then + for _id,_line in pairs(Weapons) do + if string.find(_line,"Storage") == nil then + local tbl=UTILS.Split(_line,"=") + local wpname = tbl[1] + local wpnumber = tonumber(tbl[2]) + self:SetAmount(wpname,wpnumber) + end + end + else + self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) + end + end + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Private Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 6eac89c0388e301ebdcc177fd319c4ce0f0c4dc7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 31 Dec 2024 14:04:16 +0100 Subject: [PATCH 105/349] #CTLD - also save and load static special shapes if they are set --- Moose Development/Moose/Ops/CTLD.lua | 65 +++++++++++----------------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9b6c0ead1..bde535a5d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1351,7 +1351,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.21" +CTLD.version="1.1.22" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2686,27 +2686,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) row = 1 startpos:Translate(6,heading,nil,true) end - --[[ - local initialSpacing = IsHerc and 16 or (capabilities.length+2) -- initial spacing of the first crates - local crateSpacing = 4 -- further spacing of remaining crates - local lateralSpacing = 4 -- lateral spacing of crates - local nrSideBySideCrates = 4 -- number of crates that are placed side-by-side - - if cratesneeded == 1 then - -- single crate needed spawns straight ahead - cratedistance = initialSpacing - rheading = math.fmod((heading + addon), 360) - else - --if (i - 1) % nrSideBySideCrates == 0 then - cratedistance = i == 1 and initialSpacing or (cratedistance + crateSpacing) - angleOffNose = math.ceil(math.deg(math.atan(lateralSpacing / cratedistance))) - self:I("angleOffNose = "..angleOffNose) - rheading = heading + addon - angleOffNose - --else - -- rheading = heading + addon + angleOffNose - --end - end - --]] end --local cratevec2 = cratecoord:GetVec2() @@ -4277,7 +4256,7 @@ end -- @param #number PerTroopMass Mass in kg of each soldier -- @param #number Stock Number of groups in stock. Nil for unlimited. -- @param #string SubCategory Name of sub-category (optional). -function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,SubCategory) +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,SubCategory) self:T(self.lid .. " AddTroopsCargo") self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) if not self:_CheckTemplates(Templates) then @@ -4288,6 +4267,7 @@ function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,Sub -- Troops are directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock, SubCategory) table.insert(self.Cargo_Troops,cargo) + if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return self end @@ -4324,6 +4304,7 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end table.insert(self.Cargo_Crates,cargo) + if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return self end @@ -4350,6 +4331,7 @@ function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Locatio local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) + if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return cargo end @@ -6127,7 +6109,7 @@ end local statics = nil local statics = {} - self:T(self.lid.."Bulding Statics Table for Saving") + self:T(self.lid.."Building Statics Table for Saving") for _,_cargo in pairs (stcstable) do local cargo = _cargo -- #CTLD_CARGO local object = cargo:GetPositionable() -- Wrapper.Static#STATIC @@ -6162,7 +6144,7 @@ end --local data = "LoadedData = {\n" - local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure\n" + local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure,StaticCategory,StaticType,StaticShape\n" local n = 0 for _,_grp in pairs(grouptable) do local group = _grp -- Wrapper.Group#GROUP @@ -6189,6 +6171,7 @@ end local cgotype = cargo.CargoType local cgoneed = cargo.CratesNeeded local cgomass = cargo.PerCrateMass + local scat,stype,sshape = cargo:GetStaticTypeAndShape() local structure = UTILS.GetCountPerTypeName(group) local strucdata = "" for typen,anzahl in pairs (structure) do @@ -6205,8 +6188,8 @@ end end local location = group:GetVec3() - local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s\n" - ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata) + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s\n" + ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata,scat,stype,sshape or "none") data = data .. txt end end @@ -6231,8 +6214,9 @@ end local cgomass = object.PerCrateMass local crateobj = object.Positionable local location = crateobj:GetVec3() - local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" - ,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) + local scat,stype,sshape = object:GetStaticTypeAndShape() + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,'none',%s,%s,%s\n" + ,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,scat,stype,sshape or "none") data = data .. txt end @@ -6361,47 +6345,50 @@ end for _id,_entry in pairs (loadeddata) do local dataset = UTILS.Split(_entry,",") - -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=Structure + -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=Structure,11=StaticCategory,12=StaticType,13=StaticShape local groupname = dataset[1] local vec2 = {} vec2.x = tonumber(dataset[2]) vec2.y = tonumber(dataset[4]) local cargoname = dataset[5] + local cargotemplates = dataset[6] local cargotype = dataset[7] + local size = tonumber(dataset[8]) + local mass = tonumber(dataset[9]) + local StaticCategory = dataset[11] + local StaticType = dataset[12] + local StaticShape = dataset[13] if type(groupname) == "string" and groupname ~= "STATIC" then - local cargotemplates = dataset[6] cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") cargotemplates = UTILS.Split(cargotemplates,";") - local size = tonumber(dataset[8]) - local mass = tonumber(dataset[9]) local structure = nil - if dataset[10] then + if dataset[10] and dataset[10] ~= "none" then structure = dataset[10] structure = string.gsub(structure,",","") end -- inject at Vec2 local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then - local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure) elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure) end elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then - local cargotemplates = dataset[6] - local size = tonumber(dataset[8]) - local mass = tonumber(dataset[9]) local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic = nil if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") cargotemplates = UTILS.Split(cargotemplates,";") - injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) elseif cargotype == CTLD_CARGO.Enum.STATIC or cargotype == CTLD_CARGO.Enum.REPAIR then injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) local map=cargotype:GetStaticResourceMap() injectstatic:SetStaticResourceMap(map) end From 411d20ba264bc20461773ef844b6c9ff456f5cd6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 31 Dec 2024 15:33:28 +0100 Subject: [PATCH 106/349] #SEAD - Add AGM_65 --- Moose Development/Moose/Functional/Sead.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index ac89dddb4..734dd168d 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -80,6 +80,7 @@ SEAD = { ["AGM_122"] = "AGM_122", ["AGM_84"] = "AGM_84", ["AGM_45"] = "AGM_45", + ["AGM_65"] = "AGM_65", ["ALARM"] = "ALARM", ["LD-10"] = "LD-10", ["X_58"] = "X_58", @@ -99,6 +100,7 @@ SEAD = { -- km and mach ["AGM_88"] = { 150, 3}, ["AGM_45"] = { 12, 2}, + ["AGM_65"] = { 16, 0.9}, ["AGM_122"] = { 16.5, 2.3}, ["AGM_84"] = { 280, 0.8}, ["ALARM"] = { 45, 2}, @@ -468,6 +470,7 @@ function SEAD:HandleEventShot( EventData ) local SEADWeaponName = EventData.WeaponName or "None" -- return weapon type if self:_CheckHarms(SEADWeaponName) then + --UTILS.PrintTableToLog(EventData) local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT if not SEADPlane then return self end -- case IniUnit is empty @@ -493,7 +496,7 @@ function SEAD:HandleEventShot( EventData ) if not _target or self.debug then -- AGM-88 or 154 w/o target data self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None")) if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then - self:I("**** Tracking AGM-88/154 with no target data.") + self:T("**** Tracking AGM-88/154 with no target data.") local pos0 = SEADPlane:GetCoordinate() local fheight = SEADPlane:GetHeight() self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName) From 633186ca8a454fbc3dca5ab22cf3572980cdcd24 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 31 Dec 2024 15:34:28 +0100 Subject: [PATCH 107/349] xx --- Moose Development/Moose/Functional/Sead.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 734dd168d..34b5e7d13 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -19,7 +19,7 @@ -- -- ### Authors: **applevangelist**, **FlightControl** -- --- Last Update: Oct 2024 +-- Last Update: Dec 2024 -- -- === -- @@ -157,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) self:AddTransition("*", "ManageEvasion", "*") self:AddTransition("*", "CalculateHitZone", "*") - self:I("*** SEAD - Started Version 0.4.8") + self:I("*** SEAD - Started Version 0.4.9") return self end From dc9b70db7eca6333167bf2f769469625a2dfd70e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 1 Jan 2025 14:37:28 +0100 Subject: [PATCH 108/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 34c5c5ee3..0124af4ba 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1287,9 +1287,9 @@ do -- DEBUG set = self:_PreFilterHeight(height) end - local friendlyset -- Core.Set#SET_GROUP - if self.checkforfriendlies == true then - friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterOnce() + --self.friendlyset -- Core.Set#SET_GROUP + if self.checkforfriendlies == true and self.friendlyset == nil then + self.friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterStart() end for _,_coord in pairs (set) do local coord = _coord -- get current coord to check @@ -1305,20 +1305,21 @@ do zonecheck = self:_CheckCoordinateInZones(coord) end if self.verbose and self.debug then - local dectstring = coord:ToStringLLDMS() - local samstring = samcoordinate:ToStringLLDMS() + --local dectstring = coord:ToStringLLDMS() + local samstring = samcoordinate:ToStringMGRS({MGRS_Accuracy=0}) + samstring = string.gsub(samstring,"%s","") local inrange = "false" if targetdistance <= rad then inrange = "true" end - local text = string.format("Checking SAM at %s | Targetdist %d | Rad %d | Inrange %s", samstring, targetdistance, rad, inrange) + local text = string.format("Checking SAM at %s | Tgtdist %.1fkm | Rad %.1fkm | Inrange %s", samstring, targetdistance/1000, rad/1000, inrange) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) self:T(self.lid..text) end -- friendlies around? local nofriendlies = true if self.checkforfriendlies == true then - local closestfriend, distance = friendlyset:GetClosestGroup(samcoordinate) + local closestfriend, distance = self.friendlyset:GetClosestGroup(samcoordinate) if closestfriend and distance and distance < rad then nofriendlies = false end @@ -1618,7 +1619,7 @@ do --self:I({grpname,grprange, grpheight}) elseif type == MANTIS.SamType.SHORT then table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) - -- self:I({grpname,grprange, grpheight}) + --self:I({grpname,grprange, grpheight}) self.ShoradGroupSet:Add(grpname,group) if self.autoshorad then self.Shorad.Groupset = self.ShoradGroupSet @@ -1677,9 +1678,9 @@ do function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates") local switchedon = 0 - local statusreport = REPORT:New("\nMANTIS Status") local instatusred = 0 local instatusgreen = 0 + local activeshorads = 0 local SEADactive = 0 for _,_data in pairs (samset) do local samcoordinate = _data[2] @@ -1756,22 +1757,13 @@ do instatusred=instatusred+1 end end - local activeshorads = 0 if self.Shorad then for _,_name in pairs(self.Shorad.ActiveGroups or {}) do activeshorads=activeshorads+1 end end - statusreport:Add("+-----------------------------+") - statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) - statusreport:Add(string.format("+ SAM in GREEN State: %2d",instatusgreen)) - if self.Shorad then - statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads)) - end - statusreport:Add("+-----------------------------+") - MESSAGE:New(statusreport:Text(),10,nil,true):ToAll():ToLog() end - return self + return instatusred, instatusgreen, activeshorads end --- [Internal] Check detection function @@ -1789,6 +1781,9 @@ do if rand > 65 then -- 1/3 of cases self:_RefreshSAMTable() end + local instatusred = 0 + local instatusgreen = 0 + local activeshorads = 0 -- switch SAMs on/off if (n)one of the detected groups is inside their reach if self.automode then local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height @@ -1796,10 +1791,21 @@ do local samset = self.SAM_Table_Medium -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height self:_CheckLoop(samset,detset,dlink,self.maxmidrange) local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxshortrange) + instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxshortrange) else local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxclassic) + instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) + end + if self.debug or self.verbose then + local statusreport = REPORT:New("\nMANTIS Status "..self.name) + statusreport:Add("+-----------------------------+") + statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) + statusreport:Add(string.format("+ SAM in GREEN State: %2d",instatusgreen)) + if self.Shorad then + statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads)) + end + statusreport:Add("+-----------------------------+") + MESSAGE:New(statusreport:Text(),10):ToAll():ToLog() end return self end From 21a6bcfcfbdb5483dfcd51ec427bc50f61aa34ea Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 1 Jan 2025 17:54:59 +0100 Subject: [PATCH 109/349] xx --- Moose Development/Moose/Functional/Shorad.lua | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index d4f70447f..dd0fd7637 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -21,7 +21,7 @@ -- @image Functional.Shorad.jpg -- -- Date: Nov 2021 --- Last Update: Nov 2023 +-- Last Update: Jan 2025 ------------------------------------------------------------------------- --- **SHORAD** class, extends Core.Base#BASE @@ -521,7 +521,27 @@ do -- go through set and find the one(s) to activate local TDiff = 4 for _,_group in pairs (shoradset) do - if _group:IsAnyInZone(targetzone) then + + local groupname = _group:GetName() + + if groupname == TargetGroup then + -- Shot at a SHORAD group + if self.UseEmOnOff then + _group:EnableEmission(false) + end + _group:OptionAlarmStateGreen() + self.ActiveGroups[groupname] = nil + local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + + --Shoot and Scoot + if self.shootandscoot then + self:__ShootAndScoot(1,_group) + end + + elseif _group:IsAnyInZone(targetzone) then + -- shot at a group we protect local text = string.format("Waking up SHORAD %s", _group:GetName()) self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) @@ -529,7 +549,6 @@ do _group:EnableEmission(true) end _group:OptionAlarmStateRed() - local groupname = _group:GetName() if self.ActiveGroups[groupname] == nil then -- no timer yet for this group self.ActiveGroups[groupname] = { Timing = ActiveTimer } local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit From 12dd4a918f5c4d6279e41a8d1db7ee27f9b292ce Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 2 Jan 2025 11:20:10 +0100 Subject: [PATCH 110/349] xx --- Moose Development/Moose/Core/Settings.lua | 26 +++++++++++------------ Moose Development/Moose/Globals.lua | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 81a60a7f8..066fd1fd6 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -494,7 +494,7 @@ do -- SETTINGS return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS()) end - -- @param #SETTINGS self + --- @param #SETTINGS self -- @param Wrapper.Group#GROUP MenuGroup Group for which to add menus. -- @param #table RootMenu Root menu table -- @return #SETTINGS @@ -948,49 +948,49 @@ do -- SETTINGS return self end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem ) self.A2GSystem = A2GSystem MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem ) self.A2ASystem = A2ASystem MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy ) self.LL_Accuracy = LL_Accuracy MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy ) self.MGRS_Accuracy = MGRS_Accuracy MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW ) self.Metric = MW MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuMessageTimingsSystem( MenuGroup, RootMenu, MessageType, MessageTime ) self:SetMessageTime( MessageType, MessageTime ) MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToAll() end do - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem ) --BASE:E( {PlayerUnit:GetName(), A2GSystem } ) self.A2GSystem = A2GSystem @@ -1001,7 +1001,7 @@ do -- SETTINGS end end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem ) self.A2ASystem = A2ASystem MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup ) @@ -1011,7 +1011,7 @@ do -- SETTINGS end end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy ) self.LL_Accuracy = LL_Accuracy MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) @@ -1021,7 +1021,7 @@ do -- SETTINGS end end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy ) self.MGRS_Accuracy = MGRS_Accuracy MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) @@ -1031,7 +1031,7 @@ do -- SETTINGS end end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW ) self.Metric = MW MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup ) @@ -1041,7 +1041,7 @@ do -- SETTINGS end end - -- @param #SETTINGS self + --- @param #SETTINGS self function SETTINGS:MenuGroupMessageTimingsSystem( PlayerUnit, PlayerGroup, PlayerName, MessageType, MessageTime ) self:SetMessageTime( MessageType, MessageTime ) MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToGroup( PlayerGroup ) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index 4762a10fb..771547060 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -10,7 +10,7 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU _DATABASE = DATABASE:New() -- Core.Database#DATABASE --- Settings -_SETTINGS = SETTINGS:Set() +_SETTINGS = SETTINGS:Set() -- Core.Settings#SETTINGS _SETTINGS:SetPlayerMenuOn() --- Register cargos. From 2cb58bd351035fff451caaf5a6178bf81c32092d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 2 Jan 2025 13:15:13 +0100 Subject: [PATCH 111/349] xx --- Moose Development/Moose/Core/Point.lua | 20 ++++++++++++------- .../Moose/Functional/Autolase.lua | 20 ++++++++++++++++--- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index ae3763298..964f8e1ee 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -968,8 +968,10 @@ do -- COORDINATE -- @return DCS#Distance Distance The distance in meters. function COORDINATE:Get2DDistance(TargetCoordinate) if not TargetCoordinate then return 1000000 end - local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} - local norm=UTILS.VecNorm(a) + --local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} + local a = self:GetVec2() + local b = TargetCoordinate:GetVec2() + local norm=UTILS.VecDist2D(a,b) return norm end @@ -1329,13 +1331,16 @@ do -- COORDINATE -- @param Core.Settings#SETTINGS Settings -- @param #string Language (Optional) Language "en" or "ru" -- @param #boolean MagVar If true, also state angle in magnetic + -- @param #number Precision Rounding precision, defaults to 0 -- @return #string The BR Text - function COORDINATE:GetBRText( AngleRadians, Distance, Settings, Language, MagVar ) + function COORDINATE:GetBRText( AngleRadians, Distance, Settings, Language, MagVar, Precision ) local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS - + + Precision = Precision or 0 + local BearingText = self:GetBearingText( AngleRadians, 0, Settings, MagVar ) - local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language, Precision ) local BRText = BearingText .. DistanceText @@ -2909,12 +2914,13 @@ do -- COORDINATE -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @param #boolean MagVar If true, also get angle in MagVar for BR/BRA + -- @param #number Precision Rounding precision, currently full km as default (=0) -- @return #string The BR text. - function COORDINATE:ToStringBR( FromCoordinate, Settings, MagVar ) + function COORDINATE:ToStringBR( FromCoordinate, Settings, MagVar, Precision ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( FromCoordinate ) - return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar ) + return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision ) end --- Return a BRA string from a COORDINATE to the COORDINATE. diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 423fc55fe..bc3f97c4f 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -74,7 +74,7 @@ -- @image Designation.JPG -- -- Date: 24 Oct 2021 --- Last Update: May 2024 +-- Last Update: Jan 2025 -- --- Class AUTOLASE -- @type AUTOLASE @@ -89,6 +89,7 @@ -- @field #table playermenus -- @field #boolean smokemenu -- @field #boolean threatmenu +-- @field #number RoundingPrecision -- @extends Ops.Intel#INTEL --- @@ -100,6 +101,7 @@ AUTOLASE = { alias = "", debug = false, smokemenu = true, + RoundingPrecision = 0, } --- Laser spot info @@ -118,7 +120,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.26" +AUTOLASE.version = "0.1.27" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -207,6 +209,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.playermenus = {} self.smokemenu = true self.threatmenu = true + self.RoundingPrecision = 0 -- Set some string id for output to DCS.log file. self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") @@ -600,6 +603,15 @@ function AUTOLASE:SetSmokeTargets(OnOff,Color) return self end +--- (User) Function to set rounding precision for BR distance output. +-- @param #AUTOLASE self +-- @param #number IDP Rounding precision before/after the decimal sign. Defaults to zero. Positive values round right of the decimal sign, negative ones left of the decimal sign. +-- @return #AUTOLASE self +function AUTOLASE:SetRoundingPrecsion(IDP) + self.RoundingPrecision = IDP or 0 + return self +end + --- (User) Show the "Switch smoke target..." menu entry for pilots. On by default. -- @param #AUTOLASE self -- @return #AUTOLASE self @@ -787,7 +799,9 @@ function AUTOLASE:ShowStatus(Group,Unit) elseif settings:IsA2G_LL_DMS() then locationstring = entry.coordinate:ToStringLLDMS(settings) elseif settings:IsA2G_BR() then - locationstring = entry.coordinate:ToStringBR(Group:GetCoordinate() or Unit:GetCoordinate(),settings) + -- attention this is the distance from the ASKING unit to target, not from RECCE to target! + local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate() + locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision) end end end From f67706044a1ebcf1e50dd62790406efb171b2b35 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 2 Jan 2025 15:46:42 +0100 Subject: [PATCH 112/349] #PLAYERRECCE - allow add'l position to be displayed --- Moose Development/Moose/Ops/PlayerRecce.lua | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index f6a90c9a9..aca814f62 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -80,6 +80,7 @@ -- @field #boolean smokeownposition -- @field #table SmokeOwn -- @field #boolean smokeaveragetargetpos +-- @field #boolean reporttostringbullsonly -- @extends Core.Fsm#FSM --- @@ -133,7 +134,8 @@ PLAYERRECCE = { TargetCache = nil, smokeownposition = false, SmokeOwn = {}, - smokeaveragetargetpos = false, + smokeaveragetargetpos = true, + reporttostringbullsonly = true, } --- @@ -236,6 +238,8 @@ function PLAYERRECCE:New(Name, Coalition, PlayerSet) self.minthreatlevel = 0 + self.reporttostringbullsonly = true + self.TForget = 600 self.TargetCache = FIFO:New() @@ -1274,6 +1278,9 @@ self:T(self.lid.."_ReportLaserTargets") report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") if not self.ReferencePoint then report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) + if self.reporttostringbullsonly ~= true then + report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) + end else report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) end @@ -1317,8 +1324,14 @@ function PLAYERRECCE:_ReportVisualTargets(client,group,playername) report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") if not self.ReferencePoint then report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) + if self.reporttostringbullsonly ~= true then + report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) + end else report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) + if self.reporttostringbullsonly ~= true then + report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) + end end report:Add(string.rep("-",15)) local text = report:Text() @@ -1552,6 +1565,16 @@ function PLAYERRECCE:SetMenuName(Name) return self end +--- [User] Set reporting to be BULLS only or BULLS plus playersettings based coordinate. +-- @param #PLAYERRECCE self +-- @param #boolean OnOff +-- @return #PLAYERRECCE self +function PLAYERRECCE:SetReportBullsOnly(OnOff) + self:T(self.lid.."SetReportBullsOnly: "..tostring(OnOff)) + self.reporttostringbullsonly = OnOff + return self +end + --- [User] Enable smoking of own position -- @param #PLAYERRECCE self -- @return #PLAYERRECCE self From c1c5467b9de6b7a9ddb917297477f69d627eec51 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 2 Jan 2025 17:06:42 +0100 Subject: [PATCH 113/349] xx --- Moose Development/Moose/Ops/PlayerRecce.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index aca814f62..0f3c1f556 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -933,6 +933,7 @@ function PLAYERRECCE:_LaseTarget(client,targetset) if (not oldtarget) or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then -- lost LOS or dead laser:LaseOff() + self:T(self.lid.."Target Life Points: "..target:GetLife() or "none") if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then self:__Shack(-1,client,oldtarget) --self.LaserTarget[playername] = nil From 5cc3fd723852ad6eb125ea4ff5f6109f8a7f4eaf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 2 Jan 2025 17:23:38 +0100 Subject: [PATCH 114/349] xx --- Moose Development/Moose/Ops/PlayerRecce.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 0f3c1f556..7091139cd 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -106,7 +106,7 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.1.23", + version = "0.1.24", ViewZone = {}, ViewZoneVisual = {}, ViewZoneLaser = {}, @@ -934,7 +934,7 @@ function PLAYERRECCE:_LaseTarget(client,targetset) -- lost LOS or dead laser:LaseOff() self:T(self.lid.."Target Life Points: "..target:GetLife() or "none") - if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then + if target:IsDead() or target:IsDestroyed() or target:GetDamage() > 79 or target:GetLife() <= 1 then self:__Shack(-1,client,oldtarget) --self.LaserTarget[playername] = nil else From deb0431f939ef3a4d8cb0a3ed63c51efc4d74c42 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 4 Jan 2025 13:24:40 +0100 Subject: [PATCH 115/349] #CTLD - Clarify doc for Hook loadable stuff --- Moose Development/Moose/Core/Event.lua | 3 ++- Moose Development/Moose/Ops/CTLD.lua | 23 ++++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index fe620c26a..5c993e55f 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1361,7 +1361,8 @@ function EVENT:onEvent( Event ) Event.IniDynamicCargoName = Event.IniUnitName Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+") else - Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName ) + --Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName ) + Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false ) end Event.IniCoalition = Event.IniDCSUnit:getCoalition() Event.IniCategory = Event.IniDCSUnit:getDesc().category diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index bde535a5d..73d870cc3 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Dec 2024 +-- Last Update Jan 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -805,7 +805,7 @@ do -- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. -- my_ctld.PackDistance = 35 -- Pack crates in this radius only -- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. --- my_ctld.dropAsCargoCrate = false -- Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. +-- my_ctld.dropAsCargoCrate = false -- Hercules only: Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. -- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. @@ -833,7 +833,7 @@ do -- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. -- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. -- my_ctld.RadioSoundFC3 = "beacon.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. --- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIc types for CTLD for the Chinook +-- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIC types for CTLD for the Chinook -- my_ctld.TroopUnloadDistGround = 5 -- If hovering, spawn dropped troops this far away in meters from the helo -- my_ctld.TroopUnloadDistHover = 1.5 -- If grounded, spawn dropped troops this far away in meters from the helo -- my_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules @@ -848,10 +848,11 @@ do -- -- ## 2.1.1 Moose CTLD created crate cargo -- --- Given the correct shape, Moose created cargo can be either loaded with the ground crew or via the F10 CTLD menu. **It is strongly recommend to either use the ground crew or CTLD to load/unload Moose created cargo**. Mix and match will not work here. --- Static shapes loadable *into* the Chinook are at the time of writing: +-- Given the correct shape, Moose created cargo can theoretically be either loaded with the ground crew or via the F10 CTLD menu. **It is strongly stated to avoid using shapes with +-- CTLD which can be Ground Crew loaded.** +-- Static shapes loadable *into* the Chinook and thus to **be avoided for CTLD** are at the time of writing: -- --- * Ammo crate (type "ammo_cargo") +-- * Ammo box (type "ammo_crate") -- * M117 bomb crate (type name "m117_cargo") -- * Dual shell fuel barrels (type name "barrels") -- * UH-1H net (type name "uh1h_cargo") @@ -860,12 +861,12 @@ do -- -- ## 2.1.2 Recommended settings -- --- my_ctld.basetype = "ammo_cargo" +-- my_ctld.basetype = "container_cargo" -- **DO NOT** change this to a base type which could also be loaded by F8/GC to avoid logic problems! -- my_ctld.forcehoverload = false -- no hover autoload, leads to cargo complications with ground crew created cargo items --- my_ctld.pilotmustopendoors = true -- crew must open back loading door 50% (horizontal) or more --- my_ctld.enableslingload = true -- will set cargo items as sling-loadable --- my_ctld.enableChinookGCLoading = true -- will effectively suppress the crate load and drop menus for CTLD for the Chinook --- my_ctld.movecratesbeforebuild = false -- cannot detect movement of crates at the moment +-- my_ctld.pilotmustopendoors = true -- crew must open back loading door 50% (horizontal) or more - watch out for NOT adding a back door gunner! +-- my_ctld.enableslingload = true -- will set cargo items as sling-loadable. +-- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIC types for CTLD for the Chinook. +-- my_ctld.movecratesbeforebuild = true -- leave as is at the pain of building crate still **inside** of the Hook. -- my_ctld.nobuildinloadzones = true -- don't build where you load. -- my_ctld.ChinookTroopCircleRadius = 5 -- Radius for troops dropping in a nice circle. Adjust to your planned squad size for the Chinook. -- From a3d7c9e5372d9aac927fae72634e4f797ef31945 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 4 Jan 2025 18:22:36 +0100 Subject: [PATCH 116/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 48 ++++-- Moose Development/Moose/Utilities/Enums.lua | 139 ++++++++++++++++-- Moose Development/Moose/Wrapper/Storage.lua | 39 +++-- 3 files changed, 195 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 0124af4ba..f066d9fd3 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -60,6 +60,8 @@ -- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius. -- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range. -- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. +-- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. +-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White -- @extends Core.Base#BASE @@ -329,6 +331,8 @@ MANTIS = { autoshorad = true, ShoradGroupSet = nil, checkforfriendlies = false, + SmokeDecoy = false, + SmokeDecoyColor = SMOKECOLOR.White, } --- Advanced state enumerator @@ -590,7 +594,10 @@ do self.SkateZones = nil self.SkateNumber = 3 - self.shootandscoot = false + self.shootandscoot = false + + self.SmokeDecoy = false + self.SmokeDecoyColor = SMOKECOLOR.White self.UseEmOnOff = true if EmOnOff == false then @@ -602,6 +609,7 @@ do else self.advAwacs = false end + -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -663,7 +671,7 @@ do -- TODO Version -- @field #string version - self.version="0.8.22" + self.version="0.8.23" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -899,6 +907,16 @@ do return self end + --- Function to set Short Range SAMs to spit out smoke as decoy, if an enemy plane is in range. + -- @param #MANTIS self + -- @param #boolean Onoff Set to true for on and nil/false for off. + -- @param #number Color (Optional) Color to use, defaults to `SMOKECOLOR.White` + function MANTIS:SetSmokeDecoy(Onoff,Color) + self.SmokeDecoy = Onoff + self.SmokeDecoyColor = Color or SMOKECOLOR.White + return self + end + --- Function to set number of SAMs going active on a valid, detected thread -- @param #MANTIS self -- @param #number Short Number of short-range systems activated, defaults to 1. @@ -1550,18 +1568,18 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange,grpheight,type,blind = self:_GetSAMRange(grpname) - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) --table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG then - table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) --self:T("SAM "..grpname.." is type LONG") elseif type == MANTIS.SamType.MEDIUM then - table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) --self:T("SAM "..grpname.." is type MEDIUM") elseif type == MANTIS.SamType.SHORT then - table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) --self:T("SAM "..grpname.." is type SHORT") self.ShoradGroupSet:Add(grpname,group) if not self.autoshorad then @@ -1609,16 +1627,16 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind}) -- make the table lighter, as I don't really use the zone here + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG then - table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) --self:I({grpname,grprange, grpheight}) elseif type == MANTIS.SamType.MEDIUM then - table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) --self:I({grpname,grprange, grpheight}) elseif type == MANTIS.SamType.SHORT then - table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) --self:I({grpname,grprange, grpheight}) self.ShoradGroupSet:Add(grpname,group) if self.autoshorad then @@ -1688,6 +1706,7 @@ do local radius = _data[3] local height = _data[4] local blind = _data[5] * 1.25 + 1 + local shortsam = _data[6] == MANTIS.SamType.SHORT and true or false local samgroup = GROUP:FindByName(name) local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) local suppressed = self.SuppressedGroups[name] or false @@ -1713,6 +1732,15 @@ do self:__RedState(1,samgroup) self.SamStateTracker[name] = "RED" end + if shortsam == true and self.SmokeDecoy == true then + local units = samgroup:GetUnits() or {} + local smoke = self.SmokeDecoyColor or SMOKECOLOR.White + for _,unit in pairs(units) do + if unit and unit:IsAlive() then + unit:Smoke(smoke,2,2) + end + end + end -- link in to SHORAD if available -- DONE: Test integration fully if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index bc2e7fe3f..59df545e1 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -1166,6 +1166,125 @@ ENUMS.Storage.weapons.bombs.AGM_62 = "weapons.bombs.AGM_62" ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE = "weapons.containers.{US_M10_SMOKE_TANK_WHITE}" ENUMS.Storage.weapons.missiles.MICA_T = "weapons.missiles.MICA_T" ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket" +-- 2025 +ENUMS.Storage.weapons.containers.LANTIRN = "weapons.containers.LANTIRN" +ENUMS.Storage.weapons.missiles.AGM_78B = "weapons.missiles.AGM_78B" +ENUMS.Storage.weapons.containers.uh_60l_pilot = "weapons.containers.uh-60l_pilot" +ENUMS.Storage.weapons.missiles.AIM_92E = "weapons.missiles.AIM-92E" +ENUMS.Storage.weapons.missiles.KD_63B = "weapons.missiles.KD_63B" +ENUMS.Storage.weapons.bombs.Type_200A = "weapons.bombs.Type_200A" +ENUMS.Storage.weapons.missiles.HB_AIM_7E_2 = "weapons.missiles.HB-AIM-7E-2" +ENUMS.Storage.weapons.containers.Spear = "weapons.containers.Spear" +ENUMS.Storage.weapons.missiles.LS_6 = "weapons.missiles.LS_6" +ENUMS.Storage.weapons.containers.HB_ALE_40_0_120 = "weapons.containers.HB_ALE_40_0_120" +ENUMS.Storage.weapons.containers.Fantasm = "weapons.containers.Fantasm" +ENUMS.Storage.weapons.nurs.FFAR_Mk61 = "weapons.nurs.FFAR_Mk61" +ENUMS.Storage.weapons.bombs.HB_F4E_GBU15V1 = "weapons.bombs.HB_F4E_GBU15V1" +ENUMS.Storage.weapons.containers.HB_F14_EXT_AN_APQ_167 = "weapons.containers.HB_F14_EXT_AN_APQ-167" +ENUMS.Storage.weapons.nurs.LWL_RP = "weapons.nurs.LWL_RP" +ENUMS.Storage.weapons.bombs.AGM_62_I = "weapons.bombs.AGM_62_I" +ENUMS.Storage.weapons.containers.ETHER = "weapons.containers.ETHER" +ENUMS.Storage.weapons.containers.TANGAZH = "weapons.containers.TANGAZH" +ENUMS.Storage.weapons.bombs.LYSBOMB_11086 = "weapons.bombs.LYSBOMB 11086" +ENUMS.Storage.weapons.containers.Stub_Wing = "weapons.containers.Stub_Wing" +ENUMS.Storage.weapons.missiles.AIM_9E = "weapons.missiles.AIM-9E" +ENUMS.Storage.weapons.missiles.C_701T = "weapons.missiles.C_701T" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP_100" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.missiles.CM_400AKG = "weapons.missiles.CM-400AKG" +ENUMS.Storage.weapons.missiles.C_802AK = "weapons.missiles.C_802AK" +ENUMS.Storage.weapons.missiles.KD_63 = "weapons.missiles.KD_63" +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike_Fast = "weapons.containers.HB_ORD_Pave_Spike_Fast" +ENUMS.Storage.weapons.missiles.SPIKE_ER2 = "weapons.missiles.SPIKE_ER2" +ENUMS.Storage.weapons.containers.KINGAL = "weapons.containers.KINGAL" +ENUMS.Storage.weapons.containers.LANTIRN_F14_TARGET = "weapons.containers.LANTIRN-F14-TARGET" +ENUMS.Storage.weapons.containers.SPS_141 = "weapons.containers.SPS-141" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU-3B_GROUP" +ENUMS.Storage.weapons.containers.HB_ALE_40_30_0 = "weapons.containers.HB_ALE_40_30_0" +ENUMS.Storage.weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL = "weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL" +ENUMS.Storage.weapons.containers.ALQ_184 = "weapons.containers.ALQ-184" +ENUMS.Storage.weapons.missiles.AGM_45B = "weapons.missiles.AGM_45B" +ENUMS.Storage.weapons.bombs.BLU_3_GROUP = "weapons.bombs.BLU-3_GROUP" +ENUMS.Storage.weapons.missiles.SPIKE_ER = "weapons.missiles.SPIKE_ER" +ENUMS.Storage.weapons.nurs.ARAKM70BAPPX = "weapons.nurs.ARAKM70BAPPX" +ENUMS.Storage.weapons.bombs.LYSBOMB_11088 = "weapons.bombs.LYSBOMB 11088" +ENUMS.Storage.weapons.bombs.LYSBOMB_11087 = "weapons.bombs.LYSBOMB 11087" +ENUMS.Storage.weapons.missiles.KD_20 = "weapons.missiles.KD_20" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank = "weapons.droptanks.HB_F-4E_EXT_WingTank" +ENUMS.Storage.weapons.missiles.Rb_04 = "weapons.missiles.Rb_04" +ENUMS.Storage.weapons.containers.AAQ_33 = "weapons.containers.AAQ-33" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank_EMPTY = "weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank_EMPTY" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R_EMPTY = "weapons.droptanks.HB_F-4E_EXT_WingTank_R_EMPTY" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_EMPTY = "weapons.droptanks.HB_F-4E_EXT_WingTank_EMPTY" +ENUMS.Storage.weapons.containers.uh_60l_copilot = "weapons.containers.uh-60l_copilot" +ENUMS.Storage.weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2 = "weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2" +ENUMS.Storage.weapons.containers.supply_m134 = "weapons.containers.supply_m134" +ENUMS.Storage.weapons.containers.Seahawk_Pylon = "weapons.containers.Seahawk_Pylon" +ENUMS.Storage.weapons.nurs.LWL_MPP = "weapons.nurs.LWL_MPP" +ENUMS.Storage.weapons.nurs.S_5KP = "weapons.nurs.S_5KP" +ENUMS.Storage.weapons.missiles.AIM_92J = "weapons.missiles.AIM-92J" +ENUMS.Storage.weapons.missiles.HB_AIM_7E = "weapons.missiles.HB-AIM-7E" +ENUMS.Storage.weapons.containers.ALQ_131 = "weapons.containers.ALQ-131" +ENUMS.Storage.weapons.containers.HB_F14_EXT_TARPS = "weapons.containers.HB_F14_EXT_TARPS" +ENUMS.Storage.weapons.containers.MH60_SOAR = "weapons.containers.MH60_SOAR" +ENUMS.Storage.weapons.missiles.YJ_83 = "weapons.missiles.YJ-83" +ENUMS.Storage.weapons.bombs.GBU_8_B = "weapons.bombs.GBU_8_B" +ENUMS.Storage.weapons.containers.HB_F14_EXT_ECA = "weapons.containers.HB_F14_EXT_ECA" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100" +ENUMS.Storage.weapons.nurs.M261_MPSM_Rocket = "weapons.nurs.M261_MPSM_Rocket" +ENUMS.Storage.weapons.droptanks.SEAHAWK_120_Fuel_Tank = "weapons.droptanks.SEAHAWK_120_Fuel_Tank" +ENUMS.Storage.weapons.containers.SHPIL = "weapons.containers.SHPIL" +ENUMS.Storage.weapons.bombs.GBU_39 = "weapons.bombs.GBU_39" +ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M" +ENUMS.Storage.weapons.containers.HB_ALE_40_15_90 = "weapons.containers.HB_ALE_40_15_90" +ENUMS.Storage.weapons.missiles.AIM_7E = "weapons.missiles.AIM-7E" +ENUMS.Storage.weapons.missiles.AIM_9P3 = "weapons.missiles.AIM-9P3" +ENUMS.Storage.weapons.missiles.AGM_12B = "weapons.missiles.AGM_12B" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank = "weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank" +ENUMS.Storage.weapons.containers.PAVETACK = "weapons.containers.PAVETACK" +ENUMS.Storage.weapons.missiles.LS_6_500 = "weapons.missiles.LS_6_500" +ENUMS.Storage.weapons.bombs.LYSBOMB_11089 = "weapons.bombs.LYSBOMB 11089" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU-4B_GROUP" +ENUMS.Storage.weapons.containers.ah_64d_radar = "weapons.containers.ah-64d_radar" +ENUMS.Storage.weapons.containers.F_18_LDT_POD = "weapons.containers.F-18-LDT-POD" +ENUMS.Storage.weapons.containers.HB_ALE_40_30_60 = "weapons.containers.HB_ALE_40_30_60" +ENUMS.Storage.weapons.bombs.LS_6_100 = "weapons.bombs.LS_6_100" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R = "weapons.droptanks.HB_F-4E_EXT_WingTank_R" +ENUMS.Storage.weapons.containers.SORBCIJA_R = "weapons.containers.SORBCIJA_R" +ENUMS.Storage.weapons.missiles.CATM_65K = "weapons.missiles.CATM_65K" +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike = "weapons.containers.HB_ORD_Pave_Spike" +ENUMS.Storage.weapons.containers.RobbieTank1 = "weapons.containers.RobbieTank1" +ENUMS.Storage.weapons.containers.SKY_SHADOW = "weapons.containers.SKY_SHADOW" +ENUMS.Storage.weapons.containers.SORBCIJA_L = "weapons.containers.SORBCIJA_L" +ENUMS.Storage.weapons.containers.Pavehawk = "weapons.containers.Pavehawk" +ENUMS.Storage.weapons.bombs.BLG66_EG = "weapons.bombs.BLG66_EG" +ENUMS.Storage.weapons.missiles.AGM_12C_ED = "weapons.missiles.AGM_12C_ED" +ENUMS.Storage.weapons.missiles.AIM_92C = "weapons.missiles.AIM-92C" +ENUMS.Storage.weapons.containers.MPS_410 = "weapons.containers.MPS-410" +ENUMS.Storage.weapons.missiles.HJ_12 = "weapons.missiles.HJ-12" +ENUMS.Storage.weapons.containers.AAQ_28_LITENING = "weapons.containers.AAQ-28_LITENING" +ENUMS.Storage.weapons.containers.F_18_FLIR_POD = "weapons.containers.F-18-FLIR-POD" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.containers.UH60L_Jayhawk = "weapons.containers.UH60L_Jayhawk" +ENUMS.Storage.weapons.containers.BOZ_100 = "weapons.containers.BOZ-100" +ENUMS.Storage.weapons.missiles.AGM_78A = "weapons.missiles.AGM_78A" +ENUMS.Storage.weapons.missiles.LAU_61_APKWS_M282 = "weapons.missiles.LAU_61_APKWS_M282" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU-4B_GROUP" +ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M" +ENUMS.Storage.weapons.missiles.AGM_12A = "weapons.missiles.AGM_12A" +ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Tank" +ENUMS.Storage.weapons.bombs.GBU_15_V_1_B = "weapons.bombs.GBU_15_V_1_B" +-- dupes with typos +ENUMS.Storage.weapons.bombs.BAP100 = "weapons.bombs.BAP_100" +ENUMS.Storage.weapons.bombs.BLU3B_GROUP = "weapons.bombs.BLU-3B_GROUP" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU_4B_GROUP" +ENUMS.Storage.weapons.nurs.S5M = "weapons.nurs.S-5M" -- Gazelle ENUMS.Storage.weapons.Gazelle.HMP400_100RDS = {4,15,46,1771} ENUMS.Storage.weapons.Gazelle.HMP400_200RDS = {4,15,46,1770} @@ -1177,16 +1296,16 @@ ENUMS.Storage.weapons.Gazelle.GIAT_M261_HEAP = {4,15,46,1765} ENUMS.Storage.weapons.Gazelle.GIAT_M261_APHE = {4,15,46,1764} ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680} ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679} --- Chinook -ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2476} -ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2477} -ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2478} -ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2482} -ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2483} -ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2484} -ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2479} -ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2480} -ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2481} +-- Chinook (changed) +ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2489} +ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2488} +ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2490} +ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2494} +ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2495} +ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2496} -- +ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2492} +ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2491} +ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2493} -- Huey ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right = {4,15,46,161} ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left = {4,15,46,160} diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 558368e98..3acaa9d16 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -203,7 +203,7 @@ STORAGE.Type = { --- STORAGE class version. -- @field #string version -STORAGE.version="0.1.4" +STORAGE.version="0.1.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -231,7 +231,7 @@ function STORAGE:New(AirbaseName) self.warehouse=self.airbase:getWarehouse() end - self.lid = string.format("STORAGE %s", AirbaseName) + self.lid = string.format("STORAGE %s | ", AirbaseName) return self end @@ -251,7 +251,7 @@ function STORAGE:NewFromStaticCargo(StaticCargoName) self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) end - self.lid = string.format("STORAGE %s", StaticCargoName) + self.lid = string.format("STORAGE %s | ", StaticCargoName) return self end @@ -271,7 +271,7 @@ function STORAGE:NewFromDynamicCargo(DynamicCargoName) self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) end - self.lid = string.format("STORAGE %s", DynamicCargoName) + self.lid = string.format("STORAGE %s | ", DynamicCargoName) return self end @@ -656,7 +656,6 @@ function STORAGE:SaveToFile(Path,Filename) for key,amount in pairs(lq) do DataLiquids = DataLiquids..tostring(key).."="..tostring(amount).."\n" end - --self:I(DataLiquids) UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids) if self.verbose and self.verbose > 0 then self:I(self.lid.."Saving Liquids to "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") @@ -668,7 +667,6 @@ function STORAGE:SaveToFile(Path,Filename) for key,amount in pairs(ac) do DataAircraft = DataAircraft..tostring(key).."="..tostring(amount).."\n" end - --self:I(DataAircraft) UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft) if self.verbose and self.verbose > 0 then self:I(self.lid.."Saving Aircraft to "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") @@ -677,9 +675,17 @@ function STORAGE:SaveToFile(Path,Filename) if UTILS.TableLength(wp) > 0 then DataWeapons = DataWeapons .."Weapons and Materiel in Storage:\n" - for key,amount in pairs(wp) do - DataWeapons = DataWeapons..tostring(key).."="..tostring(amount).."\n" + + for _,_category in pairs(ENUMS.Storage.weapons) do + for _,_key in pairs(_category) do + local amount = self:GetAmount(_key) + if type(_key) == "table" then + _key = "{"..table.concat(_key,",").."}" + end + DataWeapons = DataWeapons..tostring(_key).."="..tostring(amount).."\n" + end end + -- Gazelle table keys for key,amount in pairs(ENUMS.Storage.weapons.Gazelle) do amount = self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key]) @@ -705,7 +711,6 @@ function STORAGE:SaveToFile(Path,Filename) amount = self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key]) DataWeapons = DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n" end - --self:I(DataAircraft) UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons) if self.verbose and self.verbose > 0 then self:I(self.lid.."Saving Weapons to "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") @@ -777,14 +782,26 @@ function STORAGE:LoadFromFile(Path,Filename) local Ok,Weapons = UTILS.LoadFromFile(Path,Filename.."_Weapons.csv") if Ok then if self.verbose and self.verbose > 0 then - self:I(self.lid.."Loading _eapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") + self:I(self.lid.."Loading Weapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end for _id,_line in pairs(Weapons) do if string.find(_line,"Storage") == nil then local tbl=UTILS.Split(_line,"=") local wpname = tbl[1] local wpnumber = tonumber(tbl[2]) - self:SetAmount(wpname,wpnumber) + if string.find(wpname,"{") == 1 then + --self:I("Found a table: "..wpname) + wpname = string.gsub(wpname,"{","") + wpname = string.gsub(wpname,"}","") + local tbl = UTILS.Split(wpname,",") + local wptbl = {} + for _id,_key in ipairs(tbl) do + table.insert(wptbl,_id,_key) + end + self:SetAmount(wptbl,wpnumber) + else + self:SetAmount(wpname,wpnumber) + end end end else From a2c241da439b3f33165ef663304a957cbe395ec6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 5 Jan 2025 17:43:21 +0100 Subject: [PATCH 117/349] xx --- Moose Development/Moose/Core/ClientMenu.lua | 12 ++++++---- Moose Development/Moose/Core/Point.lua | 3 +++ Moose Development/Moose/Functional/Mantis.lua | 3 ++- Moose Development/Moose/Ops/CSAR.lua | 23 +++++++++++++++---- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Core/ClientMenu.lua b/Moose Development/Moose/Core/ClientMenu.lua index 59ec7169c..ee0b7e6f2 100644 --- a/Moose Development/Moose/Core/ClientMenu.lua +++ b/Moose Development/Moose/Core/ClientMenu.lua @@ -20,7 +20,7 @@ -- -- @module Core.ClientMenu -- @image Core_Menu.JPG --- last change: May 2024 +-- last change: Jan 2025 -- TODO ---------------------------------------------------------------------------------------------------------------- @@ -59,7 +59,7 @@ CLIENTMENU = { ClassName = "CLIENTMENUE", lid = "", - version = "0.1.2", + version = "0.1.3", name = nil, path = nil, group = nil, @@ -455,7 +455,7 @@ end -- @param #CLIENTMENUMANAGER self -- @param Core.Event#EVENTDATA EventData -- @return #CLIENTMENUMANAGER self -function CLIENTMENUMANAGER:_EventHandler(EventData) +function CLIENTMENUMANAGER:_EventHandler(EventData,Retry) self:T(self.lid.."_EventHandler: "..EventData.id) --self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName)) if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then @@ -468,6 +468,10 @@ function CLIENTMENUMANAGER:_EventHandler(EventData) if EventData.IniPlayerName and EventData.IniGroup then if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) + if not Retry then + -- try again in 2 secs + self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true) + end return self end --self:I(self.lid.."Join event for player: "..EventData.IniPlayerName) @@ -524,7 +528,7 @@ function CLIENTMENUMANAGER:InitAutoPropagation() self:HandleEvent(EVENTS.PilotDead, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:SetEventPriority(5) + self:SetEventPriority(6) return self end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 964f8e1ee..7f2b3d558 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -970,6 +970,9 @@ do -- COORDINATE if not TargetCoordinate then return 1000000 end --local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} local a = self:GetVec2() + if not TargetCoordinate.ClassName then + TargetCoordinate=COORDINATE:NewFromVec3(TargetCoordinate) + end local b = TargetCoordinate:GetVec2() local norm=UTILS.VecDist2D(a,b) return norm diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index f066d9fd3..28f511a91 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1733,11 +1733,12 @@ do self.SamStateTracker[name] = "RED" end if shortsam == true and self.SmokeDecoy == true then + self:I("Smoking") local units = samgroup:GetUnits() or {} local smoke = self.SmokeDecoyColor or SMOKECOLOR.White for _,unit in pairs(units) do if unit and unit:IsAlive() then - unit:Smoke(smoke,2,2) + unit:GetCoordinate():Smoke(smoke) end end end diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 4bf18ac40..8cc55125c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Sep 2024 +-- Last Update Jan 2025 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -92,7 +92,7 @@ -- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal. -- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- mycsar.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. --- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. +-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. Will also try to add ZONE and STATIC objects with this prefix once at startup. -- mycsar.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- mycsar.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. -- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. @@ -313,7 +313,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.29" +CSAR.version="1.0.30" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -2425,7 +2425,22 @@ function CSAR:onafterStart(From, Event, To) self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end - self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also? + self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() + + local staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce() + local zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce() + + if staticmashes:Count() > 0 then + for _,_mash in pairs(staticmashes.Set) do + self.mash:AddObject(_mash) + end + end + + if zonemashes:Count() > 0 then + for _,_mash in pairs(zonemashes.Set) do + self.mash:AddObject(_mash) + end + end if not self.coordinate then local csarhq = self.mash:GetRandom() From 6adb9f0839ae7f1319b9b04c2ba1235ae4e50aac Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 6 Jan 2025 17:31:03 +0100 Subject: [PATCH 118/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index dc402157d..ab45fa6c7 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -350,7 +350,7 @@ end --- Set Maximum of alive missions to stop airplanes spamming the map -- @param #EASYGCICAP self --- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Cap-Missions + Intercept-Missions + Alert5-Missionsm default is 6 +-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Cap-Missions + Intercept-Missions + Alert5-Missionsm default is 8 -- @return #EASYGCICAP self function EASYGCICAP:SetMaxAliveMissions(Maxiumum) self:T(self.lid.."SetMaxAliveMissions") @@ -1177,7 +1177,7 @@ function EASYGCICAP:_AssignIntercept(Cluster) local wings = self.wings local ctlpts = self.ManagedCP - local MaxAliveMissions = self.MaxAliveMissions * self.capgrouping + local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping local nogozoneset = self.NoGoZoneSet local ReadyFlightGroups = self.ReadyFlightGroups From a33e542df5fca1273f7f6b2b4258d47bd5ff550b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 8 Jan 2025 13:04:54 +0100 Subject: [PATCH 119/349] xx --- Moose Development/Moose/Core/Set.lua | 7 ++ .../Moose/Functional/Tiresias.lua | 98 +++++++++++++++---- Moose Development/Moose/Ops/EasyGCICAP.lua | 27 ++++- 3 files changed, 107 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 96b629660..5cd4d8681 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1538,6 +1538,13 @@ do local size = 1 if Event.IniDCSGroup then size = Event.IniDCSGroup:getSize() + elseif Event.IniDCSGroupName then + local grp = Group.getByName(Event.IniDCSGroupName) + if grp then + size = grp:getSize() + end + elseif Object:IsAlive() then + size = Object:CountAliveUnits() end if size == 1 then -- Only remove if the last unit of the group was destroyed. self:Remove( ObjectName ) diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index 83cc02bba..a459bacf0 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -27,13 +27,13 @@ -- @module Functional.Tiresias -- @image Functional.Tiresias.jpg -- --- Last Update: Dec 2023 +-- Last Update: Jan 2024 ------------------------------------------------------------------------- --- **TIRESIAS** class, extends Core.Base#BASE -- @type TIRESIAS -- @field #string ClassName --- @field #booelan debug +-- @field #boolean debug -- @field #string version -- @field #number Interval -- @field Core.Set#SET_GROUP GroundSet @@ -47,7 +47,8 @@ -- @field #number HeloSwitchRange -- @field #number PlaneSwitchRange -- @field Core.Set#SET_GROUP FlightSet --- @field #boolean SwitchAAA +-- @field #boolean SwitchAAA +-- @field #boolean SwitchSAM -- @extends Core.Fsm#FSM --- @@ -97,7 +98,7 @@ TIRESIAS = { ClassName = "TIRESIAS", debug = false, - version = "0.0.5", + version = "0.0.6", Interval = 20, GroundSet = nil, VehicleSet = nil, @@ -107,7 +108,10 @@ TIRESIAS = { AAARange = 60, -- 60% HeloSwitchRange = 10, -- NM PlaneSwitchRange = 25, -- NM + SAMSwitchRange = 75, --NM SwitchAAA = true, + SwitchSAM = false, + MantisHandlingSAM = true } --- [USER] Create a new Tiresias object and start it up. @@ -129,7 +133,7 @@ function TIRESIAS:New() self:AddTransition("*", "Status", "*") -- TIRESIAS status update. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - self.ExceptionSet = SET_GROUP:New():Clear(false) + --self.ExceptionSet = SET_GROUP:New():Clear(false) self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) @@ -169,10 +173,12 @@ end -- @param #TIRESIAS self -- @param #number HeloMiles Radius around a Helicopter in which AI ground units will be activated. Defaults to 10NM. -- @param #number PlaneMiles Radius around an Airplane in which AI ground units will be activated. Defaults to 25NM. +-- @param #number SAMMiles Radius around an Airplane in which SAM AI ground units will be activated. Defaults to 75NM. -- @return #TIRESIAS self -function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles) +function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles,SAMMiles) self.HeloSwitchRange = HeloMiles or 10 self.PlaneSwitchRange = PlaneMiles or 25 + self.SAMSwitchRange = SAMMiles or 75 return self end @@ -187,12 +193,28 @@ function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA) return self end +--- [USER] Set if TIRESIAS is also taking care or SAM installtions. +-- @param #TIRESIAS self +-- @param #boolean OnOff Set to true to switch on, false to switch off. Default is false. +-- @param #boolean MantisPresent Set to true to switch on, false to switch off. Default is true. +-- @return #TIRESIAS self +function TIRESIAS:SetSwitchSAM(OnOff,MantisPresent) + if OnOff == nil or OnOff == false then + self.SwitchSAM = false + else + self.SwitchSAM = true + end + self.MantisHandlingSAM = MantisPresent or true + return self +end + --- [USER] Add a SET_GROUP of GROUP objects as exceptions. Can be done multiple times. Does **not** work work for GROUP objects spawned into the SET after start, i.e. the groups need to exist in the game already. -- @param #TIRESIAS self -- @param Core.Set#SET_GROUP Set to add to the exception list. -- @return #TIRESIAS self function TIRESIAS:AddExceptionSet(Set) self:T(self.lid.."AddExceptionSet") + if not self.ExceptionSet then self.ExceptionSet = SET_GROUP:New():Clear(false) end local exceptions = self.ExceptionSet Set:ForEachGroupAlive( function(grp) @@ -242,9 +264,9 @@ function TIRESIAS._FilterAAA(Group) local grp = Group -- Wrapper.Group#GROUP local isaaa = grp:IsAAA() if isaaa == true and grp:IsGround() and not grp:IsShip() then - return true -- remove from SET + return true -- keep in SET else - return false -- keep in SET + return false -- remove from SET end end @@ -255,9 +277,9 @@ function TIRESIAS._FilterSAM(Group) local grp = Group -- Wrapper.Group#GROUP local issam = grp:IsSAM() if issam == true and grp:IsGround() and not grp:IsShip() then - return true -- remove from SET + return true -- keep in SET else - return false -- keep in SET + return false -- remove from SET end end @@ -269,6 +291,7 @@ function TIRESIAS:_InitGroups() -- Set all groups invisible/motionless local EngageRange = self.AAARange local SwitchAAA = self.SwitchAAA + local SwitchSAM = self.SwitchSAM --- AAA self.AAASet:ForEachGroupAlive( function(grp) @@ -287,7 +310,7 @@ function TIRESIAS:_InitGroups() AIOff = SwitchAAA, } end - if grp.Tiresias and (not grp.Tiresias.exception == true) then + if grp.Tiresias and (grp.Tiresias.exception == false) then if grp.Tiresias.invisible and grp.Tiresias.invisible == false then grp:SetCommandInvisible(true) grp.Tiresias.invisible = true @@ -332,16 +355,18 @@ function TIRESIAS:_InitGroups() grp.Tiresias = { -- #TIRESIAS.Data type = "SAM", invisible = true, - exception = false, + exception = not SwitchSAM, + AIOff = SwitchSAM, } end - if grp.Tiresias and (not grp.Tiresias.exception == true) then + if grp.Tiresias and (grp.Tiresias.exception == false) then if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then grp:SetCommandInvisible(true) grp.Tiresias.invisible = true + grp:SetAIOnOff(SwitchSAM) end end - --BASE:I(string.format("Init/Switch off SAM %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) + BASE:I(string.format("Init/Switch off SAM %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) end ) return self @@ -381,18 +406,22 @@ end function TIRESIAS:_SwitchOnGroups(group,radius) self:T(self.lid.."_SwitchOnGroups "..group:GetName().." Radius "..radius.." NM") local zone = ZONE_GROUP:New("Zone-"..group:GetName(),group,UTILS.NMToMeters(radius)) + local samzone = ZONE_GROUP:New("ZoneSAM-"..group:GetName(),group,UTILS.NMToMeters(self.SAMSwitchRange)) local ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() + local sam = SET_GROUP:New():FilterFunction(self._FilterSAM):FilterZones({samzone}):FilterOnce() local count = ground:CountAlive() if self.debug then local text = string.format("There are %d groups around this plane or helo!",count) self:I(text) end local SwitchAAA = self.SwitchAAA + local SwitchSAM = self.SwitchSAM + local MantisHandling = self.MantisHandlingSAM if ground:CountAlive() > 0 then ground:ForEachGroupAlive( function(grp) local name = grp:GetName() - if grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then + if grp.Tiresias and grp.Tiresias.exception and (grp.Tiresias.exception == false ) then if grp.Tiresias.invisible == true then grp:SetCommandInvisible(false) grp.Tiresias.invisible = false @@ -401,18 +430,39 @@ function TIRESIAS:_SwitchOnGroups(group,radius) grp:SetAIOn() grp.Tiresias.AIOff = false end - if SwitchAAA and grp.Tiresias.type == "AAA" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then + if (SwitchAAA) and (grp.Tiresias.type == "AAA") and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then grp:SetAIOn() grp:EnableEmission(true) grp.Tiresias.AIOff = false - end - --BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception))) + end + BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception))) else BASE:T("TIRESIAS - This group "..tostring(name).. " has not been initialized or is an exception!") end end ) end + if sam:CountAlive() > 0 and self.SwitchSAM == true then + sam:ForEachGroupAlive( + function(grp) + local name = grp:GetName() + BASE:I(string.format("%s - %s",name,tostring(SwitchSAM))) + UTILS.PrintTableToLog(grp.Tiresias) + if SwitchSAM and grp.Tiresias.type == "SAM" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then + BASE:I("First check passed") + if grp.Tiresias.exception ~= true then + BASE:I("Second check passed") + grp:SetAIOn() + if not MantisHandling then + grp:EnableEmission(true) + end + grp.Tiresias.AIOff = false + BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception))) + end + end + end + ) + end return self end @@ -502,14 +552,22 @@ function TIRESIAS:onafterStart(From, Event, To) end end + local SwitchSAM = self.SwitchSAM + local MantisManages = self.MantisHandlingSAM + function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object) if Object and Object:IsAlive() then BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName()) Object:SetCommandInvisible(true) + if SwitchSAM then + Object:SetAIOff() + Object:EnableEmission(not MantisManages) + end Object.Tiresias = { -- #TIRESIAS.Data type = "SAM", invisible = true, - exception = false, + exception = not SwitchSAM, + AIOff = SwitchSAM, } end end @@ -547,7 +605,7 @@ end -- @return #TIRESIAS self function TIRESIAS:onafterStatus(From, Event, To) self:T({From, Event, To}) - if self.debug then + if self.debug then local count = self.VehicleSet:CountAlive() local AAAcount = self.AAASet:CountAlive() local SAMcount = self.SAMSet:CountAlive() diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index ab45fa6c7..93f6fcf3b 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -348,9 +348,23 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch) return self end ---- Set Maximum of alive missions to stop airplanes spamming the map +--- Count alive missions in our internal stack. -- @param #EASYGCICAP self --- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Cap-Missions + Intercept-Missions + Alert5-Missionsm default is 8 +-- @return #number count +function EASYGCICAP:_CountAliveAuftrags() + local alive = 0 + 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 + alive = alive + 1 + end + end + return alive +end + +--- Set Maximum of alive missions created by this instance to stop airplanes spamming the map +-- @param #EASYGCICAP self +-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Intercept-Missions + Alert5-Missions, default is 8 -- @return #EASYGCICAP self function EASYGCICAP:SetMaxAliveMissions(Maxiumum) self:T(self.lid.."SetMaxAliveMissions") @@ -1242,9 +1256,10 @@ function EASYGCICAP:_AssignIntercept(Cluster) -- 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 AssetCount <= MaxAliveMissions then + if missioncount < MaxAliveMissions then local repeats = repeatsonfailure local InterceptAuftrag = AUFTRAG:NewINTERCEPT(contact.group) :SetMissionRange(150) @@ -1429,12 +1444,14 @@ function EASYGCICAP:onafterStatus(From,Event,To) local text = "GCICAP "..self.alias text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock text = text.."\nThreats: "..threatcount - text = text.."\nMissions: "..capmission+interceptmission + text = text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission text = text.."\n - CAP: "..capmission - text = text.."\n - Intercept: "..interceptmission text = text.."\n - AWACS: "..awacsmission text = text.."\n - TANKER: "..tankermission text = text.."\n - Recon: "..reconmission + text = text.."\nSelf managed Missions:" + text = text.."\n - Mission Limit: "..self.MaxAliveMissions + text = text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags() MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug) end self:__Status(30) From f009370e07efc80bb89ba7dafa652c085c92493a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Jan 2025 14:15:09 +0100 Subject: [PATCH 120/349] xx --- Moose Development/Moose/Sound/SRS.lua | 135 +++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 3726f309f..1d16e5b6b 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -267,6 +267,135 @@ MSRS.version="0.3.3" --- Voices -- @type MSRS.Voices MSRS.Voices = { + Amazon = { + Generative = { + en_AU = { + Olivia = "Olivia", + }, + en_GB = { + Amy = "Amy", + }, + en_US = { + Danielle = "Danielle", + Joanna = "Joanna", + Ruth = "Ruth", + Stephen = "Stephen", + }, + fr_FR = { + ["Léa"] = "Léa", + ["Rémi"] = "Rémi", + }, + de_DE = { + Vicki = "Vicki", + Daniel = "Daniel", + }, + it_IT = { + Bianca = "Bianca", + Adriano = "Adriano", + }, + es_ES = { + Lucia = "Lucia", + Sergio = "Sergio", + }, + }, + LongForm = { + en_US = { + Danielle = "Danielle", + Gregory = "Gregory", + Ivy = "Ivy", + Ruth = "Ruth", + Patrick = "Patrick", + }, + es_ES = { + Alba = "Alba", + ["Raúl"] = "Raúl", + }, + }, + Neural = { + en_AU = { + Olivia = "Olivia", + }, + en_GB = { + Amy = "Amy", + Emma = "Emma", + Brian = "Brian", + Arthur = "Arthur", + }, + en_US = { + Danielle = "Danielle", + Gregory = "Gregory", + Ivy = "Ivy", + Joanna = "Joanna", + Kendra = "Kendra", + Kimberly = "Kimberly", + Salli = "Salli", + Joey = "Joey", + Kevin = "Kevin", + Ruth = "Ruth", + Stephen = "Stephen", + }, + fr_FR = { + ["Léa"] = "Léa", + ["Rémi"] = "Rémi", + }, + de_DE = { + Vicki = "Vicki", + Daniel = "Daniel", + }, + it_IT = { + Bianca = "Bianca", + Adriano = "Adriano", + }, + es_ES = { + Lucia = "Lucia", + Sergio = "Sergio", + }, + }, + Standard = { + en_AU = { + Nicole = "Nicole", + Russel = "Russel", + }, + en_GB = { + Amy = "Amy", + Emma = "Emma", + Brian = "Brian", + }, + en_IN = { + Aditi = "Aditi", + Raveena = "Raveena", + }, + en_US = { + Ivy = "Ivy", + Joanna = "Joanna", + Kendra = "Kendra", + Kimberly = "Kimberly", + Salli = "Salli", + Joey = "Joey", + Kevin = "Kevin", + }, + fr_FR = { + Celine = "Celine", + ["Léa"] = "Léa", + Mathieu = "Mathieu", + }, + de_DE = { + Marlene = "Marlene", + Vicki = "Vicki", + Hans = "Hans", + }, + it_IT = { + Carla = "Carla", + Bianca = "Bianca", + Giorgio = "Giorgio", + }, + es_ES = { + Conchita = "Conchita", + Lucia = "Lucia", + Enrique = "Enrique", + }, + }, + }, Microsoft = { -- working ones if not using gRPC and MS ["Hedda"] = "Microsoft Hedda Desktop", -- de-DE ["Hazel"] = "Microsoft Hazel Desktop", -- en-GB @@ -974,7 +1103,7 @@ end -- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default) -- - `MSRS.Provider.GOOGLE`: Google Cloud -- - `MSRS.Provider.AZURE`: Microsoft Azure (only with DCS-gRPC backend) --- - `MSRS.Provier.AMAZON`: Amazone Web Service (only with DCS-gRPC backend) +-- - `MSRS.Provier.AMAZON`: Amazon Web Service (only with DCS-gRPC backend) -- -- Note that all providers except Microsoft Windows need as additonal information the credentials of your account. -- @@ -1442,7 +1571,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp elseif self.provider==MSRS.Provider.WINDOWS then -- Nothing to do. else - self:E("ERROR: SRS only supports WINWOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as ") + self:E("ERROR: SRS only supports WINDOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as AWS and Azure.") end if not UTILS.FileExists(fullPath) then @@ -1660,7 +1789,7 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab ssml=string.format("%s", gender, language, Text) end end - + for _,freq in pairs(Frequencies) do self:T("Calling GRPC.tts with the following parameter:") self:T({ssml=ssml, freq=freq, options=options}) From 382ad6ecd88e85d1c00f05ed8725ca319a2c2718 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 12 Jan 2025 17:02:40 +0100 Subject: [PATCH 121/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 73d870cc3..26295b17a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1352,7 +1352,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.22" +CTLD.version="1.1.23" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5583,11 +5583,12 @@ end if PreciseLocation then randomcoord = zone:GetCoordinate():GetVec2() end + local randompositions = not PreciseLocation for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitRandomizeUnits(true,20,2) + :InitRandomizeUnits(randompositions,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then From 8ffa1bd6927eaee6ce66f19946fd3bd64e1722ef Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 13 Jan 2025 11:46:07 +0100 Subject: [PATCH 122/349] #AWACS - Fix a logic error: cancel the Escort mission when the replacement has arrived - Workaround for a situation where a player has a checkin-entry in the menu and a managed group entry at the same time - which should not be the case. --- Moose Development/Moose/Ops/Awacs.lua | 48 ++++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 5c2c015a0..dee19c2ec 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.68", -- #string + version = "0.2.69", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -2170,8 +2170,10 @@ end --- [User] Set AWACS Escorts Template -- @param #AWACS self -- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts. +-- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group` +-- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=500,y=0,z=500}` - 500m behind and to the left, no vertical separation. -- @return #AWACS self -function AWACS:SetEscort(EscortNumber) +function AWACS:SetEscort(EscortNumber,Formation,OffsetVector) self:T(self.lid.."SetEscort") if EscortNumber and EscortNumber > 0 then self.HasEscorts = true @@ -2180,6 +2182,8 @@ function AWACS:SetEscort(EscortNumber) self.HasEscorts = false self.EscortNumber = 0 end + self.EscortFormation = Formation + self.OffsetVec = OffsetVector or {x=500,y=0,z=500} return self end @@ -2234,12 +2238,24 @@ function AWACS:_StartEscorts(Shiftchange) local group = AwacsFG:GetGroup() local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds + local OffsetX = 500 + local OffsetY = 500 + local OffsetZ = 500 + if self.OffsetVec then + OffsetX = self.OffsetVec.x + OffsetY = self.OffsetVec.y + OffsetZ = self.OffsetVec.z + end for i=1,self.EscortNumber do - -- every - local escort = AUFTRAG:NewESCORT(group, {x= -100*((i + (i%2))/2), y=0, z=(100 + 100*((i + (i%2))/2))*(-1)^i},45,{"Air"}) + -- every + local escort = AUFTRAG:NewESCORT(group, {x= -OffsetX*((i + (i%2))/2), y=OffsetY, z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},45,{"Air"}) escort:SetRequiredAssets(1) escort:SetTime(nil,timeonstation) + if self.Escortformation then + escort:SetFormation(self.Escortformation) + end escort:SetMissionRange(self.MaxMissionRange) + self.AirWing:AddMission(escort) self.CatchAllMissions[#self.CatchAllMissions+1] = escort @@ -3642,7 +3658,7 @@ function AWACS:_CheckIn(Group) managedgroup.LastTasking = timer.getTime() GID = managedgroup.GID - self.ManagedGrps[self.ManagedGrpID]=managedgroup + self.ManagedGrps[self.ManagedGrpID]=managedgroup local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate()) local alphacheckbullstts = self:_ToStringBULLS(Group:GetCoordinate(),false,true) @@ -3909,6 +3925,12 @@ function AWACS:_SetClientMenus() checkin = checkin, } self.clientmenus:Push(menus,cgrpname) + -- catch errors - when this entry is built we should NOT have a managed entry + local GID,hasentry = self:_GetManagedGrpID(cgrp) + if hasentry then + -- this user is checked in but has the check in entry ... not good. + self:_CheckOut(cgrp,GID,true) + end end end else @@ -6065,6 +6087,7 @@ function AWACS:_CheckAwacsStatus() end end end + -------------------------------- -- AWACS -------------------------------- @@ -6213,12 +6236,13 @@ function AWACS:_CheckAwacsStatus() report:Add("====================") + local RESMission -- Check for replacement mission - if any if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG - ESmission = self.EscortMissionReplacement[i] - local esstatus = ESmission:GetState() - local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp) - local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds + RESMission = self.EscortMissionReplacement[i] + local esstatus = RESMission:GetState() + local RESMissiontime = (timer.getTime() - self.EscortsTimeStamp) + local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - RESMissiontime),0) -- seconds ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) @@ -6226,7 +6250,7 @@ function AWACS:_CheckAwacsStatus() report:Add(string.format("Auftrag Status: %s",esstatus)) report:Add(string.format("TOS Left: %d min",ESTOSLeft)) - local OpsGroups = ESmission:GetOpsGroups() + local OpsGroups = RESMission:GetOpsGroups() local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP if OpsGroup then local OpsName = OpsGroup:GetName() or "Unknown" @@ -6238,13 +6262,13 @@ function AWACS:_CheckAwacsStatus() report:Add("***** Cannot obtain (yet) this missions OpsGroup!") end - if ESmission:IsExecuting() then + if RESMission and RESMission:IsExecuting() then -- make the actual change in the queue self.ShiftChangeEscortsFlag = false self.ShiftChangeEscortsRequested = false -- cancel old mission if ESmission and ESmission:IsNotOver() then - ESmission:Cancel() + ESmission:__Cancel(1) end self.EscortMission[i] = self.EscortMissionReplacement[i] self.EscortMissionReplacement[i] = nil From 50436b8ced50ade89ab3932292ced04d5c3f7584 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 13 Jan 2025 12:38:39 +0100 Subject: [PATCH 123/349] #AWACS - make # of escorts required assets --- Moose Development/Moose/Ops/Awacs.lua | 32 ++++++++++++--------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 3bc1b74d5..fbaa1e52a 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.69", -- #string + version = "0.2.70", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -2171,9 +2171,10 @@ end -- @param #AWACS self -- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts. -- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group` --- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=500,y=0,z=500}` - 500m behind and to the left, no vertical separation. +-- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=-500,y=0,z=500}` - 500m behind and to the right, no vertical separation. +-- @param #number EscortEngageMaxDistance Escorts engage air targets max this NM away, defaults to 45NM. -- @return #AWACS self -function AWACS:SetEscort(EscortNumber,Formation,OffsetVector) +function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance) self:T(self.lid.."SetEscort") if EscortNumber and EscortNumber > 0 then self.HasEscorts = true @@ -2183,7 +2184,8 @@ function AWACS:SetEscort(EscortNumber,Formation,OffsetVector) self.EscortNumber = 0 end self.EscortFormation = Formation - self.OffsetVec = OffsetVector or {x=500,y=0,z=500} + self.OffsetVec = OffsetVector or {x=500,y=100,z=500} + self.EscortEngageMaxDistance = EscortEngageMaxDistance or 45 return self end @@ -2238,18 +2240,12 @@ function AWACS:_StartEscorts(Shiftchange) local group = AwacsFG:GetGroup() local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds - local OffsetX = 500 - local OffsetY = 500 - local OffsetZ = 500 - if self.OffsetVec then - OffsetX = self.OffsetVec.x - OffsetY = self.OffsetVec.y - OffsetZ = self.OffsetVec.z - end - for i=1,self.EscortNumber do + + --for i=1,self.EscortNumber do -- every - local escort = AUFTRAG:NewESCORT(group, {x= -OffsetX*((i + (i%2))/2), y=OffsetY, z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},45,{"Air"}) - escort:SetRequiredAssets(1) + --local escort = AUFTRAG:NewESCORT(group, {x= -OffsetX*((i + (i%2))/2), y=OffsetY, z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},45,{"Air"}) + local escort = AUFTRAG:NewESCORT(group,self.OffsetVec,self.EscortEngageMaxDistance,{"Air"}) + escort:SetRequiredAssets(self.EscortNumber) escort:SetTime(nil,timeonstation) if self.Escortformation then escort:SetFormation(self.Escortformation) @@ -2260,11 +2256,11 @@ function AWACS:_StartEscorts(Shiftchange) self.CatchAllMissions[#self.CatchAllMissions+1] = escort if Shiftchange then - self.EscortMissionReplacement[i] = escort + self.EscortMissionReplacement[1] = escort else - self.EscortMission[i] = escort + self.EscortMission[1] = escort end - end + --end return self end From 083bf13fe427e250ff4115cdfbc4e12d3a6a980c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 13 Jan 2025 18:01:31 +0100 Subject: [PATCH 124/349] xx --- Moose Development/Moose/Ops/PlayerTask.lua | 69 ++++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 1258462a7..a4285ebe9 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -98,7 +98,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.24" +PLAYERTASK.version="0.1.25" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -1308,6 +1308,8 @@ do -- @field Core.ClientMenu#CLIENTMENU ActiveTopMenu -- @field Core.ClientMenu#CLIENTMENU ActiveInfoMenu -- @field Core.ClientMenu#CLIENTMENU MenuNoTask +-- @field #boolean InformationMenu Show Radio Info Menu +-- @field #number TaskInfoDuration How long to show the briefing info on the screen -- @extends Core.Fsm#FSM --- @@ -1663,6 +1665,8 @@ PLAYERTASKCONTROLLER = { UseTypeNames = false, Scoring = nil, MenuNoTask = nil, + InformationMenu = false, + TaskInfoDuration = 30, } --- @@ -1799,6 +1803,7 @@ PLAYERTASKCONTROLLER.Messages = { CRUISER = "Cruiser", DESTROYER = "Destroyer", CARRIER = "Aircraft Carrier", + RADIOS = "Radios", }, DE = { TASKABORT = "Auftrag abgebrochen!", @@ -1882,12 +1887,13 @@ PLAYERTASKCONTROLLER.Messages = { CRUISER = "Kreuzer", DESTROYER = "Zerstörer", CARRIER = "Flugzeugträger", + RADIOS = "Frequenzen", }, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.67" +PLAYERTASKCONTROLLER.version="0.1.69" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -1949,6 +1955,10 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.UseTypeNames = false + self.InformationMenu = false + + self.TaskInfoDuration = 30 + self.IsClientSet = false if ClientFilter and type(ClientFilter) == "table" and ClientFilter.ClassName and ClientFilter.ClassName == "SET_CLIENT" then @@ -2166,6 +2176,16 @@ function PLAYERTASKCONTROLLER:SetAllowFlashDirection(OnOff) return self end +--- [User] Set to show a menu entry to retrieve the radio frequencies used. +-- @param #PLAYERTASKCONTROLLER self +-- @param #boolean OnOff Set to `true` to switch on and `false` to switch off. Default is OFF. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetShowRadioInfoMenu(OnOff) + self:T(self.lid.."SetAllowRadioInfoMenu") + self.InformationMenu = OnOff + return self +end + --- [User] Do not show menu entries to smoke or flare targets -- @param #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self @@ -2261,7 +2281,7 @@ function PLAYERTASKCONTROLLER:_GetTextForSpeech(text) return text end ---- [User] Set repetition options for tasks +--- [User] Set repetition options for tasks. -- @param #PLAYERTASKCONTROLLER self -- @param #boolean OnOff Set to `true` to switch on and `false` to switch off (defaults to true) -- @param #number Repeats Number of repeats (defaults to 5) @@ -2279,6 +2299,16 @@ function PLAYERTASKCONTROLLER:SetTaskRepetition(OnOff, Repeats) return self end +--- [User] Set how long the briefing is shown on screen. +-- @param #PLAYERTASKCONTROLLER self +-- @param #number Seconds Duration in seconds. Defaults to 30 seconds. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetBriefingDuration(Seconds) + self:T(self.lid.."SetBriefingDuration") + self.TaskInfoDuration = Seconds or 30 + return self +end + --- [Internal] Send message to SET_CLIENT of players -- @param #PLAYERTASKCONTROLLER self -- @param #string Text the text to be send @@ -3464,6 +3494,32 @@ function PLAYERTASKCONTROLLER:_SwitchFlashing(Group, Client) return self end +function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client) + self:T(self.lid.."_ShowRadioInfo") + local playername, ttsplayername = self:_GetPlayerName(Client) + + if self.UseSRS then + local frequency = self.Frequency + local freqtext = "" + if type(frequency) == "table" then + freqtext = self.gettext:GetEntry("FREQUENCIES",self.locale) + freqtext = freqtext..table.concat(frequency,", ") + else + local freqt = self.gettext:GetEntry("FREQUENCY",self.locale) + freqtext = string.format(freqt,frequency) + end + + local switchtext = self.gettext:GetEntry("BROADCAST",self.locale) + + playername = ttsplayername or self:_GetTextForSpeech(playername) + --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) + local text = string.format(switchtext,playername,self.MenuName or self.Name,freqtext) + self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2,{Group},text,30,self.BCFrequency,self.BCModulation) + end + + return self +end + --- [Internal] Flashing directional info for a client -- @param #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self @@ -3683,7 +3739,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then - local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) + local m=MESSAGE:New(text,self.TaskInfoDuration or 30,"Tasking"):ToClient(Client) end return self end @@ -4037,6 +4093,11 @@ function PLAYERTASKCONTROLLER:_CreateJoinMenuTemplate() self.MenuNoTask = nil end + if self.InformationMenu then + local radioinfo = self.gettext:GetEntry("RADIOS",self.locale) + JoinTaskMenuTemplate:NewEntry(radioinfo,self.JoinTopMenu,self._ShowRadioInfo,self) + end + self.JoinTaskMenuTemplate = JoinTaskMenuTemplate return self From 5c1d3679262eae830adefef1d65ad49a24d84564 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 16 Jan 2025 11:10:42 +0100 Subject: [PATCH 125/349] xx --- Moose Development/Moose/Ops/Awacs.lua | 6 +++--- Moose Development/Moose/Ops/PlayerTask.lua | 11 ++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index ab5c16584..b777054fe 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -2264,11 +2264,11 @@ function AWACS:_StartEscorts(Shiftchange) self.CatchAllMissions[#self.CatchAllMissions+1] = escort if Shiftchange then - self.EscortMissionReplacement[1] = escort + self.EscortMissionReplacement[i] = escort else - self.EscortMission[1] = escort + self.EscortMission[i] = escort end - --end + end return self end diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index a4285ebe9..791d9de78 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -700,6 +700,15 @@ function PLAYERTASK:IsDone() return IsDone end +--- [User] Check if task is NOT done +-- @param #PLAYERTASK self +-- @return #boolean done +function PLAYERTASK:IsNotDone() + self:T(self.lid.."IsNotDone?") + local IsNotDone = not self:IsDone() + return IsNotDone +end + --- [User] Check if PLAYERTASK has clients assigned to it. -- @param #PLAYERTASK self -- @return #boolean hasclients @@ -1156,7 +1165,7 @@ function PLAYERTASK:onafterCancel(From, Event, To) self.TaskController:__TaskCancelled(-1,self) end self.timestamp = timer.getAbsTime() - self.FinalState = "Cancel" + self.FinalState = "Cancelled" self:__Done(-1) return self end From 77c6365e37b93ec16dfd8ae8493d8e6fca201b30 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 17 Jan 2025 09:53:16 +0100 Subject: [PATCH 126/349] xx --- .../Moose/Functional/Autolase.lua | 2 ++ Moose Development/Moose/Ops/CSAR.lua | 35 ++++++++++++++++--- Moose Development/Moose/Ops/PlayerTask.lua | 5 +-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index bc3f97c4f..7a8545b79 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -798,6 +798,8 @@ function AUTOLASE:ShowStatus(Group,Unit) locationstring = entry.coordinate:ToStringMGRS(settings) elseif settings:IsA2G_LL_DMS() then locationstring = entry.coordinate:ToStringLLDMS(settings) + elseif settings:IsA2G_LL_DDM() then + locationstring = entry.coordinate:ToStringLLDDM(settings) elseif settings:IsA2G_BR() then -- attention this is the distance from the ASKING unit to target, not from RECCE to target! local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate() diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 8cc55125c..e7805f25a 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -809,6 +809,8 @@ end -- @param #boolean noMessage -- @param #string _description Description -- @param #boolean forcedesc Use the description only for the pilot track entry +-- @return Wrapper.Group#GROUP PilotInField Pilot GROUP object +-- @return #string AliasName Alias display name function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc ) self:T(self.lid .. " _AddCsar") self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) @@ -878,7 +880,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. - return self + return _spawnedGroup, _alias end --- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. @@ -1829,8 +1831,9 @@ end --- (Internal) Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. +-- @param Wrapper.Unit#UNIT _Unit Requesting helo pilot unit -- @return #string Coordinates as Text -function CSAR:_GetPositionOfWounded(_woundedGroup) +function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit) self:T(self.lid .. " _GetPositionOfWounded") local _coordinate = _woundedGroup:GetCoordinate() local _coordinatesText = "None" @@ -1845,6 +1848,26 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end end + if _Unit and _Unit:GetPlayerName() then + local playername = _Unit:GetPlayerName() + if playername then + local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS + if settings then + self:T("Get Settings ok!") + if settings:IsA2G_MGRS() then + _coordinatesText = _coordinate:ToStringMGRS(settings) + elseif settings:IsA2G_LL_DMS() then + _coordinatesText = _coordinate:ToStringLLDMS(settings) + elseif settings:IsA2G_LL_DDM() then + _coordinatesText = _coordinate:ToStringLLDDM(settings) + elseif settings:IsA2G_BR() then + -- attention this is the distance from the ASKING unit to target, not from RECCE to target! + local startcoordinate = _Unit:GetCoordinate() + _coordinatesText = _coordinate:ToStringBR(startcoordinate,settings) + end + end + end + end return _coordinatesText end @@ -1870,13 +1893,17 @@ function CSAR:_DisplayActiveSAR(_unitName) self:T({Table=_value}) local _woundedGroup = _value.group if _woundedGroup and _value.alive then - local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup,_heli) local _helicoord = _heli:GetCoordinate() local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) local distancetext = "" - if _SETTINGS:IsImperial() then + local settings = _SETTINGS + if _heli:GetPlayerName() then + settings = _DATABASE:GetPlayerSettings(_heli:GetPlayerName()) or _SETTINGS + end + if settings:IsImperial() then distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) else distancetext = string.format("%.1fkm", _distance/1000.0) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 791d9de78..f30bc6f11 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -4446,8 +4446,9 @@ end -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS; if you use a config file for MSRS, hand in nil here. -- @param #string AccessKey (Optional) Your Google API access key. This is necessary if DCS-gRPC is used as backend; if you use a config file for MSRS, hand in nil here. -- @param Core.Point#COORDINATE Coordinate Coordinate from which the controller radio is sending +-- @param #string Backend (Optional) MSRS Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC; if you use a config file for MSRS, hand in nil here. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate) +function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend) self:T(self.lid.."SetSRS") self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.Gender = Gender or MSRS.gender or "male" -- @@ -4463,7 +4464,7 @@ function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Cultu self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} -- self.BCModulation = self.Modulation -- set up SRS - self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation) + self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,Backend) self.SRS:SetCoalition(self.Coalition) self.SRS:SetLabel(self.MenuName or self.Name) self.SRS:SetGender(self.Gender) From 2a1234f7ff7f8ea5e4a17c241f96b46d0b9d48f6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 17 Jan 2025 12:19:42 +0100 Subject: [PATCH 127/349] xxx --- Moose Development/Moose/Functional/Mantis.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 28f511a91..c25cce869 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1732,8 +1732,9 @@ do self:__RedState(1,samgroup) self.SamStateTracker[name] = "RED" end + -- TODO doesn't work if shortsam == true and self.SmokeDecoy == true then - self:I("Smoking") + self:T("Smoking") local units = samgroup:GetUnits() or {} local smoke = self.SmokeDecoyColor or SMOKECOLOR.White for _,unit in pairs(units) do From 3ed61e8a2a70d9b9e713ddc4c26424d0afa17c8b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 18 Jan 2025 15:29:35 +0100 Subject: [PATCH 128/349] xx --- Moose Development/Moose/Wrapper/Group.lua | 8 ++++++-- Moose Development/Moose/Wrapper/Positionable.lua | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 3760a84fc..ef0561f84 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -3003,7 +3003,7 @@ end -- local callsign = mygroup:GetCustomCallSign(true,false,nil,function(groupname,playername) return string.match(playername,"([%a]+)$") end) -- function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,CustomFunction,...) - --self:I("GetCustomCallSign") + self:T("GetCustomCallSign") local callsign = "Ghost 1" if self:IsAlive() then @@ -3016,8 +3016,12 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,C local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9 local callnumberminor = string.char(string.byte(callnumber,2)) -- 1 local personalized = false - local playername = IsPlayer == true and self:GetPlayerName() or shortcallsign + --local playername = IsPlayer == true and self:GetPlayerName() or shortcallsign + local playername = shortcallsign + if IsPlayer then playername = self:GetPlayerName() end + + self:T2("GetCustomCallSign outcome = "..playername) if CustomFunction and IsPlayer then local arguments = arg or {} local callsign = CustomFunction(groupname,playername,unpack(arguments)) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 735760430..df5fdcb37 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -145,7 +145,11 @@ function POSITIONABLE:GetPosition() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() - + + if self:IsInstanceOf("GROUP") then + DCSPositionable = self:GetFirstUnitAlive():GetDCSObject() + end + if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition() self:T3( PositionablePosition ) From c62dc17cd8fbef264761e99a3cd4d6f3e66196ca Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 19 Jan 2025 17:39:54 +0100 Subject: [PATCH 129/349] xxx --- Moose Development/Moose/Ops/PlayerRecce.lua | 154 +++++++++++++------- Moose Development/Moose/Ops/PlayerTask.lua | 12 +- 2 files changed, 112 insertions(+), 54 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 7091139cd..1b518dbe2 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -106,7 +106,7 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.1.24", + version = "0.1.26", ViewZone = {}, ViewZoneVisual = {}, ViewZoneLaser = {}, @@ -154,7 +154,8 @@ PLAYERRECCE.LaserRelativePos = { ["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 }, ["SA342L"] = { x = 1.7, y = 1.2, z = 0 }, ["Ka-50"] = { x = 6.1, y = -0.85 , z = 0 }, - ["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 } + ["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 }, + ["OH58D"] = {x = 0, y = 2.8, z = 0}, } --- @@ -166,7 +167,8 @@ PLAYERRECCE.MaxViewDistance = { ["SA342Minigun"] = 8000, ["SA342L"] = 8000, ["Ka-50"] = 8000, - ["Ka-50_3"] = 8000, + ["Ka-50_3"] = 8000, + ["OH58D"] = 8000, } --- @@ -178,7 +180,8 @@ PLAYERRECCE.Cameraheight = { ["SA342Minigun"] = 2.85, ["SA342L"] = 2.85, ["Ka-50"] = 0.5, - ["Ka-50_3"] = 0.5, + ["Ka-50_3"] = 0.5, + ["OH58D"] = 4.25, } --- @@ -190,7 +193,8 @@ PLAYERRECCE.CanLase = { ["SA342Minigun"] = false, -- no optics ["SA342L"] = true, ["Ka-50"] = true, - ["Ka-50_3"] = true, + ["Ka-50_3"] = true, + ["OH58D"] = false, -- has onboard and useable laser } --- @@ -546,7 +550,7 @@ function PLAYERRECCE:SetAttackSet(AttackSet) return self end ----[Internal] Check Gazelle camera in on +---[Internal] Check Helicopter camera in on -- @param #PLAYERRECCE self -- @param Wrapper.Client#CLIENT client -- @param #string playername @@ -562,6 +566,12 @@ function PLAYERRECCE:_CameraOn(client,playername) if vivihorizontal < -0.7 or vivihorizontal > 0.7 then camera = false end + elseif string.find(typename,"OH58") then + local dcsunit = Unit.getByName(client:GetName()) + local vivihorizontal = dcsunit:getDrawArgumentValue(528) or 0 -- Kiow + if vivihorizontal < -0.527 or vivihorizontal > 0.527 then + camera = false + end elseif string.find(typename,"Ka-50") then camera = true end @@ -569,6 +579,52 @@ function PLAYERRECCE:_CameraOn(client,playername) return camera end +--- [Internal] Get the view parameters from a Kiowa MMS camera +-- @param #PLAYERRECCE self +-- @param Wrapper.Unit#UNIT Kiowa +-- @return #number cameraheading in degrees. +-- @return #number cameranodding in degrees. +-- @return #number maxview in meters. +-- @return #boolean cameraison If true, camera is on, else off. +function PLAYERRECCE:_GetKiowaMMSSight(Kiowa) + self:T(self.lid.."_GetKiowaMMSSight") + local unit = Kiowa -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + local dcsunit = Unit.getByName(Kiowa:GetName()) + --[[ + shagrat — 01/01/2025 23:13 + Found the necessary ARGS for the Kiowa MMS angle and rotation: + Arg 527 vertical movement + 0 = neutral + -1.0 = max depression (30° max depression angle) + +1.0 = max elevation angle (30° max elevation angle) + + Arg 528 horizontal movement + 0 = forward (0 degr) + -0.25 = 90° left + -0.5 = rear (180°) left (max 190° = -0.527 + +0.25 = 90° right + +0.5 = 180° right (max 190° = 0.527) + --]] + local mmshorizontal = dcsunit:getDrawArgumentValue(528) or 0 + local mmsvertical = dcsunit:getDrawArgumentValue(527) or 0 + self:T(string.format("Kiowa MMS Arguments Read: H %.3f V %.3f",mmshorizontal,mmsvertical)) + local mmson = true + if mmshorizontal < -0.527 or mmshorizontal > 0.527 then mmson = false end + local horizontalview = mmshorizontal / 0.527 * 190 + local heading = unit:GetHeading() + local mmsheading = (heading+horizontalview)%360 + --local mmsyaw = mmsvertical * 30 + local mmsyaw = math.atan(mmsvertical)*40 + local maxview = self:_GetActualMaxLOSight(unit,mmsheading, mmsyaw,not mmson) + if maxview > 8000 then maxview = 8000 end + self:T(string.format("Kiowa MMS Heading %d, Yaw %d, MaxView %dm MMS On %s",mmsheading,mmsyaw,maxview,tostring(mmson))) + return mmsheading,mmsyaw,maxview,mmson + end + return 0,0,0,false +end + + --- [Internal] Get the view parameters from a Gazelle camera -- @param #PLAYERRECCE self -- @param Wrapper.Unit#UNIT Gazelle @@ -597,40 +653,15 @@ function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) vivioff = true return 0,0,0,false end - vivivertical = vivivertical / 1.10731 -- normalize + local horizontalview = vivihorizontal * -180 - local verticalview = vivivertical * 30 -- ca +/- 30° - --self:I(string.format("vivihorizontal=%.5f | vivivertical=%.5f",vivihorizontal,vivivertical)) - --self:I(string.format("horizontal=%.5f | vertical=%.5f",horizontalview,verticalview)) + --local verticalview = vivivertical * 30 -- ca +/- 30° + local verticalview = math.atan(vivivertical) + local heading = unit:GetHeading() local viviheading = (heading+horizontalview)%360 local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff) - --self:I(string.format("maxview=%.5f",maxview)) - -- visual skew - local factor = 3.15 - self.GazelleViewFactors = { - [1]=1.18, - [2]=1.32, - [3]=1.46, - [4]=1.62, - [5]=1.77, - [6]=1.85, - [7]=2.05, - [8]=2.05, - [9]=2.3, - [10]=2.3, - [11]=2.27, - [12]=2.27, - [13]=2.43, - } - local lfac = UTILS.Round(maxview,-2) - if lfac <= 1300 then - --factor = self.GazelleViewFactors[lfac/100] - factor = 3.15 - maxview = math.ceil((maxview*factor)/100)*100 - end if maxview > 8000 then maxview = 8000 end - --self:I(string.format("corrected maxview=%.5f",maxview)) return viviheading, verticalview,maxview, not vivioff end return 0,0,0,false @@ -651,20 +682,20 @@ function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff) if unit and unit:IsAlive() then local typename = unit:GetTypeName() maxview = self.MaxViewDistance[typename] or 8000 - local CamHeight = self.Cameraheight[typename] or 0 - if vnod < 0 then + local CamHeight = self.Cameraheight[typename] or 1 + if vnod < -2 then -- Looking down -- determine max distance we're looking at local beta = 90 - local gamma = math.floor(90-vnod) - local alpha = math.floor(180-beta-gamma) + local gamma = 90-math.abs(vnod) + local alpha = 90-gamma local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight local b = a / math.sin(math.rad(alpha)) local c = b * math.sin(math.rad(gamma)) maxview = c*1.2 -- +20% end end - return math.abs(maxview) + return math.ceil(math.abs(maxview)) end --- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. @@ -673,8 +704,10 @@ end -- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. -- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized -- callsigns from playername or group name. +-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername. -- @return #PLAYERRECCE self -function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign == false then self.ShortCallsign = false else @@ -682,6 +715,8 @@ function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTransla end self.Keepnumber = Keepnumber or false self.CallsignTranslations = CallsignTranslations + self.CallsignCustomFunc = CallsignCustomFunc + self.CallsignCustomArgs = arg or {} return self end @@ -817,6 +852,14 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) nod,maxview,camon = 10,1000,true angle = 10 maxview = self.MaxViewDistance[typename] or 5000 + elseif string.find(typename,"OH58") and camera then + --heading = unit:GetHeading() + nod,maxview,camon = 0,8000,true + heading,nod,maxview,camon = self:_GetKiowaMMSSight(unit) + angle = 8 + if maxview == 0 then + maxview = self.MaxViewDistance[typename] or 5000 + end else -- visual heading = unit:GetHeading() @@ -1361,6 +1404,7 @@ function PLAYERRECCE:_BuildMenus(Client) local client = _client -- Wrapper.Client#CLIENT if client and client:IsAlive() then local playername = client:GetPlayerName() + self:T("Menu for "..playername) if not self.UnitLaserCodes[playername] then self:_SetClientLaserCode(nil,nil,playername,1688) end @@ -1369,6 +1413,7 @@ function PLAYERRECCE:_BuildMenus(Client) end local group = client:GetGroup() if not self.ClientMenus[playername] then + self:T("Start Menubuild for "..playername) local canlase = self.CanLase[client:GetTypeName()] self.ClientMenus[playername] = MENU_GROUP:New(group,self.MenuName or self.Name or "RECCE") local txtonstation = self.OnStation[playername] and "ON" or "OFF" @@ -1585,6 +1630,15 @@ function PLAYERRECCE:EnableSmokeOwnPosition() return self end +--- [User] Enable auto lasing for the Kiowa OH-58D. +-- @param #PLAYERRECCE self +-- @return #PLAYERRECCE self +function PLAYERRECCE:EnableKiowaAutolase() + self:T(self.lid.."EnableKiowaAutolase") + self.CanLase.OH58D = true + return self +end + --- [User] Disable smoking of own position -- @param #PLAYERRECCE self -- @return #PLAYERRECCE @@ -1742,7 +1796,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.ReferencePoint then @@ -1751,7 +1805,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) end local text1 = "Party time!" local text2 = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext) - local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext) + local text2tts = string.format(" All stations, FACA %s on station at %s!",callsign, coordtext) text2tts = self:_GetTextForSpeech(text2tts) if self.debug then self:T(text2.."\n"..text2tts) @@ -1782,7 +1836,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.ReferencePoint then @@ -1922,7 +1976,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterIllumination(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -1965,7 +2019,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -2008,7 +2062,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -2052,7 +2106,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) @@ -2099,7 +2153,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterShack(From, Event, To, Client, Target) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) @@ -2146,7 +2200,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index f30bc6f11..dd9c18b52 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -2261,8 +2261,10 @@ end -- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. -- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized -- callsigns from playername or group name. +-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign == false then self.ShortCallsign = false else @@ -2270,6 +2272,8 @@ function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,Callsi end self.Keepnumber = Keepnumber or false self.CallsignTranslations = CallsignTranslations + self.CallsignCustomFunc = CallsignCustomFunc + self.CallsignCustomArgs = arg or {} return self end @@ -2481,7 +2485,7 @@ function PLAYERTASKCONTROLLER:_GetPlayerName(Client) if not self.customcallsigns[playername] then local playergroup = Client:GetGroup() if playergroup ~= nil then - ttsplayername = playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + ttsplayername = playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local newplayername = self:_GetTextForSpeech(ttsplayername) self.customcallsigns[playername] = newplayername ttsplayername = newplayername @@ -2621,7 +2625,7 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData) if self.customcallsigns[playername] then self.customcallsigns[playername] = nil end - playername = EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber) + playername = EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) end playername = self:_GetTextForSpeech(playername) --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) @@ -3641,7 +3645,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local pcoord = player:GetCoordinate() if pcoord:Get2DDistance(Coordinate) <= reachdist then inreach = true - local callsign = player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local playername = player:GetPlayerName() local islasing = no if self.PlayerRecce.CanLase[player:GetTypeName()] and self.PlayerRecce.AutoLase[playername] then From f77771ae2557fc9fbedcc0abf7c6d07c2bb4122d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 19 Jan 2025 18:19:54 +0100 Subject: [PATCH 130/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 46 ++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 26295b17a..bfb47f706 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1352,7 +1352,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.23" +CTLD.version="1.1.24" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5512,14 +5512,18 @@ end local match = false local cgotbl = self.Cargo_Troops local name = cargo:GetName() + local CargoObject + local CargoName for _,_cgo in pairs (cgotbl) do local cname = _cgo:GetName() if name == cname then match = true + CargoObject = _cgo + CargoName = cname break end end - return match + return match, CargoObject, CargoName end local function Cruncher(group,typename,anzahl) @@ -5565,11 +5569,24 @@ end end end - if not IsTroopsMatch(cargo) then + local match,CargoObject,CargoName = IsTroopsMatch(cargo) + + if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert(self.Cargo_Troops,cargo) + table.insert(self.Cargo_Crates,cargo) + end + + if match and CargoObject then + local stock = CargoObject:GetStock() + if stock ~= -1 and stock ~= nil and stock == 0 then + -- stock empty + self:T(self.lid.."Stock of "..CargoName.." is empty. Cannot inject.") + return + else + CargoObject:RemoveStock(1) + end end local type = cargo:GetType() -- #CTLD_CARGO.Enum @@ -5637,14 +5654,18 @@ end local match = false local cgotbl = self.Cargo_Crates local name = cargo:GetName() + local CargoObject + local CargoName for _,_cgo in pairs (cgotbl) do local cname = _cgo:GetName() if name == cname then match = true + CargoObject = _cgo + CargoName = cname break end end - return match + return match,CargoObject,CargoName end local function Cruncher(group,typename,anzahl) @@ -5690,13 +5711,26 @@ end end end - if not IsVehicMatch(cargo) then + local match,CargoObject,CargoName = IsVehicMatch(cargo) + + if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 table.insert(self.Cargo_Crates,cargo) end + if match and CargoObject then + local stock = CargoObject:GetStock() + if stock ~= -1 and stock ~= nil and stock == 0 then + -- stock empty + self:T(self.lid.."Stock of "..CargoName.." is empty. Cannot inject.") + return + else + CargoObject:RemoveStock(1) + end + end + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then -- unload From afc0671aa915aa0c2777dafd54a751ea870d10a4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Jan 2025 10:26:51 +0100 Subject: [PATCH 131/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 64 +++++++++++++++++++++------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index bfb47f706..c73600e44 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -980,7 +980,7 @@ do -- -- ## 3.5 OnAfterCratesDropped -- --- This function is called when a player has deployed crates to a DROP zone: +-- This function is called when a player has deployed crates: -- -- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- ... your code here ... @@ -1242,6 +1242,8 @@ CTLD = { TroopUnloadDistHoverHook = 5, TroopUnloadDistHover = 1.5, UserSetGroup = nil, + LoadedGroupsTable = {}, + keeploadtable = true, } ------------------------------ @@ -1352,7 +1354,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.24" +CTLD.version="1.1.25" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1419,7 +1421,8 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event. self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event. - self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Loaded", "*") -- CTLD load event. self:AddTransition("*", "Save", "*") -- CTLD save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -1520,6 +1523,8 @@ function CTLD:New(Coalition, Prefixes, Alias) self.filepath = nil self.saveinterval = 600 self.eventoninject = true + self.keeploadtable = true + self.LoadedGroupsTable = {} -- sub categories self.usesubcats = false @@ -1832,6 +1837,14 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". + --- FSM Function OnAfterLoaded. + -- @function [parent=#CTLD] OnAfterLoaded + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table LoadedGroups Table of loaded groups, each entry is a table with three values: Group, TimeStamp and CargoType. + --- FSM Function OnAfterSave. -- @function [parent=#CTLD] OnAfterSave -- @param #CTLD self @@ -3510,10 +3523,9 @@ function CTLD:_UnloadTroops(Group, Unit) local rad = 2.5+(tempcount*2) local Positions = self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - --:InitRandomizeUnits(true,20,2) - --:InitHeading(heading) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter],type) end -- template loop @@ -3934,10 +3946,12 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) --:InitRandomizeUnits(true,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) end if Repair then @@ -5496,6 +5510,7 @@ end -- @param #table Surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another! -- @param #string Structure (Optional) String object describing the current structure of the injected group; mainly for load/save to keep current state setup. + -- @param #number TimeStamp (Optional) Timestamp used internally on re-loading from disk. -- @return #CTLD self -- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type @@ -5504,7 +5519,7 @@ end -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectTroops(dropzone,InjectTroopsType,{land.SurfaceType.LAND}) - function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation,Structure) + function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation,Structure,TimeStamp) self:T(self.lid.." InjectTroops") local cargo = Cargo -- #CTLD_CARGO @@ -5607,6 +5622,7 @@ end self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(randompositions,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) @@ -5624,6 +5640,11 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end + if self.keeploadtables and TimeStamp then + local cargotype = cargo.CargoType + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) + end + if self.eventoninject then self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter],type) end @@ -5638,6 +5659,7 @@ end -- @param #table Surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another! -- @param #string Structure (Optional) String object describing the current structure of the injected group; mainly for load/save to keep current state setup. + -- @param #number TimeStamp (Optional) Timestamp used internally on re-loading from disk. -- @return #CTLD self -- @usage Use this function to pre-populate the field with Vehicles or FOB at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type @@ -5646,7 +5668,7 @@ end -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectVehicles(dropzone,InjectVehicleType) - function CTLD:InjectVehicles(Zone,Cargo,Surfacetypes,PreciseLocation,Structure) + function CTLD:InjectVehicles(Zone,Cargo,Surfacetypes,PreciseLocation,Structure,TimeStamp) self:T(self.lid.." InjectVehicles") local cargo = Cargo -- #CTLD_CARGO @@ -5752,10 +5774,12 @@ end self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) end @@ -5763,6 +5787,11 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end + if self.keeploadtables and TimeStamp then + local cargotype = cargo.CargoType + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) + end + if self.eventoninject then self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) end @@ -6180,7 +6209,7 @@ end --local data = "LoadedData = {\n" - local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure,StaticCategory,StaticType,StaticShape\n" + local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure,StaticCategory,StaticType,StaticShape,SpawnTime\n" local n = 0 for _,_grp in pairs(grouptable) do local group = _grp -- Wrapper.Group#GROUP @@ -6213,6 +6242,7 @@ end for typen,anzahl in pairs (structure) do strucdata = strucdata .. typen .. "=="..anzahl..";" end + local spawntime = group.spawntime or timer.getTime()+n if type(cgotemp) == "table" then local templates = "{" @@ -6224,8 +6254,8 @@ end end local location = group:GetVec3() - local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s\n" - ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata,scat,stype,sshape or "none") + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s,%f\n" + ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata,scat,stype,sshape or "none",spawntime) data = data .. txt end end @@ -6378,10 +6408,10 @@ end -- remove header table.remove(loadeddata, 1) - + local n=0 for _id,_entry in pairs (loadeddata) do local dataset = UTILS.Split(_entry,",") - -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=Structure,11=StaticCategory,12=StaticType,13=StaticShape + -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=Structure,11=StaticCategory,12=StaticType,13=StaticShape,14=Timestamp local groupname = dataset[1] local vec2 = {} vec2.x = tonumber(dataset[2]) @@ -6394,6 +6424,8 @@ end local StaticCategory = dataset[11] local StaticType = dataset[12] local StaticShape = dataset[13] + n=n+1 + local timestamp = tonumber(dataset[14]) or timer.getTime()+n if type(groupname) == "string" and groupname ~= "STATIC" then cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") @@ -6408,10 +6440,10 @@ end if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) - self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure) + self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) - self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure) + self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) end elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) @@ -6433,7 +6465,9 @@ end end end end - + if self.keeploadtable then -- keeploadtables + self:__Loaded(1,self.LoadedGroupsTable) + end return self end end -- end do From f533d4ec8f2367e91c4c64e5685a4e67119d7053 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Jan 2025 12:22:00 +0100 Subject: [PATCH 132/349] xxx --- Moose Development/Moose/Wrapper/Storage.lua | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 3acaa9d16..4b445613c 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -227,7 +227,7 @@ function STORAGE:New(AirbaseName) self.airbase=Airbase.getByName(AirbaseName) - if Airbase.getWarehouse then + if Airbase.getWarehouse and self.airbase then self.warehouse=self.airbase:getWarehouse() end @@ -840,6 +840,35 @@ function STORAGE:StopAutoSave() return self end +--- Try to find the #STORAGE object of one of the many "H"-Helipads in Syria. You need to put a (small, round) zone on top of it, because the name is not unique(!). +-- @param #STORAGE self +-- @param #string ZoneName The name of the zone where to find the helipad. +-- @return #STORAGE self or nil if not found. +function STORAGE:FindSyriaHHelipadWarehouse(ZoneName) + local findzone = ZONE:New(ZoneName) + local base = world.getAirbases() + for i = 1, #base do + local info = {} + --info.desc = Airbase.getDesc(base[i]) + info.callsign = Airbase.getCallsign(base[i]) + info.id = Airbase.getID(base[i]) + --info.cat = Airbase.getCategory(base[i]) + info.point = Airbase.getPoint(base[i]) + info.coordinate = COORDINATE:NewFromVec3(info.point) + info.DCSObject = base[i] + --if Airbase.getUnit(base[i]) then + --info.unitId = Airbase.getUnit(base[i]):getID() + --end + if info.callsign == "H" and findzone:IsCoordinateInZone(info.coordinate) then + info.warehouse = info.DCSObject:getWarehouse() + info.Storage = STORAGE:New(info.callsign..info.id) + info.Storage.airbase = info.DCSObject + info.Storage.warehouse = info.warehouse + return info.Storage + end + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Private Functions From ae65fce9cd8f2c485388d296f33bb47529ab417e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 25 Jan 2025 15:54:42 +0100 Subject: [PATCH 133/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index b2e85552e..c64b4f37b 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -5640,7 +5640,8 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end - if self.keeploadtables and TimeStamp then + if self.keeploadtable and TimeStamp ~= nil then + self:I("Inserting: "..cargo.CargoType) local cargotype = cargo.CargoType table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) end @@ -5787,7 +5788,8 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end - if self.keeploadtables and TimeStamp then + if self.keeploadtable and TimeStamp ~= nil then + self:I("Inserting: "..cargo.CargoType) local cargotype = cargo.CargoType table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) end @@ -6446,7 +6448,8 @@ end local StaticType = dataset[12] local StaticShape = dataset[13] n=n+1 - local timestamp = tonumber(dataset[14]) or timer.getTime()+n + local timestamp = tonumber(dataset[14]) or (timer.getTime()+n) + self:I("TimeStamp = "..timestamp) if type(groupname) == "string" and groupname ~= "STATIC" then cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") From c19d20c3f74fa78794a35ded263d898d8f57a2c6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Jan 2025 17:35:30 +0100 Subject: [PATCH 134/349] xxx --- Moose Development/Moose/Core/ClientMenu.lua | 2 +- Moose Development/Moose/Core/Menu.lua | 14 ++++++++++++-- Moose Development/Moose/Ops/CTLD.lua | 13 +++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/ClientMenu.lua b/Moose Development/Moose/Core/ClientMenu.lua index ee0b7e6f2..5e7219add 100644 --- a/Moose Development/Moose/Core/ClientMenu.lua +++ b/Moose Development/Moose/Core/ClientMenu.lua @@ -57,7 +57,7 @@ --- -- @field #CLIENTMENU CLIENTMENU = { - ClassName = "CLIENTMENUE", + ClassName = "CLIENTMENU", lid = "", version = "0.1.3", name = nil, diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index 44c15b08d..aacb018a6 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -105,6 +105,7 @@ function MENU_INDEX:PrepareCoalition( CoalitionSide ) self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} end + --- -- @param Wrapper.Group#GROUP Group function MENU_INDEX:PrepareGroup( Group ) @@ -118,9 +119,11 @@ end function MENU_INDEX:HasMissionMenu( Path ) return self.MenuMission.Menus[Path] end + function MENU_INDEX:SetMissionMenu( Path, Menu ) self.MenuMission.Menus[Path] = Menu end + function MENU_INDEX:ClearMissionMenu( Path ) self.MenuMission.Menus[Path] = nil end @@ -128,9 +131,11 @@ end function MENU_INDEX:HasCoalitionMenu( Coalition, Path ) return self.Coalition[Coalition].Menus[Path] end + function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu ) self.Coalition[Coalition].Menus[Path] = Menu end + function MENU_INDEX:ClearCoalitionMenu( Coalition, Path ) self.Coalition[Coalition].Menus[Path] = nil end @@ -138,19 +143,24 @@ end function MENU_INDEX:HasGroupMenu( Group, Path ) if Group and Group:IsAlive() then local MenuGroupName = Group:GetName() - return self.Group[MenuGroupName].Menus[Path] + if self.Group[MenuGroupName] and self.Group[MenuGroupName].Menus and self.Group[MenuGroupName].Menus[Path] then + return self.Group[MenuGroupName].Menus[Path] + end end return nil end + function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) local MenuGroupName = Group:GetName() - Group:F({MenuGroupName=MenuGroupName,Path=Path}) + --Group:F({MenuGroupName=MenuGroupName,Path=Path}) self.Group[MenuGroupName].Menus[Path] = Menu end + function MENU_INDEX:ClearGroupMenu( Group, Path ) local MenuGroupName = Group:GetName() self.Group[MenuGroupName].Menus[Path] = nil end + function MENU_INDEX:Refresh( Group ) for MenuID, Menu in pairs( self.MenuMission.Menus ) do Menu:Refresh() diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 56a33010d..3ebcd67a2 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4094,7 +4094,12 @@ function CTLD:_RefreshF10Menus() --local nohookswitch = not (isHook and self.enableChinookGCLoading) local nohookswitch = true -- top menu + if _group.CTLDTopmenu then + _group.CTLDTopmenu:Remove() + _group.CTLDTopmenu = nil + end local topmenu = MENU_GROUP:New(_group,"CTLD",nil) + _group.CTLDTopmenu = topmenu local toptroops = nil local topcrates = nil if cantroops then @@ -5701,8 +5706,8 @@ end if self.keeploadtable and TimeStamp ~= nil then self:T2("Inserting: "..cargo.CargoType) - local cargotype = cargo.CargoType - table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) + local cargotype = type + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype, CargoName=name}) end if self.eventoninject then @@ -5849,8 +5854,8 @@ end if self.keeploadtable and TimeStamp ~= nil then self:T2("Inserting: "..cargo.CargoType) - local cargotype = cargo.CargoType - table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) + local cargotype = type + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype, CargoName=name}) end if self.eventoninject then From 83e146f207342ab1716370fdbc8fcdc9ee513b24 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 27 Jan 2025 18:46:52 +0100 Subject: [PATCH 135/349] xx --- Moose Development/Moose/Core/Base.lua | 29 ++++++++++++++++++++-- Moose Development/Moose/Ops/CTLD.lua | 24 +++++++++++------- Moose Development/Moose/Ops/PlayerTask.lua | 4 +-- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 5de2096a3..fe37e9cd7 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -201,6 +201,7 @@ BASE = { States = {}, Debug = debug, Scheduler = nil, + Properties = {}, } -- @field #BASE.__ @@ -1109,6 +1110,31 @@ function BASE:ClearState( Object, StateName ) end end +--- Set one property of an object. +-- @param #BASE self +-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! +-- @param Value The value that is stored. Note that the value can be a #string, but it can also be any other type! +function BASE:SetProperty(Key,Value) + self.Properties = self.Properties or {} + self.Properties[Key] = Value +end + +--- Get one property of an object by the key. +-- @param #BASE self +-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! +-- @return Value The value that is stored. Note that the value can be a #string, but it can also be any other type! Nil if not found. +function BASE:GetProperty(Key) + self.Properties = self.Properties or {} + return self.Properties[Key] +end + +--- Get all of the properties of an object in a table. +-- @param #BASE self +-- @return #table of values, indexed by keys. +function BASE:GetProperties() + return self.Properties +end + -- Trace section -- Log a trace (only shown when trace is on) @@ -1439,5 +1465,4 @@ function BASE:I( Arguments ) env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments)) ) end -end - +end \ No newline at end of file diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3ebcd67a2..5c83896a5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -975,7 +975,7 @@ do -- -- This function is called when a player has re-boarded already deployed troops from the field: -- --- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) +-- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops, Troopname) -- ... your code here ... -- end -- @@ -1635,7 +1635,8 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. + -- @param Wrapper.Group#GROUP Troops extracted. + -- @param #string Troopname Name of the extracted group. -- @return #CTLD self --- FSM Function OnBeforeCratesPickedUp. @@ -1723,7 +1724,8 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. + -- @param Wrapper.Group#GROUP Troops extracted. + -- @param #string Troopname Name of the extracted group. -- @return #CTLD self --- FSM Function OnAfterCratesPickedUp. @@ -2533,7 +2535,8 @@ end self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!", 10, false, Group) self:_SendMessage("Troops boarding!", 10, false, Group) self:_UpdateUnitCargoMass(Unit) - self:__TroopsExtracted(running,Group, Unit, nearestGroup) + local groupname = nearestGroup:GetName() + self:__TroopsExtracted(running,Group, Unit, nearestGroup, groupname) local coord = Unit:GetCoordinate() or Group:GetCoordinate() -- Core.Point#COORDINATE local Point if coord then @@ -5654,7 +5657,7 @@ end self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert(self.Cargo_Crates,cargo) + table.insert(self.Cargo_Troops,cargo) end if match and CargoObject then @@ -6032,13 +6035,14 @@ end -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @param #string Groupname Name of the extracted #GROUP. -- @return #CTLD self - function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) + function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops, Groupname) self:T({From, Event, To}) if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then local playername = Unit:GetPlayerName() - local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0) - local dropvec2 = dropcoord:GetVec2() + --local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0) + --local dropvec2 = dropcoord:GetVec2() self.PlayerTaskQueue:ForEach( function (Task) local task = Task -- Ops.PlayerTask#PLAYERTASK @@ -6046,7 +6050,9 @@ end -- right subtype? if Event == subtype and not task:IsDone() then local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... - if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2) then + --self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")}) + local okaygroup = string.find(Groupname,task:GetProperty("ExtractName"),1,true) + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and okaygroup then if task.Clients:HasUniqueID(playername) then -- success task:__Success(-1) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index dd9c18b52..a4e1eae5d 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -1184,7 +1184,7 @@ function PLAYERTASK:onafterSuccess(From, Event, To) if self.TargetMarker then self.TargetMarker:Remove() end - if self.TaskController.Scoring then + if self.TaskController and self.TaskController.Scoring then local clients,count = self:GetClientObjects() if count > 0 then for _,_client in pairs(clients) do @@ -1242,7 +1242,7 @@ end do ------------------------------------------------------------------------------------------------------------------- -- PLAYERTASKCONTROLLER - -- TODO: PLAYERTASKCONTROLLER +-- TODO: PLAYERTASKCONTROLLER -- DONE Playername customized -- DONE Coalition-level screen info to SET based -- DONE Flash directions From 0f9742aeb4fb996aa14795de4bffaae03b90e508 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 30 Jan 2025 13:14:47 +0100 Subject: [PATCH 136/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 157 +++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index b5c7d9e19..0b3766514 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -56,6 +56,7 @@ do -- @field #string StaticType Individual type if set. -- @field #string StaticCategory Individual static category if set. -- @field #list<#string> TypeNames Table of unit types able to pick this cargo up. +-- @field #number Stock0 Initial stock, if any given. -- @extends Core.Base#BASE --- @@ -73,6 +74,7 @@ CTLD_CARGO = { HasBeenDropped = false, PerCrateMass = 0, Stock = nil, + Stock0 = nil, Mark = nil, DontShowInMenu = false, Location = nil, @@ -131,6 +133,7 @@ CTLD_CARGO = { self.HasBeenDropped = Dropped or false --#boolean self.PerCrateMass = PerCrateMass or 0 -- #number self.Stock = Stock or nil --#number + self.Stock0 = Stock or nil --#number self.Mark = nil self.Subcategory = Subcategory or "Other" self.DontShowInMenu = DontShowInMenu or false @@ -321,7 +324,7 @@ CTLD_CARGO = { --- Get Stock. -- @param #CTLD_CARGO self - -- @return #number Stock + -- @return #number Stock or -1 if unlimited. function CTLD_CARGO:GetStock() if self.Stock then return self.Stock @@ -330,6 +333,28 @@ CTLD_CARGO = { end end + --- Get Stock0. + -- @param #CTLD_CARGO self + -- @return #number Stock0 or -1 if unlimited. + function CTLD_CARGO:GetStock0() + if self.Stock0 then + return self.Stock0 + else + return -1 + end + end + + --- Get relative Stock. + -- @param #CTLD_CARGO self + -- @return #number Stock Percentage like 75, or -1 if unlimited. + function CTLD_CARGO:GetRelativeStock() + if self.Stock and self.Stock0 then + return math.floor((self.Stock/self.Stock0)*100) + else + return -1 + end + end + --- Add Stock. -- @param #CTLD_CARGO self -- @param #number Number to add, none if nil. @@ -1355,7 +1380,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.28" +CTLD.version="1.1.29" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5313,6 +5338,119 @@ end self.EngineersInField = engtable return self end + + --- User - Count both the stock and groups in the field for available cargo types. Counts only limited cargo items and only troops and vehicle/FOB crates! + -- @param #CTLD self + -- @return #table Table A table of contents with numbers. + -- @usage + -- The index is the unique cargo name. + -- Each entry in the returned table contains a table with the following entries: + -- + -- { + -- Stock0 -- number of original stock when the cargo entry was created. + -- Stock -- number of currently available stock. + -- StockR -- relative number of available stock, e.g. 75 (percent). + -- Infield -- number of groups alive in the field of this kind. + -- Inhelo -- number of troops/crates in any helo alive. Can be with decimals < 1 if e.g. you have cargo that need 4 crates, but you have 2 loaded. + -- Sum -- sum is stock + infield + inhelo. + -- } + function CTLD:_CountStockPlusInHeloPlusAliveGroups() + local Troopstable = {} + -- generics + for _id,_cargo in pairs(self.Cargo_Crates) do + local generic = _cargo -- #CTLD_CARGO + local genname = generic:GetName() + if generic and generic:GetStock0() > 0 and not Troopstable[genname] then + Troopstable[genname] = { + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 0, + Inhelo = 0, + Sum = generic:GetStock(), + } + end + end + --- + for _id,_cargo in pairs(self.Cargo_Troops) do + local generic = _cargo -- #CTLD_CARGO + local genname = generic:GetName() + if generic and generic:GetStock0() > 0 and not Troopstable[genname] then + Troopstable[genname] = { + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 0, + Inhelo = 0, + Sum = generic:GetStock(), + } + end + end + -- Troops & Built Crates + for _index, _group in pairs (self.DroppedTroops) do + if _group and _group:IsAlive() then + self:T("Looking at ".._group:GetName() .. " in the field") + local generic = self:GetGenericCargoObjectFromGroupName(_group:GetName()) -- #CTLD_CARGO + if generic then + local genname = generic:GetName() + self:T("Found Generic "..genname .. " in the field. Adding.") + if generic:GetStock0() > 0 then -- don't count unlimited stock + if not Troopstable[genname] then + Troopstable[genname] = { + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 1, + Inhelo = 0, + Sum = generic:GetStock()+1, + } + else + Troopstable[genname].Infield = Troopstable[genname].Infield + 1 + Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + Troopstable[genname].Inhelo + end + end + else + self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) + end + end + end + -- Helos + for _unitname,_loaded in pairs(self.Loaded_Cargo) do + local _unit = UNIT:FindByName(_unitname) + if _unit and _unit:IsAlive() then + local unitname = _unit:GetName() + local loadedcargo = self.Loaded_Cargo[unitname].Cargo or {} + for _,_cgo in pairs (loadedcargo) do + local cargo = _cgo -- #CTLD_CARGO + local type = cargo.CargoType + local gname = cargo.Name + local gcargo = self:_FindCratesCargoObject(gname) or self:_FindTroopsCargoObject(gname) + self:T("Looking at ".. gname .. " in the helo - type = "..type) + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS or type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then + -- valid troops/engineers + if gcargo and gcargo:GetStock0() > 0 then -- don't count unlimited stock + self:T("Adding ".. gname .. " in the helo - type = "..type) + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then + Troopstable[gname].Inhelo = Troopstable[gname].Inhelo + 1 + end + if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then + -- maybe multiple crates of the same type + local counting = gcargo.CratesNeeded + local added = 1 + if counting > 1 then + added = added/counting + end + Troopstable[gname].Inhelo = Troopstable[gname].Inhelo + added + end + Troopstable[gname].Sum = Troopstable[gname].Infield + Troopstable[gname].Stock + Troopstable[gname].Inhelo + end + end + end + end + end + return Troopstable + end + --- User - function to add stock of a certain troops type -- @param #CTLD self @@ -5542,19 +5680,24 @@ end -- @return #CTLD_CARGO The cargo object or nil if not found function CTLD:GetGenericCargoObjectFromGroupName(GroupName) local Cargotype = nil + local template = GroupName + if string.find(template,"#") then + template = string.gsub(GroupName,"#(%d+)$","") + end + template = string.gsub(template,"-(%d+)$","") for k,v in pairs(self.Cargo_Troops) do local comparison = "" if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end - if comparison == GroupName then + if comparison == template then Cargotype = v break end end if not Cargotype then - for k,v in pairs(self.Cargo_Crates) do + for k,v in pairs(self.Cargo_Crates) do -- #number, #CTLD_CARGO local comparison = "" if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end - if comparison == GroupName then + if comparison == template and v.CargoType ~= CTLD_CARGO.Enum.REPAIR then Cargotype = v break end @@ -5679,7 +5822,7 @@ end if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter - cargo.Stock = 1 + --cargo.Stock = 1 table.insert(self.Cargo_Troops,cargo) end @@ -5829,7 +5972,7 @@ end if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter - cargo.Stock = 1 + --cargo.Stock = 1 table.insert(self.Cargo_Crates,cargo) end From 8f5e519d0a47fc42ad56d336cdcbf144ebaa1104 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 30 Jan 2025 18:32:01 +0100 Subject: [PATCH 137/349] xxx --- Moose Development/Moose/Ops/CTLD.lua | 40 +++++++---- Moose Development/Moose/Utilities/Utils.lua | 75 +++++++++++++++++++++ 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 62cbb8a25..180b2c2e6 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -5349,6 +5349,8 @@ end --- User - Count both the stock and groups in the field for available cargo types. Counts only limited cargo items and only troops and vehicle/FOB crates! -- @param #CTLD self + -- @param #boolean Restock If true, restock the cargo and troop items. + -- @param #number Threshold Percentage below which to restock, used in conjunction with Restock (must be true). Defaults to 75 (percent). -- @return #table Table A table of contents with numbers. -- @usage -- The index is the unique cargo name. @@ -5361,8 +5363,9 @@ end -- Infield -- number of groups alive in the field of this kind. -- Inhelo -- number of troops/crates in any helo alive. Can be with decimals < 1 if e.g. you have cargo that need 4 crates, but you have 2 loaded. -- Sum -- sum is stock + infield + inhelo. + -- GenericCargo -- this filed holds the generic CTLD_CARGO object which drives the available stock. Only populated if Restock is true. -- } - function CTLD:_CountStockPlusInHeloPlusAliveGroups() + function CTLD:_CountStockPlusInHeloPlusAliveGroups(Restock,Threshold) local Troopstable = {} -- generics for _id,_cargo in pairs(self.Cargo_Crates) do @@ -5377,6 +5380,9 @@ end Inhelo = 0, Sum = generic:GetStock(), } + if Restock == true then + Troopstable[genname].GenericCargo = generic + end end end --- @@ -5392,6 +5398,9 @@ end Inhelo = 0, Sum = generic:GetStock(), } + if Restock == true then + Troopstable[genname].GenericCargo = generic + end end end -- Troops & Built Crates @@ -5403,19 +5412,8 @@ end local genname = generic:GetName() self:T("Found Generic "..genname .. " in the field. Adding.") if generic:GetStock0() > 0 then -- don't count unlimited stock - if not Troopstable[genname] then - Troopstable[genname] = { - Stock0 = generic:GetStock0(), - Stock = generic:GetStock(), - StockR = generic:GetRelativeStock(), - Infield = 1, - Inhelo = 0, - Sum = generic:GetStock()+1, - } - else Troopstable[genname].Infield = Troopstable[genname].Infield + 1 Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + Troopstable[genname].Inhelo - end end else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) @@ -5455,7 +5453,19 @@ end end end end + end + -- Restock? + if Restock == true then + local threshold = Threshold or 75 + for _name,_data in pairs(Troopstable) do + if _data.StockR and _data.StockR < threshold then + if _data.GenericCargo then + _data.GenericCargo:SetStock(_data.Stock0) -- refill to start level + end + end + end end + -- Return return Troopstable end @@ -5473,6 +5483,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -5491,6 +5502,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -5509,6 +5521,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -5527,6 +5540,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self @@ -5545,6 +5559,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self @@ -5563,6 +5578,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index edf00b2f7..eeff81f26 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4324,3 +4324,78 @@ end function UTILS.ScalarMult(vec, mult) return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult} end + +--- Utilities weather class for fog mainly. +-- @type UTILS.Weather +UTILS.Weather = {} + +--- Returns the current fog thickness in meters. Returns zero if fog is not present. +function UTILS.Weather.GetFogThickness() + return world.weather.getFogThickness() +end + +--- Sets the fog to the desired thickness in meters at sea level. +-- @param #number Thickness Thickness in meters. +-- Any fog animation will be discarded. +-- Valid range : 100 to 5000 meters +function UTILS.Weather.SetFogThickness(Thickness) + local value = Thickness + if value < 100 then value = 100 + elseif value > 5000 then value = 5000 end + return world.weather.setFogThickness(value) +end + +--- Removes the fog. +function UTILS.Weather.RemoveFog() + return world.weather.setFogThickness(0) +end + +--- Gets the maximum visibility distance of the current fog setting. +-- Returns 0 if no fog is present. +function UTILS.Weather.GetFogVisibilityDistanceMax() + return world.weather.getFogVisibilityDistance() +end + +--- Sets the maximum visibility at sea level in meters. +-- @param #number Thickness Thickness in meters. +-- Limit: 100 to 100000 +function UTILS.Weather.SetFogVisibilityDistance(Thickness) + local value = Thickness + if value < 100 then value = 100 + elseif value > 100000 then value = 100000 end + return world.weather.setFogVisibilityDistance(value) +end + +--- Uses data from the passed table to change the fog visibility and thickness over a desired timeframe. This allows for a gradual increase/decrease of fog values rather than abruptly applying the values. +-- Animation Key Format: {time, visibility, thickness} +-- @param #table AnimationKeys Table of AnimationKey tables +-- @usage +-- Time: in seconds 0 to infinity +-- Time is relative to when the function was called. Time value for each key must be larger than the previous key. If time is set to 0 then the fog will be applied to the corresponding visibility and thickness values at that key. Any time value greater than 0 will result in the current fog being inherited and changed to the first key. +-- Visibility: in meters 100 to 100000 +-- Thickness: in meters 100 to 5000 +-- The speed at which the visibility and thickness changes is based on the time between keys and the values that visibility and thickness are being set to. +-- +-- When the function is passed an empty table {} or nil the fog animation will be discarded and whatever the current thickness and visibility are set to will remain. +-- +-- The following will set the fog in the mission to disappear in 1 minute. +-- +-- UTILS.Weather.SetFogAnimation({ {60, 0, 0} }) +-- +-- The following will take 1 hour to get to the first fog setting, it will maintain that fog setting for another hour, then lightly removes the fog over the 2nd and 3rd hour, the completely removes the fog after 3 hours and 3 minutes from when the function was called. +-- +-- UTILS.Weather.SetFogAnimation({ +-- {3600, 10000, 3000}, -- one hour to get to that fog setting +-- {7200, 10000, 3000}, -- will maintain for 2 hours +-- {10800, 20000, 2000}, -- at 3 hours visibility will have been increased while thickness decreases slightly +-- {12600, 0, 0}, -- at 3:30 after the function was called the fog will be completely removed. +-- }) +-- +function UTILS.Weather.SetFogAnimation(AnimationKeys) + return world.weather.setFogAnimation(AnimationKeys) +end + +--- The fog animation will be discarded and whatever the current thickness and visibility are set to will remain +function UTILS.Weather.StopFogAnimation() + return world.weather.setFogAnimation({}) +end From 32e538a27d962f53df310d0181c5bfeb9bfa1cff Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 1 Feb 2025 18:39:30 +0100 Subject: [PATCH 138/349] xx --- Moose Development/Moose/Core/Message.lua | 7 ++++++- Moose Development/Moose/Ops/PlayerRecce.lua | 6 +++++- Moose Development/Moose/Sound/SRS.lua | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index a452ab582..4165bdc57 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -464,6 +464,7 @@ _MESSAGESRS = {} -- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest). -- @param #string Label (optional) Label, defaults to "MESSAGE" or the Message Category set. -- @param Core.Point#COORDINATE Coordinate (optional) Coordinate this messages originates from. +-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only @@ -471,7 +472,7 @@ _MESSAGESRS = {} -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- -function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate) +function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend) _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" @@ -489,6 +490,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G _MESSAGESRS.MSRS:SetCoordinate(Coordinate) end + if Backend then + _MESSAGESRS.MSRS:SetBackend(Backend) + end + _MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB" _MESSAGESRS.MSRS:SetCulture(Culture) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 1b518dbe2..a88685565 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -1551,8 +1551,9 @@ end -- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS. -- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest) -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS +-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC -- @return #PLAYERRECCE self -function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) +function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend) self:T(self.lid.."SetSRS") self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.Gender = Gender or MSRS.gender or "male" -- @@ -1574,6 +1575,9 @@ function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,V self.SRS:SetCulture(self.Culture) self.SRS:SetPort(self.Port) self.SRS:SetVolume(self.Volume) + if Backend then + self.SRS:SetBackend(Backend) + end if self.PathToGoogleKey then self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey) self.SRS:SetProvider(MSRS.Provider.GOOGLE) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 1d16e5b6b..c4cab9064 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1606,7 +1606,7 @@ function MSRS:_ExecCommand(command) if self.UsePowerShell == true then filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".ps1" batContent = command .. "\'" - self:I({batContent=batContent}) + self:T({batContent=batContent}) end local script=io.open(filename, "w+") @@ -2115,7 +2115,7 @@ end -- @param Core.Point#COORDINATE coordinate Coordinate to be used -- @return #MSRSQUEUE.Transmission Radio transmission table. function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate) - + self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label}) if self.TransmitOnlyWithPlayers then if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then return self @@ -2155,7 +2155,7 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr transmission.volume = volume or msrs.volume transmission.label = label or msrs.Label transmission.coordinate = coordinate or msrs.coordinate - + -- Add transmission to queue. self:AddTransmission(transmission) From 03c623a0c279fc4cebc489bfc617104a002dbe83 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 6 Feb 2025 08:52:52 +0100 Subject: [PATCH 139/349] xx --- .../Moose/Functional/Autolase.lua | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 7a8545b79..1969fcbf9 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -74,7 +74,7 @@ -- @image Designation.JPG -- -- Date: 24 Oct 2021 --- Last Update: Jan 2025 +-- Last Update: Feb 2025 -- --- Class AUTOLASE -- @type AUTOLASE @@ -90,6 +90,7 @@ -- @field #boolean smokemenu -- @field #boolean threatmenu -- @field #number RoundingPrecision +-- @field #table smokeoffset -- @extends Ops.Intel#INTEL --- @@ -120,7 +121,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.27" +AUTOLASE.version = "0.1.28" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -193,6 +194,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.reporttimelong = 30 self.smoketargets = false self.smokecolor = SMOKECOLOR.Red + self.smokeoffset = nil self.notifypilots = true self.targetsperrecce = {} self.RecceUnits = {} @@ -211,6 +213,8 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.threatmenu = true self.RoundingPrecision = 0 + self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) + -- Set some string id for output to DCS.log file. self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") @@ -614,9 +618,15 @@ end --- (User) Show the "Switch smoke target..." menu entry for pilots. On by default. -- @param #AUTOLASE self +-- @param #table Offset (Optional) Define an offset for the smoke, i.e. not directly on the unit itself, angle is degrees and distance is meters. E.g. `autolase:EnableSmokeMenu({Angle=30,Distance=20})` -- @return #AUTOLASE self -function AUTOLASE:EnableSmokeMenu() +function AUTOLASE:EnableSmokeMenu(Offset) self.smokemenu = true + if Offset then + self.smokeoffset = {} + self.smokeoffset.Distance = Offset.Distance or math.random(10,20) + self.smokeoffset.Angle = Offset.Angle or math.random(0,359) + end return self end @@ -625,6 +635,7 @@ end -- @return #AUTOLASE self function AUTOLASE:DisableSmokeMenu() self.smokemenu = false + self.smokeoffset = nil return self end @@ -1097,6 +1108,9 @@ function AUTOLASE:onafterMonitor(From, Event, To) } if self.smoketargets then local coord = unit:GetCoordinate() + if self.smokeoffset then + coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true) + end local color = self:GetSmokeColor(reccename) coord:Smoke(color) end From a4ae0d88e1c361939862d419507bca02a629ad82 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Feb 2025 12:02:22 +0100 Subject: [PATCH 140/349] xx --- Moose Development/Moose/Ops/Awacs.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 0533e13b0..09aa3324b 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.70", -- #string + version = "0.2.71", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -2244,9 +2244,9 @@ function AWACS:_StartEscorts(Shiftchange) local OffsetY = 500 local OffsetZ = 500 if self.OffsetVec then - OffsetX = self.OffsetVec.x - OffsetY = self.OffsetVec.y - OffsetZ = self.OffsetVec.z + OffsetX = self.OffsetVec.x or 500 + OffsetY = self.OffsetVec.y or 500 + OffsetZ = self.OffsetVec.z or 500 end for i=1,self.EscortNumber do From 8ea746ee196fb595e081c53a05bb86e858831243 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 10 Feb 2025 18:02:57 +0100 Subject: [PATCH 141/349] xxx --- .../Moose/Functional/ATC_Ground.lua | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 4703559b9..9456fa270 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -18,7 +18,7 @@ -- ### Author: FlightControl - Framework Design & Programming -- ### Refactoring to use the Runway auto-detection: Applevangelist -- @date August 2022 --- Last Update Oct 2024 +-- Last Update Feb 2025 -- -- === -- @@ -416,7 +416,7 @@ end -- @field #ATC_GROUND_UNIVERSAL ATC_GROUND_UNIVERSAL = { ClassName = "ATC_GROUND_UNIVERSAL", - Version = "0.0.1", + Version = "0.0.2", SetClient = nil, Airbases = nil, AirbaseList = nil, @@ -441,17 +441,25 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) self:T( { self.ClassName } ) self.Airbases = {} - - for _name,_ in pairs(_DATABASE.AIRBASES) do - self.Airbases[_name]={} - end self.AirbaseList = AirbaseList if not self.AirbaseList then self.AirbaseList = {} - for _name,_ in pairs(_DATABASE.AIRBASES) do - self.AirbaseList[_name]=_name + for _name,_base in pairs(_DATABASE.AIRBASES) do + -- DONE exclude FARPS and Ships + if _base and _base.isAirdrome == true then + self.AirbaseList[_name]=_name + self.Airbases[_name]={} + end + end + else + for _,_name in pairs(AirbaseList) do + -- DONE exclude FARPS and Ships + local airbase = _DATABASE:FindAirbase(_name) + if airbase and airbase.isAirdrome == true then + self.Airbases[_name]={} + end end end @@ -1447,11 +1455,10 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end - - -- @type ATC_GROUND_MARIANAISLANDS +--- +-- @type ATC_GROUND_MARIANAISLANDS -- @extends #ATC_GROUND - --- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND} -- From d081e115c7b73dfdbf2e96b9c3e5ac68e1405af5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 18 Feb 2025 11:12:14 +0100 Subject: [PATCH 142/349] #MANTIS 4-Tier-Approach --- Moose Development/Moose/Functional/Mantis.lua | 164 ++++++++++++------ Moose Development/Moose/Functional/Shorad.lua | 4 +- 2 files changed, 113 insertions(+), 55 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index c25cce869..4ae7b68e3 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Jan 2025 +-- Last Update: Feb 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -62,6 +62,7 @@ -- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. -- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. -- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White +-- @field #number checkcounter Counter for SAM Table refreshes -- @extends Core.Base#BASE @@ -73,7 +74,7 @@ -- -- * Moose derived Modular, Automatic and Network capable Targeting and Interception System. -- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy. --- * **Automatic mode** (default since 0.8) can set-up your SAM site network automatically for you +-- * **Automatic mode** (default since 0.8) will set-up your SAM site network automatically for you -- * **Classic mode** behaves like before -- * Leverage evasiveness from SEAD, leverage attack range setting -- * Automatic setup of SHORAD based on groups of the class "short-range" @@ -88,6 +89,7 @@ -- * SAM sites, e.g. each **group name** begins with "Red SAM" -- * EWR network and AWACS, e.g. each **group name** begins with "Red EWR" and *not* e.g. "Red SAM EWR" (overlap with "Red SAM"), "Red EWR Awacs" will be found by "Red EWR" -- * SHORAD, e.g. each **group name** begins with "Red SHORAD" and *not" e.g. just "SHORAD" because you might also have "Blue SHORAD" +-- * Point Defense, e.g. each **group name** begins with "Red AAA" and *not" e.g. just "AAA" because you might also have "Blue AAA" -- -- It's important to get this right because of the nature of the filter-system in @{Core.Set#SET_GROUP}. Filters are "greedy", that is they -- will match *any* string that contains the search string - hence we need to avoid that SAMs, EWR and SHORAD step on each other\'s toes. @@ -194,26 +196,24 @@ -- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones) -- -- --- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target: +-- ### 2.1.2 Change the number of long-, mid- and short-range, point defense systems going live on a detected target: -- --- -- parameters are numbers. Defaults are 1,2,2,6 respectively --- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic) +-- -- parameters are numbers. Defaults are 1,2,2,6,6 respectively +-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) -- --- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range" +-- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "short-range" if the range is less than 5km or if the type is AAA. -- -- ### 2.1.4 Advanced features -- --- -- switch off auto mode **before** you start MANTIS. +-- -- Option to switch off auto mode **before** you start MANTIS (not recommended) -- mybluemantis.automode = false -- --- -- switch off auto shorad **before** you start MANTIS. --- mybluemantis.autoshorad = false --- --- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. +-- -- Option to set the scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. -- -- also see engagerange below. -- self.radiusscale[MANTIS.SamType.LONG] = 1.1 -- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 -- self.radiusscale[MANTIS.SamType.SHORT] = 1.3 +-- self.radiusscale[MANTIS.SamType.POINT] = 1.4 -- -- ### 2.1.5 Friendlies check in firing range -- @@ -242,9 +242,9 @@ -- -- Use this option if you want to make use of or allow advanced SEAD tactics. -- --- # 5. Integrate SHORAD [classic mode, not necessary in automode] +-- # 5. Integrate SHORAD [classic mode, not necessary in automode, not recommended for manual setup] -- --- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in +-- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs manually. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in -- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so: -- -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() @@ -298,6 +298,7 @@ MANTIS = { SAM_Table_Long = {}, SAM_Table_Medium = {}, SAM_Table_Short = {}, + SAM_Table_PointDef = {}, lid = "", Detection = nil, AWACS_Detection = nil, @@ -333,6 +334,7 @@ MANTIS = { checkforfriendlies = false, SmokeDecoy = false, SmokeDecoyColor = SMOKECOLOR.White, + checkcounter = 1, } --- Advanced state enumerator @@ -349,6 +351,7 @@ MANTIS.SamType = { SHORT = "Short", MEDIUM = "Medium", LONG = "Long", + POINT = "Point", } --- SAM data @@ -358,6 +361,7 @@ MANTIS.SamType = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamData = { ["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B @@ -369,16 +373,16 @@ MANTIS.SamData = { ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" }, ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"}, ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, - ["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, + ["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Point", Radar="Roland" }, ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, - ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, + ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" }, ["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" }, - ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Short", Radar="Tor 9A331" }, - ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, + ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true" }, + ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" }, ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, - ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Linebacker" }, + ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, @@ -386,7 +390,7 @@ MANTIS.SamData = { ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, - ["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="Igla" }, + ["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="Igla", Point="true" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, } @@ -398,6 +402,7 @@ MANTIS.SamData = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataHDS = { -- units from HDS Mod, multi launcher options is tricky -- group name MUST contain HDS to ID launcher type correctly! @@ -419,6 +424,7 @@ MANTIS.SamDataHDS = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataSMA = { -- units from SMA Mod (Sweedish Military Assets) -- https://forum.dcs.world/topic/295202-swedish-military-assets-for-dcs-by-currenthill/ @@ -432,7 +438,7 @@ MANTIS.SamDataSMA = { ["RBS103B SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" }, ["RBS103AM SMA"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, ["RBS103BM SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" }, - ["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" }, + ["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Point", Radar="LvKv9040",Point="true" }, } --- SAM data CH @@ -442,6 +448,7 @@ MANTIS.SamDataSMA = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataCH = { -- units from CH (Military Assets by Currenthill) -- https://www.currenthill.com/ @@ -458,20 +465,20 @@ MANTIS.SamDataCH = { ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, - ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_Centurion_C_RAM" }, - ["PGZ-09 CHM"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" }, + ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, + ["PGZ-09 CHM"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" }, ["S350-9M100 CHM"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, ["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" }, ["LAV-AD CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_LAVAD" }, ["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" }, - ["PGZ-95 CHM"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_PGZ95" }, - ["LD-3000 CHM"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="CH_LD3000_stationary" }, - ["LD-3000M CHM"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="CH_LD3000" }, + ["PGZ-95 CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" }, + ["LD-3000 CHM"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="CH_LD3000_stationary", Point="true" }, + ["LD-3000M CHM"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="CH_LD3000", Point="true" }, ["FlaRakRad CHM"] = { Range=8, Blindspot=1.5, Height=6, Type="Short", Radar="HQ17A" }, ["IRIS-T SLM CHM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, ["M903PAC2KAT1 CHM"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="CH_MIM104_M903_PAC2_KAT1" }, - ["Skynex CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Short", Radar="CH_SkynexHX" }, - ["Skyshield CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Short", Radar="CH_Skyshield_Gun" }, + ["Skynex CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" }, + ["Skyshield CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" }, ["WieselOzelot CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" }, ["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" }, ["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" }, @@ -486,7 +493,7 @@ MANTIS.SamDataCH = { ["RBS103B CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" }, ["RBS103AM CHM"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, ["RBS103BM CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" }, - ["Lvkv9040M CHM"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" }, + ["Lvkv9040M CHM"] = { Range=4, Blindspot=0, Height=2.5, Type="Point", Radar="LvKv9040", Point="true" }, } ----------------------------------------------------------------------- @@ -547,6 +554,7 @@ do self.SAM_Table_Long = {} self.SAM_Table_Medium = {} self.SAM_Table_Short = {} + self.SAM_Table_PointDef = {} self.dynamic = dynamic or false self.checkradius = 25000 self.grouping = 5000 @@ -579,6 +587,7 @@ do self.radiusscale[MANTIS.SamType.LONG] = 1.1 self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 self.radiusscale[MANTIS.SamType.SHORT] = 1.3 + self.radiusscale[MANTIS.SamType.POINT] = 1.4 --self.SAMCheckRanges = {} self.usezones = false self.AcceptZones = {} @@ -587,6 +596,7 @@ do self.maxlongrange = 1 self.maxmidrange = 2 self.maxshortrange = 2 + self.maxpointdefrange =6 self.maxclassic = 6 self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP @@ -669,9 +679,12 @@ do self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) end + -- counter for SAM table updates + self.checkcounter = 1 + -- TODO Version -- @field #string version - self.version="0.8.23" + self.version="0.9.24" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -923,13 +936,15 @@ do -- @param #number Mid Number of mid-range systems activated, defaults to 2. -- @param #number Long Number of long-range systems activated, defaults to 2. -- @param #number Classic (non-automode) Number of overall systems activated, defaults to 6. + -- @param #number Point Number of point defense and AAA systems activated, defaults to 6. -- @return #MANTIS self - function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic) + function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) self:T(self.lid .. "SetMaxActiveSAMs") self.maxclassic = Classic or 6 self.maxlongrange = Long or 1 self.maxmidrange = Mid or 2 self.maxshortrange = Short or 2 + self.maxpointdefrange= Point or 6 return self end @@ -1477,6 +1492,17 @@ do end if found then break end end + --- AAA or Point Defense + if not found then + local grp = GROUP:FindByName(grpname) + if (grp and grp:IsAlive() and grp:IsAAA()) or string.find(grpname,"AAA",1,true) then + range = 2000 + height = 2000 + blind = 50 + type = MANTIS.SamType.POINT + found = true + end + end if not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end @@ -1510,7 +1536,7 @@ do end --if self.automode then for idx,entry in pairs(self.SamData) do - self:T("ID = " .. idx) + self:T2("ID = " .. idx) if string.find(grpname,idx,1,true) then local _entry = entry -- #MANTIS.SamData type = _entry.Type @@ -1524,14 +1550,25 @@ do end end --end - -- secondary filter if not found + --- Secondary - AAA or Point Defense + if not found then + local grp = GROUP:FindByName(grpname) + if (grp and grp:IsAlive() and grp:IsAAA()) or string.find(grpname,"AAA",1,true) then + range = 2000 + height = 2000 + blind = 50 + type = MANTIS.SamType.POINT + found = true + end + end + --- Tertiary filter if not found if (not found) or HDSmod or SMAMod or CHMod then range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod) elseif not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end - if string.find(grpname,"SHORAD",1,true) then - type = MANTIS.SamType.SHORT -- force short on match + if found and string.find(grpname,"SHORAD",1,true) then + type = MANTIS.SamType.POINT -- force short on match end return range, height, type, blind end @@ -1550,6 +1587,7 @@ do local SAM_Tbl_lg = {} -- table of long range SAM defense zones local SAM_Tbl_md = {} -- table of mid range SAM defense zones local SAM_Tbl_sh = {} -- table of short range SAM defense zones + local SAM_Tbl_pt = {} -- table of point defense/AAA local SEAD_Grps = {} -- table of SAM names to make evasive local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc @@ -1573,18 +1611,22 @@ do if type == MANTIS.SamType.LONG then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) - --self:T("SAM "..grpname.." is type LONG") + self:T("SAM "..grpname.." is type LONG") elseif type == MANTIS.SamType.MEDIUM then table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) - --self:T("SAM "..grpname.." is type MEDIUM") + self:T("SAM "..grpname.." is type MEDIUM") elseif type == MANTIS.SamType.SHORT then table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) - --self:T("SAM "..grpname.." is type SHORT") + table.insert( SEAD_Grps, grpname ) + self:T("SAM "..grpname.." is type SHORT") + elseif type == MANTIS.SamType.POINT then + table.insert( SAM_Tbl_pt, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T("SAM "..grpname.." is type POINT") self.ShoradGroupSet:Add(grpname,group) if not self.autoshorad then table.insert( SEAD_Grps, grpname ) - end + end end self.SamStateTracker[grpname] = "GREEN" end @@ -1593,6 +1635,7 @@ do self.SAM_Table_Long = SAM_Tbl_lg self.SAM_Table_Medium = SAM_Tbl_md self.SAM_Table_Short = SAM_Tbl_sh + self.SAM_Table_PointDef = SAM_Tbl_pt -- make SAMs evasive local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD mysead:SetEngagementRange(engagerange) @@ -1616,7 +1659,8 @@ do local SAM_Tbl = {} -- table of SAM defense zones local SAM_Tbl_lg = {} -- table of long range SAM defense zones local SAM_Tbl_md = {} -- table of mid range SAM defense zones - local SAM_Tbl_sh = {} -- table of short range SAM defense zon + local SAM_Tbl_sh = {} -- table of short range SAM defense zones + local SAM_Tbl_pt = {} -- table of point defense/AAA local SEAD_Grps = {} -- table of SAM names to make evasive local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc @@ -1627,17 +1671,21 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) + local radaralive = group:IsSAM() table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) - if type == MANTIS.SamType.LONG then + if type == MANTIS.SamType.LONG and radaralive then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) - --self:I({grpname,grprange, grpheight}) - elseif type == MANTIS.SamType.MEDIUM then + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.MEDIUM and radaralive then table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) - --self:I({grpname,grprange, grpheight}) - elseif type == MANTIS.SamType.SHORT then - table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) - --self:I({grpname,grprange, grpheight}) + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.SHORT and radaralive then + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.POINT or (not radaralive) then + table.insert( SAM_Tbl_pt, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T({grpname,grprange, grpheight}) self.ShoradGroupSet:Add(grpname,group) if self.autoshorad then self.Shorad.Groupset = self.ShoradGroupSet @@ -1649,6 +1697,7 @@ do self.SAM_Table_Long = SAM_Tbl_lg self.SAM_Table_Medium = SAM_Tbl_md self.SAM_Table_Short = SAM_Tbl_sh + self.SAM_Table_PointDef = SAM_Tbl_pt -- make SAMs evasive if self.mysead ~= nil then local mysead = self.mysead @@ -1692,7 +1741,9 @@ do -- @param #table detset Table of COORDINATES -- @param #boolean dlink Using DLINK -- @param #number limit of SAM sites to go active on a contact - -- @return #MANTIS self + -- @return #number instatusred + -- @return #number instatusgreen + -- @return #number activeshorads function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates") local switchedon = 0 @@ -1707,6 +1758,9 @@ do local height = _data[4] local blind = _data[5] * 1.25 + 1 local shortsam = _data[6] == MANTIS.SamType.SHORT and true or false + if not shortsam then + shortsam = _data[6] == MANTIS.SamType.POINT and true or false + end local samgroup = GROUP:FindByName(name) local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) local suppressed = self.SuppressedGroups[name] or false @@ -1779,7 +1833,7 @@ do end --end alive end --end check end --for loop - if self.debug then + if self.debug or self.verbose then for _,_status in pairs(self.SamStateTracker) do if _status == "GREEN" then instatusgreen=instatusgreen+1 @@ -1806,22 +1860,24 @@ do --get detected set local detset = detection:GetDetectedItemCoordinates() --self:T("Check:", {detset}) - -- randomly update SAM Table - local rand = math.random(1,100) - if rand > 65 then -- 1/3 of cases + -- update SAM Table evey 3 runs + if self.checkcounter%3 == 0 then self:_RefreshSAMTable() end + self.checkcounter = self.checkcounter + 1 local instatusred = 0 local instatusgreen = 0 local activeshorads = 0 -- switch SAMs on/off if (n)one of the detected groups is inside their reach if self.automode then local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxlongrange) + local instatusredl, instatusgreenl, activeshoradsl = self:_CheckLoop(samset,detset,dlink,self.maxlongrange) local samset = self.SAM_Table_Medium -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxmidrange) + local instatusredm, instatusgreenm, activeshoradsm = self:_CheckLoop(samset,detset,dlink,self.maxmidrange) local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxshortrange) + local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange) + local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height + instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange) else local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index dd0fd7637..68ffe9c56 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -443,7 +443,9 @@ do for _,_groups in pairs (shoradset) do local groupname = _groups:GetName() if string.find(groupname, tgtgrp, 1, true) then - returnname = true + if _groups:IsSAM() then + returnname = true + end end end return returnname From 093c3fe7c956111b886781653d976b6f4e3aa9f0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 20 Feb 2025 11:40:01 +0100 Subject: [PATCH 143/349] #AUTOLASE - improve detection for ground Recce --- .../Moose/Functional/Autolase.lua | 86 ++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 1969fcbf9..7fb980ba8 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -91,6 +91,7 @@ -- @field #boolean threatmenu -- @field #number RoundingPrecision -- @field #table smokeoffset +-- @field #boolean increasegroundawareness -- @extends Ops.Intel#INTEL --- @@ -103,6 +104,7 @@ AUTOLASE = { debug = false, smokemenu = true, RoundingPrecision = 0, + increasegroundawareness = true, } --- Laser spot info @@ -121,7 +123,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.28" +AUTOLASE.version = "0.1.29" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -212,6 +214,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.smokemenu = true self.threatmenu = true self.RoundingPrecision = 0 + self.increasegroundawareness = true self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) @@ -326,6 +329,22 @@ function AUTOLASE:SetLaserCodes( LaserCodes ) return self end +--- [User] Improve ground unit detection by using a zone scan and LOS check. +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:EnableImproveGroundUnitsDetection() + self.increasegroundawareness = true + return self +end + +--- [User] Do not improve ground unit detection by using a zone scan and LOS check. +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:DisableImproveGroundUnitsDetection() + self.increasegroundawareness = false + return self +end + --- (Internal) Function to set pilot menu. -- @param #AUTOLASE self -- @return #AUTOLASE self @@ -694,7 +713,8 @@ function AUTOLASE:CleanCurrentLasing() local unit = recce:GetUnit(1) local name = unit:GetName() if not self.RecceUnits[name] then - self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime() } + local isground = (unit and unit.IsGround) and unit:IsGround() or false + self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground } end end end @@ -944,6 +964,63 @@ function AUTOLASE:CanLase(Recce,Unit) return canlase end +--- (Internal) Function to do a zone check per ground Recce and make found units and statics "known". +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:_Prescient() + -- self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground } + for _,_data in pairs(self.RecceUnits) do + -- ground units only + if _data.isground and _data.unit and _data.unit:IsAlive() then + local unit = _data.unit -- Wrapper.Unit#UNIT + local position = unit:GetCoordinate() -- Core.Point#COORDINATE + local needsinit = false + if position then + local lastposition = unit:GetProperty("lastposition") + -- property initiated? + if not lastposition then + unit:SetProperty("lastposition",position) + lastposition = position + needsinit = true + end + -- has moved? + local dist = position:Get2DDistance(lastposition) + -- refresh? + local TNow = timer.getAbsTime() + -- check + if dist > 10 or needsinit==true or TNow - _data.timestamp > 29 then + -- init scan objects + local hasunits,hasstatics,_,Units,Statics = position:ScanObjects(self.LaseDistance,true,true,false) + -- loop found units + if hasunits then + self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName()) + for _,_target in pairs(Units) do -- _, Wrapper.Unit#UNIT + if _target:GetCoalition() ~= self.coalition then + if unit:IsLOS(_target) and (not _target:IsUnitDetected(unit))then + unit:KnowUnit(_target,true,true) + end + end + end + end + -- loop found statics + if hasstatics then + self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) + for _,_static in pairs(Statics) do -- _, Wrapper.Unit#UNIT + if _static:GetCoalition() ~= self.coalition then + local IsLOS = position:IsLOS(_static:GetCoordinate()) + if IsLOS then + unit:KnowUnit(_static,true,true) + end + end + end + end + end + end + end + end + return self +end + ------------------------------------------------------------------- -- FSM Functions ------------------------------------------------------------------- @@ -956,6 +1033,9 @@ end -- @return #AUTOLASE self function AUTOLASE:onbeforeMonitor(From, Event, To) self:T({From, Event, To}) + if self.increasegroundawareness then + self:_Prescient() + end -- Check if group has detected any units. self:UpdateIntel() return self @@ -984,7 +1064,7 @@ function AUTOLASE:onafterMonitor(From, Event, To) local grp = contact.group local coord = contact.position local reccename = contact.recce or "none" - local threat = contact.threatlevel or 0 + local threat = contact.threatlevel or 0 local reccegrp = UNIT:FindByName(reccename) if reccegrp then local reccecoord = reccegrp:GetCoordinate() From 367d1c67158586f2cea40391f883e78aaa2f8e47 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 21 Feb 2025 10:39:38 +0100 Subject: [PATCH 144/349] xx --- Moose Development/Moose/Core/Set.lua | 15 ++++++++++++++ .../Moose/Functional/Autolase.lua | 18 +++++++++-------- Moose Development/Moose/Ops/OpsZone.lua | 2 ++ Moose Development/Moose/Ops/Target.lua | 20 ++++++++++++++++++- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index a2da90730..56ec9ecab 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -531,6 +531,21 @@ do -- SET_BASE return self.SomeIteratorLimit or self:Count() end + + --- Get max threat level of all objects in the SET. + -- @param #SET_BASE self + -- @return #number Max threat level found. + function SET_BASE:GetThreatLevelMax() + local ThreatMax = 0 + for _,_unit in pairs(self.Set or {}) do + local unit = _unit -- Wrapper.Unit#UNIT + local threat = unit.GetThreatLevel and unit:GetThreatLevel() or 0 + if threat > ThreatMax then + ThreatMax = threat + end + end + return ThreatMax + end --- Filters for the defined collection. -- @param #SET_BASE self diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 7fb980ba8..88cb6c9e6 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -994,10 +994,11 @@ function AUTOLASE:_Prescient() -- loop found units if hasunits then self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName()) - for _,_target in pairs(Units) do -- _, Wrapper.Unit#UNIT - if _target:GetCoalition() ~= self.coalition then - if unit:IsLOS(_target) and (not _target:IsUnitDetected(unit))then - unit:KnowUnit(_target,true,true) + for _,_target in pairs(Units) do -- Wrapper.Unit#UNIT object here + local target = _target -- Wrapper.Unit#UNIT + if target and target:GetCoalition() ~= self.coalition then + if unit:IsLOS(target) and (not target:IsUnitDetected(unit))then + unit:KnowUnit(target,true,true) end end end @@ -1005,11 +1006,12 @@ function AUTOLASE:_Prescient() -- loop found statics if hasstatics then self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) - for _,_static in pairs(Statics) do -- _, Wrapper.Unit#UNIT - if _static:GetCoalition() ~= self.coalition then - local IsLOS = position:IsLOS(_static:GetCoordinate()) + for _,_static in pairs(Statics) do -- DCS static object here + local static = STATIC:Find(_static) + if static and static:GetCoalition() ~= self.coalition then + local IsLOS = position:IsLOS(static:GetCoordinate()) if IsLOS then - unit:KnowUnit(_static,true,true) + unit:KnowUnit(static,true,true) end end end diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 12c4b4608..bbcf4f97b 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -723,6 +723,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @return #OPSZONE self function OPSZONE:onafterStart(From, Event, To) -- Info. @@ -739,6 +740,7 @@ function OPSZONE:onafterStart(From, Event, To) self:HandleEvent(EVENTS.BaseCaptured) end + return self end --- Stop OPSZONE FSM. diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index e0b750679..fcc108087 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -387,6 +387,8 @@ function TARGET:AddObject(Object) if Object:IsInstanceOf("OPSGROUP") then self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object. + --elseif Object:IsInstanceOf("OPSZONE") then + --self:_AddObject(Object:GetZone()) else self:_AddObject(Object) end @@ -1296,11 +1298,27 @@ function TARGET:GetTargetThreatLevelMax(Target) return 0 elseif Target.Type==TARGET.ObjectType.ZONE then + + local zone = Target.Object -- Core.Zone#ZONE_RADIUS + local foundunits = {} + if zone:IsInstanceOf("ZONE_RADIUS") or zone:IsInstanceOf("ZONE_POLYGON") then + zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}) + foundunits = zone:GetScannedSetUnit() + else + foundunits = SET_UNIT:New():FilterZones({zone}):FilterOnce() + end + local ThreatMax = foundunits:GetThreatLevelMax() or 0 + return ThreatMax - return 0 + elseif Target.Type==TARGET.ObjectType.OPSZONE then + + local unitset = Target.Object:GetScannedUnitSet() -- Core.Set#SET_UNIT + local ThreatMax = unitset:GetThreatLevelMax() + return ThreatMax else self:E("ERROR: unknown target object type in GetTargetThreatLevel!") + return 0 end return self From b66a3426422248da5a20ce42ceb6246e66c37fa4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 23 Feb 2025 11:12:00 +0100 Subject: [PATCH 145/349] xxx --- Moose Development/Moose/Functional/Mantis.lua | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 4ae7b68e3..fd6dea106 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -148,6 +148,7 @@ -- **Location** is of highest importance here. Whilst AWACS in DCS has almost the "all seeing eye", EWR don't have that. Choose your location wisely, against a mountain backdrop or inside a valley even the best EWR system -- doesn't work well. Prefer higher-up locations with a good view; use F7 in-game to check where you actually placed your EWR and have a look around. Apart from the obvious choice, do also consider other radar units -- for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do. +-- **HINT** Set at least one EWR on invisible and immortal so MANTIS doesn't stop working. -- -- ## 1.2 SAM sites -- @@ -201,7 +202,7 @@ -- -- parameters are numbers. Defaults are 1,2,2,6,6 respectively -- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) -- --- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "short-range" if the range is less than 5km or if the type is AAA. +-- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "point" or if the range is less than 5km or if the type is AAA. -- -- ### 2.1.4 Advanced features -- @@ -684,7 +685,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.24" + self.version="0.9.25" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -1146,6 +1147,24 @@ do end return self end + + --- [Internal] Check if any EWR or AWACS is still alive + -- @param #MANTIS self + -- @return #boolean outcome + function MANTIS:_CheckAnyEWRAlive() + self:T(self.lid .. "_CheckAnyEWRAlive") + local alive = false + if self.EWR_Group:CountAlive() > 0 then + alive = true + end + if not alive and self.AWACS_Prefix then + local awacs = GROUP:FindByName(self.AWACS_Prefix) + if awacs and awacs:IsAlive() then + alive = true + end + end + return alive + end --- [Internal] Function to determine state of the advanced mode -- @param #MANTIS self @@ -1757,9 +1776,9 @@ do local radius = _data[3] local height = _data[4] local blind = _data[5] * 1.25 + 1 - local shortsam = _data[6] == MANTIS.SamType.SHORT and true or false + local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false if not shortsam then - shortsam = _data[6] == MANTIS.SamType.POINT and true or false + shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false end local samgroup = GROUP:FindByName(name) local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) @@ -2001,12 +2020,34 @@ do if not self.state2flag then self:_Check(self.Detection,self.DLink) end - - --[[ check Awacs - if self.advAwacs and not self.state2flag then - self:_Check(self.AWACS_Detection,false) + + local EWRAlive = self:_CheckAnyEWRAlive() + + local function FindSAMSRTR() + for i=1,1000 do + local randomsam = self.SAM_Group:GetRandom() + if randomsam and randomsam:IsAlive() then + if randomsam:IsSAM() then return randomsam end + end + end + end + + -- Switch on a random SR/TR if no EWR left over + if not EWRAlive then + local randomsam = FindSAMSRTR() -- Wrapper.Group#GROUP + if randomsam and randomsam:IsAlive() then + if self.UseEmOnOff then + randomsam:EnableEmission(true) + else + randomsam:OptionAlarmStateRed() + end + local name = randomsam:GetName() + if self.SamStateTracker[name] ~= "RED" then + self:__RedState(1,randomsam) + self.SamStateTracker[name] = "RED" + end + end end - --]] -- relocate HQ and EWR if self.autorelocate then @@ -2016,8 +2057,6 @@ do local halfintv = math.floor(timepassed / relointerval) - --self:T({timepassed=timepassed, halfintv=halfintv}) - if halfintv >= 1 then self.TimeStamp = timer.getAbsTime() self:_Relocate() From 25845c4d220a3f9f594f7e269801691ddca3558f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 23 Feb 2025 16:09:41 +0100 Subject: [PATCH 146/349] xxx --- Moose Development/Moose/Core/Set.lua | 10 ++++++++++ Moose Development/Moose/Ops/EasyGCICAP.lua | 8 ++++---- Moose Development/Moose/Wrapper/Controllable.lua | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 56ec9ecab..f2b6e6d3c 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -4611,6 +4611,16 @@ do -- SET_CLIENT end return self end + + --- Make the SET handle CA slots **only** (GROUND units used by any player). Needs active filtering with `FilterStart()` + -- @param #SET_CLIENT self + -- @return #SET_CLIENT self + function SET_CLIENT:HandleCASlots() + self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) + self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) + self:FilterFunction(function(client) if client and client:IsAlive() and client:IsGround() then return true else return false end end) + return self + end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 93f6fcf3b..c5f0ebb0b 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -252,7 +252,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.16" +EASYGCICAP.version="0.1.17" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -599,7 +599,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local TankerInvisible = self.TankerInvisible - function CAP_Wing:OnAfterFlightOnMission(From, Event, To, Flightgroup, Mission) + function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission) local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP if DespawnAfterLanding then flightgroup:SetDespawnAfterLanding() @@ -629,7 +629,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) flightgroup:SetFuelLowRTB(true) Intel:AddAgent(flightgroup) if DespawnAfterHolding then - function flightgroup:OnAfterHolding(From,Event,To) + function flightgroup:onbeforeHolding(From,Event,To) self:Despawn(1,true) end end @@ -1327,7 +1327,7 @@ function EASYGCICAP:_StartIntel() self:_AssignIntercept(Cluster) end - function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster) + function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) AssignCluster(Cluster) end diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index fbe6a31e2..12f114734 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -4310,7 +4310,7 @@ function CONTROLLABLE:OptionDisperseOnAttack( Seconds ) end --- Returns if the unit is a submarine. --- @param #POSITIONABLE self +-- @param #CONTROLLABLE self -- @return #boolean Submarines attributes result. function CONTROLLABLE:IsSubmarine() self:F2() From 79e7f9940e7f0b4a71bd1a4c943a3f39b4766934 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Mar 2025 12:39:25 +0100 Subject: [PATCH 147/349] xx --- Moose Development/Moose/Wrapper/Group.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 17f23ed3c..75ece35ee 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1227,11 +1227,14 @@ function GROUP:GetCoordinate() -- First try to get the 3D vector of the group. This uses local vec3=self:GetVec3() + local coord if vec3 then - local coord=COORDINATE:NewFromVec3(vec3) + coord=COORDINATE:NewFromVec3(vec3) + coord.Heading = self:GetHeading() or 0 return coord end + -- No luck try units and add Heading data local Units = self:GetUnits() or {} for _,_unit in pairs(Units) do @@ -1242,15 +1245,15 @@ function GROUP:GetCoordinate() local FirstUnitCoordinate = FirstUnit:GetCoordinate() if FirstUnitCoordinate then - local Heading = self:GetHeading() + local Heading = self:GetHeading() or 0 FirstUnitCoordinate.Heading = Heading return FirstUnitCoordinate end end end - -- no luck, try the API way + -- no luck, try the API way local DCSGroup = Group.getByName(self.GroupName) if DCSGroup then local DCSUnits = DCSGroup:getUnits() or {} @@ -1261,14 +1264,19 @@ function GROUP:GetCoordinate() if point then --self:I(point) local coord = COORDINATE:NewFromVec3(point) + coord.Heading = 0 + local munit = UNIT:Find(_unit) + if munit then + coord.Heading = munit:GetHeading() or 0 + end return coord end end end end - + BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) - + end From 0baa912465713f3e2ce6f2dc6769db382d521847 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 6 Mar 2025 12:25:34 +0100 Subject: [PATCH 148/349] xx --- Moose Development/Moose/Core/Zone.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 491401931..463629249 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -2046,7 +2046,7 @@ function _ZONE_TRIANGLE:New(p1, p2, p3) end self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5 - + return self end @@ -2563,7 +2563,7 @@ function ZONE_POLYGON_BASE:ReFill(Color,Alpha) self.FillTriangles = {} end -- refill - for _, triangle in pairs(self._Triangles) do + for _,triangle in pairs(self._Triangles) do local draw_ids = triangle:Fill(coalition,color,alpha,nil) self.FillTriangles = draw_ids table.combine(self.DrawID, draw_ids) @@ -3574,7 +3574,7 @@ do -- ZONE_ELASTIC -- Debug info. --self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName))) - + -- Copy all points. local points=UTILS.DeepCopy(self.points or {}) @@ -3592,6 +3592,9 @@ do -- ZONE_ELASTIC -- Update polygon verticies from points. self._.Polygon=self:_ConvexHull(points) + + self._Triangles = self:_Triangulate() + self.SurfaceArea = self:_CalculateSurfaceArea() if Draw~=false then if self.DrawID or Draw==true then From 9e69c1c17e3e9c6686ee9b776661b1d2e7026d24 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 6 Mar 2025 14:51:34 +0100 Subject: [PATCH 149/349] xx --- Moose Development/Moose/Wrapper/Controllable.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 3932b67a8..2623dc6d5 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -5733,6 +5733,14 @@ function CONTROLLABLE:DisableIRMarkerForGroup() return self end +--- [GROUND] Check if an IR Spot exists. +-- @param #CONTROLLABLE self +-- @return #boolean outcome +function CONTROLLABLE:HasIRMarker() + if self.spot then return true end + return false +end + --- [Internal] This method is called by the scheduler after enabling the IR marker. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self From 176e4f62177b8e7e2a7143b95fa5062f7c1f70c0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Mar 2025 14:38:16 +0100 Subject: [PATCH 150/349] xxx --- Moose Development/Moose/Core/Zone.lua | 34 +++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 463629249..32ec18222 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -2054,7 +2054,7 @@ end -- @param #_ZONE_TRIANGLE self -- @param #table pt The point to check -- @param #table points (optional) The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it --- @return #bool True if the point is contained, false otherwise +-- @return #boolean True if the point is contained, false otherwise function _ZONE_TRIANGLE:ContainsPoint(pt, points) points = points or self.Points @@ -3536,7 +3536,37 @@ do -- ZONE_ELASTIC return self end + + --- Remove a vertex (point) from the polygon. + -- @param #ZONE_ELASTIC self + -- @param DCS#Vec2 Vec2 Point in 2D (with x and y coordinates). + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:RemoveVertex2D(Vec2) + + local found = false + local findex = 0 + for _id,_vec2 in pairs(self.points) do + if _vec2.x == Vec2.x and _vec2.y == Vec2.y then + found = true + findex = _id + break + end + end + + if found == true and findex > 0 then + table.remove(self.points,findex) + end + return self + end + + --- Remove a vertex (point) from the polygon. + -- @param #ZONE_ELASTIC self + -- @param DCS#Vec3 Vec3 Point in 3D (with x, y and z coordinates). Only the x and z coordinates are used. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:RemoveVertex3D(Vec3) + return self:RemoveVertex2D({x=Vec3.x, y=Vec3.z}) + end --- Add a vertex (point) to the polygon. -- @param #ZONE_ELASTIC self @@ -3793,7 +3823,7 @@ end --- Checks if a point is contained within the oval. -- @param #ZONE_OVAL self -- @param #table point The point to check --- @return #bool True if the point is contained, false otherwise +-- @return #boolean True if the point is contained, false otherwise function ZONE_OVAL:IsVec2InZone(vec2) local cos, sin = math.cos, math.sin local dx = vec2.x - self.CenterVec2.x From 6c8fa5585b0b365c52d357ddb99a0be99c1443c5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 11 Mar 2025 10:51:44 +0100 Subject: [PATCH 151/349] #CONTROLLABLE - Improved IR Markers --- .../Moose/Wrapper/Controllable.lua | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index f2eba2f43..79d45ee33 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -5642,19 +5642,18 @@ end -- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. -- @return #CONTROLLABLE self function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime) - --sefl:F("NewIRMarker") - if self.ClassName == "GROUP" then + self:T2("NewIRMarker") + if self:IsInstanceOf("GROUP") then + if self.IRMarkerGroup == true then return end self.IRMarkerGroup = true self.IRMarkerUnit = false - elseif self.ClassName == "UNIT" then + elseif self:IsInstanceOf("UNIT") then + if self.IRMarkerUnit == true then return end self.IRMarkerGroup = false self.IRMarkerUnit = true end - - self.spot = nil - self.timer = nil - self.stoptimer = nil + self.Runtime = Runtime or 60 if EnableImmediately and EnableImmediately == true then self:EnableIRMarker(Runtime) end @@ -5667,19 +5666,23 @@ end -- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Else run until you call `myobject:DisableIRMarker()`. -- @return #CONTROLLABLE self function CONTROLLABLE:EnableIRMarker(Runtime) - --sefl:F("EnableIRMarker") + self:T2("EnableIRMarker") if self.IRMarkerGroup == nil then self:NewIRMarker(true,Runtime) return end - if (self.IRMarkerGroup == true) then - self:EnableIRMarkerForGroup() + if self:IsInstanceOf("GROUP") then + self:EnableIRMarkerForGroup(Runtime) return end - + + if self.timer and self.timer:IsRunning() then return self end + + local Runtime = Runtime or self.Runtime self.timer = TIMER:New(CONTROLLABLE._MarkerBlink, self) self.timer:Start(nil, 1 - math.random(1, 5) / 10 / 2, Runtime) -- start randomized + self.IRMarkerUnit = true return self end @@ -5688,14 +5691,13 @@ end -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:DisableIRMarker() - --sefl:F("DisableIRMarker") - if (self.IRMarkerGroup == true) then + self:T2("DisableIRMarker") + if self:IsInstanceOf("GROUP") then self:DisableIRMarkerForGroup() return end if self.spot then - self.spot:destroy() self.spot = nil end if self.timer and self.timer:IsRunning() then @@ -5703,9 +5705,9 @@ function CONTROLLABLE:DisableIRMarker() self.timer = nil end - if self.ClassName == "GROUP" then + if self:IsInstanceOf("GROUP") then self.IRMarkerGroup = nil - elseif self.ClassName == "UNIT" then + elseif self:IsInstanceOf("UNIT") then self.IRMarkerUnit = nil end @@ -5714,14 +5716,17 @@ end --- [GROUND] Enable the IR markers for a whole group. -- @param #CONTROLLABLE self +-- @param #number Runtime Runtime of the marker in seconds -- @return #CONTROLLABLE self -function CONTROLLABLE:EnableIRMarkerForGroup() - --self:F("EnableIRMarkerForGroup") - if self.ClassName == "GROUP" then +function CONTROLLABLE:EnableIRMarkerForGroup(Runtime) + self:T2("EnableIRMarkerForGroup") + if self:IsInstanceOf("GROUP") + then local units = self:GetUnits() or {} for _,_unit in pairs(units) do - _unit:EnableIRMarker() + _unit:EnableIRMarker(Runtime) end + self.IRMarkerGroup = true end return self end @@ -5730,8 +5735,8 @@ end -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:DisableIRMarkerForGroup() - --sefl:F("DisableIRMarkerForGroup") - if self.ClassName == "GROUP" then + self:T2("DisableIRMarkerForGroup") + if self:IsInstanceOf("GROUP") then local units = self:GetUnits() or {} for _,_unit in pairs(units) do _unit:DisableIRMarker() @@ -5745,15 +5750,15 @@ end -- @param #CONTROLLABLE self -- @return #boolean outcome function CONTROLLABLE:HasIRMarker() - if self.IRMarkerGroup == true or self.IRMarkerUnit == true then return true end + self:T2("HasIRMarker") + if self.timer and self.timer:IsRunning() then return true end return false end ---- [Internal] This method is called by the scheduler after disabling the IR marker. +--- [Internal] This method is called by the scheduler to blink the IR marker. function CONTROLLABLE._StopSpot(spot) if spot then spot:destroy() - spot=nil end end @@ -5761,7 +5766,7 @@ end -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:_MarkerBlink() - --sefl:F("_MarkerBlink") + self:T2("_MarkerBlink") if self:IsAlive() ~= true then self:DisableIRMarker() return @@ -5772,7 +5777,8 @@ function CONTROLLABLE:_MarkerBlink() local _, _, unitBBHeight, _ = self:GetObjectSize() local unitPos = self:GetPositionVec3() - if not self.spot then + if self.timer:IsRunning() then + self:T2("Create Spot") local spot = Spot.createInfraRed( self.DCSUnit, { x = 0, y = (unitBBHeight + 1), z = 0 }, From 96ad3ff98b2bbff0e2272ada53165ed62a2e627e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 11 Mar 2025 16:32:01 +0100 Subject: [PATCH 152/349] xx --- Moose Development/Moose/Ops/PlayerTask.lua | 290 ++++++++++++------ .../Moose/Wrapper/Controllable.lua | 9 +- 2 files changed, 199 insertions(+), 100 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index a5a99b20c..56bb5b53e 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -1935,6 +1935,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.TaskQueue = FIFO:New() -- Utilities.FiFo#FIFO self.TasksPerPlayer = FIFO:New() -- Utilities.FiFo#FIFO self.PrecisionTasks = FIFO:New() -- Utilities.FiFo#FIFO + self.LasingDroneSet = SET_OPSGROUP:New() -- Core.Set#SET_OPSGROUP --self.PlayerMenu = {} -- #table self.FlashPlayer = {} -- #table self.AllowFlash = false @@ -2348,6 +2349,7 @@ end -- @param Core.Point#COORDINATE HoldingPoint (Optional) Point where the drone should initially circle. If not set, defaults to BullsEye of the coalition. -- @param #number Alt (Optional) Altitude in feet. Only applies if using a FLIGHTGROUP object! Defaults to 10000. -- @param #number Speed (Optional) Speed in knots. Only applies if using a FLIGHTGROUP object! Defaults to 120. +-- @param #number MaxTravelDist (Optional) Max distance to travel to traget. Only applies if using a FLIGHTGROUP object! Defaults to 100 NM. -- @return #PLAYERTASKCONTROLLER self -- @usage -- -- Set up precision bombing, FlightGroup as lasing unit @@ -2362,35 +2364,55 @@ end -- ArmyGroup:Activate() -- taskmanager:EnablePrecisionBombing(ArmyGroup,1688) -- -function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint, Alt, Speed) +function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) self:T(self.lid.."EnablePrecisionBombing") + + if not self.LasingDroneSet then + self.LasingDroneSet = SET_OPSGROUP:New() + end + + local LasingDrone -- Ops.FlightGroup#FLIGHTGROUP FlightGroup + if FlightGroup then if FlightGroup.ClassName and (FlightGroup.ClassName == "FLIGHTGROUP" or FlightGroup.ClassName == "ARMYGROUP")then -- ok we have a FG - self.LasingDrone = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP FlightGroup - self.LasingDrone.playertask = {} - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.id = 0 + LasingDrone = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP FlightGroup + self.precisionbombing = true - self.LasingDrone:SetLaser(LaserCode) - self.LaserCode = LaserCode or 1688 - self.LasingDroneTemplate = self.LasingDrone:_GetTemplate(true) - self.LasingDroneAlt = Alt or 10000 - self.LasingDroneSpeed = Speed or 120 + + LasingDrone.playertask = {} + LasingDrone.playertask.id = 0 + LasingDrone.playertask.busy = false + LasingDrone.playertask.lasercode = LaserCode or 1688 + LasingDrone:SetLaser(LasingDrone.playertask.lasercode) + LasingDrone.playertask.template = LasingDrone:_GetTemplate(true) + LasingDrone.playertask.alt = Alt or 10000 + LasingDrone.playertask.speed = Speed or 120 + LasingDrone.playertask.maxtravel = UTILS.NMToMeters(MaxTravelDist) or UTILS.NMToMeters(100) + -- let it orbit the BullsEye if FG - if self.LasingDrone:IsFlightgroup() then - self.LasingDroneIsFlightgroup = true + if LasingDrone:IsFlightgroup() then + --settings.IsFlightgroup = true local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.Coalition )) if HoldingPoint then BullsCoordinate = HoldingPoint end - local Orbit = AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,self.LasingDroneAlt,self.LasingDroneSpeed) - self.LasingDrone:AddMission(Orbit) - elseif self.LasingDrone:IsArmygroup() then - self.LasingDroneIsArmygroup = true + local Orbit = AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,Alt,Speed) + LasingDrone:AddMission(Orbit) + elseif LasingDrone:IsArmygroup() then + --settings.IsArmygroup = true local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.Coalition )) if HoldingPoint then BullsCoordinate = HoldingPoint end local Orbit = AUFTRAG:NewONGUARD(BullsCoordinate) - self.LasingDrone:AddMission(Orbit) + LasingDrone:AddMission(Orbit) end + + self.LasingDroneSet:AddObject(FlightGroup) + + elseif FlightGroup.ClassName and (FlightGroup.ClassName == "SET_OPSGROUP") then --SET_OPSGROUP + FlightGroup:ForEachGroup( + function(group) + self:EnablePrecisionBombing(group,LaserCode,HoldingPoint,Alt,Speed) + end + ) else self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!") end @@ -2401,6 +2423,20 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi return self end +--- [User] Convenience function - add done or ground allowing precision laser-guided bombing on statics and "high-value" ground units (MBT etc) +-- @param #PLAYERTASKCONTROLLER self +-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FlightGroup (e.g. drone) to be used for lasing (one unit in one group only). +-- Can optionally be handed as Ops.ArmyGroup#ARMYGROUP - **Note** might not find an LOS spot or get lost on the way. Cannot island-hop. +-- @param #number LaserCode The lasercode to be used. Defaults to 1688. +-- @param Core.Point#COORDINATE HoldingPoint (Optional) Point where the drone should initially circle. If not set, defaults to BullsEye of the coalition. +-- @param #number Alt (Optional) Altitude in feet. Only applies if using a FLIGHTGROUP object! Defaults to 10000. +-- @param #number Speed (Optional) Speed in knots. Only applies if using a FLIGHTGROUP object! Defaults to 120. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddPrecisionBombingOpsGroup(FlightGroup,LaserCode,HoldingPoint, Alt, Speed) + self:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) + return self +end + --- [User] Allow precision laser-guided bombing on statics and "high-value" ground units (MBT etc) with player units lasing. -- @param #PLAYERTASKCONTROLLER self @@ -2923,98 +2959,135 @@ end function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() self:T(self.lid.."_CheckPrecisionTasks") if self.PrecisionTasks:Count() > 0 and self.precisionbombing then - if not self.LasingDrone or self.LasingDrone:IsDead() then - -- we need a new drone - self:E(self.lid.."Lasing drone is dead ... creating a new one!") - if self.LasingDrone then - self.LasingDrone:_Respawn(1,nil,true) - else - -- DONE: Handle ArmyGroup - if self.LasingDroneIsFlightgroup then - local FG = FLIGHTGROUP:New(self.LasingDroneTemplate) - FG:Activate() - self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + + -- alive checks + self.LasingDroneSet:ForEachGroup( + function(LasingDrone) + if not LasingDrone or LasingDrone:IsDead() then + -- we need a new drone + self:E(self.lid.."Lasing drone is dead ... creating a new one!") + if LasingDrone then + LasingDrone:_Respawn(1,nil,true) else - local FG = ARMYGROUP:New(self.LasingDroneTemplate) - FG:Activate() - self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + --[[ + -- DONE: Handle ArmyGroup + if LasingDrone:IsFlightgroup() then + local FG = FLIGHTGROUP:New(LasingDroneTemplate) + FG:Activate() + self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + else + local FG = ARMYGROUP:New(LasingDroneTemplate) + FG:Activate() + self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + end -- if LasingDroneIsFlightgroup + --]] + end -- if LasingDrone + end -- if not LasingDrone + end -- function + ) + + local function SelectDrone() + local selected = nil + self.LasingDroneSet:ForEachGroup( + function(grp) + if grp.playertask and (not grp.playertask.busy) then + selected = grp + end end - end - return self - end - -- do we have a lasing unit assigned? - if self.LasingDrone and self.LasingDrone:IsAlive() then - if self.LasingDrone.playertask and (not self.LasingDrone.playertask.busy) then + ) + return selected + end + + local SelectedDrone = SelectDrone() -- Ops.OpsGroup#OPSGROUP + + -- do we have a lasing unit assignable? + if SelectedDrone and SelectedDrone:IsAlive() then + if SelectedDrone.playertask and (not SelectedDrone.playertask.busy) then -- not busy, get a task self:T(self.lid.."Sending lasing unit to target") local task = self.PrecisionTasks:Pull() -- Ops.PlayerTask#PLAYERTASK - self.LasingDrone.playertask.id = task.PlayerTaskNr - self.LasingDrone.playertask.busy = true - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.reachmessage = false - -- move the drone to target - if self.LasingDroneIsFlightgroup then - self.LasingDrone:CancelAllMissions() - local auftrag = AUFTRAG:NewORBIT_CIRCLE(task.Target:GetCoordinate(),self.LasingDroneAlt,self.LasingDroneSpeed) - self.LasingDrone:AddMission(auftrag) - elseif self.LasingDroneIsArmygroup then - local tgtcoord = task.Target:GetCoordinate() - local tgtzone = ZONE_RADIUS:New("ArmyGroup-"..math.random(1,10000),tgtcoord:GetVec2(),3000) - local finalpos=nil -- Core.Point#COORDINATE - for i=1,50 do - finalpos = tgtzone:GetRandomCoordinate(2500,0,{land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.SHALLOW_WATER}) - if finalpos then - if finalpos:IsLOS(tgtcoord,0) then - break + -- distance check + local startpoint = SelectedDrone:GetCoordinate() + local endpoint = task.Target:GetCoordinate() + local dist = math.huge + if startpoint and endpoint then + dist = startpoint:Get2DDistance(endpoint) + end + if dist <= SelectedDrone.playertask.maxtravel then + SelectedDrone.playertask.id = task.PlayerTaskNr + SelectedDrone.playertask.busy = true + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.reachmessage = false + -- move the drone to target + if SelectedDrone:IsFlightgroup() then + SelectedDrone:CancelAllMissions() + local auftrag = AUFTRAG:NewORBIT_CIRCLE(task.Target:GetCoordinate(),SelectedDrone.playertask.alt,SelectedDrone.playertask.speed) + SelectedDrone:AddMission(auftrag) + elseif SelectedDrone:IsArmygroup() then + local tgtcoord = task.Target:GetCoordinate() + local tgtzone = ZONE_RADIUS:New("ArmyGroup-"..math.random(1,10000),tgtcoord:GetVec2(),3000) + local finalpos=nil -- Core.Point#COORDINATE + for i=1,50 do + finalpos = tgtzone:GetRandomCoordinate(2500,0,{land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.SHALLOW_WATER}) + if finalpos then + if finalpos:IsLOS(tgtcoord,0) then + break + end end end + if finalpos then + SelectedDrone:CancelAllMissions() + -- yeah we got one + local auftrag = AUFTRAG:NewARMOREDGUARD(finalpos,"Off road") + SelectedDrone:AddMission(auftrag) + else + -- could not find LOS position! + self:E("***Could not find LOS position to post ArmyGroup for lasing!") + SelectedDrone.playertask.id = 0 + SelectedDrone.playertask.busy = false + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.reachmessage = false + end end - if finalpos then - self.LasingDrone:CancelAllMissions() - -- yeah we got one - local auftrag = AUFTRAG:NewARMOREDGUARD(finalpos,"Off road") - self.LasingDrone:AddMission(auftrag) - else - -- could not find LOS position! - self:E("***Could not find LOS position to post ArmyGroup for lasing!") - self.LasingDrone.playertask.id = 0 - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.reachmessage = false - end + else + self:T(self.lid.."Lasing unit too far from target") end self.PrecisionTasks:Push(task,task.PlayerTaskNr) - elseif self.LasingDrone.playertask and self.LasingDrone.playertask.busy then + end + + local function DronesWithTask(SelectedDrone) + -- handle drones with a task + if SelectedDrone.playertask and SelectedDrone.playertask.busy then -- drone is busy, set up laser when over target - local task = self.PrecisionTasks:ReadByID(self.LasingDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK + local task = self.PrecisionTasks:ReadByID(SelectedDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK self:T("Looking at Task: "..task.PlayerTaskNr.." Type: "..task.Type.." State: "..task:GetState()) if (not task) or task:GetState() == "Done" or task:GetState() == "Stopped" then -- we're done here - local task = self.PrecisionTasks:PullByID(self.LasingDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK + local task = self.PrecisionTasks:PullByID(SelectedDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK self:_CheckTaskQueue() task = nil - if self.LasingDrone:IsLasing() then - self.LasingDrone:__LaserOff(-1) + if SelectedDrone:IsLasing() then + SelectedDrone:__LaserOff(-1) end - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.id = 0 - self.LasingDrone.playertask.reachmessage = false + SelectedDrone.playertask.busy = false + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.id = 0 + SelectedDrone.playertask.reachmessage = false self:T(self.lid.."Laser Off") else -- not done yet - local dcoord = self.LasingDrone:GetCoordinate() + local dcoord = SelectedDrone:GetCoordinate() local tcoord = task.Target:GetCoordinate() tcoord.y = tcoord.y + 2 local dist = dcoord:Get2DDistance(tcoord) -- close enough? - if dist < 3000 and not self.LasingDrone:IsLasing() then + if dist < 3000 and not SelectedDrone:IsLasing() then self:T(self.lid.."Laser On") - self.LasingDrone:__LaserOn(-1,tcoord) - self.LasingDrone.playertask.inreach = true - if not self.LasingDrone.playertask.reachmessage then + SelectedDrone:__LaserOn(-1,tcoord) + SelectedDrone.playertask.inreach = true + if not SelectedDrone.playertask.reachmessage then --local textmark = self.gettext:GetEntry("FLARETASK",self.locale) - self.LasingDrone.playertask.reachmessage = true + SelectedDrone.playertask.reachmessage = true local clients = task:GetClients() local text = "" for _,playername in pairs(clients) do @@ -3022,7 +3095,7 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() local ttsplayername = playername if self.customcallsigns[playername] then ttsplayername = self.customcallsigns[playername] - end + end -- --text = string.format("%s, %s, pointer over target for task %03d, lasing!", playername, self.MenuName or self.Name, task.PlayerTaskNr) text = string.format(pointertext, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr) if not self.NoScreenOutput then @@ -3034,18 +3107,22 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() ) if client then local m = MESSAGE:New(text,15,"Tasking"):ToClient(client) - end - end - end + end -- + end -- + end -- if self.UseSRS then self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) - end - end - end - end - end - end - end + end -- + end -- + end -- + end -- end else + end -- end handle drones with a task + end -- end function + + self.LasingDroneSet:ForEachGroup(DronesWithTask) + + end -- + end -- return self end @@ -3558,6 +3635,22 @@ function PLAYERTASKCONTROLLER:_FlashInfo() return self end +--- [Internal] Find matching drone for precision bombing task, if any is assigned. +-- @param #PLAYERTASKCONTROLLER self +-- @param #number ID Task ID to look for +-- @return Ops.OpsGroup#OPSGROUP Drone +function PLAYERTASKCONTROLLER:_FindLasingDroneForTaskID(ID) + local drone = nil + self.LasingDroneSet:ForEachGroup( + function(grp) + if grp and grp:IsAlive() and grp.playertask and grp.playertask.id and grp.playertask.id == ID then + drone = grp + end + end + ) + return drone +end + --- [Internal] Show active task info -- @param #PLAYERTASKCONTROLLER self -- @param Ops.PlayerTask#PLAYERTASK Task @@ -3585,6 +3678,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local Elevation = Coordinate:GetLandHeight() or 0 -- meters local CoordText = "" local CoordTextLLDM = nil + local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else @@ -3620,11 +3714,11 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure) -- Prec bombing if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then - if self.LasingDrone and self.LasingDrone.playertask then + if LasingDrone and LasingDrone.playertask then local yes = self.gettext:GetEntry("YES",self.locale) local no = self.gettext:GetEntry("NO",self.locale) - local inreach = self.LasingDrone.playertask.inreach == true and yes or no - local islasing = self.LasingDrone:IsLasing() == true and yes or no + local inreach = LasingDrone.playertask.inreach == true and yes or no + local islasing = LasingDrone:IsLasing() == true and yes or no local prectext = self.gettext:GetEntry("POINTERTARGETREPORT",self.locale) prectext = string.format(prectext,inreach,islasing) text = text .. prectext.." ("..self.LaserCode..")" @@ -3732,7 +3826,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText) -- POINTERTARGETLASINGTTS = ". Pointer over target and lasing." if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then - if self.LasingDrone.playertask.inreach and self.LasingDrone:IsLasing() then + if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing() then local lasingtext = self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) ttstext = ttstext .. lasingtext end diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 79d45ee33..c158eb0c6 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -5639,7 +5639,7 @@ end --- [GROUND] Create and enable a new IR Marker for the given controllable UNIT or GROUP. -- @param #CONTROLLABLE self -- @param #boolean EnableImmediately (Optionally) If true start up the IR Marker immediately. Else you need to call `myobject:EnableIRMarker()` later on. --- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. +-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. Defaults to 60 seconds. -- @return #CONTROLLABLE self function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime) self:T2("NewIRMarker") @@ -5751,7 +5751,12 @@ end -- @return #boolean outcome function CONTROLLABLE:HasIRMarker() self:T2("HasIRMarker") - if self.timer and self.timer:IsRunning() then return true end + if self:IsInstanceOf("GROUP") then + local units = self:GetUnits() or {} + for _,_unit in pairs(units) do + if _unit.timer and _unit.timer:IsRunning() then return true end + end + elseif self.timer and self.timer:IsRunning() then return true end return false end From 2fced89dfc04d01f54eba8aa761469231c3ebc6c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 11 Mar 2025 18:31:16 +0100 Subject: [PATCH 153/349] xx --- Moose Development/Moose/Ops/PlayerTask.lua | 45 +++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 56bb5b53e..673c5095c 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -2388,7 +2388,7 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi LasingDrone.playertask.template = LasingDrone:_GetTemplate(true) LasingDrone.playertask.alt = Alt or 10000 LasingDrone.playertask.speed = Speed or 120 - LasingDrone.playertask.maxtravel = UTILS.NMToMeters(MaxTravelDist) or UTILS.NMToMeters(100) + LasingDrone.playertask.maxtravel = UTILS.NMToMeters(MaxTravelDist or 50) -- let it orbit the BullsEye if FG if LasingDrone:IsFlightgroup() then @@ -2396,6 +2396,7 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.Coalition )) if HoldingPoint then BullsCoordinate = HoldingPoint end local Orbit = AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,Alt,Speed) + Orbit:SetMissionAltitude(Alt) LasingDrone:AddMission(Orbit) elseif LasingDrone:IsArmygroup() then --settings.IsArmygroup = true @@ -2410,7 +2411,7 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi elseif FlightGroup.ClassName and (FlightGroup.ClassName == "SET_OPSGROUP") then --SET_OPSGROUP FlightGroup:ForEachGroup( function(group) - self:EnablePrecisionBombing(group,LaserCode,HoldingPoint,Alt,Speed) + self:EnablePrecisionBombing(group,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) end ) else @@ -2958,6 +2959,7 @@ end -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() self:T(self.lid.."_CheckPrecisionTasks") + self:T({count=self.PrecisionTasks:Count(),enabled=self.precisionbombing}) if self.PrecisionTasks:Count() > 0 and self.precisionbombing then -- alive checks @@ -2986,26 +2988,38 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() end -- function ) - local function SelectDrone() + local function SelectDrone(coord) local selected = nil + local mindist = math.huge + local dist = math.huge self.LasingDroneSet:ForEachGroup( function(grp) if grp.playertask and (not grp.playertask.busy) then - selected = grp + local gc = grp:GetCoordinate() + if coord and gc then + dist = coord:Get2DDistance(gc) + end + if dist < mindist then + selected = grp + mindist = dist + end end end ) return selected end + + local task = self.PrecisionTasks:Pull() -- Ops.PlayerTask#PLAYERTASK + local taskpt = task.Target:GetCoordinate() - local SelectedDrone = SelectDrone() -- Ops.OpsGroup#OPSGROUP + local SelectedDrone = SelectDrone(taskpt) -- Ops.OpsGroup#OPSGROUP -- do we have a lasing unit assignable? if SelectedDrone and SelectedDrone:IsAlive() then if SelectedDrone.playertask and (not SelectedDrone.playertask.busy) then -- not busy, get a task self:T(self.lid.."Sending lasing unit to target") - local task = self.PrecisionTasks:Pull() -- Ops.PlayerTask#PLAYERTASK + local isassigned = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) -- distance check local startpoint = SelectedDrone:GetCoordinate() local endpoint = task.Target:GetCoordinate() @@ -3013,7 +3027,7 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() if startpoint and endpoint then dist = startpoint:Get2DDistance(endpoint) end - if dist <= SelectedDrone.playertask.maxtravel then + if dist <= SelectedDrone.playertask.maxtravel and (not isassigned) then SelectedDrone.playertask.id = task.PlayerTaskNr SelectedDrone.playertask.busy = true SelectedDrone.playertask.inreach = false @@ -3052,10 +3066,14 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() else self:T(self.lid.."Lasing unit too far from target") end - self.PrecisionTasks:Push(task,task.PlayerTaskNr) + end - - local function DronesWithTask(SelectedDrone) + end + + self.PrecisionTasks:Push(task,task.PlayerTaskNr) + + + local function DronesWithTask(SelectedDrone) -- handle drones with a task if SelectedDrone.playertask and SelectedDrone.playertask.busy then -- drone is busy, set up laser when over target @@ -3076,10 +3094,12 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() self:T(self.lid.."Laser Off") else -- not done yet + self:T(self.lid.."Not done yet") local dcoord = SelectedDrone:GetCoordinate() local tcoord = task.Target:GetCoordinate() tcoord.y = tcoord.y + 2 local dist = dcoord:Get2DDistance(tcoord) + self:T(self.lid.."Dist "..dist) -- close enough? if dist < 3000 and not SelectedDrone:IsLasing() then self:T(self.lid.."Laser On") @@ -3120,8 +3140,7 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() end -- end function self.LasingDroneSet:ForEachGroup(DronesWithTask) - - end -- + end -- return self end @@ -3721,7 +3740,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local islasing = LasingDrone:IsLasing() == true and yes or no local prectext = self.gettext:GetEntry("POINTERTARGETREPORT",self.locale) prectext = string.format(prectext,inreach,islasing) - text = text .. prectext.." ("..self.LaserCode..")" + text = text .. prectext.." ("..LasingDrone.playertask.lasercode..")" end end -- Buddylasing From d16124213f84a449dc8ca9ea1e703bb8725489f7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 12 Mar 2025 10:25:54 +0100 Subject: [PATCH 154/349] xxx --- Moose Development/Moose/Ops/PlayerTask.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 673c5095c..c482c81e6 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -2415,7 +2415,7 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi end ) else - self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!") + self:E(self.lid.."No OPSGROUP/SET_OPSGROUP object passed or object is not alive!") end else self.autolase = nil From 8e017f2ce068a06c6e298e8560b9ad7894b8a5dc Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 13 Mar 2025 10:49:11 +0100 Subject: [PATCH 155/349] #ZONE Some Trigger Improvements, added OnAfterZoneEmpty --- Moose Development/Moose/Core/Zone.lua | 48 ++++++++++++++++++++++- Moose Development/Moose/Wrapper/Group.lua | 2 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 32ec18222..4e97f195b 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -605,10 +605,13 @@ function ZONE_BASE:Trigger(Objects) self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning") self:AddTransition("*","EnteredZone","*") self:AddTransition("*","LeftZone","*") + self:AddTransition("*","ZoneEmpty","*") + self:AddTransition("*","ObjectDead","*") self:AddTransition("*","TriggerRunCheck","*") self:AddTransition("*","TriggerStop","TriggerStopped") self:TriggerStart() self.checkobjects = Objects + self.ObjectsInZone = false if UTILS.IsInstanceOf(Objects,"SET_BASE") then self.objectset = Objects.Set else @@ -646,6 +649,22 @@ function ZONE_BASE:Trigger(Objects) -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable leaving the zone. + + --- On After "ObjectDead" event. An observed object has left the zone. + -- @function [parent=#ZONE_BASE] OnAfterObjectDead + -- @param #ZONE_BASE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable which died. Might be nil. + + --- On After "ZoneEmpty" event. All observed objects have left the zone or are dead. + -- @function [parent=#ZONE_BASE] OnAfterZoneEmpty + -- @param #ZONE_BASE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + end --- (Internal) Check the assigned objects for being in/out of the zone @@ -659,9 +678,13 @@ function ZONE_BASE:_TriggerCheck(fromstart) -- just earmark everyone in/out for _,_object in pairs(objectset) do local obj = _object -- Wrapper.Controllable#CONTROLLABLE - if not obj.TriggerInZone then obj.TriggerInZone = {} end + if not obj.TriggerInZone then + obj.TriggerInZone = {} + obj.TriggerZoneDeadNotification = false + end if obj and obj:IsAlive() and self:IsCoordinateInZone(obj:GetCoordinate()) then obj.TriggerInZone[self.ZoneName] = true + self.ObjectsInZone = true else obj.TriggerInZone[self.ZoneName] = false end @@ -669,6 +692,7 @@ function ZONE_BASE:_TriggerCheck(fromstart) end else -- Check for changes + local objcount = 0 for _,_object in pairs(objectset) do local obj = _object -- Wrapper.Controllable#CONTROLLABLE if obj and obj:IsAlive() then @@ -683,11 +707,20 @@ function ZONE_BASE:_TriggerCheck(fromstart) -- is obj in zone? local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) + if inzone and obj.TriggerInZone[self.ZoneName] then + -- just count + objcount = objcount + 1 + self.ObjectsInZone = true + obj.TriggerZoneDeadNotification = false + end if inzone and not obj.TriggerInZone[self.ZoneName] then -- wasn't in zone before --self:I("Newly entered") self:__EnteredZone(0.5,obj) obj.TriggerInZone[self.ZoneName] = true + objcount = objcount + 1 + self.ObjectsInZone = true + obj.TriggerZoneDeadNotification = false elseif (not inzone) and obj.TriggerInZone[self.ZoneName] then -- has left the zone --self:I("Newly left") @@ -696,8 +729,21 @@ function ZONE_BASE:_TriggerCheck(fromstart) else --self:I("Not left or not entered, or something went wrong!") end + else + -- object dead + if not obj.TriggerZoneDeadNotification == true then + obj.TriggerInZone = nil + self:__ObjectDead(0.5,obj) + obj.TriggerZoneDeadNotification = true + end end end + -- zone empty? + if objcount == 0 and self.ObjectsInZone == true then + -- zone was not but is now empty + self.ObjectsInZone = false + self:__ZoneEmpty(0.5) + end end return self end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 75ece35ee..9c183238d 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2816,7 +2816,7 @@ do -- Event Handling self:EventDispatcher():Reset( self ) - for UnitID, UnitData in pairs( self:GetUnits() ) do + for UnitID, UnitData in pairs( self:GetUnits() or {}) do UnitData:ResetEvents() end From 2a6df8434ad301addaf28639b299c2ef77e4a0e8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 15 Mar 2025 10:55:05 +0100 Subject: [PATCH 156/349] #SOUND/MSRS - small fix to delete the "--ssml" tag if google provider is used in conjunction with a file based sound output. --- Moose Development/Moose/Sound/SRS.lua | 3 ++- Moose Development/Moose/Sound/SoundOutput.lua | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index c4cab9064..c2e9cf152 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1313,7 +1313,8 @@ function MSRS:PlaySoundFile(Soundfile, Delay) -- Append file. command=command..' --file="'..tostring(soundfile)..'"' - + command=string.gsub(command,"--ssml","-h") + -- Execute command. self:_ExecCommand(command) diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index 638dc9aec..c13970482 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -291,7 +291,8 @@ do -- Sound File end do -- Text-To-Speech - + + --- -- @type SOUNDTEXT -- @field #string ClassName Name of the class -- @field #string text Text to speak. From b59bd0d4f2ae2b8898280ccf2d500f1421689cdd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Mar 2025 11:02:32 +0100 Subject: [PATCH 157/349] xx --- .../Moose/Functional/Autolase.lua | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 88cb6c9e6..3b074ce8e 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -74,7 +74,7 @@ -- @image Designation.JPG -- -- Date: 24 Oct 2021 --- Last Update: Feb 2025 +-- Last Update: Mar 2025 -- --- Class AUTOLASE -- @type AUTOLASE @@ -92,6 +92,7 @@ -- @field #number RoundingPrecision -- @field #table smokeoffset -- @field #boolean increasegroundawareness +-- @field #number MonitorFrequency -- @extends Ops.Intel#INTEL --- @@ -105,6 +106,7 @@ AUTOLASE = { smokemenu = true, RoundingPrecision = 0, increasegroundawareness = true, + MonitorFrequency = 30, } --- Laser spot info @@ -123,7 +125,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.29" +AUTOLASE.version = "0.1.30" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -215,6 +217,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.threatmenu = true self.RoundingPrecision = 0 self.increasegroundawareness = true + self.MonitorFrequency = 30 self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) @@ -319,11 +322,20 @@ end -- Helper Functions ------------------------------------------------------------------- +--- [User] When using Monitor, set the frequency here in which the report will appear +-- @param #AUTOLASE self +-- @param #number Seconds Run the report loop every number of seconds defined here. +-- @return #AUTOLASE self +function AUTOLASE:SetMonitorFrequency(Seconds) + self.MonitorFrequency = Seconds or 30 + return self +end + --- [User] Set a table of possible laser codes. --- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 } . +-- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 }. -- @param #AUTOLASE self -- @param #list<#number> LaserCodes --- @return #AUTOLASE +-- @return #AUTOLASE self function AUTOLASE:SetLaserCodes( LaserCodes ) self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes } return self @@ -1203,7 +1215,7 @@ function AUTOLASE:onafterMonitor(From, Event, To) end end - self:__Monitor(-30) + self:__Monitor(self.MonitorFrequency or 30) return self end From df9f204b7aab5e884ced4df88a447c8805a1b6f9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Mar 2025 13:06:19 +0100 Subject: [PATCH 158/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 4 ++-- Moose Development/Moose/Functional/Shorad.lua | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index b5e809816..6f2142436 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -600,7 +600,7 @@ do self.maxlongrange = 1 self.maxmidrange = 2 self.maxshortrange = 2 - self.maxpointdefrange =6 + self.maxpointdefrange = 6 self.maxclassic = 6 self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP @@ -2188,7 +2188,7 @@ do local Shorad = self.Shorad local radius = self.checkradius local ontime = self.ShoradTime - Shorad:WakeUpShorad(Name, radius, ontime) + Shorad:WakeUpShorad(Name, radius, ontime, nil, true) self:__ShoradActivated(1,Name, radius, ontime) end return self diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 68ffe9c56..01b90262e 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -472,6 +472,7 @@ do -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active -- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC + -- @param #boolean ShotAt If true, function is called after a shot -- @return #SHORAD self -- @usage Use this function to integrate with other systems, example -- @@ -481,7 +482,7 @@ do -- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() - function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat) + function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt) self:T(self.lid .. " WakeUpShorad") self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) local targetcat = TargetCat or Object.Category.UNIT @@ -526,7 +527,7 @@ do local groupname = _group:GetName() - if groupname == TargetGroup then + if groupname == TargetGroup and ShotAt==true then -- Shot at a SHORAD group if self.UseEmOnOff then _group:EnableEmission(false) @@ -628,7 +629,7 @@ do _targetgroupname = tgtgrp:GetName() -- group name _targetskill = tgtgrp:GetUnit(1):GetSkill() self:T("*** Found Target = ".. _targetgroupname) - self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT) + self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT,true) end end end @@ -757,7 +758,7 @@ do -- if being shot at, find closest SHORADs to activate if shotatsams or shotatus then self:T({shotatsams=shotatsams,shotatus=shotatus}) - self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat, true) end end end From c63402371744e33c50a4a524a7358d06512033b2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 21 Mar 2025 09:22:59 +0100 Subject: [PATCH 159/349] xxx --- Moose Development/Moose/Core/Point.lua | 4 +++- Moose Development/Moose/Functional/Mantis.lua | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 7f2b3d558..9afc49b7a 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1221,7 +1221,7 @@ do -- COORDINATE local s = string.format( '%03d°', AngleDegrees ) if MagVar then - local variation = UTILS.GetMagneticDeclination() or 0 + local variation = self:GetMagneticDeclination() or 0 local AngleMagnetic = AngleDegrees - variation if AngleMagnetic < 0 then AngleMagnetic = 360-AngleMagnetic end @@ -2959,6 +2959,8 @@ do -- COORDINATE local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 ) + local magnetic = self:GetMagneticDeclination() or 0 + bearing = bearing - magnetic local rangeMetres = self:Get2DDistance(currentCoord) local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 6f2142436..f55665da5 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Feb 2025 +-- Last Update: Mar 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -399,7 +399,6 @@ MANTIS.SamData = { ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, - ["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="Igla", Point="true" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, } @@ -688,7 +687,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.26" + self.version="0.9.27" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -1492,7 +1491,7 @@ do elseif chm then SAMData = self.SamDataCH end - --self:T("Looking to auto-match for "..grpname) + --self:I("Looking to auto-match for "..grpname) for _,_unit in pairs(units) do local unit = _unit -- Wrapper.Unit#UNIT local type = string.lower(unit:GetTypeName()) From 5aeb3ba42d47d3190b5c572f639e50abd7174d63 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Mar 2025 10:42:17 +0100 Subject: [PATCH 160/349] xx --- Moose Development/Moose/Functional/Autolase.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 3b074ce8e..9fed56cfc 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -125,7 +125,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.30" +AUTOLASE.version = "0.1.31" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -1215,7 +1215,8 @@ function AUTOLASE:onafterMonitor(From, Event, To) end end - self:__Monitor(self.MonitorFrequency or 30) + local nextloop = -self.MonitorFrequency or -30 + self:__Monitor(nextloop) return self end From d2ec9642ca9c2eb52395960f3176fe8fe8bb7a4a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 27 Mar 2025 11:08:33 +0100 Subject: [PATCH 161/349] xx --- Moose Development/Moose/Ops/PlayerRecce.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index a88685565..e178b58f0 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -838,7 +838,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) local minview = 0 local typename = unit:GetTypeName() local playername = unit:GetPlayerName() - local maxview = self.MaxViewDistance[typename] or 5000 + local maxview = self.MaxViewDistance[typename] or 8000 local heading,nod,maxview,angle = 0,30,8000,10 local camon = false local name = unit:GetName() @@ -846,24 +846,25 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit) angle=10 -- Model nod and actual TV view don't compute - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 elseif string.find(typename,"Ka-50") and camera then heading = unit:GetHeading() nod,maxview,camon = 10,1000,true angle = 10 - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 elseif string.find(typename,"OH58") and camera then --heading = unit:GetHeading() nod,maxview,camon = 0,8000,true heading,nod,maxview,camon = self:_GetKiowaMMSSight(unit) angle = 8 if maxview == 0 then - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 end else -- visual heading = unit:GetHeading() - nod,maxview,camon = 10,1000,true + nod,maxview,camon = 10,3000,true + maxview = self.MaxViewDistance[typename] or 3000 angle = 45 end if laser then From c21c3ecb42d1e3e29304f7fbc22a152f11cd032b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 30 Mar 2025 16:45:17 +0200 Subject: [PATCH 162/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 122 ++++++++++++++++----------- 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 7f195ac60..6caede880 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Feb 2025 +-- Last Update April 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -923,7 +923,6 @@ do -- ["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats -- ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats -- ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, --- ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, -- ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, -- ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, -- ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 8000}, @@ -1087,11 +1086,11 @@ do -- -- ## 4.7 List Inventory -- --- Lists invetory of available units to drop or build. +-- Lists inventory of available units to drop or build. -- --- ## 5. Support for Hercules mod by Anubis +-- ## 5. Support for fixed wings -- --- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same way and for the same objects as +-- Basic support for the Hercules mod By Anubis has been build into CTLD, as well as Bronco and Mosquito - that is you can load/drop/build the same way and for the same objects as -- the helicopters (main method). -- To cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu (F8), you need to use @{#CTLD_HERCULES.New}() and link -- this object to your CTLD setup (alternative method). In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod @@ -1103,13 +1102,13 @@ do -- -- Enable these options for Hercules support: -- --- my_ctld.enableHercules = true --- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft --- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft --- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn +-- my_ctld.enableFixedWing = true +-- my_ctld.FixedMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft +-- my_ctld.FixedMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- my_ctld.FixedMaxSpeed = 77 -- 77mps or 270kph or 150kn -- --- Hint: you can **only** airdrop from the Hercules if you are "in parameters", i.e. at or below `HercMaxSpeed` and in the AGL bracket between --- `HercMinAngels` and `HercMaxAngels`! +-- Hint: you can **only** airdrop from the Hercules if you are "in parameters", i.e. at or below `FixedMaxSpeed` and in the AGLFixedMinAngelseen +-- `FixedMinAngels` and `FixedMaxAngels`! -- -- Also, the following options need to be set to `true`: -- @@ -1117,9 +1116,9 @@ do -- -- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method, use either the above OR this method, NOT both!) -- --- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: +-- Taking another approach, integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: -- --- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew +-- my_ctld.enableFixedWing = false -- avoid dual loading via CTLD F10 and F8 ground crew -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- -- You also need: @@ -1384,11 +1383,20 @@ CTLD.UnitTypeCapabilities = { ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 10800}, + ["MosquitoFBMkVI"] = {type="MosquitoFBMkVI", crates= true, troops=false, cratelimit = 2, trooplimit = 0, length = 13, cargoweightlimit = 1800}, +} + +--- Allowed Fixed Wing Types +-- @type CTLD.FixedWingTypes +CTLD.FixedWingTypes = { + ["Hercules"] = "Hercules", + ["Bronco"] = "Bronco", + ["Mosquito"] = "Mosquito", } --- CTLD class version. -- @field #string version -CTLD.version="1.1.30" +CTLD.version="1.1.31" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1528,10 +1536,11 @@ function CTLD:New(Coalition, Prefixes, Alias) self.troopdropzoneradius = 100 -- added support Hercules Mod - self.enableHercules = false - self.HercMinAngels = 165 -- for troop/cargo drop via chute - self.HercMaxAngels = 2000 -- for troop/cargo drop via chute - self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps + self.enableHercules = false -- deprecated + self.enableFixedWing = false + self.FixedMinAngels = 165 -- for troop/cargo drop via chute + self.FixedMaxAngels = 2000 -- for troop/cargo drop via chute + self.FixedMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps -- message suppression self.suppressmessages = false @@ -2015,7 +2024,7 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end -- Herc support - if self:IsHercules(_unit) and self.enableHercules then + if self:IsFixedWing(_unit) and self.enableFixedWing then local unitname = event.IniUnitName or "none" self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() @@ -2183,6 +2192,22 @@ function CTLD:_FindCratesCargoObject(Name) return nil end +--- (User) Add a new fixed wing type to the list of allowed types. +-- @param #CTLD self +-- @param #string typename The typename to add. Can be handed as Wrapper.Unit#UNIT object. Do NOT forget to `myctld:SetUnitCapabilities()` for this type! +-- @return #CTLD self +function CTLD:AddAllowedFixedWingType(typename) + if type(typename) == "string" then + self.FixedWingTypes[typename] = typename + elseif typename and typename.ClassName and typename:IsInstanceOf("UNIT") then + local TypeName = typename:GetTypeName() or "none" + self.FixedWingTypes[TypeName] = TypeName + else + self:E(self.lid.."No valid typename or no UNIT handed!") + end + return self +end + --- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot! -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object @@ -2715,7 +2740,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) return self end -- spawn crates in front of helicopter - local IsHerc = self:IsHercules(Unit) -- Herc, Bronco and Hook load from behind + local IsHerc = self:IsFixedWing(Unit) -- Herc, Bronco and Hook load from behind local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number @@ -3088,7 +3113,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype --self:I(self.lid .. " Unit can carry: " .. tostring(cando)) --- Testing local distance = self:_GetDistance(location,staticpos) - --self:I(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) + self:T(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) if distance <= finddist and (weight <= maxloadable or _ignoreweight) and restricted == false and cando == true then index = index + 1 table.insert(found, staticid, cargo) @@ -3495,16 +3520,18 @@ function CTLD:_ListInventory(Group, Unit) return self end ---- (Internal) Function to check if a unit is a Hercules C-130 or a Bronco. +--- (Internal) Function to check if a unit is an allowed fixed wing. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome -function CTLD:IsHercules(Unit) - if Unit:GetTypeName() == "Hercules" or string.find(Unit:GetTypeName(),"Bronco") then - return true - else - return false +function CTLD:IsFixedWing(Unit) + local typename = Unit:GetTypeName() or "none" + for _,_name in pairs(self.FixedWingTypes or {}) do + if typename == _name or string.find(typename,_name,1,true) then + return true + end end + return false end --- (Internal) Function to check if a unit is a CH-47 @@ -3570,7 +3597,7 @@ function CTLD:_UnloadTroops(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) + local IsHerc = self:IsFixedWing(Unit) local IsHook = self:IsHook(Unit) if IsHerc and (not IsHook) then -- no hover but airdrop here @@ -3718,7 +3745,7 @@ function CTLD:_UnloadCrates(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) + local IsHerc = self:IsFixedWing(Unit) local IsHook = self:IsHook(Unit) if IsHerc and (not IsHook) then -- no hover but airdrop here @@ -3784,7 +3811,7 @@ end function CTLD:_BuildCrates(Group, Unit,Engineering) self:T(self.lid .. " _BuildCrates") -- avoid users trying to build from flying Hercs - if self:IsHercules(Unit) and self.enableHercules and not Engineering then + if self:IsFixedWing(Unit) and self.enableFixedWing and not Engineering then local speed = Unit:GetVelocityKMH() if speed > 1 then self:_SendMessage("You need to land / stop to build something, Pilot!", 10, false, Group) @@ -4150,7 +4177,7 @@ function CTLD:_RefreshF10Menus() local firstUnit = groupObj:GetFirstUnitAlive() if firstUnit then if firstUnit:IsPlayer() then - if firstUnit:IsHelicopter() or (self.enableHercules and self:IsHercules(firstUnit)) then + if firstUnit:IsHelicopter() or (self.enableFixedWing and self:IsFixedWing(firstUnit)) then local _unit = firstUnit:GetName() _UnitList[_unit] = _unit end @@ -4362,7 +4389,7 @@ function CTLD:_RefreshF10Menus() MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() - if self:IsHercules(_unit) then + if self:IsFixedWing(_unit) then MENU_GROUP_COMMAND:New(_group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() else MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() @@ -4597,7 +4624,7 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) -- Check hover/airdrop/landed logic local grounded = not self:IsUnitInAir(Unit) local hoverunload = self:IsCorrectHover(Unit) - local isHerc = self:IsHercules(Unit) + local isHerc = self:IsFixedWing(Unit) local isHook = self:IsHook(Unit) if isHerc and not isHook then hoverunload = self:IsCorrectFlightParameters(Unit) @@ -4758,7 +4785,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) end local hoverunload = self:IsCorrectHover(Unit) - local isHerc = self:IsHercules(Unit) + local isHerc = self:IsFixedWing(Unit) local isHook = self:IsHook(Unit) if isHerc and not isHook then hoverunload = self:IsCorrectFlightParameters(Unit) @@ -4900,7 +4927,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) self.Loaded_Cargo[unitName].Cratesloaded = cratesLoaded self:_RefreshDropTroopsMenu(Group, Unit) else - local isHerc = self:IsHercules(Unit) + local isHerc = self:IsFixedWing(Unit) if isHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else @@ -5802,9 +5829,9 @@ end end local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground - local minh = self.HercMinAngels-- 1500m - local maxh = self.HercMaxAngels -- 5000m - local maxspeed = self.HercMaxSpeed -- 77 mps + local minh = self.FixedMinAngels-- 1500m + local maxh = self.FixedMaxAngels -- 5000m + local maxspeed = self.FixedMaxSpeed -- 77 mps -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 local knspeed = kmspeed / 1.86 @@ -5847,12 +5874,12 @@ end if not inhover then htxt = "false" end local text = "" if _SETTINGS:IsImperial() then - local minheight = UTILS.MetersToFeet(self.HercMinAngels) - local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + local minheight = UTILS.MetersToFeet(self.FixedMinAngels) + local maxheight = UTILS.MetersToFeet(self.FixedMaxAngels) text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) else - local minheight = self.HercMinAngels - local maxheight = self.HercMaxAngels + local minheight = self.FixedMinAngels + local maxheight = self.FixedMaxAngels text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) @@ -5865,7 +5892,7 @@ end -- @return #boolean Outcome function CTLD:CanHoverLoad(Unit) self:T(self.lid .. " CanHoverLoad") - if self:IsHercules(Unit) then return false end + if self:IsFixedWing(Unit) then return false end local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) if not outcome then outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) --and self:IsCorrectHover(Unit) @@ -5880,7 +5907,7 @@ end function CTLD:IsUnitInAir(Unit) -- get speed and height local minheight = self.minimumHoverHeight - if self.enableHercules and self:IsHercules(Unit) then + if self.enableFixedWing and self:IsFixedWing(Unit) then minheight = 5.1 -- herc is 5m AGL on the ground end local uheight = Unit:GetHeight() @@ -6727,11 +6754,12 @@ end function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) self:I(self.lid .. "Started ("..self.version..")") + if self.enableHercules then self.enableFixedWing = true end if self.UserSetGroup then self.PilotGroups = self.UserSetGroup - elseif self.useprefix or self.enableHercules then + elseif self.useprefix or self.enableFixedWing then local prefix = self.prefixes - if self.enableHercules then + if self.enableFixedWing then self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() @@ -7533,7 +7561,7 @@ CTLD_HERCULES.Types = { -- @usage -- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: -- --- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew +-- my_ctld.enableFixedWing = false -- avoid dual loading via CTLD F10 and F8 ground crew -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- -- You also need: From 0361a95f5dede1b249e5dc48377d647c42f1ac79 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 30 Mar 2025 16:56:35 +0200 Subject: [PATCH 163/349] xx --- Moose Development/Moose/AI/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Moose Development/Moose/AI/.gitignore diff --git a/Moose Development/Moose/AI/.gitignore b/Moose Development/Moose/AI/.gitignore new file mode 100644 index 000000000..73a72496a --- /dev/null +++ b/Moose Development/Moose/AI/.gitignore @@ -0,0 +1,2 @@ +/AI_Patrol.lua +/AI_BAI.lua From 22b81318b9bd3ca17b88b8392c84699e9dafb5b8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 1 Apr 2025 13:18:02 +0200 Subject: [PATCH 164/349] xx --- Moose Development/Moose/Core/Point.lua | 439 ++++------- Moose Development/Moose/Core/Set.lua | 50 +- Moose Development/Moose/Core/Spawn.lua | 10 +- Moose Development/Moose/Core/SpawnStatic.lua | 709 +++--------------- Moose Development/Moose/Core/Zone.lua | 86 +-- .../Moose/Core/Zone_Detection.lua | 4 +- Moose Development/Moose/Ops/CTLD.lua | 8 +- Moose Development/Moose/Wrapper/Group.lua | 10 +- .../Moose/Wrapper/Positionable.lua | 18 +- 9 files changed, 374 insertions(+), 960 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 9afc49b7a..31b286aa3 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1157,6 +1157,151 @@ do -- COORDINATE return vec3 end + --- Return the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The x coordinate. + function COORDINATE:GetX() + return self.x + end + + --- Return the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The y coordinate. + function COORDINATE:GetY() + return self.y + end + + --- Return the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The z coordinate. + function COORDINATE:GetZ() + return self.z + end + + --- Set the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number x The x coordinate. + -- @return #COORDINATE + function COORDINATE:SetX( x ) + self.x = x + return self + end + + --- Set the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number y The y coordinate. + -- @return #COORDINATE + function COORDINATE:SetY( y ) + self.y = y + return self + end + + --- Set the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number z The z coordinate. + -- @return #COORDINATE + function COORDINATE:SetZ( z ) + self.z = z + return self + end + + --- Add to the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number x The x coordinate value to add to the current x coordinate. + -- @return #COORDINATE + function COORDINATE:AddX( x ) + self.x = self.x + x + return self + end + + + --- Return Return the Lat(itude) coordinate of the COORDINATE (ie: (parent)COORDINATE.x). + -- @param #COORDINATE self + -- @return #number The x coordinate. + function COORDINATE:GetLat() + return self.x + end + + --- Set the Lat(itude) coordinate of the COORDINATE (ie: COORDINATE.x). + -- @param #COORDINATE self + -- @param #number x The x coordinate. + -- @return #COORDINATE + function COORDINATE:SetLat( x ) + self.x = x + return self + end + + --- Return the Lon(gitude) coordinate of the COORDINATE (ie: (parent)COORDINATE.z). + -- @param #COORDINATE self + -- @return #number The y coordinate. + function COORDINATE:GetLon() + return self.z + end + + --- Set the Lon(gitude) coordinate of the COORDINATE (ie: COORDINATE.z). + -- @param #COORDINATE self + -- @param #number y The y coordinate. + -- @return #COORDINATE + function COORDINATE:SetLon( z ) + self.z = z + return self + end + + --- Return the altitude (height) of the land at the COORDINATE. + -- @param #COORDINATE self + -- @return #number The land altitude. + function COORDINATE:GetAlt() + return self.y ~= 0 or land.getHeight( { x = self.x, y = self.z } ) + end + + --- Set the altitude of the COORDINATE. + -- @param #COORDINATE self + -- @param #number Altitude The land altitude. If nothing (nil) is given, then the current land altitude is set. + -- @return #COORDINATE + function COORDINATE:SetAlt( Altitude ) + self.y = Altitude or land.getHeight( { x = self.x, y = self.z } ) + return self + end + + --- Add to the current land height an altitude. + -- @param #COORDINATE self + -- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. + -- @return #COORDINATE + function COORDINATE:AddAlt( Altitude ) + self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 + return self + end + + + --- Return a random COORDINATE within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. + -- @param #COORDINATE self + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius + -- @return #COORDINATE + function COORDINATE:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) + self:F2( { OuterRadius, InnerRadius } ) + + return COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) + end + + --- Add to the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number y The y coordinate value to add to the current y coordinate. + -- @return #COORDINATE + function COORDINATE:AddY( y ) + self.y = self.y + y + return self + end + + --- Add to the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number z The z coordinate value to add to the current z coordinate. + -- @return #COORDINATE + function COORDINATE:AddZ( z ) + self.z = self.z +z + return self + end + --- Returns a text documenting the wind direction (from) and strength according the measurement system @{Core.Settings}. -- The text will reflect the wind like this: @@ -3474,9 +3619,18 @@ do -- COORDINATE return flat, elev end + --- Return a random COORDINATE within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. + -- @param #COORDINATE self + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius + -- @return #COORDINATE + function COORDINATE:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) + return COORDINATE:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) + end + end -do -- POINT_VEC3 +do --- The POINT_VEC3 class -- @type POINT_VEC3 @@ -3493,6 +3647,8 @@ do -- POINT_VEC3 --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- + -- **DEPRECATED - PLEASE USE COORDINATE!** + -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. -- In order to keep the credibility of the the author, -- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors, @@ -3580,130 +3736,19 @@ do -- POINT_VEC3 return self end - --- Create a new POINT_VEC3 object from Vec2 coordinates. - -- @param #POINT_VEC3 self - -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @param DCS#Distance LandHeightAdd (optional) Add a landheight. - -- @return Core.Point#POINT_VEC3 self - function POINT_VEC3:NewFromVec2( Vec2, LandHeightAdd ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) ) -- Core.Point#POINT_VEC3 - self:F2( self ) - - return self - end - - - --- Create a new POINT_VEC3 object from Vec3 coordinates. - -- @param #POINT_VEC3 self - -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return Core.Point#POINT_VEC3 self - function POINT_VEC3:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec3( Vec3 ) ) -- Core.Point#POINT_VEC3 - self:F2( self ) - - return self - end - - - - --- Return the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The x coordinate. - function POINT_VEC3:GetX() - return self.x - end - - --- Return the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The y coordinate. - function POINT_VEC3:GetY() - return self.y - end - - --- Return the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The z coordinate. - function POINT_VEC3:GetZ() - return self.z - end - - --- Set the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetX( x ) - self.x = x - return self - end - - --- Set the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetY( y ) - self.y = y - return self - end - - --- Set the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number z The z coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetZ( z ) - self.z = z - return self - end - - --- Add to the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number x The x coordinate value to add to the current x coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddX( x ) - self.x = self.x + x - return self - end - - --- Add to the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number y The y coordinate value to add to the current y coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddY( y ) - self.y = self.y + y - return self - end - - --- Add to the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number z The z coordinate value to add to the current z coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddZ( z ) - self.z = self.z +z - return self - end - - --- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param DCS#Distance OuterRadius - -- @param DCS#Distance InnerRadius - -- @return #POINT_VEC3 - function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) - - return POINT_VEC3:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) - end - end -do -- POINT_VEC2 +do - -- @type POINT_VEC2 + --- @type POINT_VEC2 -- @field DCS#Distance x The x coordinate in meters. -- @field DCS#Distance y the y coordinate in meters. -- @extends Core.Point#COORDINATE --- Defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. -- + -- **DEPRECATED - PLEASE USE COORDINATE!** + -- -- ## POINT_VEC2 constructor -- -- A new POINT_VEC2 instance can be created with: @@ -3751,166 +3796,4 @@ do -- POINT_VEC2 return self end - --- Create a new POINT_VEC2 object from Vec2 coordinates. - -- @param #POINT_VEC2 self - -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @return Core.Point#POINT_VEC2 self - function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) - - local LandHeight = land.getHeight( Vec2 ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - local self = BASE:Inherit( self, COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) ) -- #POINT_VEC2 - self:F2( self ) - - return self - end - - --- Create a new POINT_VEC2 object from Vec3 coordinates. - -- @param #POINT_VEC2 self - -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return Core.Point#POINT_VEC2 self - function POINT_VEC2:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec3( Vec3 ) ) -- #POINT_VEC2 - self:F2( self ) - - return self - end - - --- Return the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The x coordinate. - function POINT_VEC2:GetX() - return self.x - end - - --- Return the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The y coordinate. - function POINT_VEC2:GetY() - return self.z - end - - --- Set the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetX( x ) - self.x = x - return self - end - - --- Set the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetY( y ) - self.z = y - return self - end - - --- Return Return the Lat(itude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.x). - -- @param #POINT_VEC2 self - -- @return #number The x coordinate. - function POINT_VEC2:GetLat() - return self.x - end - - --- Set the Lat(itude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.x). - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetLat( x ) - self.x = x - return self - end - - --- Return the Lon(gitude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.z). - -- @param #POINT_VEC2 self - -- @return #number The y coordinate. - function POINT_VEC2:GetLon() - return self.z - end - - --- Set the Lon(gitude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.z). - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetLon( z ) - self.z = z - return self - end - - --- Return the altitude (height) of the land at the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The land altitude. - function POINT_VEC2:GetAlt() - return self.y ~= 0 or land.getHeight( { x = self.x, y = self.z } ) - end - - --- Set the altitude of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number Altitude The land altitude. If nothing (nil) is given, then the current land altitude is set. - -- @return #POINT_VEC2 - function POINT_VEC2:SetAlt( Altitude ) - self.y = Altitude or land.getHeight( { x = self.x, y = self.z } ) - return self - end - - --- Add to the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:AddX( x ) - self.x = self.x + x - return self - end - - --- Add to the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:AddY( y ) - self.z = self.z + y - return self - end - - --- Add to the current land height an altitude. - -- @param #POINT_VEC2 self - -- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. - -- @return #POINT_VEC2 - function POINT_VEC2:AddAlt( Altitude ) - self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 - return self - end - - - --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param DCS#Distance OuterRadius - -- @param DCS#Distance InnerRadius - -- @return #POINT_VEC2 - function POINT_VEC2:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { OuterRadius, InnerRadius } ) - - return POINT_VEC2:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) - end - - -- TODO: Check this to replace - --- Calculate the distance from a reference @{#POINT_VEC2}. - -- @param #POINT_VEC2 self - -- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. - -- @return DCS#Distance The distance from the reference @{#POINT_VEC2} in meters. - function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) - self:F2( PointVec2Reference ) - - local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5 - - self:T2( Distance ) - return Distance - end - end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 3f4fa2e71..0b54f06d0 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -629,14 +629,14 @@ do -- SET_BASE return self end - --- Iterate the SET_BASE while identifying the nearest object in the set from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_BASE while identifying the nearest object in the set from a @{Core.Point#COORDINATE}. -- @param #SET_BASE self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#COORDINATE} or @{Core.Point#POINT_VEC2} object (but **not** a simple DCS#Vec2!) from where to evaluate the closest object in the set. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object (but **not** a simple DCS#Vec2!) from where to evaluate the closest object in the set. -- @return Core.Base#BASE The closest object. -- @usage -- myset:FindNearestObjectFromPointVec2( ZONE:New("Test Zone"):GetCoordinate() ) - function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) - --self:F2( PointVec2 ) + function SET_BASE:FindNearestObjectFromPointVec2( Coordinate ) + --self:F2( Coordinate ) local NearestObject = nil local ClosestDistance = nil @@ -644,9 +644,9 @@ do -- SET_BASE for ObjectID, ObjectData in pairs( self.Set ) do if NearestObject == nil then NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + ClosestDistance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) else - local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + local Distance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) if Distance < ClosestDistance then NearestObject = ObjectData ClosestDistance = Distance @@ -1242,12 +1242,12 @@ do return GroupFound end - --- Iterate the SET_GROUP while identifying the nearest object from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_GROUP while identifying the nearest object from a @{Core.Point#COORDINATE}. -- @param #SET_GROUP self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest object in the set. -- @return Wrapper.Group#GROUP The closest group. - function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) - --self:F2( PointVec2 ) + function SET_GROUP:FindNearestGroupFromPointVec2( Coordinate ) + --self:F2( Coordinate ) local NearestGroup = nil -- Wrapper.Group#GROUP local ClosestDistance = nil @@ -1257,9 +1257,9 @@ do for ObjectID, ObjectData in pairs( Set ) do if NearestGroup == nil then NearestGroup = ObjectData - ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + ClosestDistance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) else - local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + local Distance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) if Distance < ClosestDistance then NearestGroup = ObjectData ClosestDistance = Distance @@ -5670,14 +5670,14 @@ do -- SET_AIRBASE return self end - --- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#COORDINATE}. -- @param #SET_AIRBASE self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. -- @return Wrapper.Airbase#AIRBASE The closest @{Wrapper.Airbase#AIRBASE}. - function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) - --self:F2( PointVec2 ) + function SET_AIRBASE:FindNearestAirbaseFromPointVec2( Coordinate ) + --self:F2( Coordinate ) - local NearestAirbase = self:FindNearestObjectFromPointVec2( PointVec2 ) + local NearestAirbase = self:FindNearestObjectFromPointVec2( Coordinate ) return NearestAirbase end @@ -6007,17 +6007,19 @@ do -- SET_CARGO return self end - --- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#POINT_VEC2}. + --- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#COORDINATE}. -- @param #SET_CARGO self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Cargo.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. - function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) -- R2.1 - --self:F2( PointVec2 ) + function SET_CARGO:FindNearestCargoFromPointVec2( Coordinate ) -- R2.1 + --self:F2( Coordinate ) - local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) + local NearestCargo = self:FindNearestObjectFromPointVec2( Coordinate ) return NearestCargo end - + + --- + -- @param #SET_CARGO self function SET_CARGO:FirstCargoWithState( State ) local FirstCargo = nil @@ -6032,6 +6034,8 @@ do -- SET_CARGO return FirstCargo end + --- + -- @param #SET_CARGO self function SET_CARGO:FirstCargoWithStateAndNotDeployed( State ) local FirstCargo = nil diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index de81f9a46..c81aea59d 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1631,7 +1631,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if SpawnTemplate then - local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) + local PointVec3 = COORDINATE:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) --self:T2( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) -- If RandomizePosition, then Randomize the formation in the zone band, keeping the template. @@ -2830,7 +2830,7 @@ end function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) --self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) + local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) --self:T2( PointVec3 ) if SpawnIndex then @@ -2906,7 +2906,7 @@ end -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group. +-- @param Core.Point#COORDINATE PointVec3 The COORDINATE coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage @@ -2954,12 +2954,12 @@ function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex ) return self:SpawnFromVec3( { x = Vec2.x, y = Height, z = Vec2.y }, SpawnIndex ) -- y can be nil. In this case, spawn on the ground for vehicles, and in the template altitude for air. end ---- Will spawn a group from a POINT_VEC2 in 3D space. +--- Will spawn a group from a COORDINATE in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 coordinates where to spawn the group. +-- @param Core.Point#COORDINATE PointVec2 The coordinates where to spawn the group. -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index a26dd1d1d..9feb2eb90 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -1,603 +1,130 @@ ---- **Core** - Spawn statics. --- --- === --- --- ## Features: --- --- * Spawn new statics from a static already defined in the mission editor. --- * Spawn new statics from a given template. --- * Spawn new statics from a given type. --- * Spawn with a custom heading and location. --- * Spawn within a zone. --- * Spawn statics linked to units, .e.g on aircraft carriers. --- --- === --- --- # Demo Missions --- --- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/SpawnStatic) --- --- --- === --- --- # YouTube Channel --- --- ## No videos yet! --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: **funkyfranky** --- --- === --- --- @module Core.SpawnStatic --- @image Core_Spawnstatic.JPG ---- @type SPAWNSTATIC --- @field #string SpawnTemplatePrefix Name of the template group. --- @field #number CountryID Country ID. --- @field #number CoalitionID Coalition ID. --- @field #number CategoryID Category ID. --- @field #number SpawnIndex Running number increased with each new Spawn. --- @field Wrapper.Unit#UNIT InitLinkUnit The unit the static is linked to. --- @field #number InitOffsetX Link offset X coordinate. --- @field #number InitOffsetY Link offset Y coordinate. --- @field #number InitOffsetAngle Link offset angle in degrees. --- @field #number InitStaticHeading Heading of the static. --- @field #string InitStaticLivery Livery for aircraft. --- @field #string InitStaticShape Shape of the static. --- @field #string InitStaticType Type of the static. --- @field #string InitStaticCategory Categrory of the static. --- @field #string InitStaticName Name of the static. --- @field Core.Point#COORDINATE InitStaticCoordinate Coordinate where to spawn the static. --- @field #boolean InitStaticDead Set static to be dead if true. --- @field #boolean InitStaticCargo If true, static can act as cargo. --- @field #number InitStaticCargoMass Mass of cargo in kg. --- @extends Core.Base#BASE +--[[ +local CA_SET=SET_CLIENT:New():HandleCASlots():FilterCoalitions("blue"):FilterStart() - ---- Allows to spawn dynamically new @{Wrapper.Static}s into your mission. --- --- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), --- and "copy" these properties to create a new static object and place it at the desired coordinate. --- --- New spawned @{Wrapper.Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method. --- By default, spawned @{Wrapper.Static}s will follow a naming convention at run-time: --- --- * Spawned @{Wrapper.Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**. --- --- # SPAWNSTATIC Constructors --- --- Firstly, we need to create a SPAWNSTATIC object that will be used to spawn new statics into the mission. There are three ways to do this. --- --- ## Use another Static --- --- A new SPAWNSTATIC object can be created using another static by the @{#SPAWNSTATIC.NewFromStatic}() function. All parameters such as position, heading, country will be initialized --- from the static. --- --- ## From a Template --- --- A SPAWNSTATIC object can also be created from a template table using the @{#SPAWNSTATIC.NewFromTemplate}(SpawnTemplate, CountryID) function. All parameters are taken from the template. --- --- ## From a Type --- --- A very basic method is to create a SPAWNSTATIC object by just giving the type of the static. All parameters must be initialized from the InitXYZ functions described below. Otherwise default values --- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map. --- --- # Setting Parameters --- --- Parameters such as the spawn position, heading, country etc. can be set via :Init*XYZ* functions. Note that these functions must be given before the actual spawn command! --- --- * @{#SPAWNSTATIC.InitCoordinate}(Coordinate) Sets the coordinate where the static is spawned. Statics are always spawnd on the ground. --- * @{#SPAWNSTATIC.InitHeading}(Heading) sets the orientation of the static. --- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this. --- * @{#SPAWNSTATIC.InitType}(StaticType) sets the type of the static. --- * @{#SPAWNSTATIC.InitShape}(StaticType) sets the shape of the static. Not all statics have this parameter. --- * @{#SPAWNSTATIC.InitNamePrefix}(NamePrefix) sets the name prefix of the spawned statics. --- * @{#SPAWNSTATIC.InitCountry}(CountryID) sets the country and therefore the coalition of the spawned statics. --- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier. --- --- # Spawning the Statics --- --- Once the SPAWNSTATIC object is created and parameters are initialized, the spawn command can be given. There are different methods where some can be used to directly set parameters --- such as position and heading. --- --- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**! --- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**! --- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**! --- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Core.Zone}. Optionally, heading and name can be given. The name **must be unique**! --- --- @field #SPAWNSTATIC SPAWNSTATIC --- -SPAWNSTATIC = { - ClassName = "SPAWNSTATIC", - SpawnIndex = 0, -} - ---- Static template table data. --- @type SPAWNSTATIC.TemplateData --- @field #string name Name of the static. --- @field #string type Type of the static. --- @field #string category Category of the static. --- @field #number x X-coordinate of the static. --- @field #number y Y-coordinate of teh static. --- @field #number heading Heading in rad. --- @field #boolean dead Static is dead if true. --- @field #string livery_id Livery name. --- @field #number unitId Unit ID. --- @field #number groupId Group ID. --- @field #table offsets Offset parameters when linked to a unit. --- @field #number mass Cargo mass in kg. --- @field #boolean canCargo Static can be a cargo. - ---- Creates the main object to spawn a @{Wrapper.Static} defined in the mission editor (ME). --- @param #SPAWNSTATIC self --- @param #string SpawnTemplateName Name of the static object in the ME. Each new static will have the name starting with this prefix. --- @param DCS#country.id SpawnCountryID (Optional) The ID of the country. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) - - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - - local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) - - if TemplateStatic then - self.SpawnTemplatePrefix = SpawnTemplateName - self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) - self.CountryID = SpawnCountryID or CountryID - self.CategoryID = CategoryID - self.CoalitionID = CoalitionID - self.SpawnIndex = 0 - else - error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) +function CA_SET:OnAfterAdded(From,Event,To,ObjectName,Object) + MESSAGE:New("Player joined CA Slot: "..ObjectName,10,"CA"):ToAll() + local client = Object -- Wrapper.Client#CLIENT + local group = client:GetGroup() + if group then + MENU_GROUP:New(group,"Test CA") end - - self:SetEventPriority( 5 ) - - return self end ---- Creates the main object to spawn a @{Wrapper.Static} given a template table. --- @param #SPAWNSTATIC self --- @param #table SpawnTemplate Template used for spawning. --- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID) - - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - - self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate) - self.SpawnTemplatePrefix = SpawnTemplate.name - self.CountryID = CountryID or country.id.USA - - return self -end - ---- Creates the main object to spawn a @{Wrapper.Static} from a given type. --- NOTE that you have to init many other parameters as spawn coordinate etc. --- @param #SPAWNSTATIC self --- @param #string StaticType Type of the static. --- @param #string StaticCategory Category of the static, e.g. "Planes". --- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID) - - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - - self.InitStaticType=StaticType - self.InitStaticCategory=StaticCategory - self.CountryID=CountryID or country.id.USA - self.SpawnTemplatePrefix=self.InitStaticType - self.TemplateStaticUnit = {} - - self.InitStaticCoordinate=COORDINATE:New(0, 0, 0) - self.InitStaticHeading=0 - - return self -end - ---- (Internal/Cargo) Init the resource table for STATIC object that should be spawned containing storage objects. --- NOTE that you have to init many other parameters as the resources. --- @param #SPAWNSTATIC self --- @param #number CombinedWeight The weight this cargo object should have (some have fixed weights!), defaults to 1kg. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:_InitResourceTable(CombinedWeight) - if not self.TemplateStaticUnit.resourcePayload then - self.TemplateStaticUnit.resourcePayload = { - ["weapons"] = {}, - ["aircrafts"] = {}, - ["gasoline"] = 0, - ["diesel"] = 0, - ["methanol_mixture"] = 0, - ["jet_fuel"] = 0, - } + local e = {} + function e:onEvent(event) + local m = {} + m[#m+1] = "Event ID: " + m[#m+1] = event.id + if event.initiator then + m[#m+1] = "\nInitiator : " + m[#m+1] = event.initiator:getName() + end + if event.weapon then + m[#m+1] = "\nWeapon : " + m[#m+1] = event.weapon :getTypeName() + end + if event.target then + m[#m+1] = "\nTarget : " + m[#m+1] = event.target :getName() + end + env.info(table.concat(m)) end - self:InitCargo(true) - self:InitCargoMass(CombinedWeight or 1) - return self -end - ---- (User/Cargo) Add to resource table for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. --- @param #SPAWNSTATIC self --- @param #string Type Type of cargo. Known types are: STORAGE.Type.WEAPONS, STORAGE.Type.LIQUIDS, STORAGE.Type.AIRCRAFT. Liquids are fuel. --- @param #string Name Name of the cargo type. Liquids can be STORAGE.LiquidName.JETFUEL, STORAGE.LiquidName.GASOLINE, STORAGE.LiquidName.MW50 and STORAGE.LiquidName.DIESEL. The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. Aircraft go by their typename. --- @param #number Amount of tons (liquids) or number (everything else) to add. --- @param #number CombinedWeight Combined weight to be set to this static cargo object. NOTE - some static cargo objects have fixed weights! --- @return #SPAWNSTATIC self -function SPAWNSTATIC:AddCargoResource(Type,Name,Amount,CombinedWeight) - if not self.TemplateStaticUnit.resourcePayload then - self:_InitResourceTable(CombinedWeight) - end - if Type == STORAGE.Type.LIQUIDS and type(Name) == "string" then - self.TemplateStaticUnit.resourcePayload[Name] = Amount - else - self.TemplateStaticUnit.resourcePayload[Type] = { - [Name] = { - ["amount"] = Amount, - } - } - end - UTILS.PrintTableToLog(self.TemplateStaticUnit) - return self -end - ---- (User/Cargo) Resets resource table to zero for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. --- Handy if you spawn from cargo statics which have resources already set. --- @param #SPAWNSTATIC self --- @return #SPAWNSTATIC self -function SPAWNSTATIC:ResetCargoResources() - self.TemplateStaticUnit.resourcePayload = nil - self:_InitResourceTable() - return self -end - ---- Initialize heading of the spawned static. --- @param #SPAWNSTATIC self --- @param Core.Point#COORDINATE Coordinate Position where the static is spawned. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitCoordinate(Coordinate) - self.InitStaticCoordinate=Coordinate - return self -end - ---- Initialize heading of the spawned static. --- @param #SPAWNSTATIC self --- @param #number Heading The heading in degrees. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitHeading(Heading) - self.InitStaticHeading=Heading - return self -end - ---- Initialize livery of the spawned static. --- @param #SPAWNSTATIC self --- @param #string LiveryName Name of the livery to use. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitLivery(LiveryName) - self.InitStaticLivery=LiveryName - return self -end - ---- Initialize type of the spawned static. --- @param #SPAWNSTATIC self --- @param #string StaticType Type of the static, e.g. "FA-18C_hornet". --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitType(StaticType) - self.InitStaticType=StaticType - return self -end - ---- Initialize shape of the spawned static. Required by some but not all statics. --- @param #SPAWNSTATIC self --- @param #string StaticShape Shape of the static, e.g. "carrier_tech_USA". --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitShape(StaticShape) - self.InitStaticShape=StaticShape - return self -end - ---- Initialize parameters for spawning FARPs. --- @param #SPAWNSTATIC self --- @param #number CallsignID Callsign ID. Default 1 (="London"). --- @param #number Frequency Frequency in MHz. Default 127.5 MHz. --- @param #number Modulation Modulation 0=AM, 1=FM. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation) - self.InitFarp=true - self.InitFarpCallsignID=CallsignID or 1 - self.InitFarpFreq=Frequency or 127.5 - self.InitFarpModu=Modulation or 0 - return self -end - ---- Initialize cargo mass. --- @param #SPAWNSTATIC self --- @param #number Mass Mass of the cargo in kg. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitCargoMass(Mass) - self.InitStaticCargoMass=Mass - return self -end - ---- Initialize as cargo. --- @param #SPAWNSTATIC self --- @param #boolean IsCargo If true, this static can act as cargo. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitCargo(IsCargo) - self.InitStaticCargo=IsCargo - return self -end - ---- Initialize as dead. --- @param #SPAWNSTATIC self --- @param #boolean IsDead If true, this static is dead. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitDead(IsDead) - self.InitStaticDead=IsDead - return self -end - ---- Initialize country of the spawned static. This determines the category. --- @param #SPAWNSTATIC self --- @param #string CountryID The country ID, e.g. country.id.USA. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitCountry(CountryID) - self.CountryID=CountryID - return self -end - ---- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc. --- @param #SPAWNSTATIC self --- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitNamePrefix(NamePrefix) - self.SpawnTemplatePrefix=NamePrefix - return self -end - ---- Init link to a unit. --- @param #SPAWNSTATIC self --- @param Wrapper.Unit#UNIT Unit The unit to which the static is linked. --- @param #number OffsetX Offset in X. --- @param #number OffsetY Offset in Y. --- @param #number OffsetAngle Offset angle in degrees. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle) - - self.InitLinkUnit=Unit - self.InitOffsetX=OffsetX or 0 - self.InitOffsetY=OffsetY or 0 - self.InitOffsetAngle=OffsetAngle or 0 - - return self -end - ---- Allows to place a CallFunction hook when a new static spawns. --- The provided method will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned. --- @param #SPAWNSTATIC self --- @param #function SpawnCallBackFunction The function to be called when a group spawns. --- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:OnSpawnStatic( SpawnCallBackFunction, ... ) - self:F( "OnSpawnStatic" ) - - self.SpawnFunctionHook = SpawnCallBackFunction - self.SpawnFunctionArguments = {} - if arg then - self.SpawnFunctionArguments = arg - end - - return self -end - ---- Spawn a new STATIC object. --- @param #SPAWNSTATIC self --- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template. --- @param #string NewName (Optional) The name of the new static. --- @return Wrapper.Static#STATIC The static spawned. -function SPAWNSTATIC:Spawn(Heading, NewName) - - if Heading then - self.InitStaticHeading=Heading - end - - if NewName then - self.InitStaticName=NewName - end - - return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID) - -end - ---- Creates a new @{Wrapper.Static} from a POINT_VEC2. --- @param #SPAWNSTATIC self --- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static. --- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. --- @param #string NewName (Optional) The name of the new static. --- @return Wrapper.Static#STATIC The static spawned. -function SPAWNSTATIC:SpawnFromPointVec2(PointVec2, Heading, NewName) - - local vec2={x=PointVec2:GetX(), y=PointVec2:GetY()} - - local Coordinate=COORDINATE:NewFromVec2(vec2) - - return self:SpawnFromCoordinate(Coordinate, Heading, NewName) -end - - ---- Creates a new @{Wrapper.Static} from a COORDINATE. --- @param #SPAWNSTATIC self --- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static. --- @param #number Heading (Optional) Heading The heading of the static in degrees. Default is 0 degrees. --- @param #string NewName (Optional) The name of the new static. --- @return Wrapper.Static#STATIC The spawned STATIC object. -function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName) - - -- Set up coordinate. - self.InitStaticCoordinate=Coordinate - - if Heading then - self.InitStaticHeading=Heading - end - - if NewName then - self.InitStaticName=NewName - end - - return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID) -end - - ---- Creates a new @{Wrapper.Static} from a @{Core.Zone}. --- @param #SPAWNSTATIC self --- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static. --- @param #number Heading (Optional)The heading of the static in degrees. Default is the heading of the template. --- @param #string NewName (Optional) The name of the new static. --- @return Wrapper.Static#STATIC The static spawned. -function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName) - - -- Spawn the new static at the center of the zone. - local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName ) - - return Static -end - ---- Spawns a new static using a given template. Additionally, the country ID needs to be specified, which also determines the coalition of the spawned static. --- @param #SPAWNSTATIC self --- @param #SPAWNSTATIC.TemplateData Template Spawn unit template. --- @param #number CountryID The country ID. --- @return Wrapper.Static#STATIC The static spawned. -function SPAWNSTATIC:_SpawnStatic(Template, CountryID) - - Template=Template or {} + world.addEventHandler(e) - if not Template.alt then - Template.alt = land.getHeight( {x = Template.x, y = Template.y} ) - end + local recce = PLAYERRECCE:New(Name,Coalition,PlayerSet) - local CountryID=CountryID or self.CountryID - - if self.InitStaticType then - Template.type=self.InitStaticType - end - - if self.InitStaticCategory then - Template.category=self.InitStaticCategory - end - - if self.InitStaticCoordinate then - Template.x = self.InitStaticCoordinate.x - Template.y = self.InitStaticCoordinate.z - Template.alt = self.InitStaticCoordinate.y or land.getHeight( {x = Template.x, y = Template.z} ) - end - - if self.InitStaticHeading then - Template.heading = math.rad(self.InitStaticHeading) - end - - if self.InitStaticShape then - Template.shape_name=self.InitStaticShape - end - - if self.InitStaticLivery then - Template.livery_id=self.InitStaticLivery - end - - if self.InitStaticDead~=nil then - Template.dead=self.InitStaticDead - end - - if self.InitStaticCargo~=nil then - Template.canCargo=self.InitStaticCargo - end - - if self.InitStaticCargoMass~=nil then - Template.mass=self.InitStaticCargoMass - end - - if self.InitLinkUnit then - Template.linkUnit=self.InitLinkUnit:GetID() - Template.linkOffset=true - Template.offsets={} - Template.offsets.y=self.InitOffsetY - Template.offsets.x=self.InitOffsetX - Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0 - end - - if self.InitFarp then - Template.heliport_callsign_id = self.InitFarpCallsignID - Template.heliport_frequency = self.InitFarpFreq - Template.heliport_modulation = self.InitFarpModu - Template.unitId=nil - end - - -- Increase spawn index counter. - self.SpawnIndex = self.SpawnIndex + 1 - - -- Name of the spawned static. - Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) - - -- Add static to the game. - local Static=nil --DCS#StaticObject - - if self.InitFarp then - - local TemplateGroup={} - TemplateGroup.units={} - TemplateGroup.units[1]=Template - - TemplateGroup.visible=true - TemplateGroup.hidden=false - TemplateGroup.x=Template.x - TemplateGroup.y=Template.y - TemplateGroup.name=Template.name - - self:T("Spawning FARP") - self:T({Template=Template}) - self:T({TemplateGroup=TemplateGroup}) - - -- ED's dirty way to spawn FARPS. - Static=coalition.addGroup(CountryID, -1, TemplateGroup) - - -- Currently DCS 2.8 does not trigger birth events if FARPS are spawned! - -- We create such an event. The airbase is registered in Core.Event - local Event = { - id = EVENTS.Birth, - time = timer.getTime(), - initiator = Static - } - -- Create BIRTH event. - world.onEvent(Event) - - else - self:T("Spawning Static") - self:T2({Template=Template}) - Static=coalition.addStaticObject(CountryID, Template) - - if Static then - self:T(string.format("Succesfully spawned static object \"%s\" ID=%d", Static:getName(), Static:getID())) - --[[ - local static=StaticObject.getByName(Static:getName()) - if static then - env.info(string.format("FF got static from StaticObject.getByName")) - else - env.error(string.format("FF error did NOT get static from StaticObject.getByName")) - end ]] - else - self:E(string.format("ERROR: DCS static object \"%s\" is nil!", tostring(Template.name))) - end - end - -- Add and register the new static. - local mystatic=_DATABASE:AddStatic(Template.name) + US_Patrol_Plane = SPAWN + :New("Bird Dog") + :InitLimit(1,4) + :OnSpawnGroup(function ( SpawnedGroup ) + -- Setup AI Patrol + PatrolZone = ZONE:New("Conflict Zone Alpha") + EngageZone = ZONE:New("Conflict Zone Alpha") + EngageZone:Draw() + AICaszone = AI_CAS_ZONE:New(PatrolZone, 100, 1000, 100, 100, EngageZone, "RADIO") + AICaszone:SetControllable(SpawnedGroup) + --AICaszone:SetEngageRange(2000) + AICaszone:__Start(1) + end + ) + :SpawnScheduled(30, 0) - -- If there is a SpawnFunction hook defined, call it. - if self.SpawnFunctionHook then - -- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. - self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments)) - end +--]] - return mystatic +local grp = GROUP:FindByName("IR Blinker") +grp:NewIRMarker(true,90) + +function DestGroup() + if grp and grp:IsAlive() then + grp:Destroy() + end +end + +function DisableMarker() + if grp and grp:IsAlive() then + grp:DisableIRMarker() + end +end + +function EnableMarker() + if grp and grp:IsAlive() then + grp:EnableIRMarker() + end +end + +function RespGroup() + if grp and not grp:IsAlive() then + grp:Respawn() + end +end + +local mymsrs = MSRS:New(nil,243,0) +local jammersound=SOUNDFILE:New("beacon.ogg", "C:\\Users\\post\\Saved Games\\DCS\\Missions\\", 2, true) +function Play() + mymsrs:PlaySoundFile(jammersound) +end + +local topmenu = MENU_COALITION:New(coalition.side.BLUE,"IR Marker Test") +local startmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Enable IR",topmenu,EnableMarker) +local stopmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Disable IR",topmenu,DisableMarker) +local destmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Destroy Group",topmenu,DestGroup) +local respmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Respawn Group",topmenu,RespGroup) +local respmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Play Sound",topmenu,Play) + +local testzone = ZONE:New("Testzone") +testzone:Trigger(grp) + +function testzone:OnAfterObjectDead(From,Event,To,Controllable) + MESSAGE:New("Object Dead",15,"Test"):ToAll():ToLog() +end + +function testzone:OnAfterZoneEmpty(From,Event,To) + MESSAGE:New("Zone Empty",15,"Test"):ToAll():ToLog() +end + +local BlueBorder = ZONE:New("Blue Border") +local RedBorder = ZONE:New("Red Border") +local Conflict = ZONE:New("Conflict") + +BlueBorder:DrawZone(-1,{0,0,1},1,{0,0,1},.2,1,true) +RedBorder:DrawZone(-1,{1,0,0},1,{1,0,0},.2,1,true) +Conflict:DrawZone(-1,{1,254/255,1/33},1,{1,254/255,1/33},.2,1,true) + +BASE:TraceOn() +BASE:TraceClass("SHORAD") + +local mymantis = MANTIS:New("Red Defense","Red SAM","Red EWR",hq,"red",true,awacs,true) +mymantis:AddZones({RedBorder},{BlueBorder},{Conflict}) +mymantis.verbose = true +mymantis.debug = true +mymantis:Start() + +local myctld = CTLD:New() + +function myctld:OnAfterCratesDropped(From,Event,To,Group,Unit,Cargotable) + if Unit and string.find(Unit:GetTypeName(),"Mosquito",1,true) then + myctld:_BuildCrates(Group,Unit,true) + end end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 4e97f195b..0ad088d6d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -213,7 +213,7 @@ end --- Returns if a PointVec3 is within the zone. -- @param #ZONE_BASE self --- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 to test. +-- @param Core.Point#COORDINATE PointVec3 The PointVec3 to test. -- @return #boolean true if the PointVec3 is within the zone. function ZONE_BASE:IsPointVec3InZone( PointVec3 ) local InZone = self:IsPointVec2InZone( PointVec3 ) @@ -227,16 +227,16 @@ function ZONE_BASE:GetVec2() return nil end ---- Returns a @{Core.Point#POINT_VEC2} of the zone. +--- Returns a @{Core.Point#COORDINATE} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. +-- @return Core.Point#COORDINATE The COORDINATE of the zone. function ZONE_BASE:GetPointVec2() --self:F2( self.ZoneName ) local Vec2 = self:GetVec2() - local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) + local PointVec2 = COORDINATE:NewFromVec2( Vec2 ) --self:T2( { PointVec2 } ) @@ -261,16 +261,16 @@ function ZONE_BASE:GetVec3( Height ) return Vec3 end ---- Returns a @{Core.Point#POINT_VEC3} of the zone. +--- Returns a @{Core.Point#COORDINATE} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. +-- @return Core.Point#COORDINATE The PointVec3 of the zone. function ZONE_BASE:GetPointVec3( Height ) --self:F2( self.ZoneName ) local Vec3 = self:GetVec3( Height ) - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) + local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) --self:T2( { PointVec3 } ) @@ -330,16 +330,16 @@ function ZONE_BASE:GetRandomVec2() return nil end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_BASE:GetRandomPointVec2() return nil end ---- Define a random @{Core.Point#POINT_VEC3} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_BASE:GetRandomPointVec3() return nil end @@ -814,8 +814,8 @@ end -- Various functions exist to find random points within the zone. -- -- * @{#ZONE_RADIUS.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. +-- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#COORDINATE} object representing a random 2D point in the zone. +-- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#COORDINATE} object representing a random 3D point in the zone. Note that the height of the point is at landheight. -- -- ## Draw zone -- @@ -1010,7 +1010,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Smoke( SmokeColor ) end return self @@ -1040,7 +1040,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Flare( FlareColor, Azimuth ) end return self @@ -1561,15 +1561,15 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) return point end ---- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2( inner, outer ) ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2( inner, outer ) ) --self:T3( { PointVec2 } ) @@ -1592,15 +1592,15 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) end ---- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC3 The @{Core.Point#POINT_VEC3} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) --self:F( self.ZoneName, inner, outer ) - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2( inner, outer ) ) + local PointVec3 = COORDINATE:NewFromVec2( self:GetRandomVec2( inner, outer ) ) --self:T3( { PointVec3 } ) @@ -2036,15 +2036,15 @@ function ZONE_GROUP:GetRandomVec2() return Point end ---- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_GROUP self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_GROUP:GetRandomPointVec2( inner, outer ) --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) --self:T3( { PointVec2 } ) @@ -2192,8 +2192,8 @@ end -- Various functions exist to find random points within the zone. -- -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#COORDINATE} object representing a random 2D point within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#COORDINATE} object representing a random 3D point at landheight within the zone. -- -- ## Draw zone -- @@ -2769,7 +2769,7 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) - POINT_VEC2:New( PointX, PointY ):Smoke( SmokeColor ) + COORDINATE:New( PointX, 0, PointY ):Smoke( SmokeColor ) end j = i i = i + 1 @@ -2804,7 +2804,7 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) - POINT_VEC2:New( PointX, PointY, AddHeight ):Flare(FlareColor, Azimuth) + COORDINATE:New( PointX, AddHeight, PointY ):Flare(FlareColor, Azimuth) end j = i i = i + 1 @@ -2880,26 +2880,26 @@ function ZONE_POLYGON_BASE:GetRandomVec2() end end ---- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Return a @{Core.Point#COORDINATE} object representing a random 2D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_POLYGON_BASE self --- @return @{Core.Point#POINT_VEC2} +-- @return @{Core.Point#COORDINATE} function ZONE_POLYGON_BASE:GetRandomPointVec2() --self:F2() - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) --self:T2( PointVec2 ) return PointVec2 end ---- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Return a @{Core.Point#COORDINATE} object representing a random 3D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_POLYGON_BASE self --- @return @{Core.Point#POINT_VEC3} +-- @return @{Core.Point#COORDINATE} function ZONE_POLYGON_BASE:GetRandomPointVec3() --self:F2() - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) + local PointVec3 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) --self:T2( PointVec3 ) @@ -3931,18 +3931,18 @@ function ZONE_OVAL:GetRandomVec2() return {x=rx, y=ry} end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_OVAL self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_OVAL:GetRandomPointVec2() - return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) + return COORDINATE:NewFromVec2(self:GetRandomVec2()) end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_OVAL self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_OVAL:GetRandomPointVec3() - return POINT_VEC3:NewFromVec3(self:GetRandomVec2()) + return COORDINATE:NewFromVec3(self:GetRandomVec2()) end --- Draw the zone on the F10 map. @@ -4082,15 +4082,15 @@ do -- ZONE_AIRBASE return ZoneVec2 end - --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. + --- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_AIRBASE self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. - -- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. + -- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_AIRBASE:GetRandomPointVec2( inner, outer ) --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) --self:T3( { PointVec2 } ) diff --git a/Moose Development/Moose/Core/Zone_Detection.lua b/Moose Development/Moose/Core/Zone_Detection.lua index 385ac5247..06c9cdeae 100644 --- a/Moose Development/Moose/Core/Zone_Detection.lua +++ b/Moose Development/Moose/Core/Zone_Detection.lua @@ -107,7 +107,7 @@ function ZONE_DETECTION:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor ) + COORDINATE:New( Point.x, AddHeight, Point.y):Smoke( SmokeColor ) end return self @@ -138,7 +138,7 @@ function ZONE_DETECTION:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Flare( FlareColor, Azimuth ) end return self diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6caede880..f0d4b1997 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -7699,7 +7699,7 @@ end --- [Internal] Function to spawn a soldier group of 10 units -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #number Cargo_Country @@ -7722,7 +7722,7 @@ end --- [Internal] Function to spawn a group -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #number Cargo_Country @@ -7746,7 +7746,7 @@ end --- [Internal] Function to spawn static cargo -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #boolean dead @@ -7768,7 +7768,7 @@ end --- [Internal] Function to spawn cargo by type at position -- @param #CTLD_HERCULES self -- @param #string Cargo_Type_name --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @return #CTLD_HERCULES self function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name, _pos) local theCargo = self.CTLD:_FindCratesCargoObject(_name) -- #CTLD_CARGO diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 9c183238d..db102dc8f 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1179,9 +1179,9 @@ function GROUP:GetAverageVec3() end end ---- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission. +--- Returns a COORDINATE object indicating the point in 2D of the first UNIT of the GROUP within the mission. -- @param #GROUP self --- @return Core.Point#POINT_VEC2 The 2D point vector of the first DCS Unit of the GROUP. +-- @return Core.Point#COORDINATE The 3D point vector of the first DCS Unit of the GROUP. -- @return #nil The first UNIT is not existing or alive. function GROUP:GetPointVec2() --self:F2(self.GroupName) @@ -1194,7 +1194,7 @@ function GROUP:GetPointVec2() return FirstUnitPointVec2 end - BASE:E( { "Cannot GetPointVec2", Group = self, Alive = self:IsAlive() } ) + BASE:E( { "Cannot get COORDINATE", Group = self, Alive = self:IsAlive() } ) return nil end @@ -2093,7 +2093,7 @@ function GROUP:Respawn( Template, Reset ) GroupUnitVec3 = Zone:GetRandomVec3() else if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then - GroupUnitVec3 = POINT_VEC3:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) + GroupUnitVec3 = COORDINATE:NewFromVec3(From):GetRandomVec3InRadius(self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner) else GroupUnitVec3 = Zone:GetVec3() end @@ -2144,7 +2144,7 @@ function GROUP:Respawn( Template, Reset ) GroupUnitVec3 = Zone:GetRandomVec3() else if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then - GroupUnitVec3 = POINT_VEC3:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) + GroupUnitVec3 = COORDINATE:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) else GroupUnitVec3 = Zone:GetVec3() end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index df5fdcb37..41857a215 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -16,7 +16,7 @@ --- @type POSITIONABLE -- @field Core.Point#COORDINATE coordinate Coordinate object. --- @field Core.Point#POINT_VEC3 pointvec3 Point Vec3 object. +-- @field Core.Point#COORDINATE pointvec3 Point Vec3 object. -- @extends Wrapper.Identifiable#IDENTIFIABLE @@ -284,9 +284,9 @@ function POSITIONABLE:GetVec2() return nil end ---- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. +--- Returns a COORDINATE object indicating the point in 2D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self --- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. +-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec2() self:F2( self.PositionableName ) @@ -296,20 +296,20 @@ function POSITIONABLE:GetPointVec2() if DCSPositionable then local PositionableVec3 = DCSPositionable:getPosition().p - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) + local PositionablePointVec2 = COORDINATE:NewFromVec3( PositionableVec3 ) -- self:F( PositionablePointVec2 ) return PositionablePointVec2 end - self:E( { "Cannot GetPointVec2", Positionable = self, Alive = self:IsAlive() } ) + self:E( { "Cannot Coordinate", Positionable = self, Alive = self:IsAlive() } ) return nil end ---- Returns a POINT_VEC3 object indicating the point in 3D of the POSITIONABLE within the mission. +--- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self --- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE. +-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec3() @@ -329,8 +329,8 @@ function POSITIONABLE:GetPointVec3() else - -- Create a new POINT_VEC3 object. - self.pointvec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) + -- Create a new COORDINATE object. + self.pointvec3 = COORDINATE:NewFromVec3( PositionableVec3 ) end From 6cb306a7e1486b64af7df440fc984f5d4b97236b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 1 Apr 2025 15:27:14 +0200 Subject: [PATCH 165/349] #DYNAMICCARGO - enhance checks for unloading cargo from hovering Hooks --- .../Moose/Wrapper/DynamicCargo.lua | 82 +++++++++++++------ 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 775b735b3..9b3d8dfd6 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -124,7 +124,7 @@ DYNAMICCARGO.AircraftDimensions = { --- DYNAMICCARGO class version. -- @field #string version -DYNAMICCARGO.version="0.0.6" +DYNAMICCARGO.version="0.0.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -376,6 +376,33 @@ end -- Private Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- [Internal] _Get helo hovering intel +-- @param #DYNAMICCARGO self +-- @param Wrapper.Unit#UNIT Unit The Unit to test +-- @param #number ropelength Ropelength to test +-- @return #boolean Outcome +function DYNAMICCARGO:_HeloHovering(Unit,ropelength) + local DCSUnit = Unit:GetDCSObject() --DCS#Unit + local hovering = false + local Height = 0 + if DCSUnit then + local UnitInAir = DCSUnit:inAir() + local UnitCategory = DCSUnit:getDesc().category + if UnitInAir == true and UnitCategory == Unit.Category.HELICOPTER then + local VelocityVec3 = DCSUnit:getVelocity() + local Velocity = UTILS.VecNorm(VelocityVec3) + local Coordinate = DCSUnit:getPoint() + local LandHeight = land.getHeight({ x = Coordinate.x, y = Coordinate.z }) + Height = Coordinate.y - LandHeight + if Velocity < 1 and Height <= ropelength and Height > 6 then -- hover lower than ropelength but higher than the normal FARP height. + hovering = true + end + end + return hovering, Height + end + return false +end + --- [Internal] _Get Possible Player Helo Nearby -- @param #DYNAMICCARGO self -- @param Core.Point#COORDINATE pos @@ -393,30 +420,37 @@ function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) local name = helo:GetPlayerName() or _DATABASE:_FindPlayerNameByUnitName(helo:GetName()) or "None" self:T(self.lid.." Checking: "..name) local hpos = helo:GetCoordinate() - -- TODO Unloading via sling load? - --local inair = hpos.y-hpos:GetLandHeight() > 4.5 and true or false -- Standard FARP is 4.5m - local inair = helo:InAir() - self:T(self.lid.." InAir: AGL/InAir: "..hpos.y-hpos:GetLandHeight().."/"..tostring(inair)) + -- TODO Check unloading via sling load? local typename = helo:GetTypeName() - if hpos and typename and inair == false then - local dimensions = DYNAMICCARGO.AircraftDimensions[typename] - if dimensions then - local delta2D = hpos:Get2DDistance(pos) - local delta3D = hpos:Get3DDistance(pos) - if self.testing then - self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) - self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) - end - if loading~=true and delta2D > dimensions.length or delta2D > dimensions.width or delta3D > dimensions.ropelength then - success = true - Helo = helo - Playername = name - end - if loading == true and delta2D < dimensions.length or delta2D < dimensions.width or delta3D < dimensions.ropelength then - success = true - Helo = helo - Playername = name - end + local dimensions = DYNAMICCARGO.AircraftDimensions[typename] + local hovering, height = self:_HeloHovering(helo,dimensions.ropelength) + local helolanded = not helo:InAir() + self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering)) + if hpos and typename and dimensions then + local delta2D = hpos:Get2DDistance(pos) + local delta3D = hpos:Get3DDistance(pos) + if self.testing then + self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) + self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) + self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height)) + end + -- unloading from ground + if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then + success = true + Helo = helo + Playername = name + end + -- unloading from hover/rope + if loading~=true and (delta2D < dimensions.length or delta2D < dimensions.width) and hovering then + success = true + Helo = helo + Playername = name + end + -- loading + if loading == true and (delta2D < dimensions.length or delta2D < dimensions.width or delta3D < dimensions.ropelength) then + success = true + Helo = helo + Playername = name end end end From 367ee31135ac81040b252b045053712a9a49bbb3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Apr 2025 09:29:38 +0200 Subject: [PATCH 166/349] xx --- .../Moose/Wrapper/DynamicCargo.lua | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index a31cfc40f..db0d79834 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -2,17 +2,17 @@ -- -- ## Main Features: -- --- * Convenient access to DCS API functions +-- * Convenient access to Ground Crew created cargo items. -- -- === -- -- ## Example Missions: -- --- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Storage). +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/). -- -- === -- --- ### Author: **Applevangelist** +-- ### Author: **Applevangelist**; additional checks **Chesster** -- -- === -- @module Wrapper.DynamicCargo @@ -435,19 +435,19 @@ function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height)) end -- unloading from ground - if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then + if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then -- Theoretically the cargo could still be attached to the sling if landed next to the cargo. But once moved again it would go back into loaded state once lifted again. success = true Helo = helo Playername = name end -- unloading from hover/rope - if loading~=true and (delta2D < dimensions.length or delta2D < dimensions.width) and hovering then + if loading~=true and delta3D > dimensions.ropelength then success = true Helo = helo Playername = name end -- loading - if loading == true and (delta2D < dimensions.length or delta2D < dimensions.width or delta3D < dimensions.ropelength) then + if loading == true and ((delta2D < dimensions.length and delta2D < dimensions.width and helolanded) or (delta3D == dimensions.ropelength and helo:InAir())) then -- Loaded via ground or sling success = true Helo = helo Playername = name @@ -468,20 +468,22 @@ function DYNAMICCARGO:_UpdatePosition() self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z)) self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z)) end - if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then + if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then -- This checks if the cargo has moved more than 0.5m since last check. If so then the cargo is loaded --------------- -- LOAD Cargo --------------- - if self.CargoState == DYNAMICCARGO.State.NEW then - local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true) + if self.CargoState == DYNAMICCARGO.State.NEW or self.CargoState == DYNAMICCARGO.State.UNLOADED then + local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true) self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername)) self.CargoState = DYNAMICCARGO.State.LOADED self.Owner = playername - _DATABASE:CreateEventDynamicCargoLoaded(self) + _DATABASE:CreateEventDynamicCargoLoaded(self) + end --------------- -- UNLOAD Cargo - --------------- - elseif self.CargoState == DYNAMICCARGO.State.LOADED then + --------------- + -- If the cargo is stationary then we need to end this condition here to check whether it is unloaded or still onboard or still hooked if anyone can hover that precisly + elseif self.CargoState == DYNAMICCARGO.State.LOADED then -- TODO add checker if we are in flight somehow -- ensure not just the helo is moving local count = _DYNAMICCARGO_HELOS:CountAlive() @@ -493,26 +495,19 @@ function DYNAMICCARGO:_UpdatePosition() local isunloaded = true local client local playername = self.Owner - if count > 0 and (agl > 0 or self.testing) then - self:T(self.lid.." Possible alive helos: "..count or -1) - if agl ~= 0 or self.testing then - isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false) - end + if count > 0 then + self:T(self.lid.." Possible alive helos: "..count or -1) + isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false) if isunloaded then self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) self.CargoState = DYNAMICCARGO.State.UNLOADED self.Owner = playername _DATABASE:CreateEventDynamicCargoUnloaded(self) - end - elseif count > 0 and agl == 0 then - self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) - self.CargoState = DYNAMICCARGO.State.UNLOADED - self.Owner = playername - _DATABASE:CreateEventDynamicCargoUnloaded(self) + end end end self.LastPosition = pos - end + --end else --------------- -- REMOVED Cargo From 2c255d48c55877f708888c78639e101990973ffe Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Apr 2025 11:48:37 +0200 Subject: [PATCH 167/349] #CONTROLLABLE:CommandSmokeOnOff(OnOff, Delay) added --- .../Moose/Wrapper/Controllable.lua | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index c158eb0c6..b9f5a0d86 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -994,6 +994,65 @@ function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,Unit return self end +--- [AIR] Set smoke on or off. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #boolean OnOff Set to true for on and false for off. Defaults to true. +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeOnOff(OnOff, Delay) + local switch = (OnOff == nil) and true or OnOff + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = switch + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeOnOff,{self,switch},Delay) + else + self:SetCommand(command) + end + return self +end + +--- [AIR] Set smoke on. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeON(Delay) + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = true + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeON,{self},Delay) + else + self:SetCommand(command) + end + return self +end + +--- [AIR] Set smoke off. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeOFF(Delay) + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = false + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeOFF,{self},Delay) + else + self:SetCommand(command) + end + return self +end + --- Set EPLRS data link on/off. -- @param #CONTROLLABLE self -- @param #boolean SwitchOnOff If true (or nil) switch EPLRS on. If false switch off. From ff5adc29d9d8b3a4f28d63bd4ea41721c0799eb6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Apr 2025 14:22:53 +0200 Subject: [PATCH 168/349] xx --- Moose Development/Moose/Sound/RadioQueue.lua | 14 +- Moose Development/Moose/Wrapper/Airbase.lua | 407 +++++++++++++++++++ 2 files changed, 414 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index a3740768e..fd90f7bc6 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -361,7 +361,6 @@ end -- @param #RADIOQUEUE self -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:Broadcast(transmission) - self:T("Broadcast") if ((transmission.soundfile and transmission.soundfile.useSRS) or transmission.soundtext) and self.msrs then self:_BroadcastSRS(transmission) @@ -377,10 +376,11 @@ function RADIOQUEUE:Broadcast(transmission) if sender then -- Broadcasting from aircraft. Only players tuned in to the right frequency will see the message. - self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName())) + self:T(self.lid..string.format("Broadcasting from aircraft %s | sender init: %s", sender:GetName(),tostring(self.senderinit))) - if not self.senderinit then + --if not self.senderinit then + -- TODO Seems to be a DCS bug - if I explode ANY unit in a group the BC assignment gets lost -- Command to set the Frequency for the transmission. local commandFrequency={ @@ -394,7 +394,7 @@ function RADIOQUEUE:Broadcast(transmission) sender:SetCommand(commandFrequency) self.senderinit=true - end + --end -- Set subtitle only if duration>0 sec. local subtitle=nil @@ -420,7 +420,7 @@ function RADIOQUEUE:Broadcast(transmission) -- Debug message. if self.Debugmode then local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") - MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAll() + MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAll():ToLog() end else @@ -452,7 +452,7 @@ function RADIOQUEUE:Broadcast(transmission) -- Debug message. if self.Debugmode then local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") - MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAll() + MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAll():ToLog() end else self:E("ERROR: Could not get vec3 to determine transmission origin! Did you specify a sender and is it still alive?") @@ -485,7 +485,7 @@ end --- Check radio queue for transmissions to be broadcasted. -- @param #RADIOQUEUE self function RADIOQUEUE:_CheckRadioQueue() - + self:T("_CheckRadioQueue") -- Check if queue is empty. if #self.queue==0 then -- Queue is now empty. Nothing to else to do. diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 3f04a8a04..ef485640e 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -909,6 +909,413 @@ AIRBASE.Iraq = { ["K1_Base"] = "K1 Base", } +--- Airbases of the Germany Cold War map +-- * AIRBASE.GermanyCW.Airracing_Frankfurt +-- * AIRBASE.GermanyCW.Airracing_Koblenz +-- * AIRBASE.GermanyCW.Airracing_Lübeck +-- * AIRBASE.GermanyCW.Allstedt +-- * AIRBASE.GermanyCW.Alt_Daber +-- * AIRBASE.GermanyCW.Altes_Lager +-- * AIRBASE.GermanyCW.Bad_Dürkheim +-- * AIRBASE.GermanyCW.Barth +-- * AIRBASE.GermanyCW.Bienenfarm +-- * AIRBASE.GermanyCW.Bindersleben +-- * AIRBASE.GermanyCW.Bitburg +-- * AIRBASE.GermanyCW.Braunschweig +-- * AIRBASE.GermanyCW.Bremen +-- * AIRBASE.GermanyCW.Briest +-- * AIRBASE.GermanyCW.Büchel +-- * AIRBASE.GermanyCW.Bückeburg +-- * AIRBASE.GermanyCW.Celle +-- * AIRBASE.GermanyCW.Cochstedt +-- * AIRBASE.GermanyCW.Damgarten +-- * AIRBASE.GermanyCW.Dedelow +-- * AIRBASE.GermanyCW.Dessau +-- * AIRBASE.GermanyCW.Fassberg +-- * AIRBASE.GermanyCW.Finow +-- * AIRBASE.GermanyCW.Frankfurt +-- * AIRBASE.GermanyCW.Fritzlar +-- * AIRBASE.GermanyCW.Fulda +-- * AIRBASE.GermanyCW.Gardelegen +-- * AIRBASE.GermanyCW.Gatow +-- * AIRBASE.GermanyCW.Gelnhausen +-- * AIRBASE.GermanyCW.Giebelstadt +-- * AIRBASE.GermanyCW.Glindbruchkippe_ +-- * AIRBASE.GermanyCW.Groß_Dölln +-- * AIRBASE.GermanyCW.Groß_Mohrdorf +-- * AIRBASE.GermanyCW.Große_Wiese +-- * AIRBASE.GermanyCW.Gärz +-- * AIRBASE.GermanyCW.Gütersloh +-- * AIRBASE.GermanyCW.H_FRG_01 +-- * AIRBASE.GermanyCW.H_FRG_02 +-- * AIRBASE.GermanyCW.H_FRG_03 +-- * AIRBASE.GermanyCW.H_FRG_04 +-- * AIRBASE.GermanyCW.H_FRG_05 +-- * AIRBASE.GermanyCW.H_FRG_06 +-- * AIRBASE.GermanyCW.H_FRG_07 +-- * AIRBASE.GermanyCW.H_FRG_08 +-- * AIRBASE.GermanyCW.H_FRG_09 +-- * AIRBASE.GermanyCW.H_FRG_10 +-- * AIRBASE.GermanyCW.H_FRG_11 +-- * AIRBASE.GermanyCW.H_FRG_12 +-- * AIRBASE.GermanyCW.H_FRG_13 +-- * AIRBASE.GermanyCW.H_FRG_14 +-- * AIRBASE.GermanyCW.H_FRG_15 +-- * AIRBASE.GermanyCW.H_FRG_16 +-- * AIRBASE.GermanyCW.H_FRG_17 +-- * AIRBASE.GermanyCW.H_FRG_18 +-- * AIRBASE.GermanyCW.H_FRG_19 +-- * AIRBASE.GermanyCW.H_FRG_20 +-- * AIRBASE.GermanyCW.H_FRG_21 +-- * AIRBASE.GermanyCW.H_FRG_23 +-- * AIRBASE.GermanyCW.H_FRG_25 +-- * AIRBASE.GermanyCW.H_FRG_27 +-- * AIRBASE.GermanyCW.H_FRG_30 +-- * AIRBASE.GermanyCW.H_FRG_31 +-- * AIRBASE.GermanyCW.H_FRG_32 +-- * AIRBASE.GermanyCW.H_FRG_34 +-- * AIRBASE.GermanyCW.H_FRG_38 +-- * AIRBASE.GermanyCW.H_FRG_39 +-- * AIRBASE.GermanyCW.H_FRG_40 +-- * AIRBASE.GermanyCW.H_FRG_41 +-- * AIRBASE.GermanyCW.H_FRG_42 +-- * AIRBASE.GermanyCW.H_FRG_43 +-- * AIRBASE.GermanyCW.H_FRG_44 +-- * AIRBASE.GermanyCW.H_FRG_45 +-- * AIRBASE.GermanyCW.H_FRG_46 +-- * AIRBASE.GermanyCW.H_FRG_47 +-- * AIRBASE.GermanyCW.H_FRG_48 +-- * AIRBASE.GermanyCW.H_FRG_49 +-- * AIRBASE.GermanyCW.H_FRG_50 +-- * AIRBASE.GermanyCW.H_FRG_51 +-- * AIRBASE.GermanyCW.H_GDR_01 +-- * AIRBASE.GermanyCW.H_GDR_02 +-- * AIRBASE.GermanyCW.H_GDR_03 +-- * AIRBASE.GermanyCW.H_GDR_04 +-- * AIRBASE.GermanyCW.H_GDR_05 +-- * AIRBASE.GermanyCW.H_GDR_06 +-- * AIRBASE.GermanyCW.H_GDR_07 +-- * AIRBASE.GermanyCW.H_GDR_08 +-- * AIRBASE.GermanyCW.H_GDR_09 +-- * AIRBASE.GermanyCW.H_GDR_10 +-- * AIRBASE.GermanyCW.H_GDR_11 +-- * AIRBASE.GermanyCW.H_GDR_12 +-- * AIRBASE.GermanyCW.H_GDR_13 +-- * AIRBASE.GermanyCW.H_GDR_14 +-- * AIRBASE.GermanyCW.H_GDR_15 +-- * AIRBASE.GermanyCW.H_GDR_16 +-- * AIRBASE.GermanyCW.H_GDR_17 +-- * AIRBASE.GermanyCW.H_GDR_18 +-- * AIRBASE.GermanyCW.H_GDR_19 +-- * AIRBASE.GermanyCW.H_GDR_21 +-- * AIRBASE.GermanyCW.H_GDR_22 +-- * AIRBASE.GermanyCW.H_GDR_24 +-- * AIRBASE.GermanyCW.H_GDR_25 +-- * AIRBASE.GermanyCW.H_GDR_26 +-- * AIRBASE.GermanyCW.H_GDR_30 +-- * AIRBASE.GermanyCW.H_GDR_31 +-- * AIRBASE.GermanyCW.H_GDR_32 +-- * AIRBASE.GermanyCW.H_GDR_33 +-- * AIRBASE.GermanyCW.H_Med_FRG_02 +-- * AIRBASE.GermanyCW.H_Med_FRG_04 +-- * AIRBASE.GermanyCW.H_Med_FRG_06 +-- * AIRBASE.GermanyCW.H_Med_FRG_09 +-- * AIRBASE.GermanyCW.H_Med_FRG_11 +-- * AIRBASE.GermanyCW.H_Med_FRG_12 +-- * AIRBASE.GermanyCW.H_Med_FRG_13 +-- * AIRBASE.GermanyCW.H_Med_FRG_14 +-- * AIRBASE.GermanyCW.H_Med_FRG_15 +-- * AIRBASE.GermanyCW.H_Med_FRG_16 +-- * AIRBASE.GermanyCW.H_Med_FRG_17 +-- * AIRBASE.GermanyCW.H_Med_FRG_21 +-- * AIRBASE.GermanyCW.H_Med_FRG_24 +-- * AIRBASE.GermanyCW.H_Med_FRG_26 +-- * AIRBASE.GermanyCW.H_Med_FRG_27 +-- * AIRBASE.GermanyCW.H_Med_FRG_29 +-- * AIRBASE.GermanyCW.H_Med_GDR_01 +-- * AIRBASE.GermanyCW.H_Med_GDR_02 +-- * AIRBASE.GermanyCW.H_Med_GDR_03 +-- * AIRBASE.GermanyCW.H_Med_GDR_08 +-- * AIRBASE.GermanyCW.H_Med_GDR_09 +-- * AIRBASE.GermanyCW.H_Med_GDR_10 +-- * AIRBASE.GermanyCW.H_Med_GDR_11 +-- * AIRBASE.GermanyCW.H_Med_GDR_12 +-- * AIRBASE.GermanyCW.H_Med_GDR_13 +-- * AIRBASE.GermanyCW.H_Med_GDR_14 +-- * AIRBASE.GermanyCW.H_Med_GDR_16 +-- * AIRBASE.GermanyCW.H_Radar_FRG_02 +-- * AIRBASE.GermanyCW.H_Radar_GDR_01 +-- * AIRBASE.GermanyCW.H_Radar_GDR_02 +-- * AIRBASE.GermanyCW.H_Radar_GDR_03 +-- * AIRBASE.GermanyCW.H_Radar_GDR_04 +-- * AIRBASE.GermanyCW.H_Radar_GDR_05 +-- * AIRBASE.GermanyCW.H_Radar_GDR_06 +-- * AIRBASE.GermanyCW.H_Radar_GDR_07 +-- * AIRBASE.GermanyCW.H_Radar_GDR_08 +-- * AIRBASE.GermanyCW.H_Radar_GDR_09 +-- * AIRBASE.GermanyCW.Hahn +-- * AIRBASE.GermanyCW.Haina +-- * AIRBASE.GermanyCW.Hamburg +-- * AIRBASE.GermanyCW.Hamburg_Finkenwerder +-- * AIRBASE.GermanyCW.Hannover +-- * AIRBASE.GermanyCW.Hasselfelde +-- * AIRBASE.GermanyCW.Herrenteich +-- * AIRBASE.GermanyCW.Hildesheim +-- * AIRBASE.GermanyCW.Hockenheim +-- * AIRBASE.GermanyCW.Holzdorf +-- * AIRBASE.GermanyCW.Kammermark +-- * AIRBASE.GermanyCW.Köthen +-- * AIRBASE.GermanyCW.Laage +-- * AIRBASE.GermanyCW.Langenselbold +-- * AIRBASE.GermanyCW.Leipzig_Halle +-- * AIRBASE.GermanyCW.Leipzig_Mockau +-- * AIRBASE.GermanyCW.Lärz +-- * AIRBASE.GermanyCW.Lübeck +-- * AIRBASE.GermanyCW.Lüneburg +-- * AIRBASE.GermanyCW.Mahlwinkel +-- * AIRBASE.GermanyCW.Mendig +-- * AIRBASE.GermanyCW.Merseburg +-- * AIRBASE.GermanyCW.Neubrandenburg +-- * AIRBASE.GermanyCW.Neuruppin +-- * AIRBASE.GermanyCW.Northeim +-- * AIRBASE.GermanyCW.Ober_Mörlen +-- * AIRBASE.GermanyCW.Obermehler_Schlotheim +-- * AIRBASE.GermanyCW.Parchim +-- * AIRBASE.GermanyCW.Peenemünde +-- * AIRBASE.GermanyCW.Pferdsfeld +-- * AIRBASE.GermanyCW.Pinnow +-- * AIRBASE.GermanyCW.Pottschutthöhe +-- * AIRBASE.GermanyCW.Ramstein +-- * AIRBASE.GermanyCW.Rinteln +-- * AIRBASE.GermanyCW.Schweinfurt +-- * AIRBASE.GermanyCW.Schönefeld +-- * AIRBASE.GermanyCW.Sembach +-- * AIRBASE.GermanyCW.Spangdahlem +-- * AIRBASE.GermanyCW.Sperenberg +-- * AIRBASE.GermanyCW.Stendal +-- * AIRBASE.GermanyCW.Tegel +-- * AIRBASE.GermanyCW.Tempelhof +-- * AIRBASE.GermanyCW.Tutow +-- * AIRBASE.GermanyCW.Uelzen +-- * AIRBASE.GermanyCW.Uetersen +-- * AIRBASE.GermanyCW.Ummern +-- * AIRBASE.GermanyCW.Verden_Scharnhorst +-- * AIRBASE.GermanyCW.Walldorf +-- * AIRBASE.GermanyCW.Waren_Vielist +-- * AIRBASE.GermanyCW.Werneuchen +-- * AIRBASE.GermanyCW.Weser_Wümme +-- * AIRBASE.GermanyCW.Wiesbaden +-- * AIRBASE.GermanyCW.Wismar +-- * AIRBASE.GermanyCW.Worms +-- * AIRBASE.GermanyCW.Wunstorf +-- * AIRBASE.GermanyCW.Zerbst +-- * AIRBASE.GermanyCW.Zweibrücken +-- +-- @field GermanyCW +AIRBASE.GermanyCW = { + ["Airracing_Frankfurt"] = "Airracing Frankfurt", + ["Airracing_Koblenz"] = "Airracing Koblenz", + ["Airracing_Luebeck"] = "Airracing Lübeck", + ["Allstedt"] = "Allstedt", + ["Alt_Daber"] = "Alt Daber", + ["Altes_Lager"] = "Altes Lager", + ["Bad_Duerkheim"] = "Bad Dürkheim", + ["Barth"] = "Barth", + ["Bienenfarm"] = "Bienenfarm", + ["Bindersleben"] = "Bindersleben", + ["Bitburg"] = "Bitburg", + ["Braunschweig"] = "Braunschweig", + ["Bremen"] = "Bremen", + ["Briest"] = "Briest", + ["Buechel"] = "Büchel", + ["Bueckeburg"] = "Bückeburg", + ["Celle"] = "Celle", + ["Cochstedt"] = "Cochstedt", + ["Damgarten"] = "Damgarten", + ["Dedelow"] = "Dedelow", + ["Dessau"] = "Dessau", + ["Fassberg"] = "Fassberg", + ["Finow"] = "Finow", + ["Frankfurt"] = "Frankfurt", + ["Fritzlar"] = "Fritzlar", + ["Fulda"] = "Fulda", + ["Gardelegen"] = "Gardelegen", + ["Gatow"] = "Gatow", + ["Gelnhausen"] = "Gelnhausen", + ["Giebelstadt"] = "Giebelstadt", + ["Glindbruchkippe_"] = "Glindbruchkippe ", + ["Gross_Doelln"] = "Groß Dölln", + ["Gross_Mohrdorf"] = "Groß Mohrdorf", + ["Grosse_Wiese"] = "Große Wiese", + ["Gaerz"] = "Gärz", + ["Guetersloh"] = "Gütersloh", + ["H_FRG_01"] = "H FRG 01", + ["H_FRG_02"] = "H FRG 02", + ["H_FRG_03"] = "H FRG 03", + ["H_FRG_04"] = "H FRG 04", + ["H_FRG_05"] = "H FRG 05", + ["H_FRG_06"] = "H FRG 06", + ["H_FRG_07"] = "H FRG 07", + ["H_FRG_08"] = "H FRG 08", + ["H_FRG_09"] = "H FRG 09", + ["H_FRG_10"] = "H FRG 10", + ["H_FRG_11"] = "H FRG 11", + ["H_FRG_12"] = "H FRG 12", + ["H_FRG_13"] = "H FRG 13", + ["H_FRG_14"] = "H FRG 14", + ["H_FRG_15"] = "H FRG 15", + ["H_FRG_16"] = "H FRG 16", + ["H_FRG_17"] = "H FRG 17", + ["H_FRG_18"] = "H FRG 18", + ["H_FRG_19"] = "H FRG 19", + ["H_FRG_20"] = "H FRG 20", + ["H_FRG_21"] = "H FRG 21", + ["H_FRG_23"] = "H FRG 23", + ["H_FRG_25"] = "H FRG 25", + ["H_FRG_27"] = "H FRG 27", + ["H_FRG_30"] = "H FRG 30", + ["H_FRG_31"] = "H FRG 31", + ["H_FRG_32"] = "H FRG 32", + ["H_FRG_34"] = "H FRG 34", + ["H_FRG_38"] = "H FRG 38", + ["H_FRG_39"] = "H FRG 39", + ["H_FRG_40"] = "H FRG 40", + ["H_FRG_41"] = "H FRG 41", + ["H_FRG_42"] = "H FRG 42", + ["H_FRG_43"] = "H FRG 43", + ["H_FRG_44"] = "H FRG 44", + ["H_FRG_45"] = "H FRG 45", + ["H_FRG_46"] = "H FRG 46", + ["H_FRG_47"] = "H FRG 47", + ["H_FRG_48"] = "H FRG 48", + ["H_FRG_49"] = "H FRG 49", + ["H_FRG_50"] = "H FRG 50", + ["H_FRG_51"] = "H FRG 51", + ["H_GDR_01"] = "H GDR 01", + ["H_GDR_02"] = "H GDR 02", + ["H_GDR_03"] = "H GDR 03", + ["H_GDR_04"] = "H GDR 04", + ["H_GDR_05"] = "H GDR 05", + ["H_GDR_06"] = "H GDR 06", + ["H_GDR_07"] = "H GDR 07", + ["H_GDR_08"] = "H GDR 08", + ["H_GDR_09"] = "H GDR 09", + ["H_GDR_10"] = "H GDR 10", + ["H_GDR_11"] = "H GDR 11", + ["H_GDR_12"] = "H GDR 12", + ["H_GDR_13"] = "H GDR 13", + ["H_GDR_14"] = "H GDR 14", + ["H_GDR_15"] = "H GDR 15", + ["H_GDR_16"] = "H GDR 16", + ["H_GDR_17"] = "H GDR 17", + ["H_GDR_18"] = "H GDR 18", + ["H_GDR_19"] = "H GDR 19", + ["H_GDR_21"] = "H GDR 21", + ["H_GDR_22"] = "H GDR 22", + ["H_GDR_24"] = "H GDR 24", + ["H_GDR_25"] = "H GDR 25", + ["H_GDR_26"] = "H GDR 26", + ["H_GDR_30"] = "H GDR 30", + ["H_GDR_31"] = "H GDR 31", + ["H_GDR_32"] = "H GDR 32", + ["H_GDR_33"] = "H GDR 33", + ["H_Med_FRG_02"] = "H Med FRG 02", + ["H_Med_FRG_04"] = "H Med FRG 04", + ["H_Med_FRG_06"] = "H Med FRG 06", + ["H_Med_FRG_09"] = "H Med FRG 09", + ["H_Med_FRG_11"] = "H Med FRG 11", + ["H_Med_FRG_12"] = "H Med FRG 12", + ["H_Med_FRG_13"] = "H Med FRG 13", + ["H_Med_FRG_14"] = "H Med FRG 14", + ["H_Med_FRG_15"] = "H Med FRG 15", + ["H_Med_FRG_16"] = "H Med FRG 16", + ["H_Med_FRG_17"] = "H Med FRG 17", + ["H_Med_FRG_21"] = "H Med FRG 21", + ["H_Med_FRG_24"] = "H Med FRG 24", + ["H_Med_FRG_26"] = "H Med FRG 26", + ["H_Med_FRG_27"] = "H Med FRG 27", + ["H_Med_FRG_29"] = "H Med FRG 29", + ["H_Med_GDR_01"] = "H Med GDR 01", + ["H_Med_GDR_02"] = "H Med GDR 02", + ["H_Med_GDR_03"] = "H Med GDR 03", + ["H_Med_GDR_08"] = "H Med GDR 08", + ["H_Med_GDR_09"] = "H Med GDR 09", + ["H_Med_GDR_10"] = "H Med GDR 10", + ["H_Med_GDR_11"] = "H Med GDR 11", + ["H_Med_GDR_12"] = "H Med GDR 12", + ["H_Med_GDR_13"] = "H Med GDR 13", + ["H_Med_GDR_14"] = "H Med GDR 14", + ["H_Med_GDR_16"] = "H Med GDR 16", + ["H_Radar_FRG_02"] = "H Radar FRG 02", + ["H_Radar_GDR_01"] = "H Radar GDR 01", + ["H_Radar_GDR_02"] = "H Radar GDR 02", + ["H_Radar_GDR_03"] = "H Radar GDR 03", + ["H_Radar_GDR_04"] = "H Radar GDR 04", + ["H_Radar_GDR_05"] = "H Radar GDR 05", + ["H_Radar_GDR_06"] = "H Radar GDR 06", + ["H_Radar_GDR_07"] = "H Radar GDR 07", + ["H_Radar_GDR_08"] = "H Radar GDR 08", + ["H_Radar_GDR_09"] = "H Radar GDR 09", + ["Hahn"] = "Hahn", + ["Haina"] = "Haina", + ["Hamburg"] = "Hamburg", + ["Hamburg_Finkenwerder"] = "Hamburg Finkenwerder", + ["Hannover"] = "Hannover", + ["Hasselfelde"] = "Hasselfelde", + ["Herrenteich"] = "Herrenteich", + ["Hildesheim"] = "Hildesheim", + ["Hockenheim"] = "Hockenheim", + ["Holzdorf"] = "Holzdorf", + ["Kammermark"] = "Kammermark", + ["Koethen"] = "Köthen", + ["Laage"] = "Laage", + ["Langenselbold"] = "Langenselbold", + ["Leipzig_Halle"] = "Leipzig Halle", + ["Leipzig_Mockau"] = "Leipzig Mockau", + ["Laerz"] = "Lärz", + ["Luebeck"] = "Lübeck", + ["Lueneburg"] = "Lüneburg", + ["Mahlwinkel"] = "Mahlwinkel", + ["Mendig"] = "Mendig", + ["Merseburg"] = "Merseburg", + ["Neubrandenburg"] = "Neubrandenburg", + ["Neuruppin"] = "Neuruppin", + ["Northeim"] = "Northeim", + ["Ober_Moerlen"] = "Ober-Mörlen", + ["Obermehler_Schlotheim"] = "Obermehler Schlotheim", + ["Parchim"] = "Parchim", + ["Peenemuende"] = "Peenemünde", + ["Pferdsfeld"] = "Pferdsfeld", + ["Pinnow"] = "Pinnow", + ["Pottschutthoehe"] = "Pottschutthöhe", + ["Ramstein"] = "Ramstein", + ["Rinteln"] = "Rinteln", + ["Schweinfurt"] = "Schweinfurt", + ["Schoenefeld"] = "Schönefeld", + ["Sembach"] = "Sembach", + ["Spangdahlem"] = "Spangdahlem", + ["Sperenberg"] = "Sperenberg", + ["Stendal"] = "Stendal", + ["Tegel"] = "Tegel", + ["Tempelhof"] = "Tempelhof", + ["Tutow"] = "Tutow", + ["Uelzen"] = "Uelzen", + ["Uetersen"] = "Uetersen", + ["Ummern"] = "Ummern", + ["Verden_Scharnhorst"] = "Verden-Scharnhorst", + ["Walldorf"] = "Walldorf", + ["Waren_Vielist"] = "Waren Vielist", + ["Werneuchen"] = "Werneuchen", + ["Weser_Wuemme"] = "Weser Wümme", + ["Wiesbaden"] = "Wiesbaden", + ["Wismar"] = "Wismar", + ["Worms"] = "Worms", + ["Wunstorf"] = "Wunstorf", + ["Zerbst"] = "Zerbst", + ["Zweibruecken"] = "Zweibrücken", +} + + --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. From 9d7986e8d1b00917c5b02c81335dc84305563844 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 6 Apr 2025 15:37:56 +0200 Subject: [PATCH 169/349] #CTLD - Allow CA Transport --- Moose Development/Moose/Ops/CTLD.lua | 70 +++++++++++++++++---- Moose Development/Moose/Utilities/Utils.lua | 8 ++- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f0d4b1997..2510aa35e 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1242,7 +1242,21 @@ do -- end -- end -- +-- ## 8. Transport crates and troops with CA (Combined Arms) trucks -- +-- You can optionally also allow to CTLD with CA trucks and other vehicles: +-- +-- -- Create a SET_CLIENT to capture CA vehicles steered by players +-- local truckers = SET_CLIENT:New():HandleCASlots():FilterCoalitions("blue"):FilterPrefixes("Truck"):FilterStart() +-- -- Allow CA transport +-- my_ctld:AllowCATransport(true,truckers) +-- -- Set truck capability by typename +-- my_ctld:SetUnitCapabilities("M 818", true, true, 2, 12, 9, 4500) +-- -- Alternatively set truck capability with a UNIT object +-- local GazTruck = UNIT:FindByName("GazTruck-1-1") +-- my_ctld:SetUnitCapabilities(GazTruck, true, true, 2, 12, 9, 4500) +-- +-- -- @field #CTLD CTLD = { ClassName = "CTLD", @@ -1277,6 +1291,7 @@ CTLD = { UserSetGroup = nil, LoadedGroupsTable = {}, keeploadtable = true, + allowCATransport = false, } ------------------------------ @@ -1384,6 +1399,7 @@ CTLD.UnitTypeCapabilities = { ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 10800}, ["MosquitoFBMkVI"] = {type="MosquitoFBMkVI", crates= true, troops=false, cratelimit = 2, trooplimit = 0, length = 13, cargoweightlimit = 1800}, + ["M 818"] = {type="M 818", crates= true, troops=true, cratelimit = 4, trooplimit = 12, length = 9, cargoweightlimit = 4500}, } --- Allowed Fixed Wing Types @@ -1396,7 +1412,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.31" +CTLD.version="1.1.32" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1606,6 +1622,10 @@ function CTLD:New(Coalition, Prefixes, Alias) math.random() end + -- CA Transport + self.allowCATransport = false -- #boolean + self.CATransportSet = nil -- Core.Set#SET_CLIENT + self:_GenerateVHFrequencies() self:_GenerateUHFrequencies() self:_GenerateFMFrequencies() @@ -1949,6 +1969,16 @@ function CTLD:_GetUnitCapabilities(Unit) return capabilities end +--- (User) Function to allow transport via Combined Arms Trucks. +-- @param #CTLD self +-- @param #boolean OnOff Switch on (true) or off (false). +-- @param Core.Set#SET_CLIENT ClientSet The CA handling client set for ground transport. +-- @return #CTLD self +function CTLD:AllowCATransport(OnOff,ClientSet) + self.allowCATransport = OnOff -- #boolean + self.CATransportSet = ClientSet -- Core.Set#SET_CLIENT + return self +end --- (Internal) Function to generate valid UHF Frequencies -- @param #CTLD self @@ -2742,6 +2772,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) -- spawn crates in front of helicopter local IsHerc = self:IsFixedWing(Unit) -- Herc, Bronco and Hook load from behind local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind + local IsTruck = Unit:IsGround() local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number @@ -2763,7 +2794,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) local rheading = 0 local angleOffNose = 0 local addon = 0 - if IsHerc or IsHook then + if IsHerc or IsHook or IsTruck then -- spawn behind the Herc addon = 180 end @@ -3093,24 +3124,24 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype if not _ignoreweight then maxloadable = self:_GetMaxLoadableMass(_unit) end - self:T2(self.lid .. " Max loadable mass: " .. maxloadable) + self:T(self.lid .. " Max loadable mass: " .. maxloadable) for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates local weight = cargo:GetMass() -- weight in kgs of this cargo local staticid = cargo:GetID() - self:T2(self.lid .. " Found cargo mass: " .. weight) + self:T(self.lid .. " Found cargo mass: " .. weight) if static and static:IsAlive() then --or cargoalive) then local restricthooktononstatics = self.enableChinookGCLoading and IsHook - --self:I(self.lid .. " restricthooktononstatics: " .. tostring(restricthooktononstatics)) + self:T(self.lid .. " restricthooktononstatics: " .. tostring(restricthooktononstatics)) local cargoisstatic = cargo:GetType() == CTLD_CARGO.Enum.STATIC and true or false - --self:I(self.lid .. " Cargo is static: " .. tostring(cargoisstatic)) + self:T(self.lid .. " Cargo is static: " .. tostring(cargoisstatic)) local restricted = cargoisstatic and restricthooktononstatics - --self:I(self.lid .. " Loading restricted: " .. tostring(restricted)) + self:T(self.lid .. " Loading restricted: " .. tostring(restricted)) local staticpos = static:GetCoordinate() --or dcsunitpos local cando = cargo:UnitCanCarry(_unit) if ignoretype == true then cando = true end - --self:I(self.lid .. " Unit can carry: " .. tostring(cando)) + self:T(self.lid .. " Unit can carry: " .. tostring(cando)) --- Testing local distance = self:_GetDistance(location,staticpos) self:T(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) @@ -4184,6 +4215,19 @@ function CTLD:_RefreshF10Menus() end end end + + -- 3) CA Units + if self.allowCATransport and self.CATransportSet then + for _,_clientobj in pairs(self.CATransportSet.Set) do + local client = _clientobj -- Wrapper.Client#CLIENT + if client:IsGround() then + local cname = client:GetName() + --self:I(self.lid.."Adding: "..cname) + _UnitList[cname] = cname + end + end + end + self.CtldUnits = _UnitList -- subcats? @@ -4212,10 +4256,15 @@ function CTLD:_RefreshF10Menus() local menus = {} for _, _unitName in pairs(self.CtldUnits) do if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then + --self:I(self.lid.."Menu not done yet") local _unit = UNIT:FindByName(_unitName) + if not _unit and self.allowCATransport then + _unit = CLIENT:FindByName(_unitName) + end if _unit and _unit:IsAlive() then local _group = _unit:GetGroup() if _group then + --self:I(self.lid.."Unit and Group exist") local capabilities = self:_GetUnitCapabilities(_unit) local cantroops = capabilities.troops local cancrates = capabilities.crates @@ -5730,9 +5779,8 @@ end local unit = nil if type(Unittype) == "string" then unittype = Unittype - elseif type(Unittype) == "table" then - unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT - unittype = unit:GetTypeName() + elseif type(Unittype) == "table" and Unittype.ClassName and Unittype:IsInstanceOf("UNIT") then + unittype = Unittype:GetTypeName() else return self end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 626580dff..9e0e60514 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2329,8 +2329,12 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " rear cargo door is open") return true end - - return false + + -- ground + local UnitDescriptor = unit:getDesc() + local IsGroundResult = (UnitDescriptor.category == Unit.Category.GROUND_UNIT) + + return IsGroundResult end -- nil From c902a1f60198ea8e19093c92dac5c0e9d30123fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 7 Apr 2025 10:40:39 +0200 Subject: [PATCH 170/349] xxx --- Moose Development/Moose/Core/Set.lua | 58 ++++++++++++++++++++++++++++ Moose Development/Moose/Ops/CTLD.lua | 6 +-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 0b54f06d0..c66b3cf57 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -2519,6 +2519,35 @@ do -- SET_UNIT ) return self end + + --- Builds a set of units which belong to groups with certain **group names**. + -- @param #SET_UNIT self + -- @param #string Prefixes The (partial) group names to look for. Can be a single string or a table of strings. + -- @return #SET_UNIT self + function SET_UNIT:FilterGroupPrefixes(Prefixes) + if type(Prefixes) == "string" then + Prefixes = {Prefixes} + end + self:FilterFunction( + function(unit,prefixes) + local outcome = false + if unit then + local grp = unit:GetGroup() + local gname = grp ~= nil and grp:GetName() or "none" + for _,_fix in pairs(prefixes or {}) do + if string.find(gname,_fix) then + outcome = true + break + end + end + else + return false + end + return outcome + end, Prefixes + ) + return self + end --- Builds a set of units having a radar of give types. -- All the units having a radar of a given type will be included within the set. @@ -4434,6 +4463,35 @@ do -- SET_CLIENT end return self end + + --- Builds a set of clients which belong to groups with certain **group names**. + -- @param #SET_CLIENT self + -- @param #string Prefixes The (partial) group names to look for. Can be anywhere in the group name. Can be a single string or a table of strings. + -- @return #SET_CLIENT self + function SET_CLIENT:FilterGroupPrefixes(Prefixes) + if type(Prefixes) == "string" then + Prefixes = {Prefixes} + end + self:FilterFunction( + function(unit,prefixes) + local outcome = false + if unit then + local grp = unit:GetGroup() + local gname = grp ~= nil and grp:GetName() or "none" + for _,_fix in pairs(prefixes or {}) do + if string.find(gname,_fix) then + outcome = true + break + end + end + else + return false + end + return outcome + end, Prefixes + ) + return self + end --- Builds a set of clients that are only active. -- Only the clients that are active will be included within the set. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 2510aa35e..565c9bf1a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4222,7 +4222,7 @@ function CTLD:_RefreshF10Menus() local client = _clientobj -- Wrapper.Client#CLIENT if client:IsGround() then local cname = client:GetName() - --self:I(self.lid.."Adding: "..cname) + self:T(self.lid.."Adding: "..cname) _UnitList[cname] = cname end end @@ -4256,7 +4256,7 @@ function CTLD:_RefreshF10Menus() local menus = {} for _, _unitName in pairs(self.CtldUnits) do if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then - --self:I(self.lid.."Menu not done yet") + self:T(self.lid.."Menu not done yet for ".._unitName) local _unit = UNIT:FindByName(_unitName) if not _unit and self.allowCATransport then _unit = CLIENT:FindByName(_unitName) @@ -4264,7 +4264,7 @@ function CTLD:_RefreshF10Menus() if _unit and _unit:IsAlive() then local _group = _unit:GetGroup() if _group then - --self:I(self.lid.."Unit and Group exist") + self:T(self.lid.."Unit and Group exist") local capabilities = self:_GetUnitCapabilities(_unit) local cantroops = capabilities.troops local cancrates = capabilities.crates From 00c5d82590f813339df40e282c0deb6c7b8ea15d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 7 Apr 2025 11:56:54 +0200 Subject: [PATCH 171/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 565c9bf1a..cd0bc8403 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1412,7 +1412,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.32" +CTLD.version="1.2.33" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2059,6 +2059,12 @@ function CTLD:_EventHandler(EventData) self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end + -- CA support + if _unit:IsGround() and self.allowCATransport then + local unitname = event.IniUnitName or "none" + self.Loaded_Cargo[unitname] = nil + self:_RefreshF10Menus() + end return elseif event.id == EVENTS.Land or event.id == EVENTS.Takeoff then local unitname = event.IniUnitName @@ -4141,6 +4147,7 @@ function CTLD:_MoveGroupToZone(Group) local groupcoord = Group:GetCoordinate() -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) + self:T({canmove=outcome, name=name, zone=zone, dist=distance,max=self.movetroopsdistance}) if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then -- yes, we can ;) local groupname = Group:GetName() @@ -5240,6 +5247,8 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) table = self.dropOffZones elseif ZoneType == CTLD.CargoZoneType.SHIP then table = self.shipZones + elseif ZoneType == CTLD.CargoZoneType.BEACON then + table = self.droppedBeacons else table = self.wpZones end @@ -5672,7 +5681,8 @@ function CTLD:IsUnitInZone(Unit,Zonetype) end local distance = self:_GetDistance(zonecoord,unitcoord) self:T("Distance Zone: "..distance) - if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and maxdist > distance then + self:T("Zone Active: "..tostring(active)) + if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and distance < maxdist then outcome = true maxdist = distance zoneret = zone From e483d1bfcad375029ad8d14bfcbd76048f7bc3c1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 14 Apr 2025 12:13:08 +0200 Subject: [PATCH 172/349] xx --- Moose Development/Moose/Wrapper/Airbase.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index ef485640e..f595f696f 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1419,7 +1419,7 @@ function AIRBASE:Register(AirbaseName) self.descriptors=self:GetDesc() -- Debug info. - --self:I({airbase=AirbaseName, descriptors=self.descriptors}) + --self:T({airbase=AirbaseName, descriptors=self.descriptors}) -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME @@ -2538,7 +2538,7 @@ function AIRBASE:GetRunwayByName(Name) -- Name including L or R, e.g. "31L". local name=self:GetRunwayName(runway) - + self:T("Check Runway Name: "..name) if name==Name:upper() then return runway end @@ -2565,7 +2565,7 @@ function AIRBASE:_InitRunways(IncludeInverse) --- Function to create a runway data table. local function _createRunway(name, course, width, length, center) - + self:T("Create Runway: name = "..name) -- Bearing in rad. local bearing=-1*course @@ -2581,6 +2581,7 @@ function AIRBASE:_InitRunways(IncludeInverse) runway.name=string.format("%02d", tonumber(namefromheading)) else runway.name=string.format("%02d", tonumber(name)) + self:T("RunwayName: "..runway.name) end --runway.name=string.format("%02d", tonumber(name)) @@ -2902,7 +2903,7 @@ function AIRBASE:GetRunwayData(magvar, mark) runway.endpoint=c2 -- Debug info. - --self:I(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d", self:GetName(), runway.idx, runway.heading, runway.length, i, j)) + self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d", self:GetName(), runway.idx, runway.heading, runway.length, i, j)) -- Debug mark if mark then @@ -3029,8 +3030,8 @@ function AIRBASE:GetRunwayIntoWind(PreferLeft) -- Loop over runways. local dotmin=nil - for i,_runway in pairs(runways) do - local runway=_runway --#AIRBASE.Runway + for i ,_runway in pairs(runways) do + local runway=_runway --#AIRBASE.Runway if PreferLeft==nil or PreferLeft==runway.isLeft then From c9414d98dacdf7d656e743448eb3bbe1692fb67e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 15 Apr 2025 11:45:22 +0200 Subject: [PATCH 173/349] #EASYGCICAP - make conflict zones setup a bit more explicit --- Moose Development/Moose/Ops/EasyGCICAP.lua | 50 ++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index c5f0ebb0b..4fcd7c2cb 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -62,6 +62,7 @@ -- @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 @@ -102,6 +103,11 @@ -- 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". -- +-- ### 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 planes, despite being on 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 EWR" in the name as EW Radar Systems. @@ -114,10 +120,10 @@ -- mywing:AddSquadron("Blue Sq1 M2000c","CAP Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.GOOD,102,"ec1.5_Vendee_Jeanne_clean") -- -- -- Add a couple of zones --- -- We'll defend our border +-- -- We'll defend our own border -- mywing:AddAcceptZone(ZONE_POLYGON:New( "Blue Border", GROUP:FindByName( "Blue Border" ) )) --- -- We'll attack intruders also here --- mywing:AddAcceptZone(ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" ))) +-- -- 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" ) )) -- @@ -125,10 +131,10 @@ -- -- 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 BlueNoGoZone = ZONE_POLYGON:New("Red Defense Zone", GROUP:FindByName( "Red Defense Zone" )) --- BlueNoGoZone:DrawZone(-1,{1,1,0},1,FillColor,FillAlpha,2,true) --- local BlueNoGoZone2 = ZONE_POLYGON:New( "Red Border", GROUP:FindByName( "Red Border" ) ) --- BlueNoGoZone2:DrawZone(-1,{1,0,0},1,FillColor,FillAlpha,4,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 CAP point (optional) -- @@ -210,6 +216,7 @@ EASYGCICAP = { repeatsonfailure = 3, GoZoneSet = nil, NoGoZoneSet = nil, + ConflictZoneSet = nil, Monitor = false, TankerInvisible = true, CapFormation = nil, @@ -252,7 +259,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.17" +EASYGCICAP.version="0.1.18" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -287,6 +294,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) 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 = 300 self.capalt = 25000 @@ -1113,7 +1121,7 @@ end -- @param Core.Zone#ZONE_BASE Zone -- @return #EASYGCICAP self function EASYGCICAP:AddAcceptZone(Zone) - self:T(self.lid.."AddAcceptZone0") + self:T(self.lid.."AddAcceptZone") self.GoZoneSet:AddZone(Zone) return self end @@ -1128,6 +1136,18 @@ function EASYGCICAP:AddRejectZone(Zone) return self end +--- Add a zone to the conflict zones set. +-- @param #EASYGCICAP self +-- @param Core.Zone#ZONE_BASE Zone +-- @return #EASYGCICAP self +function EASYGCICAP:AddConflictZone(Zone) + self:T(self.lid.."AddConflictZone") + self.ConflictZoneSet:AddZone(Zone) + self.GoZoneSet:AddZone(Zone) + return self +end + + --- (Internal) Try to assign the intercept to a FlightGroup already in air and ready. -- @param #EASYGCICAP self -- @param #table ReadyFlightGroups ReadyFlightGroups @@ -1193,6 +1213,7 @@ function EASYGCICAP:_AssignIntercept(Cluster) local ctlpts = self.ManagedCP local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping local nogozoneset = self.NoGoZoneSet + local conflictzoneset = self.ConflictZoneSet local ReadyFlightGroups = self.ReadyFlightGroups -- Aircraft? @@ -1271,18 +1292,22 @@ function EASYGCICAP:_AssignIntercept(Cluster) if nogozoneset:Count() > 0 then InterceptAuftrag:AddConditionSuccess( - function(group,zoneset) + function(group,zoneset,conflictset) local success = false if group and group:IsAlive() then local coord = group:GetCoordinate() - if coord and zoneset:IsCoordinateInZone(coord) then + 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 end return success end, contact.group, - nogozoneset + nogozoneset, + conflictzoneset ) end @@ -1316,6 +1341,7 @@ function EASYGCICAP:_StartIntel() BlueIntel:SetForgetTime(300) BlueIntel:SetAcceptZones(self.GoZoneSet) BlueIntel:SetRejectZones(self.NoGoZoneSet) + BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) BlueIntel:Start() From 2636ca166ff36cf5d7192725e2b1cd97a3acc96e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Apr 2025 17:49:03 +0200 Subject: [PATCH 174/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 7 +++++-- Moose Development/Moose/Ops/OpsZone.lua | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 4fcd7c2cb..ebe8b520b 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -259,7 +259,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.18" +EASYGCICAP.version="0.1.19" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -606,6 +606,9 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local Intel = self.Intel local TankerInvisible = self.TankerInvisible + local engagerange = self.engagerange + local GoZoneSet = self.GoZoneSet + local NoGoZoneSet = self.NoGoZoneSet function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission) local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP @@ -619,7 +622,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch() if Mission.type ~= AUFTRAG.Type.TANKER and Mission.type ~= AUFTRAG.Type.AWACS and Mission.type ~= AUFTRAG.Type.RECON then flightgroup:SetDetection(true) - flightgroup:SetEngageDetectedOn(self.engagerange,{"Air"},self.GoZoneSet,self.NoGoZoneSet) + flightgroup:SetEngageDetectedOn(engagerange,{"Air"},GoZoneSet,NoGoZoneSet) flightgroup:SetOutOfAAMRTB() if CapFormation then flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation) diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index bbcf4f97b..9a47b16ae 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -53,7 +53,8 @@ -- @field #number threatlevelCapture Threat level necessary to capture a zone. -- @field Core.Set#SET_UNIT ScanUnitSet Set of scanned units. -- @field Core.Set#SET_GROUP ScanGroupSet Set of scanned groups. --- @extends Core.Fsm#FSM +-- @field #number UpdateSeconds Run status every this many seconds. +-- @extends Core.Fsm#FSM --- *Gentlemen, when the enemy is committed to a mistake we must not interrupt him too soon.* --- Horation Nelson -- @@ -77,6 +78,7 @@ OPSZONE = { Tnut = 0, chiefs = {}, Missions = {}, + UpdateSeconds = 120, } --- OPSZONE.MISSION @@ -97,7 +99,7 @@ OPSZONE.ZoneType={ --- OPSZONE class version. -- @field #string version -OPSZONE.version="0.6.1" +OPSZONE.version="0.6.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -733,7 +735,8 @@ function OPSZONE:onafterStart(From, Event, To) self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status, self) -- Status update. - self.timerStatus:Start(1, 120) + local EveryUpdateIn = self.UpdateSeconds or 120 + self.timerStatus:Start(1, EveryUpdateIn) -- Handle base captured event. if self.airbase then From 7ded993c4993f936f0c550fc2ff0912487ad4a9e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Apr 2025 17:52:17 +0200 Subject: [PATCH 175/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index ebe8b520b..31b732a74 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -259,7 +259,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.19" +EASYGCICAP.version="0.1.20" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list From 1c247867237c2e2149fd10856796ba7c4c836f57 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 20 Apr 2025 15:51:44 +0200 Subject: [PATCH 176/349] xx --- Moose Development/Moose/Ops/PlayerTask.lua | 70 ++++++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index c482c81e6..74d53667d 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update Jan 2025 +-- @date Last Update April 2025 do @@ -1902,7 +1902,7 @@ PLAYERTASKCONTROLLER.Messages = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.69" +PLAYERTASKCONTROLLER.version="0.1.70" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -3703,6 +3703,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) else CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) end + --self:I("CoordText = "..CoordText) -- Threat Level local ThreatLevel = task.Target:GetThreatLevelMax() --local ThreatLevelText = "high" @@ -3837,7 +3838,8 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) Text = string.gsub(Text,"9","niner") CoordText = "MGRS;"..Text if self.PathToGoogleKey then - CoordText = string.format("%s",CoordText) + --CoordText = string.format("%s",CoordText) + --doesn't seem to work any longer end --self:I(self.lid.." | ".. CoordText) end @@ -3855,10 +3857,12 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) CoordText = string.gsub(ttstext," BR, "," Bee, Arr, ") end elseif task:HasFreetext() then + -- add tts freetext local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale) ttstext = ttstext .. string.format("; %s: ",brieftxt)..task:GetFreetextTTS() end + --self:I("**** TTS Text ****\n"..ttstext.."\n*****") self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,2) end else @@ -4357,7 +4361,7 @@ function PLAYERTASKCONTROLLER:SwitchDetectStatics(OnOff) return self end ---- [User] Add accept zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add an accept zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. -- @return #PLAYERTASKCONTROLLER self @@ -4371,7 +4375,7 @@ function PLAYERTASKCONTROLLER:AddAcceptZone(AcceptZone) return self end ---- [User] Add accept SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add an accept SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param Core.Set#SET_ZONE AcceptZoneSet Add a SET_ZONE to the accept zone set. -- @return #PLAYERTASKCONTROLLER self @@ -4385,7 +4389,7 @@ function PLAYERTASKCONTROLLER:AddAcceptZoneSet(AcceptZoneSet) return self end ---- [User] Add reject zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add a reject zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param Core.Zone#ZONE RejectZone Add a zone to the reject zone set. -- @return #PLAYERTASKCONTROLLER self @@ -4399,7 +4403,7 @@ function PLAYERTASKCONTROLLER:AddRejectZone(RejectZone) return self end ---- [User] Add reject SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add a reject SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param Core.Set#SET_ZONE RejectZoneSet Add a zone to the reject zone set. -- @return #PLAYERTASKCONTROLLER self @@ -4413,9 +4417,37 @@ function PLAYERTASKCONTROLLER:AddRejectZoneSet(RejectZoneSet) return self end ---- [User] Remove accept zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add a conflict zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self --- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. +-- @param Core.Zone#ZONE ConflictZone Add a zone to the conflict zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddConflictZone(ConflictZone) + self:T(self.lid.."AddConflictZone") + if self.Intel then + self.Intel:AddConflictZone(ConflictZone) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + +--- [User] Add a conflict SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Set#SET_ZONE ConflictZoneSet Add a zone to the conflict zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddConflictZoneSet(ConflictZoneSet) + self:T(self.lid.."AddConflictZoneSet") + if self.Intel then + self.Intel.conflictzoneset:AddSet(ConflictZoneSet) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + +--- [User] Remove an accept zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Zone#ZONE AcceptZone Remove this zone from the accept zone set. -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone) self:T(self.lid.."RemoveAcceptZone") @@ -4427,11 +4459,11 @@ function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone) return self end ---- [User] Remove reject zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Remove a reject zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self --- @param Core.Zone#ZONE RejectZone Add a zone to the reject zone set. +-- @param Core.Zone#ZONE RejectZone Remove this zone from the reject zone set. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:RemoveRejectZoneSet(RejectZone) +function PLAYERTASKCONTROLLER:RemoveRejectZone(RejectZone) self:T(self.lid.."RemoveRejectZone") if self.Intel then self.Intel:RemoveRejectZone(RejectZone) @@ -4441,6 +4473,20 @@ function PLAYERTASKCONTROLLER:RemoveRejectZoneSet(RejectZone) return self end +--- [User] Remove a conflict zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Zone#ZONE ConflictZone Remove this zone from the conflict zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone) + self:T(self.lid.."RemoveConflictZone") + if self.Intel then + self.Intel:RemoveConflictZone(ConflictZone) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + --- [User] Set the top menu name to a custom string. -- @param #PLAYERTASKCONTROLLER self -- @param #string Name The name to use as the top menu designation. From 46f9cf46e48edb5e5234cd595069d3b5b7cc231c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 20 Apr 2025 17:50:16 +0200 Subject: [PATCH 177/349] xxx --- Moose Development/Moose/Functional/Mantis.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 1e857599b..bd529dcd3 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Mar 2025 +-- Last Update: Apr 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -393,6 +393,7 @@ MANTIS.SamData = { ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, + ["HEMTT_C-RAM_Phalanx"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, ["SA-17"] = { Range=50, Blindspot=3, Height=30, Type="Medium", Radar="SA-17" }, @@ -689,7 +690,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.27" + self.version="0.9.28" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- From 96d52ea3931c661f0870b374d57294df90815685 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 24 Apr 2025 14:47:02 +0200 Subject: [PATCH 178/349] xxx --- Moose Development/Moose/Ops/PlayerTask.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 74d53667d..8c6361a48 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -98,7 +98,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.25" +PLAYERTASK.version="0.1.26" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -979,6 +979,12 @@ function PLAYERTASK:onafterStatus(From, Event, To) if status == "Stopped" then return self end + -- update marker in case target is moving + if self.TargetMarker then + local coordinate = self.Target:GetCoordinate() + self.TargetMarker:UpdateCoordinate(coordinate,0.5) + end + -- Check Target status local targetdead = false From 4fbbf70b8e2de740edc09f05b4ac4a621f643804 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 27 Apr 2025 11:23:16 +0200 Subject: [PATCH 179/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index bd529dcd3..9a412d768 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -374,7 +374,7 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3 MANTIS.SamData = { ["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B - ["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot" }, + ["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" }, ["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" }, ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" }, ["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" }, @@ -382,7 +382,8 @@ MANTIS.SamData = { ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" }, ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"}, ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, - ["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Point", Radar="Roland" }, + ["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, + ["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" }, ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" }, From 44c59b97e82a928705aca7ebaf3d23d71997f2fe Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 May 2025 09:34:43 +0200 Subject: [PATCH 180/349] CSAR --- Moose Development/Moose/Ops/CSAR.lua | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index e7805f25a..27eb8afa6 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -2152,12 +2152,15 @@ function CSAR:_GetClosestMASH(_heli) end for _, _mashUnit in pairs(_mashes) do - if _mashUnit and _mashUnit:IsAlive() then - local _mashcoord = _mashUnit:GetCoordinate() - _distance = self:_GetDistance(_helicoord, _mashcoord) - if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then - _shortestDistance = _distance - end + local _mashcoord + if _mashUnit and (not _mashUnit:IsInstanceOf("ZONE_BASE")) and _mashUnit:IsAlive() then + _mashcoord = _mashUnit:GetCoordinate() + elseif _mashUnit and _mashUnit:IsInstanceOf("ZONE_BASE") then + _mashcoord = _mashUnit:GetCoordinate() + end + _distance = self:_GetDistance(_helicoord, _mashcoord) + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance end end @@ -2166,6 +2169,7 @@ function CSAR:_GetClosestMASH(_heli) else return -1 end + end --- (Internal) Display onboarded rescued pilots. From 4c9210586cd274c918a7d7acde30f59e92cdccbc Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 May 2025 10:52:34 +0200 Subject: [PATCH 181/349] #CSAR - fix design issue that prevented usage of ZONE objects as MASHes --- Moose Development/Moose/Ops/CSAR.lua | 42 +++++++++++++++++----------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 27eb8afa6..76f6df917 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Jan 2025 +-- Last Update May 2025 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -313,7 +313,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.30" +CSAR.version="1.0.31" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -2120,7 +2120,11 @@ end function CSAR:_GetClosestMASH(_heli) self:T(self.lid .. " _GetClosestMASH") local _mashset = self.mash -- Core.Set#SET_GROUP - local _mashes = _mashset:GetSetObjects() -- #table + local MashSets = {} + --local _mashes = _mashset.Set-- #table + table.insert(MashSets,_mashset.Set) + table.insert(MashSets,self.zonemashes.Set) + table.insert(MashSets,self.staticmashes.Set) local _shortestDistance = -1 local _distance = 0 local _helicoord = _heli:GetCoordinate() @@ -2151,17 +2155,19 @@ function CSAR:_GetClosestMASH(_heli) _shortestDistance = distance end - for _, _mashUnit in pairs(_mashes) do - local _mashcoord - if _mashUnit and (not _mashUnit:IsInstanceOf("ZONE_BASE")) and _mashUnit:IsAlive() then - _mashcoord = _mashUnit:GetCoordinate() - elseif _mashUnit and _mashUnit:IsInstanceOf("ZONE_BASE") then - _mashcoord = _mashUnit:GetCoordinate() - end - _distance = self:_GetDistance(_helicoord, _mashcoord) - if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then - _shortestDistance = _distance - end + for _,_mashes in pairs(MashSets) do + for _, _mashUnit in pairs(_mashes or {}) do + local _mashcoord + if _mashUnit and (not _mashUnit:IsInstanceOf("ZONE_BASE")) and _mashUnit:IsAlive() then + _mashcoord = _mashUnit:GetCoordinate() + elseif _mashUnit and _mashUnit:IsInstanceOf("ZONE_BASE") then + _mashcoord = _mashUnit:GetCoordinate() + end + _distance = self:_GetDistance(_helicoord, _mashcoord) + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + end + end end if _shortestDistance ~= -1 then @@ -2458,9 +2464,10 @@ function CSAR:onafterStart(From, Event, To) self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() - local staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce() - local zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce() + self.staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce() + self.zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce() + --[[ if staticmashes:Count() > 0 then for _,_mash in pairs(staticmashes.Set) do self.mash:AddObject(_mash) @@ -2468,10 +2475,13 @@ function CSAR:onafterStart(From, Event, To) end if zonemashes:Count() > 0 then + self:T("Adding zones to self.mash SET") for _,_mash in pairs(zonemashes.Set) do self.mash:AddObject(_mash) end + self:T("Objects in SET: "..self.mash:Count()) end + --]] if not self.coordinate then local csarhq = self.mash:GetRandom() From be98ee52b75b3b93e8acf16ef46aaa94777e3458 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 3 May 2025 16:59:07 +0200 Subject: [PATCH 182/349] xxx --- Moose Development/Moose/Ops/CTLD.lua | 1112 ++++++++++++++++---------- 1 file changed, 671 insertions(+), 441 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index c0c30ec72..405ca9a07 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -20,11 +20,12 @@ -- -- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), bbirchnz (additional code!!) -- ### Repack addition for crates: **Raiden** +-- ### Additional cool features: **Lekaa** -- -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update April 2025 +-- Last Update May 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -865,6 +866,7 @@ do -- my_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook -- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook -- my_ctld.showstockinmenuitems = false -- When set to true, the menu lines will also show the remaining items in stock (that is, if you set any), downside is that the menu for all will be build every 30 seconds anew. +-- my_ctld.onestepmenu = false -- When set to true, the menu will create Drop and build, Get and load, Pack and remove, Pack and load, Pack only. it will be a 1 step solution. -- -- ## 2.1 CH-47 Chinook support -- @@ -1412,7 +1414,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.2.33" +CTLD.version="1.3.34" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1591,6 +1593,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.subcats = {} self.subcatsTroop = {} self.showstockinmenuitems = false + self.onestepmenu = false -- disallow building in loadzones self.nobuildinloadzones = true @@ -2913,10 +2916,10 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) if drop then text = string.format("Crates for %s have been dropped!",cratename) self:__CratesDropped(1, Group, Unit, droppedcargo) + else + self:_SendMessage(text, 10, false, Group) end - self:_SendMessage(text, 10, false, Group) self:_RefreshLoadCratesMenu(Group, Unit) - return self end @@ -3576,11 +3579,14 @@ end -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:IsHook(Unit) - if Unit and string.find(Unit:GetTypeName(),"CH.47") then - return true - else - return false - end + if not Unit then return false end + local typeName = Unit:GetTypeName() + if not typeName then return false end + if string.find(typeName, "CH.47") then + return true + else + return false + end end --- (Internal) Function to set troops positions of a template to a nice circle @@ -3763,89 +3769,103 @@ end -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit function CTLD:_UnloadCrates(Group, Unit) - self:T(self.lid .. " _UnloadCrates") - - if not self.dropcratesanywhere then -- #1570 - -- check if we are in DROP zone - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) - if not inzone then - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - if not self.debug then - return self - end - end - end - -- Door check - if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) - if not self.debug then return self end - end - -- check for hover unload - local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsFixedWing(Unit) - local IsHook = self:IsHook(Unit) - if IsHerc and (not IsHook) then - -- no hover but airdrop here - hoverunload = self:IsCorrectFlightParameters(Unit) - end - -- check if we\'re landed - local grounded = not self:IsUnitInAir(Unit) - -- Get what we have loaded - local unitname = Unit:GetName() - if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - -- looking for crate - local cargotable = loadedcargo.Cargo - for _,_cargo in pairs (cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and (not cargo:WasDropped() or self.allowcratepickupagain) then - -- unload crates - self:_GetCrates(Group, Unit, cargo, 1, true) - cargo:SetWasDropped(true) - cargo:SetHasMoved(true) - end - end - -- cleanup load list - local loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} + self:T(self.lid .. " _UnloadCrates") - for _,_cargo in pairs (cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - local size = cargo:GetCratesNeeded() - if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then - table.insert(loaded.Cargo,_cargo) - loaded.Troopsloaded = loaded.Troopsloaded + size - end - if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then - table.insert(loaded.Cargo,_cargo) - loaded.Cratesloaded = loaded.Cratesloaded + size + if not self.dropcratesanywhere then -- #1570 + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + if not self.debug then + return self + end end end - self.Loaded_Cargo[unitname] = nil - self.Loaded_Cargo[unitname] = loaded - - self:_UpdateUnitCargoMass(Unit) - self:_RefreshDropCratesMenu(Group,Unit) - else - if IsHerc then - self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) + if not self.debug then return self end + end + local hoverunload = self:IsCorrectHover(Unit) + local IsHerc = self:IsFixedWing(Unit) + local IsHook = self:IsHook(Unit) + if IsHerc and (not IsHook) then + hoverunload = self:IsCorrectFlightParameters(Unit) + end + local grounded = not self:IsUnitInAir(Unit) + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + local loadedcargo = self.Loaded_Cargo[unitname] or {} + local cargotable = loadedcargo.Cargo + local droppedCount = {} + local neededMap = {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo + local type = cargo:GetType() + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and (not cargo:WasDropped() or self.allowcratepickupagain) then + self:_GetCrates(Group, Unit, cargo, 1, true) + cargo:SetWasDropped(true) + cargo:SetHasMoved(true) + local cname = cargo:GetName() or "Unknown" + droppedCount[cname] = (droppedCount[cname] or 0) + 1 + if not neededMap[cname] then + neededMap[cname] = cargo:GetCratesNeeded() or 1 + end + end + end + for cname,count in pairs(droppedCount) do + local needed = neededMap[cname] or 1 + if needed > 1 then + local full = math.floor(count/needed) + local left = count % needed + if full > 0 and left == 0 then + self:_SendMessage(string.format("Dropped %d %s.",full,cname),10,false,Group) + elseif full > 0 and left > 0 then + self:_SendMessage(string.format("Dropped %d %s(s), with %d leftover crate(s).",full,cname,left),10,false,Group) + else + self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group) + end + else + self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group) + end + end + local loaded = {} + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo + local type = cargo:GetType() + local size = cargo:GetCratesNeeded() + if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then + table.insert(loaded.Cargo,_cargo) + loaded.Troopsloaded = loaded.Troopsloaded + size + end + if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + size + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + + self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group,Unit) else - self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - end + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + end + end + return self end - return self -end --- (Internal) Function to build nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #boolean Engineering If true build is by an engineering team. -function CTLD:_BuildCrates(Group, Unit,Engineering) +-- @param #boolean MultiDrop If true and not engineering or FOB, vary position a bit. +function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) self:T(self.lid .. " _BuildCrates") -- avoid users trying to build from flying Hercs if self:IsFixedWing(Unit) and self.enableFixedWing and not Engineering then @@ -3939,12 +3959,13 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) if build.CanBuild then self:_CleanUpCrates(crates,build,number) if self.buildtime and self.buildtime > 0 then - local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate()) + local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) self:__CratesBuildStarted(1,Group,Unit) + self:_RefreshDropTroopsMenu(Group,Unit) else - self:_BuildObjectFromCrates(Group,Unit,build) + self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end end end @@ -3983,13 +4004,14 @@ function CTLD:_PackCratesNearby(Group, Unit) _Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu - return self + return true end end end end end - return self + self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) + return false end --- (Internal) Function to repair nearby vehicles / FOBs @@ -4082,7 +4104,8 @@ end -- @param #CTLD.Buildable Build -- @param #boolean Repair If true this is a repair and not a new build -- @param Core.Point#COORDINATE RepairLocation Location for repair (e.g. where the destroyed unit was) -function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) +-- @param #boolean MultiDrop if true and not a repair, vary location a bit if not a FOB +function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,MultiDrop) self:T(self.lid .. " _BuildObjectFromCrates") -- Spawn-a-crate-content if Group and Group:IsAlive() or (RepairLocation and not Repair) then @@ -4099,7 +4122,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) if type(temptable) == "string" then temptable = {temptable} end - local zone = nil + local zone = nil -- Core.Zone#ZONE_RADIUS if RepairLocation and not Repair then -- timed build zone = ZONE_RADIUS:New(string.format("Build zone-%d",math.random(1,10000)),RepairLocation:GetVec2(),100) @@ -4108,6 +4131,10 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) end --local randomcoord = zone:GetRandomCoordinate(35):GetVec2() local randomcoord = Build.Coord or zone:GetRandomCoordinate(35):GetVec2() + if MultiDrop and (not Repair) and canmove then + -- coordinate may be the same, avoid + local randomcoord = zone:GetRandomCoordinate(35):GetVec2() + end if Repair then randomcoord = RepairLocation:GetVec2() end @@ -4199,310 +4226,458 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) return self end +--- (Internal) Helper - Drop **all** loaded crates nearby and build them. +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +function CTLD:_DropAndBuild(Group,Unit) + if self.nobuildinloadzones then + if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) then + self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) + return self + end + end + self:_UnloadCrates(Group,Unit) + timer.scheduleFunction(function() self:_BuildCrates(Group,Unit,false,true) end,{},timer.getTime()+1) + end + + --- (Internal) Helper - Drop a **single** crate set and build it. +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +-- @param number setIndex Index of the crate-set to drop + function CTLD:_DropSingleAndBuild(Group,Unit,setIndex) + if self.nobuildinloadzones then + if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) then + self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) + return self + end + end + self:_UnloadSingleCrateSet(Group,Unit,setIndex) + timer.scheduleFunction(function() self:_BuildCrates(Group,Unit,false) end,{},timer.getTime()+1) + end + +--- (Internal) Helper - Pack crates near the unit and load them. +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +function CTLD:_PackAndLoad(Group,Unit) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) + return self + end + if not self:_PackCratesNearby(Group,Unit) then + return self + end + timer.scheduleFunction(function() self:_LoadCratesNearby(Group,Unit) end,{},timer.getTime()+1) + return self + end + +--- (Internal) Helper - Pack crates near the unit and then remove them. +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +function CTLD:_PackAndRemove(Group,Unit) + if not self:_PackCratesNearby(Group,Unit) then + return self + end + timer.scheduleFunction(function() self:_RemoveCratesNearby(Group,Unit) end,{},timer.getTime()+1) + return self +end + +--- (Internal) Helper - get and load in one step +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +function CTLD:_GetAndLoad(Group,Unit,cargoObj) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) + return self + end + self:_GetCrates(Group,Unit,cargoObj) + + timer.scheduleFunction(function() self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name) end,{},timer.getTime()+1) +end + +-- @param Wrapper.Group#GROUP Group The player’s group that triggered the action +-- @param Wrapper.Unit#UNIT Unit The unit performing the pack-and-load +function CTLD:_GetAllAndLoad(Group,Unit) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) + return self + end + + timer.scheduleFunction(function() self:_LoadCratesNearby(Group,Unit) end,{},timer.getTime()+1) +end + --- (Internal) Housekeeping - Function to refresh F10 menus. -- @param #CTLD self -- @return #CTLD self function CTLD:_RefreshF10Menus() - self:T(self.lid .. " _RefreshF10Menus") - - -- 1) Gather all the pilot groups from our Set - local PlayerSet = self.PilotGroups - local PlayerTable = PlayerSet:GetSetObjects() - - -- 2) Rebuild the self.CtldUnits table - local _UnitList = {} - for _, groupObj in pairs(PlayerTable) do - local firstUnit = groupObj:GetFirstUnitAlive() - if firstUnit then - if firstUnit:IsPlayer() then - if firstUnit:IsHelicopter() or (self.enableFixedWing and self:IsFixedWing(firstUnit)) then - local _unit = firstUnit:GetName() - _UnitList[_unit] = _unit + self:T(self.lid .. " _RefreshF10Menus") + self.onestepmenu = self.onestepmenu or false -- hybrid toggle (default = false) + + -- 1) Gather all the pilot groups from our Set + local PlayerSet = self.PilotGroups + local PlayerTable = PlayerSet:GetSetObjects() + + -- 2) Rebuild the self.CtldUnits table + local _UnitList = {} + for _, groupObj in pairs(PlayerTable) do + local firstUnit = groupObj:GetFirstUnitAlive() + if firstUnit then + if firstUnit:IsPlayer() then + if firstUnit:IsHelicopter() or (self.enableFixedWing and self:IsFixedWing(firstUnit)) then + local _unit = firstUnit:GetName() + _UnitList[_unit] = _unit + end end end end - end - - -- 3) CA Units - if self.allowCATransport and self.CATransportSet then - for _,_clientobj in pairs(self.CATransportSet.Set) do - local client = _clientobj -- Wrapper.Client#CLIENT - if client:IsGround() then - local cname = client:GetName() - self:T(self.lid.."Adding: "..cname) - _UnitList[cname] = cname + + -- 3) CA Units + if self.allowCATransport and self.CATransportSet then + for _,_clientobj in pairs(self.CATransportSet.Set) do + local client = _clientobj -- Wrapper.Client#CLIENT + if client:IsGround() then + local cname = client:GetName() + self:T(self.lid.."Adding: "..cname) + _UnitList[cname] = cname + end end end + + self.CtldUnits = _UnitList + + -- subcats? + if self.usesubcats then + for _id,_cargo in pairs(self.Cargo_Crates) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcats[entry.Subcategory] then + self.subcats[entry.Subcategory] = entry.Subcategory + end + end + for _id,_cargo in pairs(self.Cargo_Statics) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcats[entry.Subcategory] then + self.subcats[entry.Subcategory] = entry.Subcategory + end + end + for _id,_cargo in pairs(self.Cargo_Troops) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcatsTroop[entry.Subcategory] then + self.subcatsTroop[entry.Subcategory] = entry.Subcategory + end + end + end + + local menucount = 0 + local menus = {} + for _, _unitName in pairs(self.CtldUnits) do + if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then + self:T(self.lid.."Menu not done yet for ".._unitName) + local _unit = UNIT:FindByName(_unitName) + if not _unit and self.allowCATransport then + _unit = CLIENT:FindByName(_unitName) + end + if _unit and _unit:IsAlive() then + local _group = _unit:GetGroup() + if _group then + self:T(self.lid.."Unit and Group exist") + local capabilities = self:_GetUnitCapabilities(_unit) + local cantroops = capabilities.troops + local cancrates = capabilities.crates + local unittype = _unit:GetTypeName() + local isHook = self:IsHook(_unit) + local nohookswitch = true + --local nohookswitch = not (isHook and self.enableChinookGCLoading) + -- Clear old topmenu if it existed + if _group.CTLDTopmenu then + _group.CTLDTopmenu:Remove() + _group.CTLDTopmenu = nil + end + local toptroops = nil + local topcrates = nil + local topmenu = MENU_GROUP:New(_group, "CTLD", nil) + _group.CTLDTopmenu = topmenu + + if cantroops then + local toptroops = MENU_GROUP:New(_group, "Manage Troops", topmenu) + local troopsmenu = MENU_GROUP:New(_group, "Load troops", toptroops) + _group.MyTopTroopsMenu = toptroops + + if self.usesubcats then + local subcatmenus = {} + for catName, _ in pairs(self.subcatsTroop) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, troopsmenu) + end + for _, cargoObj in pairs(self.Cargo_Troops) do + if not cargoObj.DontShowInMenu then + local stock = cargoObj:GetStock() + local menutext = cargoObj.Name + if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, menutext, subcatmenus[cargoObj.Subcategory], self._LoadTroops, self, _group, _unit, cargoObj) + end + end + else + for _, cargoObj in pairs(self.Cargo_Troops) do + if not cargoObj.DontShowInMenu then + local stock = cargoObj:GetStock() + local menutext = cargoObj.Name + if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, menutext, troopsmenu, self._LoadTroops, self, _group, _unit, cargoObj) + end + end + end + local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh() + MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() + MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() + local uName=_unit:GetName() + local loadedData=self.Loaded_Cargo[uName] + if loadedData and loadedData.Cargo then + for i,cargoObj in ipairs(loadedData.Cargo) do + if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) and not cargoObj:WasDropped() then + local name=cargoObj:GetName() or "Unknown" + local needed=cargoObj:GetCratesNeeded() or 1 + local cID=cargoObj:GetID() + local line=string.format("Drop: %s",name,needed,cID) + MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh() + end + end + end + end + if cancrates then + local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) + _group.MyTopCratesMenu = topcrates + + -- Build the “Get Crates” sub-menu items + local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + + if self.onestepmenu then + if self.usesubcats then + local subcatmenus = {} + for catName,_ in pairs(self.subcats) do + subcatmenus[catName] = MENU_GROUP:New(_group,catName,cratesmenu) + end + for _,cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + local mSet = MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) + MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) + MENU_GROUP_COMMAND:New(_group,"Get only",mSet,self._GetCrates,self,_group,_unit,cargoObj) + end + end + for _,cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + local mSet = MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) + MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) + MENU_GROUP_COMMAND:New(_group,"Get only",mSet,self._GetCrates,self,_group,_unit,cargoObj) + end + end + else + for _,cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + local mSet = MENU_GROUP:New(_group,txt,cratesmenu) + MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) + MENU_GROUP_COMMAND:New(_group,"Get only",mSet,self._GetCrates,self,_group,_unit,cargoObj) + end + end + for _,cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + local mSet = MENU_GROUP:New(_group,txt,cratesmenu) + MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) + MENU_GROUP_COMMAND:New(_group,"Get only",mSet,self._GetCrates,self,_group,_unit,cargoObj) + end + end + end + else + if self.usesubcats then + local subcatmenus = {} + for catName, _ in pairs(self.subcats) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu) -- fixed variable case + end + for _, cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) + end + end + for _, cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) + end + end + else + for _, cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) + end + end + for _, cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) + end + end + end + end + + local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) + _group.MyLoadCratesMenu=loadCratesMenu + MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) + + local dropCratesMenu = MENU_GROUP:New(_group,"Drop Crates",topcrates) + topcrates.DropCratesMenu = dropCratesMenu + + if not self.nobuildmenu then + MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() + end + + local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) + MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) + + if self.onestepmenu then + local mPack=MENU_GROUP:New(_group,"Pack crates",topcrates) + MENU_GROUP_COMMAND:New(_group,"Pack and Load",mPack,self._PackAndLoad,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Pack and Remove",mPack,self._PackAndRemove,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Pack only",mPack,self._PackCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) + else + MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) + end + + local uName = _unit:GetName() + local loadedData = self.Loaded_Cargo[uName] + if loadedData and loadedData.Cargo then + local cargoByName = {} + for _, cgo in pairs(loadedData.Cargo) do + if cgo and (not cgo:WasDropped()) then + local cname = cgo:GetName() + local cneeded = cgo:GetCratesNeeded() + cargoByName[cname] = cargoByName[cname] or { count=0, needed=cneeded } + cargoByName[cname].count = cargoByName[cname].count + 1 + end + end + for name, info in pairs(cargoByName) do + local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) + MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) + end + end + end + + ----------------------------------------------------- + -- Misc sub‐menus + ----------------------------------------------------- + MENU_GROUP_COMMAND:New(_group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Inventory", topmenu, self._ListInventory, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit) + + local smoketopmenu = MENU_GROUP:New(_group, "Smokes, Flares, Beacons", topmenu) + MENU_GROUP_COMMAND:New(_group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokeself = MENU_GROUP:New(_group, "Drop smoke now", smoketopmenu) + MENU_GROUP_COMMAND:New(_group, "Red smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Red) + MENU_GROUP_COMMAND:New(_group, "Blue smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Blue) + MENU_GROUP_COMMAND:New(_group, "Green smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Green) + MENU_GROUP_COMMAND:New(_group, "Orange smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Orange) + MENU_GROUP_COMMAND:New(_group, "White smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.White) + + MENU_GROUP_COMMAND:New(_group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() + + if self:IsFixedWing(_unit) then + MENU_GROUP_COMMAND:New(_group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + else + MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + end + + -- Mark we built the menu + self.MenusDone[_unitName] = true + self:_RefreshLoadCratesMenu(_group,_unit) + self:_RefreshDropCratesMenu(_group,_unit) + + end -- if _group + end -- if _unit + else + self:T(self.lid .. " Menus already done for this group!") + end + end -- for all pilot units + + return self end - self.CtldUnits = _UnitList - - -- subcats? - if self.usesubcats then - for _id,_cargo in pairs(self.Cargo_Crates) do - local entry = _cargo -- #CTLD_CARGO - if not self.subcats[entry.Subcategory] then - self.subcats[entry.Subcategory] = entry.Subcategory - end - end - for _id,_cargo in pairs(self.Cargo_Statics) do - local entry = _cargo -- #CTLD_CARGO - if not self.subcats[entry.Subcategory] then - self.subcats[entry.Subcategory] = entry.Subcategory - end - end - for _id,_cargo in pairs(self.Cargo_Troops) do - local entry = _cargo -- #CTLD_CARGO - if not self.subcatsTroop[entry.Subcategory] then - self.subcatsTroop[entry.Subcategory] = entry.Subcategory - end - end - end - - local menucount = 0 - local menus = {} - for _, _unitName in pairs(self.CtldUnits) do - if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then - self:T(self.lid.."Menu not done yet for ".._unitName) - local _unit = UNIT:FindByName(_unitName) - if not _unit and self.allowCATransport then - _unit = CLIENT:FindByName(_unitName) - end - if _unit and _unit:IsAlive() then - local _group = _unit:GetGroup() - if _group then - self:T(self.lid.."Unit and Group exist") - local capabilities = self:_GetUnitCapabilities(_unit) - local cantroops = capabilities.troops - local cancrates = capabilities.crates - local unittype = _unit:GetTypeName() - local isHook = self:IsHook(_unit) - local nohookswitch = true - --local nohookswitch = not (isHook and self.enableChinookGCLoading) - -- Clear old topmenu if it existed - if _group.CTLDTopmenu then - _group.CTLDTopmenu:Remove() - _group.CTLDTopmenu = nil - end - local toptroops = nil - local topcrates = nil - local topmenu = MENU_GROUP:New(_group, "CTLD", nil) - _group.CTLDTopmenu = topmenu - - if cantroops then - local toptroops = MENU_GROUP:New(_group, "Manage Troops", topmenu) - local troopsmenu = MENU_GROUP:New(_group, "Load troops", toptroops) - _group.MyTopTroopsMenu = toptroops - - if self.usesubcats then - local subcatmenus = {} - for catName, _ in pairs(self.subcatsTroop) do - subcatmenus[catName] = MENU_GROUP:New(_group, catName, troopsmenu) - end - for _, cargoObj in pairs(self.Cargo_Troops) do - if not cargoObj.DontShowInMenu then - local stock = cargoObj:GetStock() - local menutext = cargoObj.Name - if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, menutext, subcatmenus[cargoObj.Subcategory], self._LoadTroops, self, _group, _unit, cargoObj) - end - end - else - for _, cargoObj in pairs(self.Cargo_Troops) do - if not cargoObj.DontShowInMenu then - local stock = cargoObj:GetStock() - local menutext = cargoObj.Name - if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, menutext, troopsmenu, self._LoadTroops, self, _group, _unit, cargoObj) - - end - end - end - local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh() - MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() - MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() - local uName=_unit:GetName() - local loadedData=self.Loaded_Cargo[uName] - if loadedData and loadedData.Cargo then - for i,cargoObj in ipairs(loadedData.Cargo) do - if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) and not cargoObj:WasDropped() then - local name=cargoObj:GetName() or "Unknown" - local needed=cargoObj:GetCratesNeeded() or 1 - local cID=cargoObj:GetID() - local line=string.format("Drop: %s",name,needed,cID) - MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh() - end - end - end - end - if cancrates then - local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) - _group.MyTopCratesMenu = topcrates - - -- Build the “Get Crates” sub-menu items - local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates) - if self.usesubcats then - local subcatmenus = {} - for catName, _ in pairs(self.subcats) do - subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu) - end - for _, cargoObj in pairs(self.Cargo_Crates) do - if not cargoObj.DontShowInMenu then - local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) - if cargoObj.Location then txt = txt.."[R]" end - local stock = cargoObj:GetStock() - if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) - end - end - for _, cargoObj in pairs(self.Cargo_Statics) do - if not cargoObj.DontShowInMenu then - local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) - if cargoObj.Location then txt = txt.."[R]" end - local stock = cargoObj:GetStock() - if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) - end - end - else - for _, cargoObj in pairs(self.Cargo_Crates) do - if not cargoObj.DontShowInMenu then - local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) - if cargoObj.Location then txt = txt.."[R]" end - local stock = cargoObj:GetStock() - if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) - end - end - for _, cargoObj in pairs(self.Cargo_Statics) do - if not cargoObj.DontShowInMenu then - local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) - if cargoObj.Location then txt = txt.."[R]" end - local stock = cargoObj:GetStock() - if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) - end - end - end - - local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) - _group.MyLoadCratesMenu=loadCratesMenu - MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) - MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) - - local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates) - topcrates.DropCratesMenu=dropCratesMenu - - if not self.nobuildmenu then - MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() - end - - local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) - MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) - - MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) - - local uName = _unit:GetName() - local loadedData = self.Loaded_Cargo[uName] - if loadedData and loadedData.Cargo then - local cargoByName = {} - for _, cgo in pairs(loadedData.Cargo) do - if cgo and (not cgo:WasDropped()) then - local cname = cgo:GetName() - local cneeded = cgo:GetCratesNeeded() - cargoByName[cname] = cargoByName[cname] or { count=0, needed=cneeded } - cargoByName[cname].count = cargoByName[cname].count + 1 - end - end - for name, info in pairs(cargoByName) do - local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) - MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) - end - end - end - - - - ----------------------------------------------------- - -- Misc sub‐menus - ----------------------------------------------------- - MENU_GROUP_COMMAND:New(_group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "Inventory", topmenu, self._ListInventory, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit) - - local smoketopmenu = MENU_GROUP:New(_group, "Smokes, Flares, Beacons", topmenu) - MENU_GROUP_COMMAND:New(_group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokeself = MENU_GROUP:New(_group, "Drop smoke now", smoketopmenu) - MENU_GROUP_COMMAND:New(_group, "Red smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Red) - MENU_GROUP_COMMAND:New(_group, "Blue smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Blue) - MENU_GROUP_COMMAND:New(_group, "Green smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Green) - MENU_GROUP_COMMAND:New(_group, "Orange smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Orange) - MENU_GROUP_COMMAND:New(_group, "White smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.White) - - MENU_GROUP_COMMAND:New(_group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) - MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) - MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() - - if self:IsFixedWing(_unit) then - MENU_GROUP_COMMAND:New(_group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() - else - MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() - end - - -- Mark we built the menu - self.MenusDone[_unitName] = true - self:_RefreshLoadCratesMenu(_group, _unit) - self:_RefreshDropCratesMenu(_group,_unit) - - end -- if _group - end -- if _unit - else - self:T(self.lid .. " Menus already done for this group!") - end - end -- for all pilot units - - return self -end - --- (Internal) Function to refresh the menu for load crates. Triggered from land/getcrate/pack and more -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. -- @param Wrapper.Unit#UNIT Unit The calling unit. -- @return #CTLD self -function CTLD:_RefreshLoadCratesMenu(Group, Unit) - if not Group.MyLoadCratesMenu then return end - Group.MyLoadCratesMenu:RemoveSubMenus() - - local d = self.CrateDistance or 35 - local nearby, n = self:_FindCratesNearby(Group, Unit, d, true, true) - if n == 0 then - MENU_GROUP_COMMAND:New(Group, "No crates found! Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) - return - end - MENU_GROUP_COMMAND:New(Group, "Load ALL", Group.MyLoadCratesMenu, self._LoadCratesNearby, self, Group, Unit) - local cargoByName = {} - for _, crate in pairs(nearby) do - local cName = crate:GetName() - cargoByName[cName] = cargoByName[cName] or {} - table.insert(cargoByName[cName], crate) - end - - for cName, cList in pairs(cargoByName) do - local needed = cList[1]:GetCratesNeeded() or 1 - local found = #cList - - local line - if found >= needed then - line = string.format("Load %s", cName) - else - MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) - line = string.format("Load %s (%d/%d)", cName, found, needed) +function CTLD:_RefreshLoadCratesMenu(Group,Unit) + if not Group.MyLoadCratesMenu then return end + Group.MyLoadCratesMenu:RemoveSubMenus() + + local d=self.CrateDistance or 35 + local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) + if n==0 then + MENU_GROUP_COMMAND:New(Group,"No crates found! Rescan?",Group.MyLoadCratesMenu,function() self:_RefreshLoadCratesMenu(Group,Unit) end) + return + end + MENU_GROUP_COMMAND:New(Group,"Load ALL",Group.MyLoadCratesMenu,self._LoadCratesNearby,self,Group,Unit) + + local cargoByName={} + for _,crate in pairs(nearby) do + local name=crate:GetName() + cargoByName[name]=cargoByName[name] or{} + table.insert(cargoByName[name],crate) + end + + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + label=string.format("%d. Load %s",lineIndex,cName) + i=i+needed + else + label=string.format("%d. Load %s (%d/%d)",lineIndex,cName,left,needed) + i=#list+1 + end + MENU_GROUP_COMMAND:New(Group,label,Group.MyLoadCratesMenu,self._LoadSingleCrateSet,self,Group,Unit,cName) + lineIndex=lineIndex+1 + end end - MENU_GROUP_COMMAND:New(Group, line, Group.MyLoadCratesMenu, self._LoadSingleCrateSet, self, Group, Unit, cName) end -end + --- -- Loads exactly `CratesNeeded` crates for one cargoName in range. @@ -4745,78 +4920,133 @@ end -- @param Wrapper.Unit#UNIT Unit The calling unit. -- @return #CTLD self function CTLD:_RefreshDropCratesMenu(Group, Unit) - if not Group.CTLDTopmenu then return end - local topCrates = Group.MyTopCratesMenu - if not topCrates then return end - if topCrates.DropCratesMenu then - topCrates.DropCratesMenu:RemoveSubMenus() - else - topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) - end - local dropCratesMenu = topCrates.DropCratesMenu - local loadedData = self.Loaded_Cargo[Unit:GetName()] - if not loadedData or not loadedData.Cargo then - MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) - return - end - - local cargoByName={} - local dropableCrates=0 - for _,cObj in ipairs(loadedData.Cargo) do - if cObj and not cObj:WasDropped() then - local cType=cObj:GetType() - if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then - local name=cObj:GetName()or"Unknown" - cargoByName[name]=cargoByName[name]or{} - table.insert(cargoByName[name],cObj) - dropableCrates=dropableCrates+1 + if not Group.CTLDTopmenu then return end + local topCrates = Group.MyTopCratesMenu + if not topCrates then return end + if topCrates.DropCratesMenu then + topCrates.DropCratesMenu:RemoveSubMenus() + else + topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) + end + + local dropCratesMenu = topCrates.DropCratesMenu + local loadedData = self.Loaded_Cargo[Unit:GetName()] + if not loadedData or not loadedData.Cargo then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end + + local cargoByName={} + local dropableCrates=0 + for _,cObj in ipairs(loadedData.Cargo) do + if cObj and not cObj:WasDropped() then + local cType=cObj:GetType() + if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then + local name=cObj:GetName()or"Unknown" + cargoByName[name]=cargoByName[name]or{} + table.insert(cargoByName[name],cObj) + dropableCrates=dropableCrates+1 + end + end + end + + if dropableCrates==0 then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end + + ---------------------------------------------------------------------- + -- DEFAULT (“classic”) versus ONE-STEP behaviour + ---------------------------------------------------------------------- + if not self.onestepmenu then + -------------------------------------------------------------------- + -- classic menu + -------------------------------------------------------------------- + MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) + + self.CrateGroupList=self.CrateGroupList or{} + self.CrateGroupList[Unit:GetName()]={} + + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + local chunk={} + for n=i,i+needed-1 do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s",lineIndex,cName) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=i+needed + else + local chunk={} + for n=i,#list do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=#list+1 + end + lineIndex=lineIndex+1 + end + end + + else + -------------------------------------------------------------------- + -- one-step (enhanced) menu + -------------------------------------------------------------------- + local mAll=MENU_GROUP:New(Group,"Drop ALL crates",dropCratesMenu) + MENU_GROUP_COMMAND:New(Group,"Drop and build",mAll,self._DropAndBuild,self,Group,Unit) + MENU_GROUP_COMMAND:New(Group,"Drop only",mAll,self._UnloadCrates,self,Group,Unit) + + self.CrateGroupList=self.CrateGroupList or{} + self.CrateGroupList[Unit:GetName()]={} + + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + local chunk={} + for n=i,i+needed-1 do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s",lineIndex,cName) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) + MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) + MENU_GROUP_COMMAND:New(Group,"Drop only",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=i+needed + else + local chunk={} + for n=i,#list do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=#list+1 + end + lineIndex=lineIndex+1 + end end end end - if dropableCrates==0 then - MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) - return - end - - MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) - self.CrateGroupList=self.CrateGroupList or{} - self.CrateGroupList[Unit:GetName()]={} - - local lineIndex=1 - for cName,list in pairs(cargoByName) do - local needed=list[1]:GetCratesNeeded() or 1 - table.sort(list,function(a,b)return a:GetID()=needed then - local chunk={} - for n=i,i+needed-1 do - table.insert(chunk,list[n]) - end - local label=string.format("%d. %s",lineIndex,cName) - table.insert(self.CrateGroupList[Unit:GetName()],chunk) - local setIndex=#self.CrateGroupList[Unit:GetName()] - MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) - i=i+needed - else - local chunk={} - for n=i,#list do - table.insert(chunk,list[n]) - end - local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) - table.insert(self.CrateGroupList[Unit:GetName()],chunk) - local setIndex=#self.CrateGroupList[Unit:GetName()] - MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) - i=#list+1 - end - lineIndex=lineIndex+1 - end - end -end - --- (Internal) Function to unload a single Troop group by ID. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. @@ -8167,4 +8397,4 @@ end ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- +------------------------------------------------------------------- \ No newline at end of file From c1855cefd3c446e56c785ffdafe760941c43e8b5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 May 2025 08:30:35 +0200 Subject: [PATCH 183/349] CTLD --- Moose Development/Moose/Ops/CTLD.lua | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 405ca9a07..22fc3ff3a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -7214,13 +7214,17 @@ end -- right subtype? if Event == subtype and not task:IsDone() then local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... - --self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")}) - local okaygroup = string.find(Groupname,task:GetProperty("ExtractName"),1,true) - if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and okaygroup then - if task.Clients:HasUniqueID(playername) then - -- success - task:__Success(-1) + self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")}) + if task:GetProperty("ExtractName") then + local okaygroup = string.find(Groupname,task:GetProperty("ExtractName"),1,true) + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and okaygroup then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end end + else + self:T({Text="'ExtractName' Property not set",Name=Groupname,Property=task.Type}) end end end From 2b83d516d550066fae63f5113ec81697be99382a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 May 2025 17:48:50 +0200 Subject: [PATCH 184/349] xxx --- Moose Development/Moose/Functional/Mantis.lua | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 9a412d768..b815ab352 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -74,10 +74,9 @@ -- -- * Moose derived Modular, Automatic and Network capable Targeting and Interception System. -- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy. --- * **Automatic mode** (default since 0.8) will set-up your SAM site network automatically for you --- * **Classic mode** behaves like before --- * Leverage evasiveness from SEAD, leverage attack range setting --- * Automatic setup of SHORAD based on groups of the class "short-range" +-- * **Automatic mode** (default) will set-up your SAM site network automatically for you. +-- * Leverage evasiveness from SEAD, leverage attack range setting. +-- * Automatic setup of SHORAD based on groups of the class "short-range". -- -- # 0. Base considerations and naming conventions -- @@ -136,7 +135,8 @@ -- Set up your SAM sites in the mission editor. Name the groups using a systematic approach like above. -- Set up your EWR system in the mission editor. Name the groups using a systematic approach like above. Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. -- Search Radars usually have "SR" or "STR" in their names. Use the encyclopedia in the mission editor to inform yourself. --- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one** group per SAM location. SA-15 TOR systems offer a good missile defense. +-- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one unit ** per group (multiple groups) for the SAM location. +-- Else, evasive manoevers might club up all defenders in one place. Red SA-15 TOR systems offer a good missile defense. -- -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. -- @@ -188,7 +188,7 @@ -- -- ## 2.1 Auto mode features -- --- ### 2.1.1 You can now add Accept-, Reject- and Conflict-Zones to your setup, e.g. to consider borders or de-militarized zones: +-- ### 2.1.1 You can add Accept-, Reject- and Conflict-Zones to your setup, e.g. to consider borders or de-militarized zones: -- -- -- Parameters are tables of Core.Zone#ZONE objects! -- -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when @@ -205,9 +205,6 @@ -- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "point" or if the range is less than 5km or if the type is AAA. -- -- ### 2.1.4 Advanced features --- --- -- Option to switch off auto mode **before** you start MANTIS (not recommended) --- mybluemantis.automode = false -- -- -- Option to set the scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. -- -- also see engagerange below. @@ -220,6 +217,12 @@ -- -- -- For some scenarios, like Cold War, it might be useful not to activate SAMs if friendly aircraft are around to avoid death by friendly fire. -- mybluemantis.checkforfriendlies = true +-- +-- ### 2.1.6 Shoot & Scoot +-- +-- -- Option to make the (driveable) SHORAD units drive around and shuffle positions +-- -- We use a SET_ZONE for that, number of zones to consider defaults to three, Random is true for random coordinates and Formation is e.g. "Vee". +-- mybluemantis:AddScootZones(ZoneSet, Number, Random, Formation) -- -- # 3. Default settings [both modes unless stated otherwise] -- @@ -242,26 +245,8 @@ -- E.g. mymantis:SetAdvancedMode( true, 90 ) -- -- Use this option if you want to make use of or allow advanced SEAD tactics. --- --- # 5. Integrate SHORAD [classic mode, not necessary in automode, not recommended for manual setup] --- --- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs manually. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in --- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so: --- --- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() --- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue") --- -- now set up MANTIS --- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") --- mymantis:AddShorad(myshorad,720) --- mymantis:Start() -- --- If you systematically name your SHORAD groups starting with "Blue SHORAD" you'll need exactly **one** SHORAD instance to manage all SHORAD groups. --- --- (Optionally) you can remove the link later on with --- --- mymantis:RemoveShorad() --- --- # 6. Integrated SEAD +-- # 5. Integrated SEAD -- -- MANTIS is using @{Functional.Sead#SEAD} internally to both detect and evade HARM attacks. No extra efforts needed to set this up! -- Once a HARM attack is detected, MANTIS (via SEAD) will shut down the radars of the attacked SAM site and take evasive action by moving the SAM From 6ff2725a1de18a64cf9f8bd6bfa04c520b4995e5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 May 2025 08:49:21 +0200 Subject: [PATCH 185/349] #MANTIS - Make DLINK cache time configureable --- Moose Development/Moose/Functional/Mantis.lua | 24 +++++++++++++++---- Moose Development/Moose/Ops/Intelligence.lua | 16 ++++++++++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index b815ab352..81f2c3d63 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Apr 2025 +-- Last Update: May 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -62,7 +62,8 @@ -- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. -- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. -- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White --- @field #number checkcounter Counter for SAM Table refreshes +-- @field #number checkcounter Counter for SAM Table refreshes. +-- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay. -- @extends Core.Base#BASE @@ -321,6 +322,7 @@ MANTIS = { SmokeDecoy = false, SmokeDecoyColor = SMOKECOLOR.White, checkcounter = 1, + DLinkCacheTime = 120, } --- Advanced state enumerator @@ -612,7 +614,8 @@ do self.advAwacs = false end - + self:SetDLinkCacheTime() + -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -676,7 +679,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.28" + self.version="0.9.29" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -1026,6 +1029,15 @@ do end return self end + + --- Function to set how long INTEL DLINK remembers contacts. + -- @param #MANTIS self + -- @param #number seconds Remember this many seconds + -- @return #MANTIS self + function MANTIS:SetDLinkCacheTime(seconds) + self.DLinkCacheTime = math.abs(seconds or 120) + return self + end --- Function to set the detection interval -- @param #MANTIS self @@ -1418,7 +1430,9 @@ do --IntelTwo:SetClusterRadius(5000) IntelTwo:Start() - local IntelDlink = INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,300) + local CacheTime = self.DLinkCacheTime or 120 + local IntelDlink = INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) + IntelDlink:__Start(1) self:SetUsingDLink(IntelDlink) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index f9996cf91..a893557b3 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -2324,7 +2324,7 @@ INTEL_DLINK = { verbose = 0, lid = nil, alias = nil, - cachetime = 300, + cachetime = 120, interval = 20, contacts = {}, clusters = {}, @@ -2333,7 +2333,7 @@ INTEL_DLINK = { --- Version string -- @field #string version -INTEL_DLINK.version = "0.0.1" +INTEL_DLINK.version = "0.0.2" --- Function to instantiate a new object -- @param #INTEL_DLINK self @@ -2385,7 +2385,7 @@ function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime) end -- Cache time - self.cachetime = Cachetime or 300 + self:SetDLinkCacheTime(Cachetime or 120) -- Interval self.interval = Interval or 20 @@ -2477,6 +2477,16 @@ function INTEL_DLINK:onafterStart(From, Event, To) return self end + --- Function to set how long INTEL DLINK remembers contacts. + -- @param #INTEL_DLINK self + -- @param #number seconds Remember this many seconds. Defaults to 180. + -- @return #INTEL_DLINK self + function INTEL_DLINK:SetDLinkCacheTime(seconds) + self.cachetime = math.abs(seconds or 120) + self:I(self.lid.."Caching for "..self.cachetime.." seconds.") + return self + end + --- Function to collect data from the various #INTEL -- @param #INTEL_DLINK self -- @param #string From The From state From 5e45e4a8ecddbaef03f124e986f6817386d70a4f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 May 2025 10:05:25 +0200 Subject: [PATCH 186/349] xxx --- Moose Development/Moose/Ops/Intelligence.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index a893557b3..f582f0675 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -2384,15 +2384,15 @@ function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime) self.alias="SPECTRE" end - -- Cache time - self:SetDLinkCacheTime(Cachetime or 120) - -- Interval self.interval = Interval or 20 -- Set some string id for output to DCS.log file. self.lid=string.format("INTEL_DLINK %s | ", self.alias) + -- Cache time + self:SetDLinkCacheTime(Cachetime or 120) + -- Start State. self:SetStartState("Stopped") From 1231541053c898d445cf99c532341ca7eb6ee72f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 May 2025 11:39:42 +0200 Subject: [PATCH 187/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 4 ++-- Moose Development/Moose/Ops/PlayerTask.lua | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 81f2c3d63..43c821b88 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -133,8 +133,7 @@ -- -- # 0.1 Set-up in the mission editor -- --- Set up your SAM sites in the mission editor. Name the groups using a systematic approach like above. --- Set up your EWR system in the mission editor. Name the groups using a systematic approach like above. Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. +-- Set up your SAM sites in the mission editor. Name the groups using a systematic approach like above.Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. -- Search Radars usually have "SR" or "STR" in their names. Use the encyclopedia in the mission editor to inform yourself. -- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one unit ** per group (multiple groups) for the SAM location. -- Else, evasive manoevers might club up all defenders in one place. Red SA-15 TOR systems offer a good missile defense. @@ -1036,6 +1035,7 @@ do -- @return #MANTIS self function MANTIS:SetDLinkCacheTime(seconds) self.DLinkCacheTime = math.abs(seconds or 120) + if self.DLinkCacheTime < 5 then self.DLinkCacheTime = 5 end return self end diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 8c6361a48..ce95f00a4 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -556,6 +556,7 @@ end -- @param #PLAYERTASK self -- @param #SET_BASE CaptureSquadGroupNamePrefix The prefix of the group name that needs to capture the zone. -- @param #number Coalition The coalition that needs to capture the zone. +-- @param #boolean CheckClientInZone If true, a CLIENT assigned to this task also needs to be in the zone for the task to be successful. -- @return #PLAYERTASK self -- @usage -- -- We can use either STATIC, SET_STATIC, SCENERY or SET_SCENERY as target objects. @@ -570,20 +571,20 @@ end -- -- -- We set CaptureSquadGroupNamePrefix the group name prefix as set in the ME or the spawn of the group that need to be present at the OpsZone like a capture squad, -- -- and set the capturing Coalition in order to trigger a successful task. --- mytask:AddOpsZoneCaptureSuccessCondition("capture-squad", coalition.side.BLUE) +-- mytask:AddOpsZoneCaptureSuccessCondition("capture-squad", coalition.side.BLUE, false) -- -- playerTaskManager:AddPlayerTaskToQueue(mytask) -function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix, Coalition) +function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone) local task = self task:AddConditionSuccess( function(target) if target:IsInstanceOf("OPSZONE") then - return task:_CheckCaptureOpsZoneSuccess(target, CaptureSquadGroupNamePrefix, Coalition, true) + return task:_CheckCaptureOpsZoneSuccess(target, CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone or true) elseif target:IsInstanceOf("SET_OPSZONE") then local successes = 0 local isClientInZone = false target:ForEachZone(function(opszone) - if task:_CheckCaptureOpsZoneSuccess(opszone, CaptureSquadGroupNamePrefix, Coalition) then + if task:_CheckCaptureOpsZoneSuccess(opszone, CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone or true) then successes = successes + 1 end From 9a10b3493341b7fc7180344e279957abcb056dec Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 May 2025 18:24:45 +0200 Subject: [PATCH 188/349] xxx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 31b732a74..943518b2e 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -70,6 +70,7 @@ -- @field #boolean DespawnAfterLanding -- @field #boolean DespawnAfterHolding -- @field #list ListOfAuftrag +-- @field #string defaulttakeofftype Take off type -- @extends Core.Fsm#FSM --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. @@ -223,7 +224,8 @@ EASYGCICAP = { ReadyFlightGroups = {}, DespawnAfterLanding = false, DespawnAfterHolding = true, - ListOfAuftrag = {} + ListOfAuftrag = {}, + defaulttakeofftype = "hot", } --- Internal Squadron data type @@ -259,7 +261,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.20" +EASYGCICAP.version="0.1.21" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -312,6 +314,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.DespawnAfterLanding = false self.DespawnAfterHolding = true self.ListOfAuftrag = {} + self.defaulttakeofftype = "hot" -- Set some string id for output to DCS.log file. self.lid=string.format("EASYGCICAP %s | ", self.alias) @@ -400,6 +403,16 @@ function EASYGCICAP:SetDefaultRepeatOnFailure(Retries) return self end +--- Add default take off type for the airwings. +-- @param #EASYGCICAP self +-- @param #string Takeoff Can be "hot", "cold", or "air" - default is "hot". +-- @return #EASYGCICAP self +function EASYGCICAP:SetDefaultTakeOffType(Takeoff) + self:T(self.lid.."SetDefaultTakeOffType") + self.defaulttakeofftype = Takeoff or "hot" + return self +end + --- Set default CAP Speed in knots -- @param #EASYGCICAP self -- @param #number Speed Speed defaults to 300 @@ -596,9 +609,8 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) if #self.ManagedREC > 0 then CAP_Wing:SetNumberRecon(1) end - --local PatrolCoordinateKutaisi = ZONE:New(CapZoneName):GetCoordinate() - --CAP_Wing:AddPatrolPointCAP(PatrolCoordinateKutaisi,self.capalt,UTILS.KnotsToAltKIAS(self.capspeed,self.capalt),self.capdir,self.capleg) - CAP_Wing:SetTakeoffHot() + + CAP_Wing:SetTakeoffType(self.defaulttakeofftype) CAP_Wing:SetLowFuelThreshold(0.3) CAP_Wing.RandomAssetScore = math.random(50,100) CAP_Wing:Start() From 45bb0fff8a57e0365f8a4982cf403663e08312c8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 May 2025 11:48:14 +0200 Subject: [PATCH 189/349] xx --- Moose Development/Moose/Core/Database.lua | 2 ++ Moose Development/Moose/Functional/Mantis.lua | 26 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 4aec2393a..85bde137c 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -872,6 +872,8 @@ end -- @return Wrapper.Group#GROUP The found GROUP. function DATABASE:FindGroup( GroupName ) + if type(GroupName) ~= "string" or GroupName == "" then return end + local GroupFound = self.GROUPS[GroupName] if GroupFound == nil and GroupName ~= nil and self.Templates.Groups[GroupName] == nil then diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 43c821b88..55df35a60 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -64,6 +64,7 @@ -- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White -- @field #number checkcounter Counter for SAM Table refreshes. -- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay. +-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true -- @extends Core.Base#BASE @@ -322,6 +323,7 @@ MANTIS = { SmokeDecoyColor = SMOKECOLOR.White, checkcounter = 1, DLinkCacheTime = 120, + logsamstatus = false, } --- Advanced state enumerator @@ -647,6 +649,8 @@ do table.insert(self.ewr_templates,awacs) end + self.logsamstatus = false + self:T({self.ewr_templates}) self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition) @@ -678,7 +682,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.29" + self.version="0.9.30" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -1695,7 +1699,9 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) - local radaralive = group:IsSAM() + -- TODO the below might stop working at some point after some hours, needs testing + --local radaralive = group:IsSAM() + local radaralive = true table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG and radaralive then @@ -1878,8 +1884,9 @@ do -- @param #MANTIS self -- @param Functional.Detection#DETECTION_AREAS detection Detection object -- @param #boolean dlink + -- @param #boolean reporttolog -- @return #MANTIS self - function MANTIS:_Check(detection,dlink) + function MANTIS:_Check(detection,dlink,reporttolog) self:T(self.lid .. "Check") --get detected set local detset = detection:GetDetectedItemCoordinates() @@ -1906,7 +1913,8 @@ do local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) end - if self.debug or self.verbose then + + local function GetReport() local statusreport = REPORT:New("\nMANTIS Status "..self.name) statusreport:Add("+-----------------------------+") statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) @@ -1915,7 +1923,15 @@ do statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads)) end statusreport:Add("+-----------------------------+") + return statusreport + end + + if self.debug or self.verbose then + local statusreport = GetReport() MESSAGE:New(statusreport:Text(),10):ToAll():ToLog() + elseif reporttolog == true then + local statusreport = GetReport() + MESSAGE:New(statusreport:Text(),10):ToLog() end return self end @@ -2023,7 +2039,7 @@ do self:T({From, Event, To}) -- check detection if not self.state2flag then - self:_Check(self.Detection,self.DLink) + self:_Check(self.Detection,self.DLink,self.logsamstatus) end local EWRAlive = self:_CheckAnyEWRAlive() From 5351c4f093fb6a1cc2cbca15b555d3dbadccbc86 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 May 2025 13:43:39 +0200 Subject: [PATCH 190/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 965762d07..8ec4939d7 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1863,7 +1863,7 @@ do end --end alive end --end check end --for loop - if self.debug or self.verbose then + if self.debug or self.verbose or self.logsamstatus then for _,_status in pairs(self.SamStateTracker) do if _status == "GREEN" then instatusgreen=instatusgreen+1 From daa2b7da4da54350e8215e65fd64862cd6ea5f78 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 May 2025 13:58:55 +0200 Subject: [PATCH 191/349] xxx --- Moose Development/Moose/Functional/Mantis.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 8ec4939d7..96526516a 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1798,7 +1798,7 @@ do if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then activeshorad = true end - if IsInZone and not suppressed and not activeshorad then --check any target in zone and not currently managed by SEAD + if IsInZone and (not suppressed) and (not activeshorad) then --check any target in zone and not currently managed by SEAD if samgroup:IsAlive() then -- switch on SAM local switch = false From e456573eecf2a88f25031d34de1eb783c2cda80e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 21 May 2025 10:04:50 +0200 Subject: [PATCH 192/349] #CSAR fix for ADF beacons --- Moose Development/Moose/Ops/CSAR.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 76f6df917..f1a1578ae 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -313,7 +313,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.31" +CSAR.version="1.0.32" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -468,7 +468,7 @@ function CSAR:New(Coalition, Template, Alias) -- added 1.0.15 self.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane - self.ADFRadioPwr = 1000 + self.ADFRadioPwr = 500 -- added 1.0.16 self.PilotWeight = 80 @@ -2333,9 +2333,9 @@ end -- @param #CSAR self -- @param Wrapper.Group#GROUP _group Group #GROUP object. -- @param #number _freq Frequency to use --- @param #string _name Beacon Name to use +-- @param #string BeaconName Beacon Name to use -- @return #CSAR self -function CSAR:_AddBeaconToGroup(_group, _freq, _name) +function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName) self:T(self.lid .. " _AddBeaconToGroup") if self.CreateRadioBeacons == false then return end local _group = _group @@ -2356,10 +2356,11 @@ function CSAR:_AddBeaconToGroup(_group, _freq, _name) if _radioUnit then local name = _radioUnit:GetName() local Frequency = _freq -- Freq in Hertz - local name = _radioUnit:GetName() + --local name = _radioUnit:GetName() local Sound = "l10n/DEFAULT/"..self.radioSound local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} - trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,_name) -- Beacon in MP only runs for exactly 30secs straight + self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) + trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight end end @@ -2380,9 +2381,13 @@ function CSAR:_RefreshRadioBeacons() local group = pilot.group local frequency = pilot.frequency or 0 -- thanks to @Thrud local bname = pilot.BeaconName or pilot.name..math.random(1,100000) - trigger.action.stopRadioTransmission(bname) + --trigger.action.stopRadioTransmission(bname) if group and group:IsAlive() and frequency > 0 then - self:_AddBeaconToGroup(group,frequency,bname) + --self:_AddBeaconToGroup(group,frequency,bname) + else + if frequency > 0 then + trigger.action.stopRadioTransmission(bname) + end end end end From efceb095d25657ec63a87ea7e6b123b9174332cf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 24 May 2025 15:53:29 +0200 Subject: [PATCH 193/349] xx --- Moose Development/Moose/Ops/ATIS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index f53f953d0..107808898 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -2798,7 +2798,7 @@ function ATIS:onafterBroadcast( From, Event, To ) end _RUNACT = subtitle - alltext = alltext .. ";\n" .. subtitle + --alltext = alltext .. ";\n" .. subtitle -- Runway length. if self.rwylength then From 740f90a5136db3992d5c07c8c0498b7b8f5376f8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 26 May 2025 07:41:08 +0200 Subject: [PATCH 194/349] xx --- Moose Development/Moose/Ops/Auftrag.lua | 58 +++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index fb4d0adbe..68a819a7b 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1715,6 +1715,42 @@ function AUFTRAG:NewSEAD(Target, Altitude) return mission end +--- **[AIR]** Create a SEAD in Zone mission. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE TargetZone The target zone to attack. +-- @param #number Altitude Engage altitude in feet. Default 25000 ft. +-- @param #table TargetTypes Table of string of DCS known target types, defaults to {"Air defence"}. See [DCS Target Attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) +-- @param #number Duration Engage this much time when the AUFTRAG starts executing. +-- @return #AUFTRAG self +function AUFTRAG:NewSEADInZone(TargetZone, Altitude, TargetTypes, Duration) + + local mission=AUFTRAG:New(AUFTRAG.Type.SEAD) + + mission:_TargetFromObject(TargetZone) + + -- DCS Task options: + mission.engageWeaponType=ENUMS.WeaponFlag.Auto + mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) + mission.engageZone = TargetZone + mission.engageTargetTypes = TargetTypes or {"Air defence"} + + -- Mission options: + mission.missionTask=ENUMS.MissionTask.SEAD + mission.missionAltitude=mission.engageAltitude + mission.missionFraction=0.2 + mission.optionROE=ENUMS.ROE.OpenFire + mission.optionROT=ENUMS.ROT.EvadeFire + + mission.categories={AUFTRAG.Category.AIRCRAFT} + + mission.DCStask=mission:GetDCSMissionTask() + + mission:SetDuration(Duration or 1800) + + return mission +end + --- **[AIR]** Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object. @@ -4822,6 +4858,11 @@ function AUFTRAG:CheckGroupsDone() self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] but count of alive OPSGROUP is zero. Mission DONE!", self.status, self:GetState())) return true end + + if (self:IsStarted() or self:IsExecuting()) and self:CountOpsGroups()>0 then + self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState())) + return true + end return true end @@ -6305,9 +6346,20 @@ function AUFTRAG:GetDCSMissionTask() -- Add enroute task SEAD. Disabled that here because the group enganges everything on its route. --local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil, self.TargetType) --table.insert(self.enrouteTasks, DCStask) - - self:_GetDCSAttackTask(self.engageTarget, DCStasks) - + + if self.engageZone then + + local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil, self.engageTargetTypes) + table.insert(self.enrouteTasks, DCStask) + local OrbitTask = CONTROLLABLE.TaskOrbitCircle(nil,self.engageAltitude,self.missionSpeed,self.engageZone:GetCoordinate()) + table.insert(DCStasks, OrbitTask) + + else + + self:_GetDCSAttackTask(self.engageTarget, DCStasks) + + end + elseif self.type==AUFTRAG.Type.STRIKE then -------------------- From f7a86deba5465bb89c8431b3cb10eba008253f58 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 26 May 2025 12:29:48 +0200 Subject: [PATCH 195/349] xxx --- Moose Development/Moose/Ops/Auftrag.lua | 27 ++++++++++++++----- .../Moose/Wrapper/Controllable.lua | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 68a819a7b..97e7dbe64 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1738,7 +1738,7 @@ function AUFTRAG:NewSEADInZone(TargetZone, Altitude, TargetTypes, Duration) -- Mission options: mission.missionTask=ENUMS.MissionTask.SEAD mission.missionAltitude=mission.engageAltitude - mission.missionFraction=0.2 + mission.missionFraction=0.7 mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.EvadeFire @@ -6349,11 +6349,26 @@ function AUFTRAG:GetDCSMissionTask() if self.engageZone then - local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil, self.engageTargetTypes) - table.insert(self.enrouteTasks, DCStask) - local OrbitTask = CONTROLLABLE.TaskOrbitCircle(nil,self.engageAltitude,self.missionSpeed,self.engageZone:GetCoordinate()) - table.insert(DCStasks, OrbitTask) - + --local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil, self.engageTargetTypes) + --table.insert(self.enrouteTasks, DCStask) + self.engageZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) + local ScanUnitSet = self.engageZone:GetScannedSetUnit() + local SeadUnitSet = SET_UNIT:New() + for _,_unit in pairs (ScanUnitSet.Set) do + local unit = _unit -- Wrapper.Unit#UNTI + if unit and unit:IsAlive() and unit:HasSEAD() then + self:T("Adding UNIT for SEAD: "..unit:GetName()) + local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,4161536) + table.insert(DCStasks, task) + SeadUnitSet:AddUnit(unit) + end + end + self.engageTarget = TARGET:New(SeadUnitSet) + --local OrbitTask = CONTROLLABLE.TaskOrbitCircle(nil,self.engageAltitude,self.missionSpeed,self.engageZone:GetCoordinate()) + --local Point = self.engageZone:GetVec2() + --local OrbitTask = CONTROLLABLE.TaskOrbitCircleAtVec2(nil,Point,self.engageAltitude,self.missionSpeed) + --table.insert(DCStasks, OrbitTask) + else self:_GetDCSAttackTask(self.engageTarget, DCStasks) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index b9f5a0d86..3efddeef4 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1432,7 +1432,7 @@ end -- @param #number Speed The speed [m/s] flying when holding the position. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) - self:F2( { self.ControllableName, Point, Altitude, Speed } ) + --self:F2( { self.ControllableName, Point, Altitude, Speed } ) local DCSTask = { id = 'Orbit', From 618a5ca9be9466ea987b7fe9d2f9b3eb457dea13 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 30 May 2025 19:39:08 +0200 Subject: [PATCH 196/349] xx --- Moose Development/Moose/Ops/CSAR.lua | 30 +++++----------------- Moose Development/Moose/Ops/PlayerTask.lua | 2 +- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index f1a1578ae..98b8a2128 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -313,7 +313,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.32" +CSAR.version="1.0.33" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -2116,7 +2116,8 @@ end --- (Internal) Determine distance to closest MASH. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT --- @return #CSAR self +-- @return #number Distance in meters +-- @return #string MASH Name as string function CSAR:_GetClosestMASH(_heli) self:T(self.lid .. " _GetClosestMASH") local _mashset = self.mash -- Core.Set#SET_GROUP @@ -2128,31 +2129,13 @@ function CSAR:_GetClosestMASH(_heli) local _shortestDistance = -1 local _distance = 0 local _helicoord = _heli:GetCoordinate() - - local function GetCloseAirbase(coordinate,Coalition,Category) - - local a=coordinate:GetVec3() - local distmin=math.huge - local airbase=nil - for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do - local b=DCSairbase:getPoint() - - local c=UTILS.VecSubstract(a,b) - local dist=UTILS.VecNorm(c) - - if dist Date: Fri, 30 May 2025 20:50:55 +0200 Subject: [PATCH 197/349] xx --- Moose Development/Moose/Ops/PlayerTask.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 36d567590..7c912235f 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update April 2025 +-- @date Last Update May 2025 do @@ -98,7 +98,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.26" +PLAYERTASK.version="0.1.27" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -1951,7 +1951,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.taskinfomenu = false self.activehasinfomenu = false self.MenuName = nil - self.menuitemlimit = 5 + self.menuitemlimit = 6 self.holdmenutime = 30 self.MarkerReadOnly = false @@ -2581,7 +2581,7 @@ function PLAYERTASKCONTROLLER:SetMenuOptions(InfoMenu,ItemLimit,HoldTime) if self.activehasinfomenu then self:EnableTaskInfoMenu() end - self.menuitemlimit = ItemLimit or 5 + self.menuitemlimit = ItemLimit+1 or 6 self.holdmenutime = HoldTime or 30 return self end From 0233fef4754d189a026f732c025416a5c5d12fab Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 8 Jun 2025 18:41:25 +0200 Subject: [PATCH 198/349] xx --- Moose Development/Moose/Core/Point.lua | 85 ++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 35ad76b4c..83a3720e6 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2124,21 +2124,32 @@ do -- COORDINATE -- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min. -- @param #number Delay (Optional) Delay before the smoke is started in seconds. -- @param #string Name (Optional) Name if you want to stop the smoke early (normal duration: 5mins) + -- @param #boolean Offset (Optional) If true, offset the smokle a bit. + -- @param #number Direction (Optional) If Offset is true this is the direction of the offset, 1-359 (degrees). Default random. + -- @param #number Distance (Optional) If Offset is true this is the distance of the offset in meters. Default random 10-20. -- @return #COORDINATE self - function COORDINATE:Smoke( SmokeColor, Duration, Delay, Name) - self:F2( { SmokeColor, Name, Duration, Delay } ) + function COORDINATE:Smoke( SmokeColor, Duration, Delay, Name, Offset,Direction,Distance) + self:F2( { SmokeColor, Name, Duration, Delay, Offset } ) SmokeColor=SmokeColor or SMOKECOLOR.Green if Delay and Delay>0 then - self:ScheduleOnce(Delay, COORDINATE.Smoke, self, SmokeColor, Duration, 0, Name) + self:ScheduleOnce(Delay, COORDINATE.Smoke, self, SmokeColor, Duration, 0, Name, Direction,Distance) else -- Create a name which is used to stop the smoke manually self.firename = Name or "Smoke-"..math.random(1,100000) -- Create smoke - trigger.action.smoke( self:GetVec3(), SmokeColor, self.firename ) + if Offset or self.SmokeOffset then + local Angle = Direction or self:GetSmokeOffsetDirection() + local Distance = Distance or self:GetSmokeOffsetDistance() + local newpos = self:Translate(Distance,Angle,true,false) + local newvec3 = newpos:GetVec3() + trigger.action.smoke( newvec3, SmokeColor, self.firename ) + else + trigger.action.smoke( self:GetVec3(), SmokeColor, self.firename ) + end -- Stop smoke if Duration and Duration>0 then @@ -2148,6 +2159,72 @@ do -- COORDINATE return self end + + --- Get the offset direction when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @return #number Direction in degrees. + function COORDINATE:GetSmokeOffsetDirection() + local direction = self.SmokeOffsetDirection or math.random(1,359) + return direction + end + + --- Set the offset direction when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @param #number Direction (Optional) This is the direction of the offset, 1-359 (degrees). Default random. + -- @return #COORDINATE self + function COORDINATE:SetSmokeOffsetDirection(Direction) + if self then + self.SmokeOffsetDirection = Direction or math.random(1,359) + return self + else + COORDINATE.SmokeOffsetDirection = Direction or math.random(1,359) + end + end + + --- Get the offset distance when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @return #number Distance Distance in meters. + function COORDINATE:GetSmokeOffsetDistance() + local distance = self.SmokeOffsetDistance or math.random(10,20) + return distance + end + + --- Set the offset distance when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @param #number Distance (Optional) This is the distance of the offset in meters. Default random 10-20. + -- @return #COORDINATE self + function COORDINATE:SetSmokeOffsetDistance(Distance) + if self then + self.SmokeOffsetDistance = Distance or math.random(10,20) + return self + else + COORDINATE.SmokeOffsetDistance = Distance or math.random(10,20) + end + end + + --- Set the offset on when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @return #COORDINATE self + function COORDINATE:SwitchSmokeOffsetOn() + if self then + self.SmokeOffset = true + return self + else + COORDINATE.SmokeOffset = true + end + end + + --- Set the offset off when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @return #COORDINATE self + function COORDINATE:SwitchSmokeOffsetOff() + if self then + self.SmokeOffset = false + return self + else + COORDINATE.SmokeOffset = false + end + end --- Stops smoking the point in a color. -- @param #COORDINATE self From 2c15d7ed00880d48aa0024139223318968db1ab3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 10 Jun 2025 10:02:37 +0200 Subject: [PATCH 199/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 943518b2e..89a3198d2 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -261,7 +261,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.21" +EASYGCICAP.version="0.1.22" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -582,6 +582,13 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local DespawnAfterLanding = self.DespawnAfterLanding local DespawnAfterHolding = self.DespawnAfterHolding + -- Check STATIC name + local check = STATIC:FindByName(Airbasename,false) + if check == nil then + MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end + -- Create Airwing local CAP_Wing = AIRWING:New(Airbasename,Alias) CAP_Wing:SetVerbosityLevel(0) @@ -816,6 +823,11 @@ function EASYGCICAP:_SetCAPPatrolPoints() self:T(self.lid.."_SetCAPPatrolPoints") for _,_data in pairs(self.ManagedCP) do local data = _data --#EASYGCICAP.CapPoint + self:T("Airbasename = "..data.AirbaseName) + if not self.wings[data.AirbaseName] then + MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING local Coordinate = data.Coordinate local Altitude = data.Altitude From 14300229fb306af8e9576154f1e97753cb3303db Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 10 Jun 2025 18:05:19 +0200 Subject: [PATCH 200/349] #UTILS - Small fix for GetReportingName to distinguish Shark from Mainstay --- Moose Development/Moose/Utilities/Utils.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 19e6889b3..f0c7b4aaf 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1913,6 +1913,13 @@ end function UTILS.GetReportingName(Typename) local typename = string.lower(Typename) + + -- special cases - Shark and Manstay have "A-50" in the name + if string.find(typename,"ka-50",1,true) then + return "Shark" + elseif string.find(typename,"a-50",1,true) then + return "Mainstay" + end for name, value in pairs(ENUMS.ReportingName.NATO) do local svalue = string.lower(value) From 8717faf7a11684d7535ba1e4422b916bc88b396b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 12 Jun 2025 09:10:07 +0200 Subject: [PATCH 201/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 89a3198d2..f8def0685 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -261,7 +261,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.22" +EASYGCICAP.version="0.1.23" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -785,6 +785,11 @@ function EASYGCICAP:_SetTankerPatrolPoints() self:T(self.lid.."_SetTankerPatrolPoints") for _,_data in pairs(self.ManagedTK) do local data = _data --#EASYGCICAP.CapPoint + self:T("Airbasename = "..data.AirbaseName) + if not self.wings[data.AirbaseName] then + MESSAGE:New(self.lid.."You are trying to create a TANKER point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING local Coordinate = data.Coordinate local Altitude = data.Altitude @@ -804,6 +809,11 @@ function EASYGCICAP:_SetAwacsPatrolPoints() self:T(self.lid.."_SetAwacsPatrolPoints") for _,_data in pairs(self.ManagedEWR) do local data = _data --#EASYGCICAP.CapPoint + self:T("Airbasename = "..data.AirbaseName) + if not self.wings[data.AirbaseName] then + MESSAGE:New(self.lid.."You are trying to create an AWACS point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING local Coordinate = data.Coordinate local Altitude = data.Altitude @@ -847,6 +857,11 @@ function EASYGCICAP:_SetReconPatrolPoints() self:T(self.lid.."_SetReconPatrolPoints") for _,_data in pairs(self.ManagedREC) do local data = _data --#EASYGCICAP.CapPoint + self:T("Airbasename = "..data.AirbaseName) + if not self.wings[data.AirbaseName] then + MESSAGE:New(self.lid.."You are trying to create a RECON point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING local Coordinate = data.Coordinate local Altitude = data.Altitude From b62b39e9e82abf942f816b622b51e4cefe4a3111 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 15 Jun 2025 15:37:34 +0200 Subject: [PATCH 202/349] xx --- Moose Development/Moose/Ops/Auftrag.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 97e7dbe64..221b6e20f 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1428,7 +1428,7 @@ function AUFTRAG:NewCAP(ZoneCAP, Altitude, Speed, Coordinate, Heading, Leg, Targ mission:_SetLogID() -- DCS task parameters: - mission.engageZone=ZoneCAP + mission.engageZone=ZoneCAP or Coordinate mission.engageTargetTypes=TargetTypes or {"Air"} -- Mission options: @@ -6191,8 +6191,16 @@ function AUFTRAG:GetDCSMissionTask() ----------------- -- CAP Mission -- ----------------- - - local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil, self.engageZone:GetVec2(), self.engageZone:GetRadius(), self.engageTargetTypes, Priority) + + local Vec2 = self.engageZone:GetVec2() + local Radius + if self.engageZone:IsInstanceOf("COORDINATE") then + Radius = UTILS.NMToMeters(20) + else + Radius = self.engageZone:GetRadius() + end + + local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil, Vec2, Radius, self.engageTargetTypes, Priority) table.insert(self.enrouteTasks, DCStask) From eea75c669f56ef3ca6765f6e52111d61359b9349 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Jun 2025 17:46:44 +0200 Subject: [PATCH 203/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 5b2ecc4ef..78bf8a4fc 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1414,7 +1414,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.34" +CTLD.version="1.3.35" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5971,16 +5971,22 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) for index,cargozone in pairs(zones[i]) do local CZone = cargozone --#CTLD.CargoZone local zonename = CZone.name - local zone = nil + local zone = nil -- Core.Zone#ZONE_RADIUS + local airbasezone = false if i == 4 then zone = UNIT:FindByName(zonename) else zone = ZONE:FindByName(zonename) if not zone then zone = AIRBASE:FindByName(zonename):GetZone() + airbasezone = true end end local zonecoord = zone:GetCoordinate() + -- Avoid smoke/flares on runways + if (i==1 or 1==3) and airbasezone==true and zone:IsInstanceOf("ZONE_BASE") then + zonecoord = zone:GetRandomCoordinate(inner,outer,{land.SurfaceType.LAND}) + end if zonecoord then local active = CZone.active local color = CZone.color From f08783f800d2e13e78c26eca1b0aa569f6c76568 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 18 Jun 2025 17:46:50 +0200 Subject: [PATCH 204/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 96526516a..eacd718d8 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1830,7 +1830,7 @@ do -- link in to SHORAD if available -- DONE: Test integration fully if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early - local Shorad = self.Shorad + local Shorad = self.Shorad --Functional.Shorad#SHORAD local radius = self.checkradius local ontime = self.ShoradTime Shorad:WakeUpShorad(name, radius, ontime) From cb08e4a57730aa291f1bc0d27d0e59622be568ae Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 29 Jun 2025 15:50:47 +0200 Subject: [PATCH 205/349] xxx --- Moose Development/Moose/Core/Set.lua | 1 + Moose Development/Moose/Ops/Awacs.lua | 30 ++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 6d41edf12..865c994c6 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -6690,6 +6690,7 @@ do -- SET_ZONE else self.objectset = {Objects} end + self:_TriggerCheck(true) self:__TriggerRunCheck(self.Checktime) return self diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 09aa3324b..093478141 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.71", -- #string + version = "0.2.72", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -1242,6 +1242,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self:AddTransition("*", "Intercept", "*") self:AddTransition("*", "InterceptSuccess", "*") self:AddTransition("*", "InterceptFailure", "*") + self:AddTransition("*", "VIDSuccess", "*") + self:AddTransition("*", "VIDFailure", "*") self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -1365,18 +1367,38 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station -- @param #string To To state. --- On After "InterceptSuccess" event. Intercept successful. - -- @function [parent=#AWACS] OnAfterIntercept + -- @function [parent=#AWACS] OnAfterInterceptSuccess -- @param #AWACS self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- On After "InterceptFailure" event. Intercept failure. - -- @function [parent=#AWACS] OnAfterIntercept + -- @function [parent=#AWACS] OnAfterInterceptFailure -- @param #AWACS self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. + + --- On After "VIDSuccess" event. Intercept successful. + -- @function [parent=#AWACS] OnAfterVIDSuccess + -- @param #AWACS self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number GID Managed group ID (Player) + -- @param Wrapper.Group#GROUP Group (Player) Group done the VID + -- @param #AWACS.ManagedContact Contact The contact that was VID'd + + --- On After "VIDFailure" event. Intercept failure. + -- @function [parent=#AWACS] OnAfterVIDFailure + -- @param #AWACS self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number GID Managed group ID (Player) + -- @param Wrapper.Group#GROUP Group (Player) Group done the VID + -- @param #AWACS.ManagedContact Contact The contact that was VID'd return self end @@ -3263,12 +3285,14 @@ function AWACS:_VID(Group,Declaration) local vidpos = self.gettext:GetEntry("VIDPOS",self.locale) text = string.format(vidpos,Callsign,self.callsigntxt, Declaration) self:T(text) + self:__VIDSuccess(3,GID,group,cluster) else -- too far away self:T("Contact VID not close enough") local vidneg = self.gettext:GetEntry("VIDNEG",self.locale) text = string.format(vidneg,Callsign,self.callsigntxt) self:T(text) + self:__VIDFailure(3,GID,group,cluster) end self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) end From a33d71d6effba3d28885241c9298b4d97bef6ab5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 4 Jul 2025 16:25:17 +0200 Subject: [PATCH 206/349] xx --- Moose Development/Moose/Ops/Auftrag.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 221b6e20f..3d1eed84e 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -4861,10 +4861,10 @@ function AUFTRAG:CheckGroupsDone() if (self:IsStarted() or self:IsExecuting()) and self:CountOpsGroups()>0 then self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState())) - return true + return false end - return true + return false end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 43c7a65942de202219ebb973014aa17a518765aa Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 18 Jul 2025 17:57:00 +0200 Subject: [PATCH 207/349] xx --- Moose Development/Moose/Core/SpawnStatic.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index b41ac1b0c..344d1a5d7 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -149,6 +149,7 @@ function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) self.CategoryID = CategoryID self.CoalitionID = CoalitionID self.SpawnIndex = 0 + self.StaticCopyFrom = SpawnTemplateName else error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) end @@ -607,6 +608,19 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments)) end - + + if self.StaticCopyFrom ~= nil then + mystatic.StaticCopyFrom = self.StaticCopyFrom + if not _DATABASE.Templates.Statics[Template.name] then + local TemplateGroup={} + TemplateGroup.units={} + TemplateGroup.units[1]=Template + TemplateGroup.x=Template.x + TemplateGroup.y=Template.y + TemplateGroup.name=Template.name + _DATABASE:_RegisterStaticTemplate( TemplateGroup, self.CoalitionID, self.CategoryID, CountryID ) + end + end + return mystatic end From a491c34824077553e6a172d4fb5e8559a6fd40b7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 15:56:23 +0200 Subject: [PATCH 208/349] #AICSAR - Added functionality to use rescue zones instead of pure distance. Fixed helo not despawning after landing back at base. --- Moose Development/Moose/Functional/AICSAR.lua | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Functional/AICSAR.lua b/Moose Development/Moose/Functional/AICSAR.lua index a05f47127..7ec2f73dc 100644 --- a/Moose Development/Moose/Functional/AICSAR.lua +++ b/Moose Development/Moose/Functional/AICSAR.lua @@ -22,7 +22,7 @@ -- === -- -- ### Author: **Applevangelist** --- Last Update Sept 2023 +-- Last Update July 2025 -- -- === -- @module Functional.AICSAR @@ -57,6 +57,8 @@ -- @field #number Speed Default speed setting for the helicopter FLIGHTGROUP is 100kn. -- @field #boolean UseEventEject In case Event LandingAfterEjection isn't working, use set this to true. -- @field #number Delay In case of UseEventEject wait this long until we spawn a landed pilot. +-- @field #boolean UseRescueZone If true, use a rescue zone and not the max distance to FARP/MASH +-- @field Core.Zone#ZONE_RADIUS RescueZone Use this zone as operational area for the AICSAR instance. -- @extends Core.Fsm#FSM @@ -153,10 +155,10 @@ -- To set up AICSAR for SRS TTS output, add e.g. the following to your script: -- -- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required) --- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\google.json") +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\google.json") -- -- -- alternatively for MS Desktop TTS (voices need to be installed locally first!) --- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female") +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female") -- -- -- define a different voice for the downed pilot(s) -- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male") @@ -177,7 +179,7 @@ -- -- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so: -- --- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,nil,5002) +-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",270,radio.modulation.AM,nil,5002) -- -- or -- @@ -191,7 +193,7 @@ -- @field #AICSAR AICSAR = { ClassName = "AICSAR", - version = "0.1.16", + version = "0.1.18", lid = "", coalition = coalition.side.BLUE, template = "", @@ -236,6 +238,8 @@ AICSAR = { Altitude = 1500, UseEventEject = false, Delay = 100, + UseRescueZone = false, + RescueZone = nil, } -- TODO Messages @@ -304,8 +308,9 @@ AICSAR.RadioLength = { -- @param #string Helotemplate Helicopter template name. -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start. -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue. +-- @param #number Helonumber Max number of alive Ai Helos at the same time. Defaults to three. -- @return #AICSAR self -function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) +function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Helonumber) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) @@ -373,7 +378,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- limit number of available helos at the same time self.limithelos = true - self.helonumber = 3 + self.helonumber = Helonumber or 3 -- localization self:InitLocalization() @@ -524,10 +529,20 @@ function AICSAR:InitLocalization() return self end +--- [User] Use a defined zone as area of operation and not the distance to FARP. +-- @param #AICSAR self +-- @param Core.Zone#ZONE Zone The operational zone to use. Downed pilots in this area will be rescued. Can be any known #ZONE type. +-- @return #AICSAR self +function AICSAR:SetUsingRescueZone(Zone) + self.UseRescueZone = true + self.RescueZone = Zone + return self +end + --- [User] Switch sound output on and use SRS output for sound files. -- @param #AICSAR self -- @param #boolean OnOff Switch on (true) or off (false). --- @param #string Path Path to your SRS Server Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" +-- @param #string Path Path to your SRS Server External Audio Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\\\ExternalAudio" -- @param #number Frequency Defaults to 243 (guard) -- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM -- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor. @@ -538,7 +553,7 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) self.SRSRadio = OnOff and true self.SRSTTSRadio = false self.SRSFrequency = Frequency or 243 - self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRS:SetLabel("ACSR") self.SRS:SetCoalition(self.coalition) self.SRSModulation = Modulation or radio.modulation.AM @@ -556,7 +571,7 @@ end -- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()` -- @param #AICSAR self -- @param #boolean OnOff Switch on (true) or off (false). --- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" +-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #number Frequency (Optional) Defaults to 243 (guard) -- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM -- @param #number Port (Optional) Port of the SRS, defaults to 5002. @@ -570,7 +585,7 @@ function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Cultur self.SRSTTSRadio = OnOff and true self.SRSRadio = false self.SRSFrequency = Frequency or 243 - self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRSModulation = Modulation or radio.modulation.AM self.SRSPort = Port or MSRS.port or 5002 if OnOff then @@ -693,7 +708,7 @@ function AICSAR:_EjectEventHandler(EventData) local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) local _country = _event.initiator:getCountry() local _coalition = coalition.getCountryCoalition( _country ) - local data = UTILS.DeepCopy(EventData) + --local data = UTILS.DeepCopy(EventData) Unit.destroy(_event.initiator) -- shagrat remove static Pilot model self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition) end @@ -708,7 +723,14 @@ end -- @return #AICSAR self function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition) - local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) + local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) + if self.UseRescueZone == true and self.RescueZone ~= nil then + if self.RescueZone:IsCoordinateInZone(_LandingPos) then + distancetofarp = self.maxdistance - 10 + else + distancetofarp = self.maxdistance + 10 + end + end -- Mayday Message local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local text = "" @@ -795,7 +817,13 @@ function AICSAR:_EventHandler(EventData, FromEject) -- DONE: add distance check local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) - + if self.UseRescueZone == true and self.RescueZone ~= nil then + if self.RescueZone:IsCoordinateInZone(_LandingPos) then + distancetofarp = self.maxdistance - 10 + else + distancetofarp = self.maxdistance + 10 + end + end -- Mayday Message local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local text = "" @@ -817,7 +845,6 @@ function AICSAR:_EventHandler(EventData, FromEject) if _coalition == self.coalition then if self.verbose then MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) - -- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog() end if self.SRSRadio then local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) @@ -869,6 +896,7 @@ function AICSAR:_GetFlight() :InitUnControlled(true) :OnSpawnGroup( function(Group) + Group:OptionPreferVerticalLanding() self:__HeloOnDuty(1,Group) end ) @@ -892,7 +920,7 @@ function AICSAR:_InitMission(Pilot,Index) --local pilotset = SET_GROUP:New() --pilotset:AddGroup(Pilot) - -- Cargo transport assignment. + -- Cargo transport assignment. local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone) --opstransport:SetVerbosity(3) @@ -934,6 +962,10 @@ function AICSAR:_InitMission(Pilot,Index) helo:__UnloadingDone(5) end + function helo:OnAfterLandAtAirbase(From,Event,To,airbase) + helo:Despawn(2) + end + self.helos[Index] = helo return self @@ -984,7 +1016,9 @@ function AICSAR:_CheckHelos() local name = helo:GetName() self:T("Helo group "..name.." in state "..state) if state == "Arrived" then - helo:__Stop(5) + --helo:__Stop(5) + helo.OnAfterDead = nil + helo:Despawn(35) self.helos[_index] = nil end else @@ -1025,7 +1059,7 @@ function AICSAR:_CheckQueue(OpsGroup) if self:_CheckInMashZone(_pilot) then self:T("Pilot" .. _pilot.GroupName .. " rescued!") if OpsGroup then - OpsGroup:Despawn(10) + --OpsGroup:Despawn(10) else _pilot:Destroy(true,10) end From 78bfbb11f95c1b50945b05977dafdbaaf2ed67fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 15:57:46 +0200 Subject: [PATCH 209/349] #AIRBOSS - fix TTS path in docu --- Moose Development/Moose/Ops/Airboss.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 1754ab219..d47e6da06 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3080,12 +3080,12 @@ end --- Set up SRS for usage without sound files -- @param #AIRBOSS self --- @param #string PathToSRS Path to SRS folder, e.g. "C:\\Program Files\\DCS-SimpleRadio-Standalone". +-- @param #string PathToSRS Path to SRS TTS folder, e.g. "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio". -- @param #number Port Port of the SRS server, defaults to 5002. -- @param #string Culture (Optional, Airboss Culture) Culture, defaults to "en-US". -- @param #string Gender (Optional, Airboss Gender) Gender, e.g. "male" or "female". Defaults to "male". -- @param #string Voice (Optional, Airboss Voice) Set to use a specific voice. Will **override gender and culture** settings. --- @param #string GoogleCreds (Optional) Path to Google credentials, e.g. "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourgooglekey.json". +-- @param #string GoogleCreds (Optional) Path to Google credentials, e.g. "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourgooglekey.json". -- @param #number Volume (Optional) E.g. 0.75. Defaults to 1.0 (loudest). -- @param #table AltBackend (Optional) See MSRS for details. -- @return #AIRBOSS self From 33154ebcdc547da2d3e954922280a6b953fa8502 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 15:58:29 +0200 Subject: [PATCH 210/349] xxx --- Moose Development/Moose/Functional/Autolase.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 9fed56cfc..1e43a737c 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -493,7 +493,7 @@ end --- (User) Function enable sending messages via SRS. -- @param #AUTOLASE self -- @param #boolean OnOff Switch usage on and off --- @param #string Path Path to SRS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone +-- @param #string Path Path to SRS TTS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio -- @param #number Frequency Frequency to send, e.g. 243 -- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM -- @param #string Label (Optional) Short label to be used on the SRS Client Overlay @@ -508,7 +508,7 @@ end function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) if OnOff then self.useSRS = true - self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRSFreq = Frequency or 271 self.SRSMod = Modulation or radio.modulation.AM self.Gender = Gender or MSRS.gender or "male" From aa4b4a5e2f13127d52e82b541dbe9d915c20ba3b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 15:59:16 +0200 Subject: [PATCH 211/349] xxx --- Moose Development/Moose/Ops/Awacs.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 6533ace03..c4d4632fa 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -237,7 +237,7 @@ do -- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm. -- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25) -- -- Set up SRS on port 5010 - change the below to your path and port --- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010) +-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio","female","en-GB",5010) -- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON" -- testawacs:SetRejectionZone(ZONE:FindByName("Red Border")) -- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS. @@ -255,7 +255,7 @@ do -- -- The CAP station zone is called "Fremont". We will be on 255 AM. Note the Orbit Zone is given as *nil* in the `New()`-Statement -- local testawacs = AWACS:New("GCI Senaki",AwacsAW,"blue",AIRBASE.Caucasus.Senaki_Kolkhi,nil,ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM ) -- -- Set up SRS on port 5010 - change the below to your path and port --- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010) +-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio","female","en-GB",5010) -- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON" -- testawacs:SetRejectionZone(ZONE:FindByName("Red Border")) -- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS. @@ -1123,7 +1123,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self.EscortMissionReplacement = {} -- SRS - self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.Gender = "female" self.Culture = "en-GB" self.Voice = nil @@ -2113,7 +2113,7 @@ end --- [User] Set AWACS SRS TTS details - see @{Sound.SRS} for details. `SetSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible. -- @param #AWACS self --- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #string Gender Defaults to "male" -- @param #string Culture Defaults to "en-US" -- @param #number Port Defaults to 5002 @@ -2126,7 +2126,7 @@ end -- @return #AWACS self function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend) self:T(self.lid.."SetSRS") - self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.Gender = Gender or MSRS.gender or "male" self.Culture = Culture or MSRS.culture or "en-US" self.Port = Port or MSRS.port or 5002 From c78471cc8fe23e3acffc8371486d8522b614eac6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 16:00:27 +0200 Subject: [PATCH 212/349] xxx --- Moose Development/Moose/Ops/CSAR.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 817cd19b9..8dede4270 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -130,7 +130,7 @@ -- ## 2.2 SRS Features and Other Features -- -- mycsar.useSRS = false -- Set true to use FF\'s SRS integration --- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\ExternalAudio\\" -- adjust your own path in your SRS installation -- server(!) -- mycsar.SRSchannel = 300 -- radio channel -- mycsar.SRSModulation = radio.modulation.AM -- modulation -- mycsar.SRSport = 5002 -- and SRS Server port @@ -481,7 +481,7 @@ function CSAR:New(Coalition, Template, Alias) -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) self.useSRS = false -- Use FF\'s SRS integration - self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone" -- adjust your own path in your server(!) + self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation self.SRSport = 5002 -- port From ccb1055c6fae76a27a698b368155c7539b7abb63 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 16:01:21 +0200 Subject: [PATCH 213/349] xxx --- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 41b2fb35a..33307d155 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -3079,7 +3079,7 @@ function FLIGHTGROUP:onbeforeLandAtAirbase(From, Event, To, airbase) local Tsuspend=nil if airbase==nil then - self:T(self.lid.."ERROR: Airbase is nil in LandAtAirase() call!") + self:T(self.lid.."ERROR: Airbase is nil in LandAtAirbase() call!") allowed=false end From d40b7447285a55ee8a1a3b68c82ad2dbc32d4685 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 16:02:03 +0200 Subject: [PATCH 214/349] xxx --- Moose Development/Moose/Core/Message.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index ff74527f2..86dfd1858 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -452,7 +452,7 @@ end _MESSAGESRS = {} --- Set up MESSAGE generally to allow Text-To-Speech via SRS and TTS functions. `SetMSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible. --- @param #string PathToSRS (optional) Path to SRS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" or your configuration file setting. +-- @param #string PathToSRS (optional) Path to SRS TTS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio" or your configuration file setting. -- @param #number Port Port (optional) number of SRS, defaults to 5002 or your configuration file setting. -- @param #string PathToCredentials (optional) Path to credentials file for Google. -- @param #number Frequency Frequency in MHz. Can also be given as a #table of frequencies. @@ -468,13 +468,13 @@ _MESSAGESRS = {} -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend) - _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" _MESSAGESRS.frequency = Frequency or MSRS.frequencies or 243 _MESSAGESRS.modulation = Modulation or MSRS.modulations or radio.modulation.AM @@ -535,7 +535,7 @@ end -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- @@ -567,7 +567,7 @@ end -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSBlue() -- @@ -589,7 +589,7 @@ end -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSRed() -- @@ -611,7 +611,7 @@ end -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSAll() -- From 22b8cab5e173da4b49ffa2a804a45fc8f755eefd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 16:02:44 +0200 Subject: [PATCH 215/349] xxx --- Moose Development/Moose/Ops/PlayerRecce.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index e178b58f0..544c25034 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -1544,7 +1544,7 @@ end -- @param #PLAYERRECCE self -- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations! -- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies! --- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #string Gender (Optional) Defaults to "male" -- @param #string Culture (Optional) Defaults to "en-US" -- @param #number Port (Optional) Defaults to 5002 @@ -1556,7 +1556,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend) self:T(self.lid.."SetSRS") - self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- + self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- self.Gender = Gender or MSRS.gender or "male" -- self.Culture = Culture or MSRS.culture or "en-US" -- self.Port = Port or MSRS.port or 5002 -- From 2d9a99738ec0b387a8ddf036c1ea6bddbe0e2a86 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 16:03:17 +0200 Subject: [PATCH 216/349] xxx --- Moose Development/Moose/Ops/PlayerTask.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 7c912235f..7ce6d57c2 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -1440,9 +1440,9 @@ do -- taskmanager:AddRejectZone(ZONE:FindByName("RejectZone")) -- -- -- Set up using SRS for messaging --- local hereSRSPath = "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- local hereSRSPath = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- local hereSRSPort = 5002 --- -- local hereSRSGoogle = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourkey.json" +-- -- local hereSRSGoogle = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourkey.json" -- taskmanager:SetSRS({130,255},{radio.modulation.AM,radio.modulation.AM},hereSRSPath,"female","en-GB",hereSRSPort,"Microsoft Hazel Desktop",0.7,hereSRSGoogle) -- -- -- Controller will announce itself under these broadcast frequencies, handy to use cold-start frequencies here of your aircraft @@ -4606,7 +4606,7 @@ end -- @param #PLAYERTASKCONTROLLER self -- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations! -- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies! --- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #string Gender (Optional) Defaults to "male" -- @param #string Culture (Optional) Defaults to "en-US" -- @param #number Port (Optional) Defaults to 5002 @@ -4620,7 +4620,7 @@ end -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend) self:T(self.lid.."SetSRS") - self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- + self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- self.Gender = Gender or MSRS.gender or "male" -- self.Culture = Culture or MSRS.culture or "en-US" -- self.Port = Port or MSRS.port or 5002 -- From b66a29e20913974444157e5248c1744eaade6110 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 19 Jul 2025 16:03:23 +0200 Subject: [PATCH 217/349] xxx --- Moose Development/Moose/Sound/SRS.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 526094f5a..5b9358b2f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -632,7 +632,7 @@ end -- set the path to the exe file via @{#MSRS.SetPath}. -- -- @param #MSRS self --- @param #string Path Path to SRS directory. Default `C:\\Program Files\\DCS-SimpleRadio-Standalone`. +-- @param #string Path Path to SRS directory. Default `C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio`. -- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies. -- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations. -- @param #string Backend Backend used: `MSRS.Backend.SRSEXE` (default) or `MSRS.Backend.GRPC`. @@ -767,13 +767,13 @@ end --- Set path to SRS install directory. More precisely, path to where the `DCS-SR-ExternalAudio.exe` is located. -- @param #MSRS self --- @param #string Path Path to the directory, where the sound file is located. Default is `C:\\Program Files\\DCS-SimpleRadio-Standalone`. +-- @param #string Path Path to the directory, where the sound file is located. Default is `C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio`. -- @return #MSRS self function MSRS:SetPath(Path) self:F( {Path=Path} ) -- Set path. - self.path=Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.path=Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- Remove (back)slashes. local n=1 ; local nmax=1000 @@ -1817,7 +1817,7 @@ end -- -- -- Moose MSRS default Config -- MSRS_Config = { --- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- Path to SRS install directory. +-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio", -- Path to SRS install directory. -- Port = 5002, -- Port of SRS server. Default 5002. -- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc". -- Frequency = {127, 243}, -- Default frequences. Must be a table 1..n entries! @@ -1837,7 +1837,7 @@ end -- -- Google Cloud -- gcloud = { -- voice = "en-GB-Standard-A", -- The Google Cloud voice to use (see https://cloud.google.com/text-to-speech/docs/voices). --- credentials="C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- Full path to credentials JSON file (only for SRS-TTS.exe backend) +-- credentials="C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourfilename.json", -- Full path to credentials JSON file (only for SRS-TTS.exe backend) -- key="Your access Key", -- Google API access key (only for DCS-gRPC backend) -- }, -- -- Amazon Web Service @@ -1905,7 +1905,7 @@ function MSRS:LoadConfigFile(Path,Filename) local Self = self or MSRS --#MSRS - Self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + Self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" Self.port = MSRS_Config.Port or 5002 Self.backend = MSRS_Config.Backend or MSRS.Backend.SRSEXE Self.frequencies = MSRS_Config.Frequency or {127,243} From 61509f35df729105eff4f797d69e21d73a31a6b1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 20 Jul 2025 12:29:43 +0200 Subject: [PATCH 218/349] xxx --- Moose Development/Moose/Functional/Mantis.lua | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index eacd718d8..5ca1bd06c 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: May 2025 +-- Last Update: July 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -125,10 +125,10 @@ -- * SA-2 (with V759 missile, e.g. "Red SAM SA-2 HDS") -- * SA-2 (with HQ-2 launcher, use HQ-2 in the group name, e.g. "Red SAM HQ-2" ) -- * SA-3 (with V601P missile, e.g. "Red SAM SA-3 HDS") --- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS") --- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS") --- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS") --- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS") +-- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS" with 5P85CE launcher) +-- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS" with 5P85SE launcher) +-- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS 2" for the 9A82 variant and "Red SAM SA-12 HDS 1" for the 9A83 variant) +-- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS 2" for the 9A82ME variant and "Red SAM SA-23 HDS 2" for the 9A83ME variant) -- -- The other HDS types work like the rest of the known SAM systems. -- @@ -406,10 +406,10 @@ MANTIS.SamDataHDS = { -- group name MUST contain HDS to ID launcher type correctly! ["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" }, ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" }, - ["SA-10C HDS 2"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85DE ln"}, -- V55RUD - ["SA-10C HDS 1"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD - ["SA-12 HDS 2"] = { Range=100, Blindspot=10, Height=25, Type="Long" , Radar="S-300V 9A82 l"}, - ["SA-12 HDS 1"] = { Range=75, Blindspot=1, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, + ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD + ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD + ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"}, + ["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, ["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" }, ["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" }, ["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, @@ -682,7 +682,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.30" + self.version="0.9.31" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -2110,7 +2110,7 @@ do if self.debug and self.verbose then self:I(self.lid .. "Status Report") for _name,_state in pairs(self.SamStateTracker) do - self:I(string.format("Site %s\tStatus %s",_name,_state)) + self:I(string.format("Site %s | Status %s",_name,_state)) end end local interval = self.detectinterval * -1 From 6cb262e04d0e1141d468f19df59795eb0178de42 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 20 Jul 2025 14:02:59 +0200 Subject: [PATCH 219/349] xxx --- Moose Development/Moose/Functional/Mantis.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 5ca1bd06c..4b761e99f 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -111,7 +111,7 @@ -- * Silkworm (though strictly speaking this is a surface to ship missile) -- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19 -- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!) --- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2 +-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2 -- -- * From SMA: RBS98M, RBS70, RBS90, RBS90M, RBS103A, RBS103B, RBS103AM, RBS103BM, Lvkv9040M -- **NOTE** If you are using the Swedish Military Assets (SMA), please note that the **group name** for RBS-SAM types also needs to contain the keyword "SMA" @@ -128,7 +128,8 @@ -- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS" with 5P85CE launcher) -- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS" with 5P85SE launcher) -- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS 2" for the 9A82 variant and "Red SAM SA-12 HDS 1" for the 9A83 variant) --- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS 2" for the 9A82ME variant and "Red SAM SA-23 HDS 2" for the 9A83ME variant) +-- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS 2" for the 9A82ME variant and "Red SAM SA-23 HDS 1" for the 9A83ME variant) +-- * SAMP/T (launcher dependent range, e.g. "Blue SAM SAMPT Block 1 HDS" for Block 1, "Blue SAM SAMPT Block 1INT HDS", "Blue SAM SAMPT Block 2 HDS") -- -- The other HDS types work like the rest of the known SAM systems. -- @@ -274,6 +275,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", + version = "0.9.32", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -385,7 +387,7 @@ MANTIS.SamData = { ["HEMTT_C-RAM_Phalanx"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, - ["SA-17"] = { Range=50, Blindspot=3, Height=30, Type="Medium", Radar="SA-17" }, + ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" }, ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, @@ -408,11 +410,15 @@ MANTIS.SamDataHDS = { ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" }, ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD + ["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " }, ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"}, ["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, ["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" }, ["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" }, ["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, + ["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher + ["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher + ["SAMPT Block 2 HDS"] = { Range=200, Blindspot=10, Height=70, Type="long", Radar="SAMPT_MLT_Blk2" }, -- Block 2 Launcher } --- SAM data SMA @@ -680,9 +686,6 @@ do -- counter for SAM table updates self.checkcounter = 1 - -- TODO Version - -- @field #string version - self.version="0.9.31" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- From 777886a8e7b5ecccf710f1357d81bd76b529dc8d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 21 Jul 2025 14:50:58 +0200 Subject: [PATCH 220/349] xxx --- Moose Development/Moose/Utilities/Utils.lua | 113 ++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 17f6bc039..b6ac21de1 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4259,6 +4259,119 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, return ReturnObjects, ADFName, MarkerID end +--- Spawn a MASH at a given coordinate, optionally, add an ADF Beacon. +-- @param #string Name Unique Name of the Mash. +-- @param Core.Point#COORDINATE Coordinate Coordinate where to spawn the MASH. Can be given as a Core.Zone#ZONE object, in this case we take the center coordinate. +-- @param #number Country Country ID the MASH belongs to, e.g. country.id.USA or country.id.RUSSIA. +-- @param #number ADF (Optional) ADF Frequency in kHz (Kilohertz), if given activate an ADF Beacon at the location of the MASH. +-- @param #string Livery (Optional) The livery of the static CH-47, defaults to dark green. +-- @param #table Templates (Optional) You can hand in your own template table of numbered(!) entries. Each entry consist of a relative(!) x,y position and data of a +-- static, shape_name is optional. Also, livery_id is optional, but is applied to the helicopter static only. +-- @usage +-- -- MASH Template example, this one is the built in one used in the function: +-- MASHTemplates = { +-- [1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,}, +-- [2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,}, +-- [3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,}, +-- [4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id = "us army dark green",}, +-- [5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,}, +-- [6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,}, +-- [7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,}, +-- [8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,}, +-- [9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,}, +-- [10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,}, +-- [11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,}, +-- [12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,}, +-- [13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,}, +-- [14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,}, +-- [15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,}, +-- [16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,}, +-- [17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,}, +-- [18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,}, +-- } +-- +function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,Templates) + + -- Basic objects table + + local MASHTemplates = { + [1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,}, + [2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,}, + [3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,}, + [4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id = "us army dark green",}, + [5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,}, + [6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,}, + [7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,}, + [8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,}, + [9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,}, + [10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,}, + [11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,}, + [12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,}, + [13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,}, + [14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,}, + [15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,}, + [16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,}, + [17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,}, + [18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,}, + } + + if Templates then MASHTemplates=Templates end + + -- locals + local name = Name or "Florence Nightingale" + local positionVec2 + local positionVec3 + local ReturnStatics = {} + local CountryID = Country or country.id.USA + local livery = "us army dark green" + + -- check for coordinate or zone + if type(Coordinate) == "table" then + if Coordinate:IsInstanceOf("COORDINATE") or Coordinate:IsInstanceOf("ZONE_BASE") then + positionVec2 = Coordinate:GetVec2() + positionVec3 = Coordinate:GetVec3() + end + else + BASE:E("Spawn MASH - no ZONE or COORDINATE handed!") + return + end + + -- position + local BaseX = positionVec2.x + local BaseY = positionVec2.y + + -- Statics + for id,object in pairs(MASHTemplates) do + local NewName = string.format("%s#%3d",name,id) + local vec2 = {x=BaseX+object.x,y=BaseY+object.y} + local Coordinate=COORDINATE:NewFromVec2(vec2) + local static = SPAWNSTATIC:NewFromType(object.type,object.category,CountryID) + if object.shape_name and object.shape_name ~= "none" then + static:InitShape(object.shape_name) + end + if object.category == "Helicopters" then + if object.livery_id ~= nil then + livery = object.livery_id + end + static:InitLivery(livery) + end + static:SpawnFromCoordinate(Coordinate,object.heading,NewName) + table.insert(ReturnStatics,static) + end + + -- Beacon + local ADFName + if ADF and type(ADF) == "number" then + local ADFFreq = ADF*1000 -- KHz to Hz + local Sound = "l10n/DEFAULT/beacon.ogg" + ADFName = Name .. " ADF "..tostring(ADF).."KHz" + --BASE:I(string.format("Adding MASH Beacon %d KHz Name %s",ADF,ADFName)) + trigger.action.radioTransmission(Sound, positionVec3, 0, true, ADFFreq, 250, ADFName) + end + + return ReturnStatics, ADFName +end + --- Converts a Vec2 to a Vec3. -- @param vec the 2D vector -- @param y optional new y axis (altitude) value. If omitted it's 0. From 0457fc85c76d0f04a0cd147a401cf309735d3bce Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 22 Jul 2025 13:07:11 +0200 Subject: [PATCH 221/349] #CTLD - added FSM event "CratesPacked" #UTILS - more options for MASH building --- Moose Development/Moose/Ops/CTLD.lua | 28 +++++++++++++++++++-- Moose Development/Moose/Utilities/Utils.lua | 25 ++++++++++++++---- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 75d09b498..51896bab7 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -25,7 +25,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update May 2025 +-- Last Update July 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -1414,7 +1414,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.35" +CTLD.version="1.3.36" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1481,6 +1481,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event. self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event. + self:AddTransition("*", "CratesPacked", "*") -- CTLD repack event. self:AddTransition("*", "HelicopterLost", "*") -- CTLD lost event. self:AddTransition("*", "Load", "*") -- CTLD load event. self:AddTransition("*", "Loaded", "*") -- CTLD load event. @@ -1759,6 +1760,17 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. -- @return #CTLD self + + --- FSM Function OnBeforeCratesPacked. + -- @function [parent=#CTLD] OnBeforeCratesPacked + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate that was repacked. + -- @return #CTLD self --- FSM Function OnBeforeTroopsRTB. -- @function [parent=#CTLD] OnBeforeTroopsRTB @@ -1889,6 +1901,17 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. -- @return #CTLD self + + --- FSM Function OnAfterCratesPacked. + -- @function [parent=#CTLD] OnAfterCratesPacked + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate that was repacked. + -- @return #CTLD self --- FSM Function OnAfterTroopsRTB. -- @function [parent=#CTLD] OnAfterTroopsRTB @@ -4012,6 +4035,7 @@ function CTLD:_PackCratesNearby(Group, Unit) _Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu + self:__CratesPacked(1,Group,Unit,_entry) return true end end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 00ed738b2..b184e46aa 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4265,6 +4265,10 @@ end -- @param #number Country Country ID the MASH belongs to, e.g. country.id.USA or country.id.RUSSIA. -- @param #number ADF (Optional) ADF Frequency in kHz (Kilohertz), if given activate an ADF Beacon at the location of the MASH. -- @param #string Livery (Optional) The livery of the static CH-47, defaults to dark green. +-- @param #boolean DeployHelo (Optional) If true, deploy the helicopter static. +-- @param #number MASHRadio MASH Radio Frequency, defaults to 127.5. +-- @param #number MASHRadioModulation MASH Radio Modulation, defaults to radio.modulation.AM. +-- @param #number MASHCallsign Defaults to CALLSIGN.FARP.Berlin. -- @param #table Templates (Optional) You can hand in your own template table of numbered(!) entries. Each entry consist of a relative(!) x,y position and data of a -- static, shape_name is optional. Also, livery_id is optional, but is applied to the helicopter static only. -- @return #table Table of Wrapper.Static#STATIC objects that were spawned. @@ -4292,7 +4296,7 @@ end -- [18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,}, -- } -- -function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,Templates) +function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,DeployHelo,MASHRadio,MASHRadioModulation,MASHCallsign,Templates) -- Basic objects table @@ -4326,6 +4330,9 @@ function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,Templates) local ReturnStatics = {} local CountryID = Country or country.id.USA local livery = "us army dark green" + local MASHRadio = MASHRadio or 127.5 + local MASHRadioModulation = MASHRadioModulation or radio.modulation.AM + local MASHCallsign = MASHCallsign or CALLSIGN.FARP.Berlin -- check for coordinate or zone if type(Coordinate) == "table" then @@ -4341,7 +4348,7 @@ function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,Templates) -- position local BaseX = positionVec2.x local BaseY = positionVec2.y - + -- Statics for id,object in pairs(MASHTemplates) do local NewName = string.format("%s#%3d",name,id) @@ -4351,14 +4358,22 @@ function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,Templates) if object.shape_name and object.shape_name ~= "none" then static:InitShape(object.shape_name) end - if object.category == "Helicopters" then + if object.category == "Helicopters" and DeployHelo == true then if object.livery_id ~= nil then livery = object.livery_id end static:InitLivery(livery) + local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName) + table.insert(ReturnStatics,newstatic) + elseif object.category == "Heliports" then + static:InitFARP(MASHCallsign,MASHRadio,MASHRadioModulation,false,false) + local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName) + table.insert(ReturnStatics,newstatic) + elseif object.category ~= "Helicopters" and object.category ~= "Heliports" then + local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName) + table.insert(ReturnStatics,newstatic) end - static:SpawnFromCoordinate(Coordinate,object.heading,NewName) - table.insert(ReturnStatics,static) + end -- Beacon From a64cc0ba4bf8f1f8dfe785cb083abdb747c683cc Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Jul 2025 12:36:20 +0200 Subject: [PATCH 222/349] xxx --- Moose Development/Moose/Wrapper/Airbase.lua | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 13b5b5f52..80c4d20cb 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -2634,6 +2634,7 @@ function AIRBASE:_InitRunways(IncludeInverse) --runway.name=string.format("%02d", tonumber(name)) runway.magheading=tonumber(runway.name)*10 + runway.idx=runway.magheading runway.heading=heading runway.width=width or 0 runway.length=length or 0 @@ -2946,6 +2947,7 @@ function AIRBASE:GetRunwayData(magvar, mark) local runway={} --#AIRBASE.Runway runway.heading=hdg runway.idx=idx + runway.magheading=idx runway.length=c1:Get2DDistance(c2) runway.position=c1 runway.endpoint=c2 @@ -2961,6 +2963,57 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Add runway. table.insert(runways, runway) + end + + -- Look for identical (parallel) runways, e.g. 03L and 03R at Nellis. + local rpairs={} + for i,_ri in pairs(runways) do + local ri=_ri --#AIRBASE.Runway + for j,_rj in pairs(runways) do + local rj=_rj --#AIRBASE.Runway + if i 0 + return ((b.z - a.z)*(c.x - a.x) - (b.x - a.x)*(c.z - a.z)) > 0 + end + + for i,j in pairs(rpairs) do + local ri=runways[i] --#AIRBASE.Runway + local rj=runways[j] --#AIRBASE.Runway + + -- Draw arrow. + --ri.center:ArrowToAll(rj.center) + + local c0=ri.center + + -- Vector in the direction of the runway. + local a=UTILS.VecTranslate(c0, 1000, ri.heading) + + -- Vector from runway i to runway j. + local b=UTILS.VecSubstract(rj.center, ri.center) + b=UTILS.VecAdd(ri.center, b) + + -- Check if rj is left of ri. + local left=isLeft(c0, a, b) + + --env.info(string.format("Found pair %s: i=%d, j=%d, left==%s", ri.name, i, j, tostring(left))) + + if left then + ri.isLeft=false + rj.isLeft=true + else + ri.isLeft=true + rj.isLeft=false + end + + --break end return runways From 04f275e273a93b3b3f10fca7f29d97af209723bc Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Jul 2025 15:47:47 +0200 Subject: [PATCH 223/349] xxx --- Moose Development/Moose/Wrapper/Airbase.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 80c4d20cb..386c9a960 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1433,7 +1433,7 @@ AIRBASE.SpotStatus = { --- Runway data. -- @type AIRBASE.Runway -- @field #string name Runway name. --- @field #string idx Runway ID: heading 070° ==> idx="07". +-- @field #string idx Runway ID: heading 070° ==> idx="07". Mostly same as magheading. -- @field #number heading True heading of the runway in degrees. -- @field #number magheading Magnetic heading of the runway in degrees. This is what is marked on the runway. -- @field #number length Length of runway in meters. @@ -2991,14 +2991,14 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Draw arrow. --ri.center:ArrowToAll(rj.center) - local c0=ri.center + local c0=ri.position -- Vector in the direction of the runway. local a=UTILS.VecTranslate(c0, 1000, ri.heading) -- Vector from runway i to runway j. - local b=UTILS.VecSubstract(rj.center, ri.center) - b=UTILS.VecAdd(ri.center, b) + local b=UTILS.VecSubstract(rj.position, ri.position) + b=UTILS.VecAdd(ri.position, b) -- Check if rj is left of ri. local left=isLeft(c0, a, b) From 2b29ae4f9cd539eade8c556a96c44838b8a7ac01 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 24 Jul 2025 16:17:32 +0200 Subject: [PATCH 224/349] xx --- .../Moose/Wrapper/Controllable.lua | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index e82835405..20f35ee09 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -168,16 +168,25 @@ -- * @{#CONTROLLABLE.OptionAlarmStateGreen} -- * @{#CONTROLLABLE.OptionAlarmStateRed} -- --- ## 5.4) Jettison weapons: +-- ## 5.4) [AIR] Jettison weapons: -- -- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat} -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- --- ## 5.5) Air-2-Air missile attack range: +-- ## 5.5) [AIR] Air-2-Air missile attack range: -- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. -- -- # 6) [GROUND] IR Maker Beacons for GROUPs and UNITs -- * @{#CONTROLLABLE:NewIRMarker}(): Create a blinking IR Marker on a GROUP or UNIT. +-- +-- # 7) [HELICOPTER] Units prefer vertical landing and takeoffs: +-- * @{#CONTROLLABLE.OptionPreferVerticalLanding}(): Set aircraft to prefer vertical landing and takeoff. +-- +-- # 8) [AIRCRAFT] Landing approach options +-- * @{#CONTROLLABLE.SetOptionLandingStraightIn}(): Landing approach straight in. +-- * @{#CONTROLLABLE.SetOptionLandingForcePair}(): Landing approach in pairs for groups > 1 unit. +-- * @{#CONTROLLABLE.SetOptionLandingRestrictPair}(): Landing approach single. +-- * @{#CONTROLLABLE.SetOptionLandingOverheadBreak}(): Landing approach overhead break. -- -- @field #CONTROLLABLE CONTROLLABLE = { @@ -4203,6 +4212,50 @@ function CONTROLLABLE:OptionEngageRange( EngageRange ) return nil end +--- [AIR] Set how the AI lands on an airfield. Here: Straight in. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetOptionLandingStraightIn() + self:F2( { self.ControllableName } ) + if self:IsAir() then + self:SetOption("36","0") + end + return self +end + +--- [AIR] Set how the AI lands on an airfield. Here: In pairs (if > 1 aircraft in group) +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetOptionLandingForcePair() + self:F2( { self.ControllableName } ) + if self:IsAir() then + self:SetOption("36","1") + end + return self +end + +--- [AIR] Set how the AI lands on an airfield. Here: No landing in pairs. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetOptionLandingRestrictPair() + self:F2( { self.ControllableName } ) + if self:IsAir() then + self:SetOption("36","2") + end + return self +end + +--- [AIR] Set how the AI lands on an airfield. Here: Overhead break. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetOptionLandingOverheadBreak() + self:F2( { self.ControllableName } ) + if self:IsAir() then + self:SetOption("36","3") + end + return self +end + --- [AIR] Set how the AI uses the onboard radar. -- @param #CONTROLLABLE self -- @param #number Option Options are: `NEVER = 0, FOR_ATTACK_ONLY = 1,FOR_SEARCH_IF_REQUIRED = 2, FOR_CONTINUOUS_SEARCH = 3` From 8ff79e4131b6f4e4a5a094d35e535daf8f0f9dc6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 24 Jul 2025 16:19:42 +0200 Subject: [PATCH 225/349] xx --- Moose Development/Moose/Ops/AirWing.lua | 56 ++++++++++++++++++++- Moose Development/Moose/Ops/Auftrag.lua | 6 ++- Moose Development/Moose/Ops/FlightGroup.lua | 55 ++++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index f181b02ea..4447ed86b 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -974,6 +974,46 @@ function AIRWING:SetTakeoffAir() return self end +--- Set the aircraft of the AirWing to land straight in. +-- @param #AIRWING self +-- @return #FLIGHTGROUP self +function AIRWING:SetLandingStraightIn() + self.OptionLandingStraightIn = true + return self +end + +--- Set the aircraft of the AirWing to land in pairs for groups > 1 aircraft. +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:SetLandingForcePair() + self.OptionLandingForcePair = true + return self +end + +--- Set the aircraft of the AirWing to NOT land in pairs. +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:SetLandingRestrictPair() + self.OptionLandingRestrictPair = true + return self +end + +--- Set the aircraft of the AirWing to land after overhead break. +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:SetLandingOverheadBreak() + self.OptionLandingOverheadBreak = true + return self +end + +--- [Helicopter] Set the aircraft of the AirWing to prefer vertical takeoff and landing. +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:SetOptionPreferVerticalLanding() + self.OptionPreferVerticalLanding = true + return self +end + --- Set despawn after landing. Aircraft will be despawned after the landing event. -- Can help to avoid DCS AI taxiing issues. -- @param #AIRWING self @@ -1464,7 +1504,21 @@ function AIRWING:onafterFlightOnMission(From, Event, To, FlightGroup, Mission) self:T(self.lid..string.format("Group %s on %s mission %s", FlightGroup:GetName(), Mission:GetType(), Mission:GetName())) if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission) - end + end + -- Landing Options + if self.OptionLandingForcePair then + FlightGroup:SetOptionLandingForcePair() + elseif self.OptionLandingOverheadBreak then + FlightGroup:SetOptionLandingOverheadBreak() + elseif self.OptionLandingRestrictPair then + FlightGroup:SetOptionLandingRestrictPair() + elseif self.OptionLandingStraightIn then + FlightGroup:SetOptionLandingStraightIn() + end + -- Landing Options Helo + if self.OptionPreferVerticalLanding then + FlightGroup:SetOptionPreferVertical() + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index e2e94d412..f0c2a1eaf 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1789,8 +1789,9 @@ end -- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. -- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options. +-- @param #boolean Divebomb If true, use a dive bombing attack approach. -- @return #AUFTRAG self -function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType) +function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType, Divebomb) local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) @@ -1807,6 +1808,7 @@ function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType) mission.missionFraction=0.5 mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.NoReaction -- No reaction is better. + mission.optionDivebomb = Divebomb or nil -- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed. mission.dTevaluate=5*60 @@ -6164,7 +6166,7 @@ function AUFTRAG:GetDCSMissionTask() local coords = self.engageTarget:GetCoordinates() for _, coord in pairs(coords) do - local DCStask = CONTROLLABLE.TaskBombing(nil, coord:GetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) + local DCStask = CONTROLLABLE.TaskBombing(nil, coord:GetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, self.optionDivebomb) table.insert(DCStasks, DCStask) end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 58aba8276..93ab210ae 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -779,6 +779,61 @@ function FLIGHTGROUP:SetJettisonWeapons(Switch) return self end +--- Set the aircraft to land straight in. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionLandingStraightIn() + self.OptionLandingStraightIn = true + if self:GetGroup():IsAlive() then + self:GetGroup():SetOptionLandingStraightIn() + end + return self +end + +--- Set the aircraft to land in pairs. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionLandingForcePair() + self.OptionLandingForcePair = true + if self:GetGroup():IsAlive() then + self:GetGroup():SetOptionLandingForcePair() + end + return self +end + +--- Set the aircraft to NOT land in pairs. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionLandingRestrictPair() + self.OptionLandingRestrictPair = true + if self:GetGroup():IsAlive() then + self:GetGroup():SetOptionLandingRestrictPair() + end + return self +end + +--- Set the aircraft to land after overhead break. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionLandingOverheadBreak() + self.OptionLandingOverheadBreak = true + if self:GetGroup():IsAlive() then + self:GetGroup():SetOptionLandingOverheadBreak() + end + return self +end + +--- [HELICOPTER] Set the aircraft to prefer takeoff and landing vertically. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionPreferVertical() + self.OptionPreferVertical = true + if self:GetGroup():IsAlive() then + self:GetGroup():OptionPreferVerticalLanding() + end + return self +end + --- Set if group is ready for taxi/takeoff if controlled by a `FLIGHTCONTROL`. -- @param #FLIGHTGROUP self -- @param #boolean ReadyTO If `true`, flight is ready for takeoff. From 8db60412baed6e5f2bf29abb1274099d9a458874 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jul 2025 14:47:49 +0200 Subject: [PATCH 226/349] ^xx --- Moose Development/Moose/Utilities/Utils.lua | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index aeddb7fa9..2915f9e09 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4640,3 +4640,44 @@ end function UTILS.GetSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions) return Disposition.getSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions) end + + +--- Find the point on the radius of a circle closest to a point outside of the radius. +-- @param DCS#Vec2 Vec1 Simple Vec2 marking the middle of the circle. +-- @param #number Radius The radius of the circle. +-- @param DCS#Vec2 Vec2 Simple Vec2 marking the point outside of the circle. +-- @return DCS#Vec2 Vec2 point on the radius. +function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) + local r = Radius + local cx = Vec1.x or 1 + local cy = Vec1.y or 1 + local px = Vec2.x or 1 + local py = Vec2.y or 1 + + -- Berechne den Vektor vom Mittelpunkt zum externen Punkt + local dx = px - cx + local dy = py - cy + + -- Berechne die Länge des Vektors + local dist = math.sqrt(dx * dx + dy * dy) + + -- Wenn der Punkt im Mittelpunkt liegt, wähle einen Punkt auf der X-Achse + if dist == 0 then + return {x=cx + r, y=cy} + end + + -- Normalisiere den Vektor (richtungsweise Vektor mit Länge 1) + local norm_dx = dx / dist + local norm_dy = dy / dist + + -- Berechne den Punkt auf dem Rand des Kreises + local qx = cx + r * norm_dx + local qy = cy + r * norm_dy + + local shift_factor = 1 + qx = qx + shift_factor * norm_dx + qy = qy + shift_factor * norm_dy + + return {x=qx, y=qy} +end + From e543ee576f33091fedc755d905ad2b4bb6e31ec6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jul 2025 14:59:11 +0200 Subject: [PATCH 227/349] xx --- Moose Development/Moose/Utilities/Utils.lua | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 2915f9e09..739a22bee 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4641,6 +4641,44 @@ function UTILS.GetSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions) return Disposition.getSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions) end +--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param Core.Zone#ZONE Zone to search. +-- @param #number (Optional) PosRadius Required clear radius around each position. (Default is math.min(Radius/10, 200)) +-- @param #number (Optional) NumPositions Number of positions to find. (Default 50) +-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found. +function UTILS.GetClearZonePositions(Zone, PosRadius, NumPositions) + local radius = PosRadius or math.min(Zone:GetRadius()/10, 200) + local clearPositions = UTILS.GetSimpleZones(Zone:GetVec3(), Zone:GetRadius(), radius, NumPositions or 50) + if clearPositions and #clearPositions > 0 then + local validZones = {} + for _, vec2 in pairs(clearPositions) do + if Zone:IsVec2InZone(vec2) then + table.insert(validZones, vec2) + end + end + if #validZones > 0 then + return validZones, radius + end + end + return nil +end + + +--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param Core.Zone#ZONE Zone to search. +-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200)) +-- @param #number NumPositions (Optional) Number of positions to find. (Default 50) +-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found. +-- @return #number Assigned radius for the found zones. nil if no clear positions are found. +function UTILS.GetRandomClearZoneCoordinate(Zone, PosRadius, NumPositions) + local clearPositions = UTILS.GetClearZonePositions(Zone, PosRadius, NumPositions) + if clearPositions and #clearPositions > 0 then + local randomPosition, radius = clearPositions[math.random(1, #clearPositions)] + return COORDINATE:NewFromVec2(randomPosition), radius + end + + return nil +end --- Find the point on the radius of a circle closest to a point outside of the radius. -- @param DCS#Vec2 Vec1 Simple Vec2 marking the middle of the circle. From 56c8e223924940496a3a9e946cf83678b20af0a5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jul 2025 19:04:34 +0200 Subject: [PATCH 228/349] xx --- .../Moose/Core/ScheduleDispatcher.lua | 2 +- Moose Development/Moose/Core/Zone.lua | 13 + .../Moose/Functional/Autolase.lua | 6 +- .../Moose/Functional/Tiresias.lua | 1027 +++++++++-------- Moose Development/Moose/Utilities/Utils.lua | 1 + 5 files changed, 539 insertions(+), 510 deletions(-) diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index 8459d3c66..7f0102943 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -175,7 +175,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr local Name = Info.name or "?" local ErrorHandler = function( errmsg ) - env.info( "Error in timer function: " .. errmsg ) + env.info( "Error in timer function: " .. errmsg or "" ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 7095d9f62..8cdafb401 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -534,6 +534,19 @@ function ZONE_BASE:GetZoneProbability() return self.ZoneProbability end +--- Get the coordinate on the radius of the zone nearest to Outsidecoordinate. Useto e.g. find an ingress point. +-- @param #ZONE_BASE self +-- @param Core.Point#COORDINATE Outsidecoordinate The coordinate outside of the zone from where to look. +-- @return Core.Point#COORDINATE CoordinateOnRadius +function ZONE_BASE:FindNearestCoordinateOnRadius(Outsidecoordinate) + local Vec1 = self:GetVec2() + local Radius = self:GetRadius() + local Vec2 = Outsidecoordinate:GetVec2() + local Point = UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) + local rc = COORDINATE:NewFromVec2(Point) + return rc +end + --- Get the zone taking into account the randomization probability of a zone to be selected. -- @param #ZONE_BASE self -- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 1e43a737c..1772e24a3 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -105,7 +105,7 @@ AUTOLASE = { debug = false, smokemenu = true, RoundingPrecision = 0, - increasegroundawareness = true, + increasegroundawareness = false, MonitorFrequency = 30, } @@ -216,7 +216,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.smokemenu = true self.threatmenu = true self.RoundingPrecision = 0 - self.increasegroundawareness = true + self.increasegroundawareness = false self.MonitorFrequency = 30 self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) @@ -1020,7 +1020,7 @@ function AUTOLASE:_Prescient() self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) for _,_static in pairs(Statics) do -- DCS static object here local static = STATIC:Find(_static) - if static and static:GetCoalition() ~= self.coalition then + if static and static:GetCoalition() ~= self.coalition and static:GetCoordinate() then local IsLOS = position:IsLOS(static:GetCoordinate()) if IsLOS then unit:KnowUnit(static,true,true) diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index 1e1e46ae5..51cc395f4 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -1,579 +1,589 @@ ---- **Functional** - TIRESIAS - manages AI behaviour. --- --- === --- --- The @{#TIRESIAS} class is working in the back to keep your large-scale ground units in check. --- --- ## Features: --- --- * Designed to keep CPU and Network usage lower on missions with a lot of ground units. --- * Does not affect ships to keep the Navy guys happy. --- * Does not affect OpsGroup type groups. --- * Distinguishes between SAM groups, AAA groups and other ground groups. --- * Exceptions can be defined to keep certain actions going. --- * Works coalition-independent in the back --- * Easy setup. --- --- === --- --- ## Missions: --- --- ### [TIRESIAS](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master) --- --- === --- --- ### Author : **applevangelist ** --- --- @module Functional.Tiresias --- @image Functional.Tiresias.jpg --- --- Last Update: Jan 2024 +----- **Functional** - TIRESIAS - manages AI behaviour (OPTIMIZED VERSION). -------------------------------------------------------------------------- ---- **TIRESIAS** class, extends Core.Base#BASE --- @type TIRESIAS --- @field #string ClassName --- @field #boolean debug --- @field #string version --- @field #number Interval --- @field Core.Set#SET_GROUP GroundSet --- @field #number Coalition --- @field Core.Set#SET_GROUP VehicleSet --- @field Core.Set#SET_GROUP AAASet --- @field Core.Set#SET_GROUP SAMSet --- @field Core.Set#SET_GROUP ExceptionSet --- @field Core.Set#SET_OPSGROUP OpsGroupSet --- @field #number AAARange --- @field #number HeloSwitchRange --- @field #number PlaneSwitchRange --- @field Core.Set#SET_GROUP FlightSet --- @field #boolean SwitchAAA --- @field #boolean SwitchSAM --- @extends Core.Fsm#FSM +---- === + +--- The @{#TIRESIAS} class is working in the back to keep your large-scale ground units in check. +-- +-- -- Features: +-- +-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units. +-- * Does not affect ships to keep the Navy guys happy. +-- * Does not affect OpsGroup type groups. +-- * Distinguishes between SAM groups, AAA groups and other ground groups. +-- * Exceptions can be defined to keep certain actions going. +-- * Works coalition-independent in the back +-- * Easy setup. +-- +-- === +-- +-- ## Optimizations Applied: +-- +-- * Cached frequently used functions and constants +-- * Reduced string concatenations and formatting +-- * Optimized loop structures and conditions +-- * Pre-allocated tables where possible +-- * Reduced function call overhead +-- * Improved memory management +-- +---- === +-- +---- #-- Author : **applevangelist ** (Optimized by AI) --- --- @type TIRESIAS.Data --- @field #string type --- @field #number range --- @field #boolean invisible --- @field #boolean AIOff --- @field #boolean exception +-- - @module Functional.Tiresias +-- - @image Functional.Tiresias.jpg +--- Last Update: Dec 2023 (Optimized July 2025) ---- *Tiresias, Greek demi-god and shapeshifter, blinded by the Gods, works as oracle for you.* (Wiki) +--- **TIRESIAS** class, extends Core.Base#BASE +-- @type TIRESIAS +-- @field #string ClassName +-- @field #boolean debug +-- @field #string version +-- @field #number Interval +-- @field Core.Set#SET_GROUP GroundSet +-- @field #number Coalition +-- @field Core.Set#SET_GROUP VehicleSet +-- @field Core.Set#SET_GROUP AAASet +-- @field Core.Set#SET_GROUP SAMSet +-- @field Core.Set#SET_GROUP ExceptionSet +-- @field Core.Set#SET_OPSGROUP OpsGroupSet +-- @field #number AAARange +-- @field #number HeloSwitchRange +-- @field #number PlaneSwitchRange +-- @field Core.Set#SET_GROUP FlightSet +-- @field #boolean SwitchAAA +-- @field #string lid +-- @field #table _cached_zones +-- @extends Core.Fsm#FSM + +--- +-- @type TIRESIAS.Data +-- @field #string type +-- @field #number range +-- @field #boolean invisible +-- @field #boolean AIOff +-- @field #boolean exception + +--- +-- *Tiresias, Greek demi-god and shapeshifter, blinded by the Gods, works as oracle for you.* (Wiki) -- --- === +-- === -- --- ## TIRESIAS Concept --- --- * Designed to keep CPU and Network usage lower on missions with a lot of ground units. --- * Does not affect ships to keep the Navy guys happy. --- * Does not affect OpsGroup type groups. --- * Distinguishes between SAM groups, AAA groups and other ground groups. --- * Exceptions can be defined in SET_GROUP objects to keep certain actions going. --- * Works coalition-independent in the back --- * Easy setup. --- --- ## Setup --- --- Setup is a one-liner: --- --- local blinder = TIRESIAS:New() --- --- Optionally you can set up exceptions, e.g. for convoys driving around --- --- local exceptionset = SET_GROUP:New():FilterCoalitions("red"):FilterPrefixes("Convoy"):FilterStart() --- local blinder = TIRESIAS:New() --- blinder:AddExceptionSet(exceptionset) --- --- Options --- --- -- Setup different radius for activation around helo and airplane groups (applies to AI and humans) --- blinder:SetActivationRanges(10,25) -- defaults are 10, and 25 +-- ## TIRESIAS Concept -- --- -- Setup engagement ranges for AAA (non-advanced SAM units like Flaks etc) and if you want them to be AIOff --- blinder:SetAAARanges(60,true) -- defaults are 60, and true +-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units. +-- * Does not affect ships to keep the Navy guys happy. +-- * Does not affect OpsGroup type groups. +-- * Distinguishes between SAM groups, AAA groups and other ground groups. +-- * Exceptions can be defined in SET_GROUP objects to keep certain actions going. +-- * Works coalition-independent in the back +-- * Easy setup. -- --- @field #TIRESIAS +-- ## Setup +-- -- Setup is a one-liner: +-- +-- local blinder = TIRESIAS:New() +-- +-- -- Optionally you can set up exceptions, e.g. for convoys driving around +-- +-- local exceptionset = SET_GROUP:New():FilterCoalitions(" red" ):FilterPrefixes(" Convoy" ):FilterStart() +-- local blinder = TIRESIAS:New() +-- blinder:AddExceptionSet(exceptionset) +-- +-- -- Options +-- +-- -- Setup different radius for activation around helo and airplane groups (applies to AI and humans) +-- blinder:SetActivationRanges(10,25) -- defaults are 10, and 25 +-- +-- -- Setup engagement ranges for AAA (non-advanced SAM units like Flaks etc) and if you want them to be AIOff +-- blinder:SetAAARanges(60,true) -- defaults are 60, and true +-- +--- +-- @field #TIRESIAS TIRESIAS = { - ClassName = "TIRESIAS", - debug = false, - version = "0.0.6", - Interval = 20, - GroundSet = nil, - VehicleSet = nil, - AAASet = nil, - SAMSet = nil, - ExceptionSet = nil, - AAARange = 60, -- 60% - HeloSwitchRange = 10, -- NM - PlaneSwitchRange = 25, -- NM - SAMSwitchRange = 75, --NM - SwitchAAA = true, - SwitchSAM = false, - MantisHandlingSAM = true -} + ClassName = "TIRESIAS", + debug = true, + version = " 0.0.6-OPT" , + Interval = 20, + GroundSet = nil, + VehicleSet = nil, + AAASet = nil, + SAMSet = nil, + ExceptionSet = nil, + AAARange = 60, -- 60% + HeloSwitchRange = 10, -- NM + PlaneSwitchRange = 25, -- NM + SwitchAAA = true, + _cached_zones = {}, -- Cache for zone objects + } ---- [USER] Create a new Tiresias object and start it up. --- @param #TIRESIAS self --- @return #TIRESIAS self +--- +-- [USER] Create a new Tiresias object and start it up. +-- @param #TIRESIAS self +-- @return #TIRESIAS self function TIRESIAS:New() - -- Inherit everything from FSM class. - local self = BASE:Inherit(self, FSM:New()) -- #TIRESIAS - - --- FSM Functions --- - - -- Start State. - self:SetStartState("Stopped") - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") -- TIRESIAS status update. - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - - --self.ExceptionSet = SET_GROUP:New():Clear(false) - - self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) - - self.lid = string.format("TIRESIAS %s | ",self.version) - - self:I(self.lid.."Managing ground groups!") - - --- Triggers the FSM event "Stop". Stops TIRESIAS and all its event handlers. - -- @function [parent=#TIRESIAS] Stop - -- @param #TIRESIAS self - - --- Triggers the FSM event "Stop" after a delay. Stops TIRESIAS and all its event handlers. - -- @function [parent=#TIRESIAS] __Stop - -- @param #TIRESIAS self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Start". Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance. - -- @function [parent=#TIRESIAS] Start - -- @param #TIRESIAS self - - --- Triggers the FSM event "Start" after a delay. Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance. - -- @function [parent=#TIRESIAS] __Start - -- @param #TIRESIAS self - -- @param #number delay Delay in seconds. - - self:__Start(1) + -- Inherit everything from FSM class. + local self = BASE:Inherit(self, FSM:New()) -- #TIRESIAS + + --- FSM Functions --- + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- TIRESIAS status update. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + self.ExceptionSet = nil --SET_GROUP:New():Clear(false) + self._cached_zones = {} -- Initialize zone cache + + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + + -- Cache the log identifier to avoid string concatenation in loops + self.lid = "TIRESIAS " .. self.version .. " | " + + self:I(self.lid .. "Managing ground groups!") + + --- Triggers the FSM event "Stop". Stops TIRESIAS and all its event handlers. + -- @function [parent=#TIRESIAS] Stop + -- @param #TIRESIAS self + + --- Triggers the FSM event "Stop" after a delay. Stops TIRESIAS and all its event handlers. + -- @function [parent=#TIRESIAS] __Stop + -- @param #TIRESIAS self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Start". Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance. + -- @function [parent=#TIRESIAS] Start + -- @param #TIRESIAS self + + --- Triggers the FSM event "Start" after a delay. Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance. + -- @function [parent=#TIRESIAS] __Start + -- @param #TIRESIAS self + -- @param #number delay Delay in seconds. + + self:__Start(1) + return self end -------------------------------------------------------------------------------------------------------------- --- --- Helper Functions --- -------------------------------------------------------------------------------------------------------------- +----- ----[USER] Set activation radius for Helos and Planes in Nautical Miles. --- @param #TIRESIAS self --- @param #number HeloMiles Radius around a Helicopter in which AI ground units will be activated. Defaults to 10NM. --- @param #number PlaneMiles Radius around an Airplane in which AI ground units will be activated. Defaults to 25NM. --- @param #number SAMMiles Radius around an Airplane in which SAM AI ground units will be activated. Defaults to 75NM. --- @return #TIRESIAS self -function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles,SAMMiles) +--- +-- Helper Functions +--- + +--- [USER] Set activation radius for Helos and Planes in Nautical Miles. +-- @param #TIRESIAS self +-- @param #number HeloMiles Radius around a Helicopter in which AI ground units will be activated. Defaults to 10NM. +-- @param #number PlaneMiles Radius around an Airplane in which AI ground units will be activated. Defaults to 25NM. +-- @return #TIRESIAS self +function TIRESIAS:SetActivationRanges(HeloMiles, PlaneMiles) self.HeloSwitchRange = HeloMiles or 10 self.PlaneSwitchRange = PlaneMiles or 25 - self.SAMSwitchRange = SAMMiles or 75 + -- Clear zone cache when ranges change + self._cached_zones = {} return self end ---[USER] Set AAA Ranges - AAA equals non-SAM systems which qualify as AAA in DCS world. --- @param #TIRESIAS self --- @param #number FiringRange The engagement range that AAA units will be set to. Can be 0 to 100 (percent). Defaults to 60. --- @param #boolean SwitchAAA Decide if these system will have their AI switched off, too. Defaults to true. --- @return #TIRESIAS self -function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA) +-- @param #TIRESIAS self +-- @param #number FiringRange The engagement range that AAA units will be set to. Can be 0 to 100 (percent). Defaults to 60. +-- @param #boolean SwitchAAA Decide if these system will have their AI switched off, too. Defaults to true. +-- @return #TIRESIAS self +function TIRESIAS:SetAAARanges(FiringRange, SwitchAAA) self.AAARange = FiringRange or 60 self.SwitchAAA = (SwitchAAA == false) and false or true return self end ---- [USER] Set if TIRESIAS is also taking care or SAM installtions. --- @param #TIRESIAS self --- @param #boolean OnOff Set to true to switch on, false to switch off. Default is false. --- @param #boolean MantisPresent Set to true to switch on, false to switch off. Default is true. --- @return #TIRESIAS self -function TIRESIAS:SetSwitchSAM(OnOff,MantisPresent) - if OnOff == nil or OnOff == false then - self.SwitchSAM = false - else - self.SwitchSAM = true - end - self.MantisHandlingSAM = MantisPresent or true - return self -end - --- [USER] Add a SET_GROUP of GROUP objects as exceptions. Can be done multiple times. Does **not** work work for GROUP objects spawned into the SET after start, i.e. the groups need to exist in the game already. --- @param #TIRESIAS self --- @param Core.Set#SET_GROUP Set to add to the exception list. --- @return #TIRESIAS self +-- @param #TIRESIAS self +-- @param Core.Set#SET_GROUP Set to add to the exception list. +-- @return #TIRESIAS self function TIRESIAS:AddExceptionSet(Set) - self:T(self.lid.."AddExceptionSet") - if not self.ExceptionSet then self.ExceptionSet = SET_GROUP:New():Clear(false) end + self:T(self.lid .. " AddExceptionSet" ) + + if not self.ExceptionSet then + self.ExceptionSet = SET_GROUP:New() + end + local exceptions = self.ExceptionSet + + -- Cache the exception data structure for reuse + local exception_data = { + type = " Exception" , + exception = true, + } + Set:ForEachGroupAlive( function(grp) - if not grp.Tiresias then - grp.Tiresias = { -- #TIRESIAS.Data - type = "Exception", - exception = true, - } - exceptions:AddGroup(grp,true) + local inAAASet = self.AAASet:IsIncludeObject(grp) + local inVehSet = self.VehicleSet:IsIncludeObject(grp) + local inSAMSet = self.SAMSet:IsIncludeObject(grp) + if grp:IsGround() and (not grp.Tiresias) and (not inAAASet) and (not inVehSet) and (not inSAMSet) then + grp.Tiresias = exception_data + exceptions:AddGroup(grp, true) + BASE:T(" TIRESIAS: Added exception group: " .. grp:GetName()) end - BASE:T("TIRESIAS: Added exception group: "..grp:GetName()) end - ) + ) return self end ---- [INTERNAL] Filter Function --- @param Wrapper.Group#GROUP Group --- @return #boolean isin +--- [INTERNAL] Filter Function - Optimized with cached calls +-- @param Wrapper.Group#GROUP Group +-- @return #boolean isin function TIRESIAS._FilterNotAAA(Group) - local grp = Group -- Wrapper.Group#GROUP - local isaaa = grp:IsAAA() - if isaaa == true and grp:IsGround() and not grp:IsShip() then - return false -- remove from SET - else - return true -- keep in SET + local grp = Group -- Wrapper.Group#GROUP + -- Cache method calls to reduce overhead + local is_air = grp:IsAir() + local is_ship = grp:IsShip() + local is_AAA = grp:IsAAA() + if is_air or grp:IsShip() then -- air or ship - no AAA + return true -- keep in SET end + return not is_AAA -- remove AAA, keep others end ---- [INTERNAL] Filter Function --- @param Wrapper.Group#GROUP Group --- @return #boolean isin +--- [INTERNAL] Filter Function - Optimized with cached calls +-- @param Wrapper.Group#GROUP Group +-- @return #boolean isin function TIRESIAS._FilterNotSAM(Group) - local grp = Group -- Wrapper.Group#GROUP - local issam = grp:IsSAM() - if issam == true and grp:IsGround() and not grp:IsShip() then - return false -- remove from SET - else - return true -- keep in SET + local grp = Group -- Wrapper.Group#GROUP + -- Cache method calls to reduce overhead + local is_air = grp:IsGround() + local is_ship = grp:IsShip() + local is_SAM = grp:IsSAM() + if is_air or grp:IsShip() then + return true -- keep in SET end + return not is_SAM -- remove SAM, keep others end ---- [INTERNAL] Filter Function --- @param Wrapper.Group#GROUP Group --- @return #boolean isin +--- [INTERNAL] Filter Function - Optimized with cached calls +-- @param Wrapper.Group#GROUP Group +-- @return #boolean isin function TIRESIAS._FilterAAA(Group) - local grp = Group -- Wrapper.Group#GROUP - local isaaa = grp:IsAAA() - if isaaa == true and grp:IsGround() and not grp:IsShip() then - return true -- keep in SET - else - return false -- remove from SET + local grp = Group -- Wrapper.Group#GROUP + -- Cache method calls to reduce overhead + local is_ground = grp:IsGround() + if (not is_ground) or grp:IsShip() then + return false -- not AAA end + return grp:IsAAA() -- only AAA end ---- [INTERNAL] Filter Function --- @param Wrapper.Group#GROUP Group --- @return #boolean isin +--- [INTERNAL] Filter Function - Optimized with cached calls +-- @param Wrapper.Group#GROUP Group +-- @return #boolean isin function TIRESIAS._FilterSAM(Group) - local grp = Group -- Wrapper.Group#GROUP - local issam = grp:IsSAM() - if issam == true and grp:IsGround() and not grp:IsShip() then - return true -- keep in SET - else - return false -- remove from SET + local grp = Group -- Wrapper.Group#GROUP + -- Cache method calls to reduce overhead + local is_ground = grp:IsGround() + if (not is_ground) or grp:IsShip() then + return false -- not SAM end + return grp:IsSAM() -- only SAM end ---- [INTERNAL] Init Groups --- @param #TIRESIAS self --- @return #TIRESIAS self +--- [INTERNAL] Init Groups - Optimized with reduced function calls +-- @param #TIRESIAS self +-- @return #TIRESIAS self function TIRESIAS:_InitGroups() - self:T(self.lid.."_InitGroups") - -- Set all groups invisible/motionless - local EngageRange = self.AAARange - local SwitchAAA = self.SwitchAAA - local SwitchSAM = self.SwitchSAM - --- AAA - self.AAASet:ForEachGroupAlive( - function(grp) - if not grp.Tiresias then - grp:OptionEngageRange(EngageRange) +self:T(self.lid .. " _InitGroups" ) + +-- Cache frequently used values +local EngageRange = self.AAARange +local SwitchAAA = self.SwitchAAA + +-- Pre-create data structures to avoid repeated table creation +local aaa_data_template = { + type = " AAA" , + invisible = true, + range = EngageRange, + exception = false, + AIOff = SwitchAAA, + } + +local vehicle_data_template = { + type = " Vehicle" , + invisible = true, + AIOff = true, + exception = false, + } + +local sam_data_template = { + type = " SAM" , + invisible = true, + exception = false, + } + +--- AAA - Optimized loop +self.AAASet:ForEachGroupAlive( + function(grp) + local tiresias_data = grp.Tiresias + if not tiresias_data then + grp:OptionEngageRange(EngageRange) + grp:SetCommandInvisible(true) + if SwitchAAA then + grp:SetAIOff() + grp:EnableEmission(false) + end + grp.Tiresias = aaa_data_template + elseif not tiresias_data.exception == true then + if not tiresias_data.invisible == true then grp:SetCommandInvisible(true) - if SwitchAAA then + tiresias_data.invisible = true + if SwitchAAA == true then grp:SetAIOff() grp:EnableEmission(false) - end - grp.Tiresias = { -- #TIRESIAS.Data - type = "AAA", - invisible = true, - range = EngageRange, - exception = false, - AIOff = SwitchAAA, - } - end - if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias.invisible == false then - grp:SetCommandInvisible(true) - grp.Tiresias.invisible = true - if SwitchAAA then - grp:SetAIOff() - grp:EnableEmission(false) - grp.Tiresias.AIOff = true - end + tiresias_data.AIOff = true end end - --BASE:I(string.format("Init/Switch off AAA %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) end + end ) - --- Vehicles - self.VehicleSet:ForEachGroupAlive( - function(grp) - if not grp.Tiresias then + +--- Vehicles - Optimized loop +self.VehicleSet:ForEachGroupAlive( + function(grp) + local tiresias_data = grp.Tiresias + if not tiresias_data then + grp:SetAIOff() + grp:SetCommandInvisible(true) + grp.Tiresias = vehicle_data_template + elseif not tiresias_data.exception == true then + if not tiresias_data.invisible then + grp:SetCommandInvisible(true) grp:SetAIOff() - grp:SetCommandInvisible(true) - grp.Tiresias = { -- #TIRESIAS.Data - type = "Vehicle", - invisible = true, - AIOff = true, - exception = false, - } + tiresias_data.invisible = true + tiresias_data.AIOff = true end - if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias and grp.Tiresias.invisible == false then - grp:SetCommandInvisible(true) - grp:SetAIOff() - grp.Tiresias.invisible = true - grp.Tiresias.AIOff = true - end - end - --BASE:I(string.format("Init/Switch off Vehicle %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) - end + end + end ) - --- SAM - self.SAMSet:ForEachGroupAlive( - function(grp) - if not grp.Tiresias then + +--- SAM - Optimized loop +self.SAMSet:ForEachGroupAlive( + function(grp) + local tiresias_data = grp.Tiresias + if not tiresias_data then + grp:SetCommandInvisible(true) + grp.Tiresias = sam_data_template + elseif not tiresias_data.exception == true then + if not tiresias_data.invisible then grp:SetCommandInvisible(true) - grp.Tiresias = { -- #TIRESIAS.Data - type = "SAM", - invisible = true, - exception = not SwitchSAM, - AIOff = SwitchSAM, - } + tiresias_data.invisible = true end - if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias and grp.Tiresias.invisible == false then - grp:SetCommandInvisible(true) - grp.Tiresias.invisible = true - grp:SetAIOnOff(SwitchSAM) - end - end - BASE:I(string.format("Init/Switch off SAM %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) end + end ) - return self + +return self end ---- [INTERNAL] Event handler function --- @param #TIRESIAS self --- @param Core.Event#EVENTDATA EventData --- @return #TIRESIAS self +--- [INTERNAL] Event handler function - Optimized +-- @param #TIRESIAS self +-- @param Core.Event#EVENTDATA EventData +-- @return #TIRESIAS self function TIRESIAS:_EventHandler(EventData) - self:T(string.format("%s Event = %d",self.lid, EventData.id)) - local event = EventData -- Core.Event#EVENTDATA + self:T(string.format(" %s Event = %d" , self.lid, EventData.id)) + + local event = EventData -- Core.Event#EVENTDATA if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then - --local _coalition = event.IniCoalition - --if _coalition ~= self.Coalition then - -- return --ignore! - --end - local unitname = event.IniUnitName or "none" - local _unit = event.IniUnit local _group = event.IniGroup if _group and _group:IsAlive() then - local radius = self.PlaneSwitchRange - if _group:IsHelicopter() then - radius = self.HeloSwitchRange - end - self:_SwitchOnGroups(_group,radius) + -- Cache the radius calculation + local radius = _group:IsHelicopter() and self.HeloSwitchRange or self.PlaneSwitchRange + self:_SwitchOnGroups(_group, radius) end end return self end ---- [INTERNAL] Switch Groups Behaviour --- @param #TIRESIAS self --- @param Wrapper.Group#GROUP group --- @param #number radius Radius in NM --- @return #TIRESIAS self -function TIRESIAS:_SwitchOnGroups(group,radius) - self:T(self.lid.."_SwitchOnGroups "..group:GetName().." Radius "..radius.." NM") - local zone = ZONE_GROUP:New("Zone-"..group:GetName(),group,UTILS.NMToMeters(radius)) - local samzone = ZONE_GROUP:New("ZoneSAM-"..group:GetName(),group,UTILS.NMToMeters(self.SAMSwitchRange)) +--- [INTERNAL] Switch Groups Behaviour - Optimized with zone caching +-- @param #TIRESIAS self +-- @param Wrapper.Group#GROUP group +-- @param #number radius Radius in NM +-- @return #TIRESIAS self +function TIRESIAS:_SwitchOnGroups(group, radius) + self:T(self.lid .. " _SwitchOnGroups " .. group:GetName() .. " Radius " .. radius .. " NM" ) + + -- Use cached zones to reduce object creation + local group_name = group:GetName() + local cache_key = group_name .. " _" .. radius + local zone = self._cached_zones[cache_key] + + if not zone then + zone = ZONE_GROUP:New(" Zone-" .. group_name, group, UTILS.NMToMeters(radius)) + self._cached_zones[cache_key] = zone + else + -- Update zone center to current group position + zone:UpdateFromGroup(group) + end + local ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() - local sam = SET_GROUP:New():FilterFunction(self._FilterSAM):FilterZones({samzone}):FilterOnce() local count = ground:CountAlive() + if self.debug then - local text = string.format("There are %d groups around this plane or helo!",count) - self:I(text) + self:I(string.format(" There are %d groups around this plane or helo!" , count)) end + + if count > 0 then + -- Cache values outside the loop local SwitchAAA = self.SwitchAAA - local SwitchSAM = self.SwitchSAM - local MantisHandling = self.MantisHandlingSAM - if ground:CountAlive() > 0 then - ground:ForEachGroupAlive( - function(grp) - local name = grp:GetName() - if grp:GetCoalition() ~= group:GetCoalition() - and grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then - if grp.Tiresias.invisible == true then - grp:SetCommandInvisible(false) - grp.Tiresias.invisible = false - end - if grp.Tiresias.type == "Vehicle" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then - grp:SetAIOn() - grp.Tiresias.AIOff = false - end - if (SwitchAAA) and (grp.Tiresias.type == "AAA") and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then - grp:SetAIOn() - grp:EnableEmission(true) - grp.Tiresias.AIOff = false - end - BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception))) - else - BASE:T("TIRESIAS - This group "..tostring(name).. " has not been initialized or is an exception!") + local group_coalition = group:GetCoalition() + + ground:ForEachGroupAlive( + function(grp) + local tiresias_data = grp.Tiresias + if grp:GetCoalition() ~= group_coalition + and tiresias_data + and tiresias_data.type + and not tiresias_data.exception == true then + + -- Make group visible if invisible + if tiresias_data.invisible == true then + grp:SetCommandInvisible(false) + tiresias_data.invisible = false end - end - ) - end - if sam:CountAlive() > 0 and self.SwitchSAM == true then - sam:ForEachGroupAlive( - function(grp) - local name = grp:GetName() - BASE:I(string.format("%s - %s",name,tostring(SwitchSAM))) - UTILS.PrintTableToLog(grp.Tiresias) - if SwitchSAM and grp.Tiresias.type == "SAM" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then - BASE:I("First check passed") - if grp.Tiresias.exception ~= true then - BASE:I("Second check passed") - grp:SetAIOn() - if not MantisHandling then - grp:EnableEmission(true) - end - grp.Tiresias.AIOff = false - BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception))) - end + + -- Handle AI activation based on type + local grp_type = tiresias_data.type + if grp_type == "Vehicle" and tiresias_data.AIOff == true then + grp:SetAIOn() + tiresias_data.AIOff = false + elseif SwitchAAA == true and grp_type == "AAA" and tiresias_data.AIOff == true then + grp:SetAIOn() + grp:EnableEmission(true) + tiresias_data.AIOff = false end + else + BASE:T("TIRESIAS - This group " .. tostring(grp:GetName()) .. " has not been initialized or is an exception!") end - ) + end + ) + end return self end -------------------------------------------------------------------------------------------------------------- --- --- FSM Functions --- -------------------------------------------------------------------------------------------------------------- +----- ---- [INTERNAL] FSM Function --- @param #TIRESIAS self --- @param #string From --- @param #string Event --- @param #string To --- @return #TIRESIAS self +--- +-- FSM Functions +---- + +--- [INTERNAL] FSM Function - Optimized initialization +-- @param #TIRESIAS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #TIRESIAS self function TIRESIAS:onafterStart(From, Event, To) - self:T({From, Event, To}) - - local VehicleSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart() - local AAASet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart() - local SAMSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart() - local OpsGroupSet = SET_OPSGROUP:New():FilterActive(true):FilterStart() - self.FlightSet = SET_GROUP:New():FilterCategories({"plane","helicopter"}):FilterStart() - - local EngageRange = self.AAARange - - local ExceptionSet = self.ExceptionSet - if self.ExceptionSet then - function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object) - BASE:I("TIRESIAS: EXCEPTION Object Added: "..Object:GetName()) - if Object and Object:IsAlive() then - Object.Tiresias = { -- #TIRESIAS.Data - type = "Exception", - exception = true, - } +self:T({From, Event, To}) + +-- Create sets with optimized filters +local VehicleSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart() +local AAASet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart() +local SAMSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart() +local OpsGroupSet = SET_OPSGROUP:New():FilterActive(true):FilterStart() +self.FlightSet = SET_GROUP:New():FilterCategories({" plane" ," helicopter" }):FilterStart() + +-- Cache frequently used values +local EngageRange = self.AAARange +local SwitchAAA = self.SwitchAAA +local ExceptionSet = self.ExceptionSet + +-- Pre-create data templates to reduce object creation +local exception_data = { + type = " Exception" , + exception = true, + } + +local vehicle_data = { + type = " Vehicle" , + invisible = true, + AIOff = true, + exception = false, + } + +local aaa_data = { + type = " AAA" , + invisible = true, + range = EngageRange, + exception = false, + AIOff = SwitchAAA, + } + +local sam_data = { + type = " SAM" , + invisible = true, + exception = false, + } + +if ExceptionSet then + function ExceptionSet:OnAfterAdded(From, Event, To, ObjectName, Object) + BASE:I(" TIRESIAS: EXCEPTION Object Added: " .. Object:GetName()) + if Object and Object:IsAlive() then + Object.Tiresias = exception_data Object:SetAIOn() Object:SetCommandInvisible(false) Object:EnableEmission(true) - end - end - - local OGS = OpsGroupSet:GetAliveSet() - for _,_OG in pairs(OGS or {}) do - local OG = _OG -- Ops.OpsGroup#OPSGROUP - local grp = OG:GetGroup() - ExceptionSet:AddGroup(grp,true) - end - - function OpsGroupSet:OnAfterAdded(From,Event,To,ObjectName,Object) - local grp = Object:GetGroup() - ExceptionSet:AddGroup(grp,true) end end + + -- Process existing OpsGroups more efficiently + local OGS = OpsGroupSet:GetAliveSet() + for _, _OG in pairs(OGS or {}) do + local OG = _OG -- Ops.OpsGroup#OPSGROUP + local grp = OG:GetGroup() + ExceptionSet:AddGroup(grp, true) + end - function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object) - BASE:I("TIRESIAS: VEHCILE Object Added: "..Object:GetName()) - if Object and Object:IsAlive() then + function OpsGroupSet:OnAfterAdded(From, Event, To, ObjectName, Object) + local grp = Object:GetGroup() + ExceptionSet:AddGroup(grp, true) + end +end + +-- Optimized event handlers with pre-created data objects +function VehicleSet:OnAfterAdded(From, Event, To, ObjectName, Object) + BASE:T(" TIRESIAS: VEHICLE Object Added: " .. Object:GetName()) + if Object and Object:IsAlive() then + Object:SetAIOff() + Object:SetCommandInvisible(true) + Object.Tiresias = vehicle_data + end +end + +function AAASet:OnAfterAdded(From, Event, To, ObjectName, Object) + if Object and Object:IsAlive() then + BASE:I(" TIRESIAS: AAA Object Added: " .. Object:GetName()) + Object:OptionEngageRange(EngageRange) + Object:SetCommandInvisible(true) + if SwitchAAA then Object:SetAIOff() - Object:SetCommandInvisible(true) - Object.Tiresias = { -- #TIRESIAS.Data - type = "Vehicle", - invisible = true, - AIOff = true, - exception = false, - } + Object:EnableEmission(false) end + Object.Tiresias = aaa_data end +end - local SwitchAAA = self.SwitchAAA - - function AAASet:OnAfterAdded(From,Event,To,ObjectName,Object) - if Object and Object:IsAlive() then - BASE:I("TIRESIAS: AAA Object Added: "..Object:GetName()) - Object:OptionEngageRange(EngageRange) - Object:SetCommandInvisible(true) - if SwitchAAA then - Object:SetAIOff() - Object:EnableEmission(false) - end - Object.Tiresias = { -- #TIRESIAS.Data - type = "AAA", - invisible = true, - range = EngageRange, - exception = false, - AIOff = SwitchAAA, - } - end - end - - local SwitchSAM = self.SwitchSAM - local MantisManages = self.MantisHandlingSAM - - function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object) - if Object and Object:IsAlive() then - BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName()) - Object:SetCommandInvisible(true) - if SwitchSAM then - Object:SetAIOff() - Object:EnableEmission(not MantisManages) - end - Object.Tiresias = { -- #TIRESIAS.Data - type = "SAM", - invisible = true, - exception = not SwitchSAM, - AIOff = SwitchSAM, - } - end +function SAMSet:OnAfterAdded(From, Event, To, ObjectName, Object) + if Object and Object:IsAlive() then + BASE:T(" TIRESIAS: SAM Object Added: " .. Object:GetName()) + Object:SetCommandInvisible(true) + Object.Tiresias = sam_data end +end + -- Store references self.VehicleSet = VehicleSet self.AAASet = AAASet self.SAMSet = SAMSet @@ -581,71 +591,76 @@ function TIRESIAS:onafterStart(From, Event, To) self:_InitGroups() - self:__Status(1) + self:__Status(1) return self end --- [INTERNAL] FSM Function --- @param #TIRESIAS self --- @param #string From --- @param #string Event --- @param #string To --- @return #TIRESIAS self +-- @param #TIRESIAS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #TIRESIAS self function TIRESIAS:onbeforeStatus(From, Event, To) self:T({From, Event, To}) - if self:GetState() == "Stopped" then - return false - end - return self + return self:GetState() ~= " Stopped" end ---- [INTERNAL] FSM Function --- @param #TIRESIAS self --- @param #string From --- @param #string Event --- @param #string To --- @return #TIRESIAS self +--- [INTERNAL] FSM Function - Optimized status processing +-- @param #TIRESIAS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #TIRESIAS self function TIRESIAS:onafterStatus(From, Event, To) self:T({From, Event, To}) + if self.debug then local count = self.VehicleSet:CountAlive() local AAAcount = self.AAASet:CountAlive() local SAMcount = self.SAMSet:CountAlive() - local text = string.format("Overall: %d | Vehicles: %d | AAA: %d | SAM: %d",count+AAAcount+SAMcount,count,AAAcount,SAMcount) - self:I(text) + self:I(string.format(" Overall: %d | Vehicles: %d | AAA: %d | SAM: %d" , + count + AAAcount + SAMcount, count, AAAcount, SAMcount)) end + self:_InitGroups() - if self.FlightSet:CountAlive() > 0 then + + -- Process flight groups more efficiently + local flight_count = self.FlightSet:CountAlive() + if flight_count > 0 then local Set = self.FlightSet:GetAliveSet() - for _,_plane in pairs(Set) do + -- Cache range values outside loop + local helo_range = self.HeloSwitchRange + local plane_range = self.PlaneSwitchRange + + for _, _plane in pairs(Set or {}) do local plane = _plane -- Wrapper.Group#GROUP - local radius = self.PlaneSwitchRange - if plane:IsHelicopter() then - radius = self.HeloSwitchRange - end - self:_SwitchOnGroups(_plane,radius) + local radius = plane:IsHelicopter() and helo_range or plane_range + self:_SwitchOnGroups(plane, radius) end end - if self:GetState() ~= "Stopped" then + + if self:GetState() ~= " Stopped" then self:__Status(self.Interval) end + return self end --- [INTERNAL] FSM Function --- @param #TIRESIAS self --- @param #string From --- @param #string Event --- @param #string To --- @return #TIRESIAS self +-- @param #TIRESIAS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #TIRESIAS self function TIRESIAS:onafterStop(From, Event, To) self:T({From, Event, To}) self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + -- Clear zone cache on stop to free memory + self._cached_zones = {} return self end -------------------------------------------------------------------------------------------------------------- --- --- End --- -------------------------------------------------------------------------------------------------------------- +----- +---- End +----- \ No newline at end of file diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ac71ae3f6..739a22bee 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4718,3 +4718,4 @@ function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) return {x=qx, y=qy} end + From 3768d57639a55f92806fad71736801fb482aa4ed Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 27 Jul 2025 19:23:47 +0200 Subject: [PATCH 229/349] #TIRESIA - Avoid creating SETs all the time for player groups, cached now --- Moose Development/Moose/Functional/Tiresias.lua | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index 51cc395f4..8010abfcd 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -33,7 +33,7 @@ -- - @module Functional.Tiresias -- - @image Functional.Tiresias.jpg ---- Last Update: Dec 2023 (Optimized July 2025) +--- Last Update: July 2025 --- **TIRESIAS** class, extends Core.Base#BASE -- @type TIRESIAS @@ -55,6 +55,7 @@ -- @field #boolean SwitchAAA -- @field #string lid -- @field #table _cached_zones +-- @field #table _cached_groupsets -- @extends Core.Fsm#FSM --- @@ -104,7 +105,7 @@ TIRESIAS = { ClassName = "TIRESIAS", debug = true, - version = " 0.0.6-OPT" , + version = " 0.0.7-OPT" , Interval = 20, GroundSet = nil, VehicleSet = nil, @@ -116,6 +117,7 @@ TIRESIAS = { PlaneSwitchRange = 25, -- NM SwitchAAA = true, _cached_zones = {}, -- Cache for zone objects + _cached_groupsets = {}, -- Cache for group_set objects } --- @@ -418,6 +420,7 @@ function TIRESIAS:_SwitchOnGroups(group, radius) local group_name = group:GetName() local cache_key = group_name .. " _" .. radius local zone = self._cached_zones[cache_key] + local ground = self._cached_groupsets[cache_key] if not zone then zone = ZONE_GROUP:New(" Zone-" .. group_name, group, UTILS.NMToMeters(radius)) @@ -427,7 +430,13 @@ function TIRESIAS:_SwitchOnGroups(group, radius) zone:UpdateFromGroup(group) end - local ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() + if not ground then + ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() + self._cached_groupsets[cache_key] = ground + else + ground:FilterZones({zone},true):FilterOnce() + end + local count = ground:CountAlive() if self.debug then From fc37149e9d4e25adc27f832125766642465c9721 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Jul 2025 17:38:11 +0200 Subject: [PATCH 230/349] xx --- Moose Development/Moose/Core/Zone.lua | 3 ++- .../Moose/Functional/Tiresias.lua | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 6ed990101..11a7ce24c 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1280,7 +1280,8 @@ end function ZONE_RADIUS:GetScannedSetGroup() self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP - + + self.ScanSetGroup:Clear(false) self.ScanSetGroup.Set={} if self.ScanData then diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index 8010abfcd..034787ada 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -105,7 +105,7 @@ TIRESIAS = { ClassName = "TIRESIAS", debug = true, - version = " 0.0.7-OPT" , + version = " 0.0.7a-OPT" , Interval = 20, GroundSet = nil, VehicleSet = nil, @@ -419,8 +419,8 @@ function TIRESIAS:_SwitchOnGroups(group, radius) -- Use cached zones to reduce object creation local group_name = group:GetName() local cache_key = group_name .. " _" .. radius - local zone = self._cached_zones[cache_key] - local ground = self._cached_groupsets[cache_key] + local zone = self._cached_zones[cache_key] -- Core.Zone#ZONE_RADIUS + --local ground = self._cached_groupsets[cache_key] -- Core.Set#SET_GROUP if not zone then zone = ZONE_GROUP:New(" Zone-" .. group_name, group, UTILS.NMToMeters(radius)) @@ -430,12 +430,14 @@ function TIRESIAS:_SwitchOnGroups(group, radius) zone:UpdateFromGroup(group) end - if not ground then - ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() - self._cached_groupsets[cache_key] = ground - else - ground:FilterZones({zone},true):FilterOnce() - end + --if not ground then + --ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() + --self._cached_groupsets[cache_key] = ground + --else + --ground:FilterZones({zone},true):FilterOnce() + zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) + local ground = zone:GetScannedSetGroup() + --end local count = ground:CountAlive() From 052e1c386717dc1de9e06f5b30da6c0b7c033a43 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 31 Jul 2025 12:33:58 +0200 Subject: [PATCH 231/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 66 +++++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index f8def0685..8eba99f25 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -71,6 +71,8 @@ -- @field #boolean DespawnAfterHolding -- @field #list ListOfAuftrag -- @field #string defaulttakeofftype Take off type +-- @field #number FuelLowThreshold +-- @field #number FuelCriticalThreshold -- @extends Core.Fsm#FSM --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. @@ -226,6 +228,8 @@ EASYGCICAP = { DespawnAfterHolding = true, ListOfAuftrag = {}, defaulttakeofftype = "hot", + FuelLowThreshold = 25, + FuelCriticalThreshold = 10, } --- Internal Squadron data type @@ -261,7 +265,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.23" +EASYGCICAP.version="0.1.25" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -315,6 +319,8 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.DespawnAfterHolding = true self.ListOfAuftrag = {} self.defaulttakeofftype = "hot" + self.FuelLowThreshold = 25 + self.FuelCriticalThreshold = 10 -- Set some string id for output to DCS.log file. self.lid=string.format("EASYGCICAP %s | ", self.alias) @@ -339,6 +345,50 @@ end -- Functions ------------------------------------------------------------------------- +--- Get a specific managed AirWing by name +-- @param #EASYGCICAP self +-- @param #string AirbaseName Airbase name of the home of this wing. +-- @return Ops.AirWing#AIRWING Airwing or nil if not found +function EASYGCICAP:GetAirwing(AirbaseName) + self:T(self.lid.."GetAirwing") + if self.wings[AirbaseName] then + return self.wings[AirbaseName][1] + end + return nil +end + +--- Get a table of all managed AirWings +-- @param #EASYGCICAP self +-- @return #table Table of Ops.AirWing#AIRWING Airwings +function EASYGCICAP:GetAirwingTable() + self:T(self.lid.."GetAirwingTable") + local Wingtable = {} + for _,_object in pairs(self.wings or {}) do + table.insert(Wingtable,_object[1]) + end + return Wingtable +end + +--- Set "fuel low" threshold for CAP and INTERCEPT flights. +-- @param #EASYGCICAP self +-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 25. +-- @return #EASYGCICAP self +function EASYGCICAP:SetFuelLow(Percent) + self:T(self.lid.."SetFuelLow") + self.FuelLowThreshold = Percent or 25 + return self +end + +--- Set "fuel critical" threshold for CAP and INTERCEPT flights. +-- @param #EASYGCICAP self +-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 10. +-- @return #EASYGCICAP self +function EASYGCICAP:SetFuelCritical(Percent) + self:T(self.lid.."SetFuelCritical") + self.FuelCriticalThreshold = Percent or 10 + return self +end + --- Set CAP formation. -- @param #EASYGCICAP self -- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group @@ -359,7 +409,7 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch) return self end ---- Count alive missions in our internal stack. +--- (internal) Count alive missions in our internal stack. -- @param #EASYGCICAP self -- @return #number count function EASYGCICAP:_CountAliveAuftrags() @@ -628,6 +678,8 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local engagerange = self.engagerange local GoZoneSet = self.GoZoneSet local NoGoZoneSet = self.NoGoZoneSet + local FuelLow = self.FuelLowThreshold or 25 + local FuelCritical = self.FuelCriticalThreshold or 10 function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission) local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP @@ -639,10 +691,15 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename)) flightgroup:GetGroup():CommandEPLRS(true,5) flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch() + flightgroup:GetGroup():SetOptionLandingOverheadBreak() if Mission.type ~= AUFTRAG.Type.TANKER and Mission.type ~= AUFTRAG.Type.AWACS and Mission.type ~= AUFTRAG.Type.RECON then flightgroup:SetDetection(true) flightgroup:SetEngageDetectedOn(engagerange,{"Air"},GoZoneSet,NoGoZoneSet) flightgroup:SetOutOfAAMRTB() + flightgroup:SetFuelLowRTB(true) + flightgroup:SetFuelLowThreshold(FuelLow) + flightgroup:SetFuelCriticalRTB(true) + flightgroup:SetFuelCriticalThreshold(FuelCritical) if CapFormation then flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation) end @@ -1404,7 +1461,7 @@ function EASYGCICAP:_StartIntel() end ------------------------------------------------------------------------- --- FSM Functions +-- TODO FSM Functions ------------------------------------------------------------------------- --- (Internal) FSM Function onafterStart @@ -1535,5 +1592,8 @@ end function EASYGCICAP:onafterStop(From,Event,To) self:T({From,Event,To}) self.Intel:Stop() + for _,_wing in pairs(self.wings or {}) do + _wing:Stop() + end return self end From 7ccdec671a00c09024567ba19dcade885f3c31c5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 1 Aug 2025 09:22:44 +0200 Subject: [PATCH 232/349] xxx --- Moose Development/Moose/Ops/FlightGroup.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 93ab210ae..1e99bff52 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -4667,10 +4667,12 @@ function FLIGHTGROUP:GetParking(airbase) local coords={} for clientname, client in pairs(clients) do local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) - local units=template.units - for i,unit in pairs(units) do - local coord=COORDINATE:New(unit.x, unit.alt, unit.y) - coords[unit.name]=coord + if template then + local units=template.units + for i,unit in pairs(units) do + local coord=COORDINATE:New(unit.x, unit.alt, unit.y) + coords[unit.name]=coord + end end end return coords From 8684fc13acf5d9c02ad349e53f3ff1bbae51c59c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 1 Aug 2025 14:01:28 +0200 Subject: [PATCH 233/349] xx --- Moose Development/Moose/Ops/FlightControl.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 2db7c3942..bc13affeb 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -2464,7 +2464,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Payer Menu +-- Player Menu ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create player menu. From bc464008c9aada21c14ad426910dd32aab484c79 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 7 Aug 2025 18:47:16 +0200 Subject: [PATCH 234/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 8eba99f25..92f6defe1 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -7,7 +7,7 @@ -- ------------------------------------------------------------------------- -- Date: September 2023 --- Last Update: July 2024 +-- Last Update: Aug 2025 ------------------------------------------------------------------------- -- --- **Ops** - Easy GCI & CAP Manager @@ -265,7 +265,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.25" +EASYGCICAP.version="0.1.26" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -633,7 +633,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local DespawnAfterHolding = self.DespawnAfterHolding -- Check STATIC name - local check = STATIC:FindByName(Airbasename,false) + local check = STATIC:FindByName(Airbasename,false) or UNIT:FindByName(Airbasename) if check == nil then MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog() return @@ -967,7 +967,7 @@ end -- @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 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. -- @return #EASYGCICAP self From 56022f5e1ec5f1ede29b4655a446b6dfa54c58cf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 10 Aug 2025 13:20:38 +0200 Subject: [PATCH 235/349] xx --- Moose Development/Moose/Ops/Auftrag.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f0c2a1eaf..b4e6128a1 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1727,7 +1727,7 @@ function AUFTRAG:NewSEADInZone(TargetZone, Altitude, TargetTypes, Duration) local mission=AUFTRAG:New(AUFTRAG.Type.SEAD) - mission:_TargetFromObject(TargetZone) + --mission:_TargetFromObject(TargetZone) -- DCS Task options: mission.engageWeaponType=ENUMS.WeaponFlag.Auto From 5411c4f38d19876da4f680c0c853fe4dde66a8f3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 10 Aug 2025 13:20:45 +0200 Subject: [PATCH 236/349] xx --- Moose Development/Moose/Sound/SRS.lua | 260 ++++++++++++++++++++------ 1 file changed, 199 insertions(+), 61 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 5b9358b2f..3432e0f0c 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -443,28 +443,32 @@ MSRS.Voices = { ["en_AU_Standard_B"] = 'en-AU-Standard-B', -- [2] MALE ["en_AU_Standard_C"] = 'en-AU-Standard-C', -- [3] FEMALE ["en_AU_Standard_D"] = 'en-AU-Standard-D', -- [4] MALE - ["en_IN_Standard_A"] = 'en-IN-Standard-A', -- [5] FEMALE - ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE - ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE - ["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE + -- IN + ["en_IN_Standard_A"] = 'en-IN-Standard-A', -- Female + ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- Male + ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- Male + ["en_IN_Standard_D"] = 'en-IN-Standard-D', -- Female + ["en_IN_Standard_E"] = 'en-IN-Standard-E', -- Female + ["en_IN_Standard_F"] = 'en-IN-Standard-F', -- Male -- 2025 changes - ["en_GB_Standard_A"] = 'en-GB-Standard-N', -- [9] FEMALE - ["en_GB_Standard_B"] = 'en-GB-Standard-O', -- [10] MALE - ["en_GB_Standard_C"] = 'en-GB-Standard-N', -- [11] FEMALE - ["en_GB_Standard_D"] = 'en-GB-Standard-O', -- [12] MALE - ["en_GB_Standard_F"] = 'en-GB-Standard-N', -- [13] FEMALE - ["en_GB_Standard_O"] = 'en-GB-Standard-O', -- [12] MALE - ["en_GB_Standard_N"] = 'en-GB-Standard-N', -- [13] FEMALE - ["en_US_Standard_A"] = 'en-US-Standard-A', -- [14] MALE - ["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE - ["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE - ["en_US_Standard_D"] = 'en-US-Standard-D', -- [17] MALE - ["en_US_Standard_E"] = 'en-US-Standard-E', -- [18] FEMALE - ["en_US_Standard_F"] = 'en-US-Standard-F', -- [19] FEMALE - ["en_US_Standard_G"] = 'en-US-Standard-G', -- [20] FEMALE - ["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE - ["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE - ["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE + ["en_GB_Standard_A"] = 'en-GB-Standard-A', -- Female + ["en_GB_Standard_B"] = 'en-GB-Standard-B', -- Male + ["en_GB_Standard_C"] = 'en-GB-Standard-C', -- Female + ["en_GB_Standard_D"] = 'en-GB-Standard-D', -- Male + ["en_GB_Standard_F"] = 'en-GB-Standard-F', -- Female + ["en_GB_Standard_N"] = 'en-GB-Standard-N', -- Female + ["en_GB_Standard_O"] = 'en-GB-Standard-O', -- Male + -- US + ["en_US_Standard_A"] = 'en-US-Standard-A', -- Male + ["en_US_Standard_B"] = 'en-US-Standard-B', -- Male + ["en_US_Standard_C"] = 'en-US-Standard-C', -- Female + ["en_US_Standard_D"] = 'en-US-Standard-D', -- Male + ["en_US_Standard_E"] = 'en-US-Standard-E', -- Female + ["en_US_Standard_F"] = 'en-US-Standard-F', -- Female + ["en_US_Standard_G"] = 'en-US-Standard-G', -- Female + ["en_US_Standard_H"] = 'en-US-Standard-H', -- Female + ["en_US_Standard_I"] = 'en-US-Standard-I', -- Male + ["en_US_Standard_J"] = 'en-US-Standard-J', -- Male -- 2025 catalog changes ["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female ["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male @@ -474,14 +478,15 @@ MSRS.Voices = { ["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male ["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female -- 2025 catalog changes - ["de_DE_Standard_A"] = "de-DE-Standard-G", -- Female - ["de_DE_Standard_B"] = "de-DE-Standard-H", -- Male - ["de_DE_Standard_C"] = "de-DE-Standard-G", -- Female - ["de_DE_Standard_D"] = "de-DE-Standard-H", -- Male - ["de_DE_Standard_E"] = "de-DE-Standard-H", -- Male - ["de_DE_Standard_F"] = "de-DE-Standard-G", -- Female - ["de_DE_Standard_H"] = "de-DE-Standard-H", -- Male - ["de_DE_Standard_G"] = "de-DE-Standard-G", -- Female + ["de_DE_Standard_A"] = 'de-DE-Standard-A', -- Female + ["de_DE_Standard_B"] = 'de-DE-Standard-B', -- Male + ["de_DE_Standard_C"] = 'de-DE-Standard-C', -- Female + ["de_DE_Standard_D"] = 'de-DE-Standard-D', -- Male + ["de_DE_Standard_E"] = 'de-DE-Standard-E', -- Male + ["de_DE_Standard_F"] = 'de-DE-Standard-F', -- Female + ["de_DE_Standard_G"] = 'de-DE-Standard-G', -- Female + ["de_DE_Standard_H"] = 'de-DE-Standard-H', -- Male + -- ES ["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female ["es_ES_Standard_B"] = "es-ES-Standard-F", -- Male ["es_ES_Standard_C"] = "es-ES-Standard-E", -- Female @@ -497,32 +502,36 @@ MSRS.Voices = { ["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male }, Wavenet = { - ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE - ["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- [2] MALE - ["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- [3] FEMALE - ["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- [4] MALE - ["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- [5] FEMALE - ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE - ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE - ["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE + ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- Female + ["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- Male + ["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- Female + ["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- Male + -- IN + ["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- Female + ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- Male + ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- Male + ["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- Female + ["en_IN_Wavenet_E"] = 'en-IN-Wavenet-E', -- Female + ["en_IN_Wavenet_F"] = 'en-IN-Wavenet-F', -- Male -- 2025 changes - ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-N', -- [9] FEMALE - ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-O', -- [10] MALE - ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-N', -- [11] FEMALE - ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-O', -- [12] MALE - ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-N', -- [13] FEMALE + ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE + ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE + ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE + ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE + ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE ["en_GB_Wavenet_O"] = 'en-GB-Wavenet-O', -- [12] MALE - ["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE - ["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- [14] MALE - ["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- [15] MALE - ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE - ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE - ["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- [18] FEMALE - ["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- [19] FEMALE - ["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- [20] FEMALE - ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE - ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE - ["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE + ["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE + -- US + ["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- Male + ["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- Male + ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- Female + ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- Male + ["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- Female + ["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- Female + ["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- Female + ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- Female + ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- Male + ["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- Male -- 2025 catalog changes ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male @@ -532,14 +541,15 @@ MSRS.Voices = { ["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male ["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female -- 2025 catalog changes - ["de_DE_Wavenet_A"] = "de-DE-Wavenet-G", -- Female - ["de_DE_Wavenet_B"] = "de-DE-Wavenet-H", -- Male - ["de_DE_Wavenet_C"] = "de-DE-Wavenet-G", -- Female - ["de_DE_Wavenet_D"] = "de-DE-Wavenet-H", -- Male - ["de_DE_Wavenet_E"] = "de-DE-Wavenet-H", -- Male - ["de_DE_Wavenet_F"] = "de-DE-Wavenet-G", -- Female - ["de_DE_Wavenet_H"] = "de-DE-Wavenet-H", -- Male - ["de_DE_Wavenet_G"] = "de-DE-Wavenet-G", -- Female + ["de_DE_Wavenet_A"] = 'de-DE-Wavenet-A', -- Female + ["de_DE_Wavenet_B"] = 'de-DE-Wavenet-B', -- Male + ["de_DE_Wavenet_C"] = 'de-DE-Wavenet-C', -- Female + ["de_DE_Wavenet_D"] = 'de-DE-Wavenet-D', -- Male + ["de_DE_Wavenet_E"] = 'de-DE-Wavenet-E', -- Male + ["de_DE_Wavenet_F"] = 'de-DE-Wavenet-F', -- Female + ["de_DE_Wavenet_G"] = 'de-DE-Wavenet-G', -- Female + ["de_DE_Wavenet_H"] = 'de-DE-Wavenet-H', -- Male + -- ES ["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male ["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female ["es_ES_Wavenet_D"] = "es-ES-Wavenet-E", -- Female @@ -553,6 +563,134 @@ MSRS.Voices = { ["it_IT_Wavenet_E"] = "it-IT-Wavenet-E", -- Female ["it_IT_Wavenet_F"] = "it-IT-Wavenet-F", -- Male } , + Chirp3HD = { + ["en_GB_Chirp3_HD_Aoede"] = 'en-GB-Chirp3-HD-Aoede', -- Female + ["en_GB_Chirp3_HD_Charon"] = 'en-GB-Chirp3-HD-Charon', -- Male + ["en_GB_Chirp3_HD_Fenrir"] = 'en-GB-Chirp3-HD-Fenrir', -- Male + ["en_GB_Chirp3_HD_Kore"] = 'en-GB-Chirp3-HD-Kore', -- Female + ["en_GB_Chirp3_HD_Leda"] = 'en-GB-Chirp3-HD-Leda', -- Female + ["en_GB_Chirp3_HD_Orus"] = 'en-GB-Chirp3-HD-Orus', -- Male + ["en_GB_Chirp3_HD_Puck"] = 'en-GB-Chirp3-HD-Puck', -- Male + ["en_GB_Chirp3_HD_Zephyr"] = 'en-GB-Chirp3-HD-Zephyr', -- Female + --["de_DE_Chirp3_HD_Aoede"] = 'de-DE-Chirp3-HD-Aoede', -- Female (Datenfehler im Original) + ["en_US_Chirp3_HD_Charon"] = 'en-US-Chirp3-HD-Charon', -- Male + ["en_US_Chirp3_HD_Fenrir"] = 'en-US-Chirp3-HD-Fenrir', -- Male + ["en_US_Chirp3_HD_Kore"] = 'en-US-Chirp3-HD-Kore', -- Female + ["en_US_Chirp3_HD_Leda"] = 'en-US-Chirp3-HD-Leda', -- Female + ["en_US_Chirp3_HD_Orus"] = 'en-US-Chirp3-HD-Orus', -- Male + ["en_US_Chirp3_HD_Puck"] = 'en-US-Chirp3-HD-Puck', -- Male + --["de_DE_Chirp3_HD_Zephyr"] = 'de-DE-Chirp3-HD-Zephyr', -- Female (Datenfehler im Original) + -- DE + ["de_DE_Chirp3_HD_Aoede"] = 'de-DE-Chirp3-HD-Aoede', -- Female + ["de_DE_Chirp3_HD_Charon"] = 'de-DE-Chirp3-HD-Charon', -- Male + ["de_DE_Chirp3_HD_Fenrir"] = 'de-DE-Chirp3-HD-Fenrir', -- Male + ["de_DE_Chirp3_HD_Kore"] = 'de-DE-Chirp3-HD-Kore', -- Female + ["de_DE_Chirp3_HD_Leda"] = 'de-DE-Chirp3-HD-Leda', -- Female + ["de_DE_Chirp3_HD_Orus"] = 'de-DE-Chirp3-HD-Orus', -- Male + ["de_DE_Chirp3_HD_Puck"] = 'de-DE-Chirp3-HD-Puck', -- Male + ["de_DE_Chirp3_HD_Zephyr"] = 'de-DE-Chirp3-HD-Zephyr', -- Female + -- AU + ["en_AU_Chirp3_HD_Aoede"] = 'en-AU-Chirp3-HD-Aoede', -- Female + ["en_AU_Chirp3_HD_Charon"] = 'en-AU-Chirp3-HD-Charon', -- Male + ["en_AU_Chirp3_HD_Fenrir"] = 'en-AU-Chirp3-HD-Fenrir', -- Male + ["en_AU_Chirp3_HD_Kore"] = 'en-AU-Chirp3-HD-Kore', -- Female + ["en_AU_Chirp3_HD_Leda"] = 'en-AU-Chirp3-HD-Leda', -- Female + ["en_AU_Chirp3_HD_Orus"] = 'en-AU-Chirp3-HD-Orus', -- Male + ["en_AU_Chirp3_HD_Puck"] = 'en-AU-Chirp3-HD-Puck', -- Male + ["en_AU_Chirp3_HD_Zephyr"] = 'en-AU-Chirp3-HD-Zephyr', -- Female + -- IN + ["en_IN_Chirp3_HD_Aoede"] = 'en-IN-Chirp3-HD-Aoede', -- Female + ["en_IN_Chirp3_HD_Charon"] = 'en-IN-Chirp3-HD-Charon', -- Male + ["en_IN_Chirp3_HD_Fenrir"] = 'en-IN-Chirp3-HD-Fenrir', -- Male + ["en_IN_Chirp3_HD_Kore"] = 'en-IN-Chirp3-HD-Kore', -- Female + ["en_IN_Chirp3_HD_Leda"] = 'en-IN-Chirp3-HD-Leda', -- Female + ["en_IN_Chirp3_HD_Orus"] = 'en-IN-Chirp3-HD-Orus', -- Male + }, + ChirpHD = { + ["en_US_Chirp_HD_D"] = 'en-US-Chirp-HD-D', -- Male + ["en_US_Chirp_HD_F"] = 'en-US-Chirp-HD-F', -- Female + ["en_US_Chirp_HD_O"] = 'en-US-Chirp-HD-O', -- Female + -- DE + ["de_DE_Chirp_HD_D"] = 'de-DE-Chirp-HD-D', -- Male + ["de_DE_Chirp_HD_F"] = 'de-DE-Chirp-HD-F', -- Female + ["de_DE_Chirp_HD_O"] = 'de-DE-Chirp-HD-O', -- Female + -- AU + ["en_AU_Chirp_HD_D"] = 'en-AU-Chirp-HD-D', -- Male + ["en_AU_Chirp_HD_F"] = 'en-AU-Chirp-HD-F', -- Female + ["en_AU_Chirp_HD_O"] = 'en-AU-Chirp-HD-O', -- Female + -- IN + ["en_IN_Chirp_HD_D"] = 'en-IN-Chirp-HD-D', -- Male + ["en_IN_Chirp_HD_F"] = 'en-IN-Chirp-HD-F', -- Female + ["en_IN_Chirp_HD_O"] = 'en-IN-Chirp-HD-O', -- Female + }, + }, + Neural2 = { + ["en_GB_Neural2_A"] = 'en-GB-Neural2-A', -- Female + ["en_GB_Neural2_B"] = 'en-GB-Neural2-B', -- Male + ["en_GB_Neural2_C"] = 'en-GB-Neural2-C', -- Female + ["en_GB_Neural2_D"] = 'en-GB-Neural2-D', -- Male + ["en_GB_Neural2_F"] = 'en-GB-Neural2-F', -- Female + ["en_GB_Neural2_N"] = 'en-GB-Neural2-N', -- Female + ["en_GB_Neural2_O"] = 'en-GB-Neural2-O', -- Male + -- US + ["en_US_Neural2_A"] = 'en-US-Neural2-A', -- Male + ["en_US_Neural2_C"] = 'en-US-Neural2-C', -- Female + ["en_US_Neural2_D"] = 'en-US-Neural2-D', -- Male + ["en_US_Neural2_E"] = 'en-US-Neural2-E', -- Female + ["en_US_Neural2_F"] = 'en-US-Neural2-F', -- Female + ["en_US_Neural2_G"] = 'en-US-Neural2-G', -- Female + ["en_US_Neural2_H"] = 'en-US-Neural2-H', -- Female + ["en_US_Neural2_I"] = 'en-US-Neural2-I', -- Male + ["en_US_Neural2_J"] = 'en-US-Neural2-J', -- Male + -- DE + ["de_DE_Neural2_G"] = 'de-DE-Neural2-G', -- Female + ["de_DE_Neural2_H"] = 'de-DE-Neural2-H', -- Male + -- AU + ["en_AU_Neural2_A"] = 'en-AU-Neural2-A', -- Female + ["en_AU_Neural2_B"] = 'en-AU-Neural2-B', -- Male + ["en_AU_Neural2_C"] = 'en-AU-Neural2-C', -- Female + ["en_AU_Neural2_D"] = 'en-AU-Neural2-D', -- Male + -- IN + ["en_IN_Neural2_A"] = 'en-IN-Neural2-A', -- Female + ["en_IN_Neural2_B"] = 'en-IN-Neural2-B', -- Male + ["en_IN_Neural2_C"] = 'en-IN-Neural2-C', -- Male + ["en_IN_Neural2_D"] = 'en-IN-Neural2-D', -- Female + }, + News = { + ["en_GB_News_G"] = 'en-GB-News-G', -- Female + ["en_GB_News_H"] = 'en-GB-News-H', -- Female + ["en_GB_News_I"] = 'en-GB-News-I', -- Female + ["en_GB_News_J"] = 'en-GB-News-J', -- Male + ["en_GB_News_K"] = 'en-GB-News-K', -- Male + ["en_GB_News_L"] = 'en-GB-News-L', -- Male + ["en_GB_News_M"] = 'en-GB-News-M', -- Male + -- US + ["en_US_News_K"] = 'en-US-News-K', -- Female + ["en_US_News_L"] = 'en-US-News-L', -- Female + ["en_US_News_N"] = 'en-US-News-N', -- Male + -- AU + ["en_AU_News_E"] = 'en-AU-News-E', -- Female + ["en_AU_News_F"] = 'en-AU-News-F', -- Female + ["en_AU_News_G"] = 'en-AU-News-G', -- Male + }, + Casual = { + ["en_US_Casual_K"] = 'en-US-Casual-K', -- Male + }, + Polyglot = { + ["en_US_Polyglot_1"] = 'en-US-Polyglot-1', -- Male + ["de_DE_Polyglot_1"] = 'de-DE-Polyglot-1', -- Male + ["en_AU_Polyglot_1"] = 'en-AU-Polyglot-1', -- Male + }, + Studio = { + -- Englisch (UK) - Studio + ["en_GB_Studio_B"] = 'en-GB-Studio-B', -- Male + ["en_GB_Studio_C"] = 'en-GB-Studio-C', -- Female + -- Englisch (USA) - Studio + ["en_US_Studio_O"] = 'en-US-Studio-O', -- Female + ["en_US_Studio_Q"] = 'en-US-Studio-Q', -- Male + -- DE + ["de_DE_Studio_B"] = 'de-DE-Studio-B', -- Male + ["de_DE_Studio_C"] = 'de-DE-Studio-C', -- Female }, } From 95e7daa27c18e1015210e690696d0c286ced3263 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 10 Aug 2025 13:22:45 +0200 Subject: [PATCH 237/349] yy --- Moose Development/Moose/Ops/AirWing.lua | 58 ++++++++++++++-------- Moose Development/Moose/Ops/EasyGCICAP.lua | 39 ++++++++++++--- 2 files changed, 70 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 4447ed86b..a4b1b58e5 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -159,6 +159,8 @@ AIRWING = { -- @field #number refuelsystem Refueling system type: `0=Unit.RefuelingSystem.BOOM_AND_RECEPTACLE`, `1=Unit.RefuelingSystem.PROBE_AND_DROGUE`. -- @field #number noccupied Number of flights on this patrol point. -- @field Wrapper.Marker#MARKER marker F10 marker. +-- @field #boolean IsZonePoint flag for using a (moving) zone as point for patrol etc. +-- @field Core.Zone#ZONE_BASE patrolzone in case Patrol coordinate was handed as zone, store here. --- Patrol zone. -- @type AIRWING.PatrolZone @@ -187,13 +189,14 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.9.6" +AIRWING.version="0.9.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Check that airbase has enough parking spots if a request is BIG. +-- DONE: Allow (moving) zones as base for patrol points. -- DONE: Spawn in air ==> Needs WAREHOUSE update. -- DONE: Spawn hot. -- DONE: Make special request to transfer squadrons to anther airwing (or warehouse). @@ -807,13 +810,22 @@ function AIRWING:_PatrolPointMarkerText(point) end --- Update marker of the patrol point. +-- @param #AIRWING self -- @param #AIRWING.PatrolData point Patrol point table. function AIRWING:UpdatePatrolPointMarker(point) - if self.markpoints then -- sometimes there's a direct call from #OPSGROUP + + if self and self.markpoints then -- sometimes there's a direct call from #OPSGROUP local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed) - - point.marker:UpdateText(text, 1) + + if point.IsZonePoint and point.IsZonePoint == true and point.patrolzone then + -- update position + local Coordinate = point.patrolzone:GetCoordinate() + point.marker:UpdateCoordinate(Coordinate) + point.marker:UpdateText(text, 1.5) + else + point.marker:UpdateText(text, 1) + end end end @@ -821,7 +833,7 @@ end --- Create a new generic patrol point. -- @param #AIRWING self -- @param #string Type Patrol point type, e.g. "CAP" or "AWACS". Default "Unknown". --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing. 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 Orbit altitude in feet. Default random between Angels 10 and 20. -- @param #number Heading Heading in degrees. Default random (0, 360] degrees. -- @param #number LegLength Length of race-track orbit in NM. Default 15 NM. @@ -830,14 +842,16 @@ end -- @return #AIRWING.PatrolData Patrol point table. function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) - -- Check if a zone was passed instead of a coordinate. - if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE") then - Coordinate=Coordinate:GetCoordinate() - end - local patrolpoint={} --#AIRWING.PatrolData patrolpoint.type=Type or "Unknown" patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10, 15)), math.random(360)) + if Coordinate:IsInstanceOf("ZONE_BASE") then + patrolpoint.IsZonePoint = true + patrolpoint.patrolzone = Coordinate + patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate() + else + patrolpoint.IsZonePoint = false + end patrolpoint.heading=Heading or math.random(360) patrolpoint.leg=LegLength or 15 patrolpoint.altitude=Altitude or math.random(10,20)*1000 @@ -847,7 +861,7 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL if self.markpoints then patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll() - AIRWING.UpdatePatrolPointMarker(patrolpoint) + self:UpdatePatrolPointMarker(patrolpoint) end return patrolpoint @@ -855,7 +869,7 @@ end --- Add a patrol Point for CAP missions. -- @param #AIRWING self --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. 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 Orbit altitude in feet. -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. @@ -872,7 +886,7 @@ end --- Add a patrol Point for RECON missions. -- @param #AIRWING self --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. 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 Orbit altitude in feet. -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. @@ -889,7 +903,7 @@ end --- Add a patrol Point for TANKER missions. -- @param #AIRWING self --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. 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 Orbit altitude in feet. -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. @@ -907,7 +921,7 @@ end --- Add a patrol Point for AWACS missions. -- @param #AIRWING self --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. 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 Orbit altitude in feet. -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. @@ -1176,6 +1190,10 @@ function AIRWING:_GetPatrolData(PatrolPoints, RefuelSystem) for _,_patrolpoint in pairs(PatrolPoints) do local patrolpoint=_patrolpoint --#AIRWING.PatrolData + if patrolpoint.IsZonePoint and patrolpoint.IsZonePoint == true and patrolpoint.patrolzone then + -- update + patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate() + end if (RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem) or RefuelSystem==nil or patrolpoint.refuelsystem==nil then return patrolpoint end @@ -1235,7 +1253,7 @@ function AIRWING:CheckCAP() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(missionCAP) @@ -1287,7 +1305,7 @@ function AIRWING:CheckRECON() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(missionRECON) @@ -1332,7 +1350,7 @@ function AIRWING:CheckTANKER() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(mission) @@ -1351,7 +1369,7 @@ function AIRWING:CheckTANKER() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(mission) @@ -1389,7 +1407,7 @@ function AIRWING:CheckAWACS() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(mission) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 92f6defe1..93ec9b202 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -1,10 +1,15 @@ ------------------------------------------------------------------------- -- Easy CAP/GCI Class, based on OPS classes ------------------------------------------------------------------------- --- Documentation +-- +-- ## Documentation: -- -- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyGCICAP.html -- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyGCICAP). +-- ------------------------------------------------------------------------- -- Date: September 2023 -- Last Update: Aug 2025 @@ -73,6 +78,7 @@ -- @field #string defaulttakeofftype Take off type -- @field #number FuelLowThreshold -- @field #number FuelCriticalThreshold +-- @field #boolean showpatrolpointmarks -- @extends Core.Fsm#FSM --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. @@ -230,6 +236,7 @@ EASYGCICAP = { defaulttakeofftype = "hot", FuelLowThreshold = 25, FuelCriticalThreshold = 10, + showpatrolpointmarks = false, } --- Internal Squadron data type @@ -265,7 +272,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.26" +EASYGCICAP.version="0.1.27" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -321,6 +328,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.defaulttakeofftype = "hot" self.FuelLowThreshold = 25 self.FuelCriticalThreshold = 10 + self.showpatrolpointmarks = false -- Set some string id for output to DCS.log file. self.lid=string.format("EASYGCICAP %s | ", self.alias) @@ -379,6 +387,19 @@ function EASYGCICAP:SetFuelLow(Percent) return self end +--- Set markers on the map for Patrol Points. +-- @param #EASYGCICAP self +-- @param #boolean onoff Set to true to switch markers on. +-- @return #EASYGCICAP self +function EASYGCICAP:ShowPatrolPointMarkers(onoff) + if onoff then + self.showpatrolpointmarks = true + else + self.showpatrolpointmarks = false + end + return self +end + --- Set "fuel critical" threshold for CAP and INTERCEPT flights. -- @param #EASYGCICAP self -- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 10. @@ -648,6 +669,10 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) CAP_Wing:SetRespawnAfterDestroyed() CAP_Wing:SetNumberCAP(self.capgrouping) CAP_Wing:SetCapCloseRaceTrack(true) + + if self.showpatrolpointmarks then + CAP_Wing:ShowPatrolPointMarkers(true) + end if self.capOptionVaryStartTime then CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime) @@ -738,14 +763,14 @@ end --- Add a CAP patrol point to a Wing -- @param #EASYGCICAP self -- @param #string AirbaseName Name of the Wing's airbase --- @param Core.Point#COORDINATE Coordinate. +-- @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 #EASYGCICAP self function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) - self:T(self.lid.."AddPatrolPointCAP "..Coordinate:ToStringLLDDM()) + self:T(self.lid.."AddPatrolPointCAP")--..Coordinate:ToStringLLDDM()) local EntryCAP = {} -- #EASYGCICAP.CapPoint EntryCAP.AirbaseName = AirbaseName EntryCAP.Coordinate = Coordinate @@ -763,7 +788,7 @@ end --- Add a RECON patrol point to a Wing -- @param #EASYGCICAP self -- @param #string AirbaseName Name of the Wing's airbase --- @param Core.Point#COORDINATE Coordinate. +-- @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. -- @param #number Speed Defaults to 300 knots. -- @param #number Heading Defaults to 90 degrees (East). @@ -788,7 +813,7 @@ end --- Add a TANKER patrol point to a Wing -- @param #EASYGCICAP self -- @param #string AirbaseName Name of the Wing's airbase --- @param Core.Point#COORDINATE Coordinate. +-- @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. -- @param #number Speed Defaults to 300 knots. -- @param #number Heading Defaults to 90 degrees (East). @@ -813,7 +838,7 @@ end --- Add an AWACS patrol point to a Wing -- @param #EASYGCICAP self -- @param #string AirbaseName Name of the Wing's airbase --- @param Core.Point#COORDINATE Coordinate. +-- @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. -- @param #number Speed Defaults to 300 knots. -- @param #number Heading Defaults to 90 degrees (East). From ece9e008c2725cbc39cdce0e59ae8a09773ffa39 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 10 Aug 2025 13:40:01 +0200 Subject: [PATCH 238/349] xx --- Moose Development/Moose/Functional/Tiresias.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index 034787ada..b94411785 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -30,8 +30,8 @@ ---- #-- Author : **applevangelist ** (Optimized by AI) --- --- - @module Functional.Tiresias --- - @image Functional.Tiresias.jpg +-- @module Functional.Tiresias +-- @image Functional.Tiresias.jpg --- Last Update: July 2025 From e49003940f0a3553e6e5a3c4d3a26e2146c986d6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Aug 2025 09:12:50 +0200 Subject: [PATCH 239/349] #MANTIS - Added Pantsir S1, TOR M2, IRIS-T SLM to main man SAM data (from CH mod) --- Moose Development/Moose/Functional/Mantis.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index cd3ee9cae..67816cf04 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -108,6 +108,10 @@ -- * Patriot -- * Rapier -- * Roland +-- * IRIS-T SLM +-- * Pantsir S1 +-- * TOR M2 +-- * C-RAM -- * Silkworm (though strictly speaking this is a surface to ship missile) -- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19 -- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!) @@ -276,7 +280,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", - version = "0.9.33", + version = "0.9.34", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -385,7 +389,7 @@ MANTIS.SamData = { ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, - ["HEMTT_C-RAM_Phalanx"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, + ["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" }, @@ -396,6 +400,10 @@ MANTIS.SamData = { ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, ["NIKE"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" }, ["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" }, + -- CH Added to DCS core 2.9.19.x + ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, + ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, + ["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, } --- SAM data HDS @@ -1288,7 +1296,7 @@ do end end -- rejectzones - if #self.RejectZones > 0 and inzone then -- maybe in accept zone, but check the overlaps + if #self.RejectZones > 0 then for _,_zone in pairs(self.RejectZones) do local zone = _zone -- Core.Zone#ZONE if zone:IsCoordinateInZone(coord) then @@ -1299,7 +1307,7 @@ do end end -- conflictzones - if #self.ConflictZones > 0 and not inzone then -- if not already accepted, might be in conflict zones + if #self.ConflictZones > 0 then for _,_zone in pairs(self.ConflictZones) do local zone = _zone -- Core.Zone#ZONE if zone:IsCoordinateInZone(coord) then From 8c5efdbe55a5414555bbe3736d3218a48c470100 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Aug 2025 17:15:54 +0200 Subject: [PATCH 240/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 67816cf04..9714fbe80 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: July 2025 +-- Last Update: August 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -308,7 +308,7 @@ MANTIS = { adv_state = 0, AWACS_Prefix = "", advAwacs = false, - verbose = false, + verbose = true, awacsrange = 250000, Shorad = nil, ShoradLink = false, @@ -401,8 +401,8 @@ MANTIS.SamData = { ["NIKE"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" }, ["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" }, -- CH Added to DCS core 2.9.19.x - ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, - ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, + ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" }, + ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, ["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, } @@ -469,15 +469,15 @@ MANTIS.SamDataCH = { -- https://www.currenthill.com/ -- group name MUST contain CHM to ID launcher type correctly! ["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" }, - ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, + ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true" }, ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, ["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" }, ["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, ["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, ["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, - ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, - ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" }, - ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, + ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, + ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true" }, + ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true" }, ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, @@ -583,7 +583,7 @@ do self.advanced = false self.adv_ratio = 100 self.adv_state = 0 - self.verbose = false + self.verbose = true self.Adv_EWR_Group = nil self.AWACS_Prefix = awacs or nil self.awacsrange = 250000 --DONE: 250km, User Function to change @@ -893,7 +893,11 @@ do self.AcceptZones = AcceptZones or {} self.RejectZones = RejectZones or {} self.ConflictZones = ConflictZones or {} - if #self.AcceptZones > 0 or #self.RejectZones > 0 or #self.ConflictZones > 0 then + self.AcceptZonesNo = UTILS.TableLength(self.AcceptZones) + self.RejectZonesNo = UTILS.TableLength(self.RejectZones) + self.ConflictZonesNo = UTILS.TableLength(self.ConflictZones) + self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) + if self.AcceptZonesNo > 0 or self.RejectZonesNo > 0 or self.ConflictZonesNo > 0 then self.usezones = true end return self @@ -1285,7 +1289,8 @@ do self:T(self.lid.."_CheckCoordinateInZones") local inzone = false -- acceptzones - if #self.AcceptZones > 0 then + self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) + if self.AcceptZonesNo > 0 then for _,_zone in pairs(self.AcceptZones) do local zone = _zone -- Core.Zone#ZONE if zone:IsCoordinateInZone(coord) then @@ -1296,7 +1301,7 @@ do end end -- rejectzones - if #self.RejectZones > 0 then + if self.RejectZonesNo > 0 then for _,_zone in pairs(self.RejectZones) do local zone = _zone -- Core.Zone#ZONE if zone:IsCoordinateInZone(coord) then @@ -1307,7 +1312,7 @@ do end end -- conflictzones - if #self.ConflictZones > 0 then + if self.ConflictZonesNo > 0 then for _,_zone in pairs(self.ConflictZones) do local zone = _zone -- Core.Zone#ZONE if zone:IsCoordinateInZone(coord) then @@ -1373,6 +1378,7 @@ do end -- check accept/reject zones local zonecheck = true + self:T("self.usezones = "..tostring(self.usezones)) if self.usezones then -- DONE zonecheck = self:_CheckCoordinateInZones(coord) From d13781ec320b16a00de49a6e0e94d18eb636ba91 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 15 Aug 2025 14:26:54 +0200 Subject: [PATCH 241/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 4 ++-- Moose Development/Moose/Ops/AirWing.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 9714fbe80..2e1ff3b18 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -308,7 +308,7 @@ MANTIS = { adv_state = 0, AWACS_Prefix = "", advAwacs = false, - verbose = true, + verbose = false, awacsrange = 250000, Shorad = nil, ShoradLink = false, @@ -583,7 +583,7 @@ do self.advanced = false self.adv_ratio = 100 self.adv_state = 0 - self.verbose = true + self.verbose = false self.Adv_EWR_Group = nil self.AWACS_Prefix = awacs or nil self.awacsrange = 250000 --DONE: 250km, User Function to change diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index a4b1b58e5..1e11793e3 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -845,7 +845,7 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL local patrolpoint={} --#AIRWING.PatrolData patrolpoint.type=Type or "Unknown" patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10, 15)), math.random(360)) - if Coordinate:IsInstanceOf("ZONE_BASE") then + if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE") then patrolpoint.IsZonePoint = true patrolpoint.patrolzone = Coordinate patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate() From f400d0212add95f532c806006f5fe129533ce2ea Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 28 Aug 2025 13:18:18 +0200 Subject: [PATCH 242/349] xx --- .../Moose/Functional/Warehouse.lua | 1 + Moose Development/Moose/Ops/Cohort.lua | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 807f57f98..36f5429c6 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -6134,6 +6134,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol unit.onboard_num=asset.modex[i] end if asset.callsign then + --UTILS.PrintTableToLog(asset.callsign) unit.callsign=asset.callsign[i] end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 26f54e66d..c18d838d6 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -88,7 +88,7 @@ COHORT = { --- COHORT class version. -- @field #string version -COHORT.version="0.3.6" +COHORT.version="0.3.7" --- Global variable to store the unique(!) cohort names _COHORTNAMES={} @@ -100,6 +100,7 @@ _COHORTNAMES={} -- DONE: Create FLOTILLA class. -- DONE: Added check for properties. -- DONE: Make general so that PLATOON and SQUADRON can inherit this class. +-- DONE: Better setting of call signs. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -515,10 +516,12 @@ end -- @param #COHORT self -- @param #number Callsign Callsign from CALLSIGN.Aircraft, e.g. "Chevy" for CALLSIGN.Aircraft.CHEVY. -- @param #number Index Callsign index, Chevy-**1**. +-- @param #string CallsignString (optional) Set this for tasks like TANKER, AWACS or KIOWA and the like, which have special names. E.g. "Darkstar" or "Roughneck". -- @return #COHORT self -function COHORT:SetCallsign(Callsign, Index) +function COHORT:SetCallsign(Callsign, Index, CallsignString) self.callsignName=Callsign self.callsignIndex=Index + self.callsignClearName=CallsignString self.callsign={} self.callsign.NumberSquad=Callsign self.callsign.NumberGroup=Index @@ -679,7 +682,16 @@ end function COHORT:GetCallsign(Asset) if self.callsignName then - + --[[ + ["callsign"] = + { + [2] = 1, + ["name"] = "Darkstar11", + [3] = 1, + [1] = 5, + [4] = "Darkstar11", + }, -- end of ["callsign"] + ]] Asset.callsign={} for i=1,Asset.nunits do @@ -695,12 +707,16 @@ function COHORT:GetCallsign(Asset) else self.callsigncounter=self.callsigncounter+1 end + callsign["name"] = self.callsignClearName or UTILS.GetCallsignName(self.callsignName) or "None" + callsign["name"] = string.format("%s%d%d",callsign["name"],callsign[2],callsign[3]) + callsign[4] = callsign["name"] Asset.callsign[i]=callsign self:T3({callsign=callsign}) - --TODO: there is also a table entry .name, which is a string. + --DONE: there is also a table entry .name, which is a string. + --UTILS.PrintTableToLog(callsign) end From 4d48b42aea27775e7bea0075d3d4d8cc82f05c69 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 31 Aug 2025 18:21:48 +0200 Subject: [PATCH 243/349] xx --- Moose Development/Moose/Ops/Airboss.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 89f6f5b37..f3763304b 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3126,8 +3126,8 @@ function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volum self.SRS:SetCulture(Culture or "en-US") --self.SRS:SetFrequencies(Frequencies) self.SRS:SetGender(Gender or "male") - self.SRS:SetPath(PathToSRS) - self.SRS:SetPort(Port or 5002) + --self.SRS:SetPath(PathToSRS) + self.SRS:SetPort(Port or MSRS.port or 5002) self.SRS:SetLabel(self.AirbossRadio.alias or "AIRBOSS") self.SRS:SetCoordinate(self.carrier:GetCoordinate()) self.SRS:SetVolume(Volume or 1) @@ -3138,7 +3138,10 @@ function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volum if Voice then self.SRS:SetVoice(Voice) end - self.SRS:SetVolume(Volume or 1.0) + if (not Voice) and self.SRS and self.SRS:GetProvider() == MSRS.Provider.GOOGLE then + self.SRS.voice = MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B + end + --self.SRS:SetVolume(Volume or 1.0) -- SRSQUEUE self.SRSQ = MSRSQUEUE:New("AIRBOSS") self.SRSQ:SetTransmitOnlyWithPlayers(true) From c197c842e88072ca94c2306dfabdc30e363ec59c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 7 Sep 2025 18:58:32 +0200 Subject: [PATCH 244/349] #CLIENTMENU - add missing func from documentation --- Moose Development/Moose/Core/ClientMenu.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/ClientMenu.lua b/Moose Development/Moose/Core/ClientMenu.lua index 5e7219add..fea72914d 100644 --- a/Moose Development/Moose/Core/ClientMenu.lua +++ b/Moose Development/Moose/Core/ClientMenu.lua @@ -20,7 +20,7 @@ -- -- @module Core.ClientMenu -- @image Core_Menu.JPG --- last change: Jan 2025 +-- last change: Sept 2025 -- TODO ---------------------------------------------------------------------------------------------------------------- @@ -417,7 +417,7 @@ end CLIENTMENUMANAGER = { ClassName = "CLIENTMENUMANAGER", lid = "", - version = "0.1.6", + version = "0.1.7", name = nil, clientset = nil, menutree = {}, @@ -806,6 +806,16 @@ function CLIENTMENUMANAGER:ResetMenuComplete() return self end +--- Remove the entry and all entries below the given entry from the client's F10 menus. +-- @param #CLIENTMENUMANAGER self +-- @param #CLIENTMENU Entry The entry to remove +-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. +-- @return #CLIENTMENUMANAGER self +function CLIENTMENUMANAGER:DeleteEntry(Entry,Client) + self:T(self.lid.."DeleteEntry") + return self:DeleteF10Entry(Entry,Client) +end + --- Remove the entry and all entries below the given entry from the client's F10 menus. -- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENU Entry The entry to remove From 6abd76a40b07052e895b9c8b49a9dbb9af83a09e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 15 Sep 2025 09:27:44 +0200 Subject: [PATCH 245/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index f0700332d..5c789141f 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -273,7 +273,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.27" +EASYGCICAP.version="0.1.28" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1209,7 +1209,9 @@ function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, Air Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetMissionRange(self.missionrange) Squadron_One:SetRadio(Frequency,Modulation) - Squadron_One:AddTacanChannel(TACAN,TACAN) + if TACAN then + Squadron_One:AddTacanChannel(TACAN,TACAN) + end local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING From 0532ceb1cf0e213422868e872efb3801a6cee57c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 17 Sep 2025 12:04:14 +0200 Subject: [PATCH 246/349] #PLAYERTASK - Ensure call task failed on the controller --- Moose Development/Moose/Ops/PlayerTask.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 7ce6d57c2..95a98fcfd 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -98,7 +98,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.27" +PLAYERTASK.version="0.1.28" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -1227,7 +1227,10 @@ function PLAYERTASK:onafterFailed(From, Event, To) self.TargetMarker:Remove() end self.FinalState = "Failed" - self:__Done(-1) + if self.TaskController then + self.TaskController:__TaskFailed(-1,self) + end + self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count = self:GetClientObjects() From 71d93f2b67f6004a7321b4021a86061f1f6e6abd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 25 Sep 2025 12:14:12 +0200 Subject: [PATCH 247/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 556b03038..9dff3ecec 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1417,7 +1417,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.37" +CTLD.version="1.3.38" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3328,6 +3328,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) self:_RefreshLoadCratesMenu(Group, Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) + self:__CratesPickedUp(1, Group, Unit, loaded.Cargo) end end return self From 41855fc63b05bded9b865edb606be6057b078e35 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 25 Sep 2025 15:16:28 +0200 Subject: [PATCH 248/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 5c789141f..8a902cad5 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -79,6 +79,7 @@ -- @field #number FuelLowThreshold -- @field #number FuelCriticalThreshold -- @field #boolean showpatrolpointmarks +-- @field #table EngageTargetTypes -- @extends Core.Fsm#FSM --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. @@ -237,6 +238,7 @@ EASYGCICAP = { FuelLowThreshold = 25, FuelCriticalThreshold = 10, showpatrolpointmarks = false, + EngageTargetTypes = {"Air"}, } --- Internal Squadron data type @@ -273,7 +275,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.28" +EASYGCICAP.version="0.1.30" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -330,6 +332,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.FuelLowThreshold = 25 self.FuelCriticalThreshold = 10 self.showpatrolpointmarks = false + self.EngageTargetTypes = {"Air"} -- Set some string id for output to DCS.log file. self.lid=string.format("EASYGCICAP %s | ", self.alias) @@ -608,6 +611,17 @@ 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"}, +-- or {"Helicopters"} or combinations like {"Bombers", "Fighters", "UAVs"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes). +-- @return #EASYGCICAP self +function EASYGCICAP:SetCAPEngageTargetTypes(types) + self.EngageTargetTypes = types or {"Air"} + return self +end + --- Add an AirWing to the manager -- @param #EASYGCICAP self -- @param #string Airbasename @@ -706,6 +720,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local NoGoZoneSet = self.NoGoZoneSet local FuelLow = self.FuelLowThreshold or 25 local FuelCritical = self.FuelCriticalThreshold or 10 + local EngageTypes = self.EngageTargetTypes or {"Air"} function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission) local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP @@ -720,7 +735,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) flightgroup:GetGroup():SetOptionLandingOverheadBreak() if Mission.type ~= AUFTRAG.Type.TANKER and Mission.type ~= AUFTRAG.Type.AWACS and Mission.type ~= AUFTRAG.Type.RECON then flightgroup:SetDetection(true) - flightgroup:SetEngageDetectedOn(engagerange,{"Air"},GoZoneSet,NoGoZoneSet) + flightgroup:SetEngageDetectedOn(engagerange,EngageTypes,GoZoneSet,NoGoZoneSet) flightgroup:SetOutOfAAMRTB() flightgroup:SetFuelLowRTB(true) flightgroup:SetFuelLowThreshold(FuelLow) From 729f987cf1c79f412dad8b0ba76ad73983febb76 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 7 Oct 2025 11:38:44 +0200 Subject: [PATCH 249/349] xx --- Moose Development/Moose/Functional/Scoring.lua | 6 ++++-- Moose Development/Moose/Ops/Awacs.lua | 12 +++++++++++- Moose Development/Moose/Ops/CSAR.lua | 18 +++++++++++------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index c3f4bd4b6..2c49243f8 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -321,7 +321,9 @@ function SCORING:New( GameName, SavePath, AutoSave ) -- Create the CSV file. self.AutoSavePath = SavePath self.AutoSave = AutoSave or true - self:OpenCSV( GameName ) + if self.AutoSave == true then + self:OpenCSV( GameName ) + end return self @@ -1935,7 +1937,7 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, TargetUnitType = TargetUnitType or "" TargetUnitName = TargetUnitName or "" - if lfs and io and os and self.AutoSave then + if lfs and io and os and self.AutoSave == true and self.CSVFile ~= nil then self.CSVFile:write( '"' .. self.GameName .. '"' .. ',' .. '"' .. self.RunTime .. '"' .. ',' .. diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index a2dff4db3..062f32d7b 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.72", -- #string + version = "0.2.73", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -1596,6 +1596,16 @@ function AWACS:SetLocale(Locale) return self end +--- [User] Set own coordinate for BullsEye. +-- @param #AWACS self +-- @param Core.Point#COORDINATE +-- @return #AWACS self +function AWACS:SetBullsCoordinate(Coordinate) + self:T(self.lid.."SetBullsCoordinate") + self.AOCoordinate = Coordinate + return self +end + --- [User] Set the max mission range flights can be away from their home base. -- @param #AWACS self -- @param #number NM Distance in nautical miles diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index d9d743d95..9ba328b5c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update July 2025 +-- Last Update Oct 2025 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -315,7 +315,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.33" +CSAR.version="1.0.34" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1244,7 +1244,10 @@ function CSAR:_EventHandler(EventData) if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then self:__Landed(2,_event.IniUnitName, _place) - self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true) + local IsHeloBase = false + local ABName = _place:GetName() + if ABName and string.find(ABName,"^H") then IsHeloBase = true end -- if name starts with an H it's an (possibly elevated) helo base on current maps + self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) end @@ -1731,8 +1734,9 @@ end -- @param #string heliname Heli name -- @param #string groupname Group name -- @param #boolean isairport If true, EVENT.Landing took place at an airport or FARP --- @param #boolean noreschedule If true, do not try to reschedule this is distances are not ok (coming from landing event) -function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule) +-- @param #boolean noreschedule If true, do not try to reschedule this if distances are not ok (coming from landing event) +-- @param #boolean IsHeloBase If true, landing took place at a Helo Base (name "H ..." on current maps) +function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule, IsHeloBase) self:T(self.lid .. " _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit = self:_GetSARHeli(heliname) @@ -1758,7 +1762,7 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule) self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) - if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then + if ( _dist < self.FARPRescueDistance or isairport ) and ((_heliUnit:InAir() == false) or (IsHeloBase == true)) then self:T(self.lid.."[Drop off debug] Distance ok, door check") if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true, true) @@ -1773,7 +1777,7 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule) --queue up if not noreschedule then self:__Returning(5,heliname,_woundedGroupName, isairport) - self:ScheduleOnce(5,self._ScheduledSARFlight,self,heliname,groupname, isairport, noreschedule) + self:ScheduleOnce(5,self._ScheduledSARFlight,self,heliname,groupname, isairport, noreschedule, IsHeloBase) end return self end From 53b1415e87ff7036f08f9f69bfce779857051721 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 7 Oct 2025 17:48:22 +0200 Subject: [PATCH 250/349] xx --- Moose Development/Moose/Ops/Auftrag.lua | 17 +++++++++++++++++ Moose Development/Moose/Ops/Legion.lua | 11 ++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 41bac80e0..f14440690 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -4016,6 +4016,23 @@ function AUFTRAG:IsOver() return over end +--- Check if mission is repeatable. +-- @param #AUFTRAG self +-- @return #boolean If true, mission is repeatable. +function AUFTRAG:IsRepeatable() + local repeatmeS=self.repeatedSuccess 1800 then + mission = nil + end end -- Check that runway is operational and that carrier is not recovering. @@ -761,7 +770,7 @@ function LEGION:CheckMissionQueue() -- Reduce number of reinforcements. if reinforce then mission.reinforce=mission.reinforce-#assets - self:I(self.lid..string.format("Reinforced with N=%d Nreinforce=%d", #assets, mission.reinforce)) + self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d", #assets, mission.reinforce)) end return true From e0eee614c7c3785bfafef8c7455e60261a542025 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 8 Oct 2025 12:27:01 +0200 Subject: [PATCH 251/349] #CONTROLLABLE - Added `CONTROLLABLE:OptionAAAMinFiringHeightMeters`, `CONTROLLABLE:OptionAAAMinFiringHeightFeet`,`CONTROLLABLE:OptionAAAMaxFiringHeightMeters`, `CONTROLLABLE:OptionAAAMaxFiringHeightFeet` --- .../Moose/Wrapper/Controllable.lua | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 20f35ee09..2985f7baf 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -4163,7 +4163,7 @@ function CONTROLLABLE:OptionRestrictBurner( RestrictBurner ) end ---- Sets Controllable Option for A2A attack range for AIR FIGHTER units. +--- [AIR] Sets Controllable Option for A2A attack range for AIR FIGHTER units. -- @param #CONTROLLABLE self -- @param #number range Defines the range -- @return #CONTROLLABLE self @@ -4188,6 +4188,66 @@ function CONTROLLABLE:OptionAAAttackRange( range ) return nil end +--- [GROUND/AAA] Sets Controllable Option for Ground AAA minimum firing height. +-- @param #CONTROLLABLE self +-- @param #number meters The minimum height in meters. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) + self:F2( { self.ControllableName } ) + local meters = meters or 20 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsGround()() then + self:SetOption(27, meters) + end + end + return self + end + return nil +end + +--- [GROUND/AAA] Sets Controllable Option for Ground AAA maximum firing height. +-- @param #CONTROLLABLE self +-- @param #number meters The maximum height in meters. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) + self:F2( { self.ControllableName } ) + local meters = meters or 1000 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsGround()() then + self:SetOption(29, meters) + end + end + return self + end + return nil +end + +--- [GROUND/AAA] Sets Controllable Option for Ground AAA minimum firing height. +-- @param #CONTROLLABLE self +-- @param #number feet The minimum height in feet. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) + self:F2( { self.ControllableName } ) + local feet = feet or 60 + return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end + +--- [GROUND/AAA] Sets Controllable Option for Ground AAA maximum firing height. +-- @param #CONTROLLABLE self +-- @param #number feet The maximum height in feet. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) + self:F2( { self.ControllableName } ) + local feet = feet or 3000 + return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end + --- Defines the range at which a GROUND unit/group is allowed to use its weapons automatically. -- @param #CONTROLLABLE self -- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. From befb957b3fbfe638d314b3d9979553f8bf380337 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 8 Oct 2025 16:01:32 +0200 Subject: [PATCH 252/349] xx --- Moose Development/Moose/Wrapper/Controllable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 2985f7baf..780dc31c8 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -4199,7 +4199,7 @@ function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) if DCSControllable then local Controller = self:_GetController() if Controller then - if self:IsGround()() then + if self:IsGround() then self:SetOption(27, meters) end end @@ -4219,7 +4219,7 @@ function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) if DCSControllable then local Controller = self:_GetController() if Controller then - if self:IsGround()() then + if self:IsGround() then self:SetOption(29, meters) end end From 1d8a03f9d4e36ba6c6cd77ae121a5433170497e3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 12 Oct 2025 16:45:50 +0200 Subject: [PATCH 253/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index dcab663fb..ae62d5e64 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -25,7 +25,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update July 2025 +-- Last Update Oct 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ From c2e81415a1fe4823cf8c1bbec59adf9a108194a5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 12 Oct 2025 17:21:45 +0200 Subject: [PATCH 254/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index ae62d5e64..adfacc5af 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4559,7 +4559,7 @@ function CTLD:_RefreshF10Menus() end end else - if self.usesubcats then + if self.usesubcats == true then local subcatmenus = {} for catName, _ in pairs(self.subcats) do subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu) -- fixed variable case From 4b1d81d5829ef2eb384e54a18ccf1d5dfb439a25 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 19 Oct 2025 14:38:42 +0200 Subject: [PATCH 255/349] #TIRESIAS - Fixed a problem when a script wants to add exceptions pre-start. --- Moose Development/Moose/Functional/Tiresias.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index b94411785..9d5168748 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -33,7 +33,7 @@ -- @module Functional.Tiresias -- @image Functional.Tiresias.jpg ---- Last Update: July 2025 +--- Last Update: Oct 2025 --- **TIRESIAS** class, extends Core.Base#BASE -- @type TIRESIAS @@ -104,8 +104,8 @@ -- @field #TIRESIAS TIRESIAS = { ClassName = "TIRESIAS", - debug = true, - version = " 0.0.7a-OPT" , + debug = false, + version = " 0.0.8" , Interval = 20, GroundSet = nil, VehicleSet = nil, @@ -140,7 +140,7 @@ function TIRESIAS:New() self:AddTransition("*", "Status", "*") -- TIRESIAS status update. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - self.ExceptionSet = nil --SET_GROUP:New():Clear(false) + self.ExceptionSet = SET_GROUP:New() --:Clear(false) self._cached_zones = {} -- Initialize zone cache self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) @@ -224,10 +224,10 @@ function TIRESIAS:AddExceptionSet(Set) Set:ForEachGroupAlive( function(grp) - local inAAASet = self.AAASet:IsIncludeObject(grp) - local inVehSet = self.VehicleSet:IsIncludeObject(grp) - local inSAMSet = self.SAMSet:IsIncludeObject(grp) - if grp:IsGround() and (not grp.Tiresias) and (not inAAASet) and (not inVehSet) and (not inSAMSet) then + --local inAAASet = self.AAASet:IsIncludeObject(grp) + --local inVehSet = self.VehicleSet:IsIncludeObject(grp) + --local inSAMSet = self.SAMSet:IsIncludeObject(grp) + if grp:IsGround() and (not grp.Tiresias) then --and (not inAAASet) and (not inVehSet) and (not inSAMSet) then grp.Tiresias = exception_data exceptions:AddGroup(grp, true) BASE:T(" TIRESIAS: Added exception group: " .. grp:GetName()) From d74d337d2ff729f7c6277a368ea5b75611d5c66e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 23 Oct 2025 08:09:56 +0200 Subject: [PATCH 256/349] xx --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 246e960d1..8b8644165 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -12230,7 +12230,7 @@ function AIRBOSS:GetHeadingIntoWind_new( vdeck, magnetic, coord ) -- Ship heading so cross wind is min for the given wind. -- local intowind = (540 + (windto - magvar + math.deg(theta) )) % 360 -- VNAO Edit: Using old heading into wind algorithm - local intowind = self:GetHeadingIntoWind_old(vdeck) -- VNAO Edit: Using old heading into wind algorithm + local intowind = self:GetHeadingIntoWind_old(vdeck,magnetic) -- VNAO Edit: Using old heading into wind algorithm return intowind, v end From 33dd158df4bfba701a3a66850865d21bad6fb4e1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Oct 2025 16:18:13 +0200 Subject: [PATCH 257/349] xx --- Moose Development/Moose/Utilities/Utils.lua | 128 +++++++++++++++----- 1 file changed, 98 insertions(+), 30 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 8c337b178..9d52ee972 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4137,6 +4137,45 @@ function UTILS.LCGRandom() return UTILS.lcg.seed / UTILS.lcg.m end +--- Create a table of grid-points for n points. +-- @param #number startVec2 Starting DCS#Vec2 map coordinate, e.g. `{x=63598575,y=-63598575}` +-- @param #number n Number of points to generate. +-- @param #number spacingX Horizonzal spacing (meters). +-- @param #number spacingY Vertical spacing (meters). +-- @return #table Grid Table of DCS#Vec2 entries. +function UTILS.GenerateGridPoints(startVec2, n, spacingX, spacingY) + local points = {} + local gridSize = math.ceil(math.sqrt(n)) + local count = 0 + local n = n or 1 + local spacingX = spacingX or 100 + local spacingY = spacingY or 100 + local startX = startVec2.x or 100 + local startY = startVec2.y or 100 + + for row = 0, gridSize - 1 do + for col = 0, gridSize - 1 do + if count >= n then + break + end + + local point = { + x = startX + (col * spacingX), + y = startY + (row * spacingY) + } + + table.insert(points, point) + count = count + 1 + end + + if count >= n then + break + end + end + + return points +end + --- Spawns a new FARP of a defined type and coalition and functional statics (fuel depot, ammo storage, tent, windsock) around that FARP to make it operational. -- Adds vehicles from template if given. Fills the FARP warehouse with liquids and known materiels. -- References: [DCS Forum Topic](https://forum.dcs.world/topic/282989-farp-equipment-to-run-it) @@ -4157,10 +4196,38 @@ end -- @param #string F10Text Text to display on F10 map if given. Handy to post things like the ADF beacon Frequency, Callsign and ATC Frequency. -- @param #boolean DynamicSpawns If true, allow Dynamic Spawns from this FARP. -- @param #boolean HotStart If true and DynamicSpawns is true, allow hot starts for Dynamic Spawns from this FARP. +-- @param #number NumberPads If given, spawn this number of pads. +-- @param #number SpacingX For NumberPads > 1, space this many meters horizontally. Defaults to 100. +-- @param #number SpacingY For NumberPads > 1, space this many meters vertically. Defaults to 100. -- @return #list Table of spawned objects and vehicle object (if given). -- @return #string ADFBeaconName Name of the ADF beacon, to be able to remove/stop it later. -- @return #number MarkerID ID of the F10 Text, to be able to remove it later. -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) + + local function PopulateStorage(Name,liquids,equip,airframes) + local newWH = STORAGE:New(Name) + if liquids and liquids > 0 then + -- Storage fill-up + newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons + newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) + newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) + newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) + end + + if equip and equip > 0 then + for cat,nitem in pairs(ENUMS.Storage.weapons) do + for name,item in pairs(nitem) do + newWH:SetItem(item,equip) + end + end + end + + if airframes and airframes > 0 then + for typename in pairs (CSAR.AircraftType) do + newWH:SetItem(typename,airframes) + end + end + end -- Set Defaults local farplocation = Coordinate @@ -4181,12 +4248,36 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, local Country = Country or (Coalition == coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects = {} - -- Spawn FARP - local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" - newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" - newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) - local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) - table.insert(ReturnObjects,spawnedfarp) + -- many FARPs + local NumberPads = NumberPads or 1 + local SpacingX = SpacingX or 100 + local SpacingY = SpacingY or 100 + local FarpVec2 = Coordinate:GetVec2() + + if NumberPads > 1 then + local Grid = UTILS.GenerateGridPoints(FarpVec2, NumberPads, SpacingX, SpacingY) + for id,gridpoint in ipairs(Grid) do + -- Spawn FARP + local location = COORDINATE:NewFromVec2(gridpoint) + local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" + newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" + newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) + local spawnedfarp = newfarp:SpawnFromCoordinate(location,0,Name.."-"..id) + table.insert(ReturnObjects,spawnedfarp) + + PopulateStorage(Name.."-"..id,liquids,equip,airframes) + end + else + -- Spawn FARP + local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" + newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" + newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) + local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) + table.insert(ReturnObjects,spawnedfarp) + + PopulateStorage(Name,liquids,equip,airframes) + end + -- Spawn Objects local FARPStaticObjectsNato = { ["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"}, @@ -4220,29 +4311,6 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, table.insert(ReturnObjects,spawnedvehicle) end - local newWH = STORAGE:New(Name) - if liquids and liquids > 0 then - -- Storage fill-up - newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons - newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) - newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) - newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) - end - - if equip and equip > 0 then - for cat,nitem in pairs(ENUMS.Storage.weapons) do - for name,item in pairs(nitem) do - newWH:SetItem(item,equip) - end - end - end - - if airframes and airframes > 0 then - for typename in pairs (CSAR.AircraftType) do - newWH:SetItem(typename,airframes) - end - end - local ADFName if ADF and type(ADF) == "number" then local ADFFreq = ADF*1000 -- KHz to Hz From 13217f764e8abffd78d7a91a07273b0bf14b044c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Oct 2025 16:26:30 +0200 Subject: [PATCH 258/349] #CTLD - fix double function entry --- Moose Development/Moose/Ops/CTLD.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3f844a1a8..87d394e01 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -119,7 +119,6 @@ CTLD_CARGO = { -- @param #boolean DontShowInMenu Show this item in menu or not (default: false == show it). -- @param Core.Zone#ZONE Location (optional) Where the cargo is available (one location only). -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory, DontShowInMenu, Location) function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory, DontShowInMenu, Location) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO From df454cb59e7598e4a520488beb0eb6503aa4a951 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 25 Oct 2025 16:54:11 +0200 Subject: [PATCH 259/349] xx --- Moose Development/Moose/Utilities/Utils.lua | 54 +++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 9d52ee972..8f1cc9c90 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4255,9 +4255,33 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, local FarpVec2 = Coordinate:GetVec2() if NumberPads > 1 then - local Grid = UTILS.GenerateGridPoints(FarpVec2, NumberPads, SpacingX, SpacingY) + local Grid = UTILS.GenerateGridPoints(FarpVec2, NumberPads, SpacingX, SpacingY) + local groupData = { + ["visible"] = true, + ["hidden"] = false, + ["units"] = {}, + ["y"] = 0, -- Group center latitude + ["x"] = 0, -- Group center longitude + ["name"] = Name, + } + local unitData = { + ["category"] = "Heliports", + ["type"] = STypeName, -- FARP type + ["y"] = 0, -- Latitude coordinate (meters) + ["x"] = 0, -- Longitude coordinate (meters) + ["name"] = Name, + ["heading"] = 0, -- Heading in radians + ["heliport_modulation"] = mod, -- 0 = AM, 1 = FM + ["heliport_frequency"] = freq, -- Radio frequency in MHz + ["heliport_callsign_id"] = callsign, -- Callsign ID + ["dead"] = false, + ["shape_name"] = SShapeName, + ["dynamicSpawn"] = DynamicSpawns, + ["allowHotStart"] = HotStart, + } for id,gridpoint in ipairs(Grid) do -- Spawn FARP + --[[ local location = COORDINATE:NewFromVec2(gridpoint) local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" @@ -4265,8 +4289,32 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, local spawnedfarp = newfarp:SpawnFromCoordinate(location,0,Name.."-"..id) table.insert(ReturnObjects,spawnedfarp) - PopulateStorage(Name.."-"..id,liquids,equip,airframes) - end + PopulateStorage(Name.."-"..id,liquids,equip,airframes) + --]] + local UnitTemplate = UTILS.DeepCopy(unitData) + UnitTemplate.x = gridpoint.x + UnitTemplate.y = gridpoint.y + UnitTemplate.name = Name.."-"..id + table.insert(groupData.units,UnitTemplate) + if id==1 then + groupData.x = gridpoint.x + groupData.y = gridpoint.y + end + end + BASE:I("Spawning FARP") + UTILS.PrintTableToLog(groupData,1) + local Static=coalition.addGroup(Country, -1, groupData) + -- Currently DCS >= 2.8 does not trigger birth events if FARPS are spawned! + -- We create such an event. The airbase is registered in Core.Event + local Event = { + id = EVENTS.Birth, + time = timer.getTime(), + initiator = Static + } + -- Create BIRTH event. + world.onEvent(Event) + + PopulateStorage(Name.."-1",liquids,equip,airframes) else -- Spawn FARP local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" From a93ade98132b5854f4728a6c3bd82d60936fc504 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 25 Oct 2025 16:55:26 +0200 Subject: [PATCH 260/349] xx --- Moose Development/Moose/Utilities/Utils.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 8f1cc9c90..bd4f4390d 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4301,8 +4301,8 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, groupData.y = gridpoint.y end end - BASE:I("Spawning FARP") - UTILS.PrintTableToLog(groupData,1) + --BASE:I("Spawning FARP") + --UTILS.PrintTableToLog(groupData,1) local Static=coalition.addGroup(Country, -1, groupData) -- Currently DCS >= 2.8 does not trigger birth events if FARPS are spawned! -- We create such an event. The airbase is registered in Core.Event From 6e38e214b61c446416f09648e1c34e948aff6296 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Oct 2025 14:28:23 +0100 Subject: [PATCH 261/349] xx --- Moose Development/Moose/Functional/Scoring.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index 2c49243f8..d9427d279 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -320,8 +320,8 @@ function SCORING:New( GameName, SavePath, AutoSave ) -- Create the CSV file. self.AutoSavePath = SavePath - self.AutoSave = AutoSave or true - if self.AutoSave == true then + self.AutoSave = (AutoSave == nil or AutoSave == true) and true or false + if self.AutoSavePath and self.AutoSave == true then self:OpenCSV( GameName ) end From b07accdb3d1ba0ed129485260fc35f5685502235 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Nov 2025 09:54:16 +0100 Subject: [PATCH 262/349] xx --- Moose Development/Moose/Ops/CSAR.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 5538b3689..ddfb67043 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1232,7 +1232,7 @@ function CSAR:_EventHandler(EventData) local _place = _event.Place -- Wrapper.Airbase#AIRBASE if _place == nil then - self:T(self.lid .. " Landing Place Nil") + self:T(self.lid .. " Landing Place nil") return self -- error! end From e197f885102be9f8cb4213752a898f1ebb8997ce Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Nov 2025 13:04:37 +0100 Subject: [PATCH 263/349] xx --- Moose Development/Moose/Ops/PlayerTask.lua | 127 ++++++++++++++++----- 1 file changed, 96 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 13cc66eba..1f5269ce9 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update May 2025 +-- @date Last Update Oct 2025 do @@ -59,6 +59,8 @@ do -- @field #string FinalState -- @field #string TypeName -- @field #number PreviousCount +-- @field #boolean CanSmoke +-- @field #boolean ShowThreatDetails -- @extends Core.Fsm#FSM @@ -94,11 +96,13 @@ PLAYERTASK = { NextTaskFailure = {}, FinalState = "none", PreviousCount = 0, + CanSmoke = true, + ShowThreatDetails = true, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.28" +PLAYERTASK.version="0.1.29" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -231,6 +235,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. + -- @param #boolen Silent If true, suppress message output on cancel. --- On After "Planned" event. Task has been planned. -- @function [parent=#PLAYERTASK] OnAfterPilotPlanned @@ -468,6 +473,26 @@ function PLAYERTASK:SetSubType(Type) return self end +--- [USER] Set if a task can have a smoke marker. +-- @param #PLAYERTASK self +-- @param #boolean OnOff If true (default) it can be smoke, false if not. +-- @return #PLAYERTASK self +function PLAYERTASK:SetCanSmoke(OnOff) + self:T(self.lid.."AddSSetCanSmokeubType") + self.CanSmoke = OnOff + return self +end + +--- [USER] Set if a task can show threat details. +-- @param #PLAYERTASK self +-- @param #boolean OnOff If true (default) it can be shown, false if not. +-- @return #PLAYERTASK self +function PLAYERTASK:SetShowThreatDetails(OnOff) + self:T(self.lid.."SetShowThreatDetails") + self.ShowThreatDetails = OnOff + return self +end + --- [USER] Get task sub type description from this task. -- @param #PLAYERTASK self -- @return #string Type or nil @@ -1173,11 +1198,12 @@ end -- @param #string From -- @param #string Event -- @param #string To +-- @param #boolean Silent -- @return #PLAYERTASK self -function PLAYERTASK:onafterCancel(From, Event, To) +function PLAYERTASK:onafterCancel(From, Event, To, Silent) self:T({From, Event, To}) if self.TaskController then - self.TaskController:__TaskCancelled(-1,self) + self.TaskController:__TaskCancelled(-1,self, Silent) end self.timestamp = timer.getAbsTime() self.FinalState = "Cancelled" @@ -1606,7 +1632,7 @@ do -- -- The event is triggered when a task was cancelled manually. Use @{#PLAYERTASKCONTROLLER.OnAfterTaskCancelled}()` to link into this event: -- --- function taskmanager:OnAfterTaskCancelled(From, Event, To, Task) +-- function taskmanager:OnAfterTaskCancelled(From, Event, To, Task, Silent) -- ... your code here ... -- end -- @@ -1770,12 +1796,15 @@ PLAYERTASKCONTROLLER.Messages = { THREATMEDIUM = "medium", THREATLOW = "low", THREATTEXT = "%s\nThreat: %s\nTargets left: %d\nCoord: %s", + NOTHREATTEXT = "%s\nNo target information available.", ELEVATION = "\nTarget Elevation: %s %s", METER = "meter", FEET = "feet", THREATTEXTTTS = "%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.", + NOTHREATTEXTTTS = "%s, %s. No target information available.", MARKTASK = "%s, %s, copy, task %03d location marked on map!", SMOKETASK = "%s, %s, copy, task %03d location smoked!", + NOSMOKETASK = "%s, %s, negative, task %03d location cannot be smoked!", FLARETASK = "%s, %s, copy, task %03d location illuminated!", ABORTTASK = "All stations, %s, %s has aborted %s task %03d!", UNKNOWN = "Unknown", @@ -1854,12 +1883,15 @@ PLAYERTASKCONTROLLER.Messages = { THREATMEDIUM = "mittel", THREATLOW = "niedrig", THREATTEXT = "%s\nGefahrstufe: %s\nZiele: %d\nKoord: %s", + NOTHREATTEXT = "%s\nKeine Zielinformation verfügbar.", ELEVATION = "\nZiel Höhe: %s %s", METER = "Meter", FEET = "Fuss", THREATTEXTTTS = "%s, %s. Zielinformation zu %s. Gefahrstufe %s. Ziele %d. Zielposition %s.", + NOTHREATTEXTTTS = "%s, %s. Keine Zielinformation verfügbar.", MARKTASK = "%s, %s, verstanden, Zielposition %03d auf der Karte markiert!", SMOKETASK = "%s, %s, verstanden, Zielposition %03d mit Rauch markiert!", + NOSMOKETASK = "%s, %s, negativ, Zielposition %03d kann nicht markiert werden!", FLARETASK = "%s, %s, verstanden, Zielposition %03d beleuchtet!", ABORTTASK = "%s, an alle, %s hat Auftrag %s %03d abgebrochen!", UNKNOWN = "Unbekannt", @@ -1920,7 +1952,7 @@ PLAYERTASKCONTROLLER.Messages = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.70" +PLAYERTASKCONTROLLER.version="0.1.71" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -2061,6 +2093,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) -- @param #string Event Event. -- @param #string To To state. -- @param Ops.PlayerTask#PLAYERTASK Task + -- @param #boolean Silent If true suppress message output. --- On After "TaskFailed" event. Task has failed. -- @function [parent=#PLAYERTASKCONTROLLER] OnAfterTaskFailed @@ -2741,11 +2774,12 @@ end --- [User] Manually cancel a specific task -- @param #PLAYERTASKCONTROLLER self --- @param Ops.PlayerTask#PLAYERTASK Task The task to be cancelled +-- @param Ops.PlayerTask#PLAYERTASK Task The task to be cancelled. +-- @param #boolean Silent If true suppress message output. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:CancelTask(Task) +function PLAYERTASKCONTROLLER:CancelTask(Task,Silent) self:T(self.lid.."CancelTask") - Task:__Cancel(-1) + Task:__Cancel(-1,Silent) return self end @@ -3734,6 +3768,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local Elevation = Coordinate:GetLandHeight() or 0 -- meters local CoordText = "" local CoordTextLLDM = nil + local ShowThreatInfo = task.ShowThreatDetails local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) @@ -3757,7 +3792,12 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local clientlist, clientcount = task:GetClients() local ThreatGraph = "[" .. string.rep( "■", ThreatLevel ) .. string.rep( "□", 10 - ThreatLevel ) .. "]: "..ThreatLevel local ThreatLocaleText = self.gettext:GetEntry("THREATTEXT",self.locale) - text = string.format(ThreatLocaleText, taskname, ThreatGraph, targets, CoordText) + if ShowThreatInfo == true then + text = string.format(ThreatLocaleText, taskname, ThreatGraph, targets, CoordText) + else + ThreatLocaleText = self.gettext:GetEntry("NOTHREATTEXT",self.locale) + text = string.format(ThreatLocaleText, taskname) + end local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS -- Core.Settings#SETTINGS local elevationmeasure = self.gettext:GetEntry("FEET",self.locale) if settings:IsMetric() then @@ -3880,9 +3920,19 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) end --self:I(self.lid.." | ".. CoordText) end + local ttstext local ThreatLocaleTextTTS = self.gettext:GetEntry("THREATTEXTTTS",self.locale) - local ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText) + -- THREATTEXT = "%s\nThreat: %s\nTargets left: %d\nCoord: %s", + -- THREATTEXTTTS = "%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.", + if ShowThreatInfo == true then + ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText) + else + ThreatLocaleTextTTS = self.gettext:GetEntry("NOTHREATTEXTTTS",self.locale) + ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name) + end + -- POINTERTARGETLASINGTTS = ". Pointer over target and lasing." + if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing() then local lasingtext = self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) @@ -3951,15 +4001,25 @@ function PLAYERTASKCONTROLLER:_SmokeTask(Group, Client) local text = "" if self.TasksPerPlayer:HasUniqueID(playername) then local task = self.TasksPerPlayer:ReadByID(playername) -- Ops.PlayerTask#PLAYERTASK - task:SmokeTarget() - local textmark = self.gettext:GetEntry("SMOKETASK",self.locale) - text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr) - self:T(self.lid..text) - --local m=MESSAGE:New(text,"10","Tasking"):ToAll() - if self.UseSRS then - self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) + if task.CanSmoke == true then + task:SmokeTarget() + local textmark = self.gettext:GetEntry("SMOKETASK",self.locale) + text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr) + self:T(self.lid..text) + --local m=MESSAGE:New(text,"10","Tasking"):ToAll() + if self.UseSRS then + self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) + end + self:__TaskTargetSmoked(5,task) + else + local textmark = self.gettext:GetEntry("NOSMOKETASK",self.locale) + text = string.format(textmark, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr) + self:T(self.lid..text) + --local m=MESSAGE:New(text,"10","Tasking"):ToAll() + if self.UseSRS then + self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) + end end - self:__TaskTargetSmoked(5,task) else text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end @@ -4793,22 +4853,27 @@ end -- @param #string Event -- @param #string To -- @param Ops.PlayerTask#PLAYERTASK Task +-- @param #boolean Silent If true, suppress message output on cancel. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:onafterTaskCancelled(From, Event, To, Task) +function PLAYERTASKCONTROLLER:onafterTaskCancelled(From, Event, To, Task, Silent) self:T({From, Event, To}) self:T(self.lid.."TaskCancelled") - local canceltxt = self.gettext:GetEntry("TASKCANCELLED",self.locale) - local canceltxttts = self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale) - local taskname = string.format(canceltxt, Task.PlayerTaskNr, tostring(Task.Type)) - if not self.NoScreenOutput then - self:_SendMessageToClients(taskname,15) - --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + if Silent ~= true then + local canceltxt = self.gettext:GetEntry("TASKCANCELLED",self.locale) + local canceltxttts = self.gettext:GetEntry("TASKCANCELLEDTTS",self.locale) + local taskname = string.format(canceltxt, Task.PlayerTaskNr, tostring(Task.Type)) + + if self.NoScreenOutput ~= true then + self:_SendMessageToClients(taskname,15) + --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + end + + if self.UseSRS then + taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType)) + self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) + end + end - if self.UseSRS then - taskname = string.format(canceltxttts, self.MenuName or self.Name, Task.PlayerTaskNr, tostring(Task.TTSType)) - self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) - end - local clients=Task:GetClientObjects() for _,client in pairs(clients) do self:_RemoveMenuEntriesForTask(Task,client) From 255a652d4d45c5fb860df4410298a9e9e96b5612 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Nov 2025 16:52:30 +0100 Subject: [PATCH 264/349] xx --- Moose Development/Moose/Core/Set.lua | 231 ++++++++------------------- 1 file changed, 66 insertions(+), 165 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 135d6cf82..c67de6ad4 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -89,11 +89,23 @@ do -- SET_BASE Index = {}, Database = nil, CallScheduler = nil, - + Filter = {}, + FilterCoalitionNumbers = { + [coalition.side.RED+1] = "red", + [coalition.side.BLUE+1] = "blue", + [coalition.side.NEUTRAL+1] = "neutral", + }, + FilterMeta = { + Coalitions = { + ["red"] = coalition.side.RED, + ["blue"] = coalition.side.BLUE, + ["neutral"] = coalition.side.NEUTRAL, + }, + }, } --- Filters - -- @type SET_BASE.Filters + -- @type SET_BASE.Filter -- @field #table Coalition Coalitions -- @field #table Prefix Prefixes. @@ -197,6 +209,32 @@ do -- SET_BASE return self end + + --- Builds a set of objects of same coalitions. + -- Possible current coalitions are red, blue and neutral. + -- @param #SET_BASE self + -- @param #string Coalitions Can take the following values: "red", "blue", "neutral" and coalition.side.RED, coalition.side.BLUE,coalition.side.NEUTRAL + -- @param #boolean Clear If `true`, clear any previously defined filters. + -- @return #SET_BASE self + function SET_BASE:FilterCoalitions( Coalitions, Clear ) + + if Clear or (not self.Filter.Coalitions) then + self.Filter.Coalitions = {} + end + + -- Ensure table. + if type(Coalitions) ~= "table" then Coalitions = {Coalitions} end + for CoalitionID, Coalition in pairs( Coalitions ) do + local coalition = Coalition + if type(Coalition) == "number" then + coalition = self.FilterCoalitionNumbers[Coalition+1] or "unknown" + --self:I("Filter Coaltion for "..coalition) + end + self.Filter.Coalitions[coalition] = coalition + end + + return self + end --- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self @@ -1343,21 +1381,6 @@ do -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @param #boolean Clear If `true`, clear any previously defined filters. -- @return #SET_GROUP self - function SET_GROUP:FilterCoalitions( Coalitions, Clear ) - - if Clear or (not self.Filter.Coalitions) then - self.Filter.Coalitions = {} - end - - -- Ensure table. - Coalitions = UTILS.EnsureTable(Coalitions, false) - - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - - return self - end --- Builds a set of groups out of categories. -- Possible current categories are plane, helicopter, ground, ship. @@ -2381,25 +2404,13 @@ do -- SET_UNIT local UnitFound = self.Set[UnitName] return UnitFound end - + --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_UNIT self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_UNIT self - function SET_UNIT:FilterCoalitions( Coalitions ) - self.Filter.Coalitions = {} - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - - return self - end --- Builds a set of units out of categories. -- Possible current categories are plane, helicopter, ground, ship. @@ -3597,25 +3608,13 @@ do -- SET_STATIC local StaticFound = self.Set[StaticName] return StaticFound end - + --- Builds a set of units of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_STATIC self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_STATIC self - function SET_STATIC:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self - end - + --- Builds a set of statics in zones. -- @param #SET_STATIC self @@ -4390,25 +4389,13 @@ do -- SET_CLIENT end return self end - + --- Builds a set of clients of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CLIENT self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_CLIENT self - function SET_CLIENT:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self - end - + --- Builds a set of clients out of categories. -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_CLIENT self @@ -5106,24 +5093,12 @@ do -- SET_PLAYER local ClientFound = self.Set[PlayerName] return ClientFound end - + --- Builds a set of clients of coalitions joined by specific players. -- Possible current coalitions are red, blue and neutral. -- @param #SET_PLAYER self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_PLAYER self - function SET_PLAYER:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self - end --- Builds a set of players in zones. -- @param #SET_PLAYER self @@ -5592,24 +5567,12 @@ do -- SET_AIRBASE return RandomAirbase end - + --- Builds a set of airbases of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_AIRBASE self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_AIRBASE self - function SET_AIRBASE:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self - end --- Builds a set of airbases out of categories. -- Possible current categories are plane, helicopter, ground, ship. @@ -5896,7 +5859,7 @@ do -- SET_CARGO return self end - --- (R2.1) Add CARGO to SET_CARGO. + --- Add CARGO to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Cargo.Cargo#CARGO Cargo A single cargo. -- @return Core.Set#SET_CARGO self @@ -5907,7 +5870,7 @@ do -- SET_CARGO return self end - --- (R2.1) Add CARGOs to SET_CARGO. + --- Add CARGOs to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param #string AddCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self @@ -5922,7 +5885,7 @@ do -- SET_CARGO return self end - --- (R2.1) Remove CARGOs from SET_CARGO. + --- Remove CARGOs from SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Cargo.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. -- @return Core.Set#SET_CARGO self @@ -5937,7 +5900,7 @@ do -- SET_CARGO return self end - --- (R2.1) Finds a Cargo based on the Cargo Name. + --- Finds a Cargo based on the Cargo Name. -- @param #SET_CARGO self -- @param #string CargoName -- @return Cargo.Cargo#CARGO The found Cargo. @@ -5946,26 +5909,14 @@ do -- SET_CARGO local CargoFound = self.Set[CargoName] return CargoFound end - - --- (R2.1) Builds a set of cargos of coalitions. + + --- Builds a set of cargos of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CARGO self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_CARGO self - function SET_CARGO:FilterCoalitions( Coalitions ) -- R2.1 - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self - end - - --- (R2.1) Builds a set of cargos of defined cargo types. + + --- Builds a set of cargos of defined cargo types. -- Possible current types are those types known within DCS world. -- @param #SET_CARGO self -- @param #string Types Can take those type strings known within DCS world. @@ -5983,7 +5934,7 @@ do -- SET_CARGO return self end - --- (R2.1) Builds a set of cargos of defined countries. + --- Builds a set of cargos of defined countries. -- Possible current countries are those known within DCS world. -- @param #SET_CARGO self -- @param #string Countries Can take those country strings known within DCS world. @@ -6019,7 +5970,7 @@ do -- SET_CARGO return self end - --- (R2.1) Starts the filtering. + --- Starts the filtering. -- @param #SET_CARGO self -- @return #SET_CARGO self function SET_CARGO:FilterStart() -- R2.1 @@ -6044,7 +5995,7 @@ do -- SET_CARGO return self end - --- (R2.1) Handles the Database to check on an event (birth) that the Object was added in the Database. + --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA Event @@ -6056,7 +6007,7 @@ do -- SET_CARGO return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- (R2.1) Handles the Database to check on any event that Object exists in the Database. + --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA Event @@ -6068,7 +6019,7 @@ do -- SET_CARGO return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end - --- (R2.1) Iterate the SET_CARGO and call an iterator function for each CARGO, providing the CARGO and optional parameters. + --- Iterate the SET_CARGO and call an iterator function for each CARGO, providing the CARGO and optional parameters. -- @param #SET_CARGO self -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. -- @return #SET_CARGO self @@ -6080,7 +6031,7 @@ do -- SET_CARGO return self end - --- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#COORDINATE}. + --- Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#COORDINATE}. -- @param #SET_CARGO self -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Cargo.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. @@ -6155,7 +6106,7 @@ do -- SET_CARGO return FirstCargo end - --- (R2.1) + --- -- @param #SET_CARGO self -- @param AI.AI_Cargo#AI_CARGO MCargo -- @return #SET_CARGO self @@ -6214,7 +6165,7 @@ do -- SET_CARGO return MCargoInclude end - --- (R2.1) Handles the OnEventNewCargo event for the Set. + --- Handles the OnEventNewCargo event for the Set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData function SET_CARGO:OnEventNewCargo( EventData ) -- R2.1 @@ -6228,7 +6179,7 @@ do -- SET_CARGO end end - --- (R2.1) Handles the OnDead or OnCrash event for alive units set. + --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData function SET_CARGO:OnEventDeleteCargo( EventData ) -- R2.1 @@ -6707,7 +6658,6 @@ do -- SET_ZONE else self.objectset = {Objects} end - self:_TriggerCheck(true) self:__TriggerRunCheck(self.Checktime) return self @@ -7316,28 +7266,11 @@ do -- SET_OPSZONE return self end - + --- Builds a set of groups of coalitions. Possible current coalitions are red, blue and neutral. -- @param #SET_OPSZONE self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`. -- @return #SET_OPSZONE self - function SET_OPSZONE:FilterCoalitions(Coalitions) - - -- Create an empty set. - if not self.Filter.Coalitions then - self.Filter.Coalitions={} - end - - -- Ensure we got a table. - Coalitions=UTILS.EnsureTable(Coalitions, false) - - -- Set filter. - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - - return self - end --- Filters for the defined collection. -- @param #SET_OPSZONE self @@ -7931,26 +7864,6 @@ do -- SET_OPSGROUP -- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`. -- @param #boolean Clear If `true`, clear any previously defined filters. -- @return #SET_OPSGROUP self - function SET_OPSGROUP:FilterCoalitions(Coalitions, Clear) - - -- Create an empty set. - if Clear or not self.Filter.Coalitions then - self.Filter.Coalitions={} - end - - -- Ensure we got a table. - if type(Coalitions)~="table" then - Coalitions = {Coalitions} - end - - -- Set filter. - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - - return self - end - --- Builds a set of groups out of categories. -- @@ -8922,24 +8835,12 @@ do -- SET_DYNAMICCARGO --self:T2( DCargoInclude ) return DCargoInclude end - + --- Builds a set of dynamic cargo of defined coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_DYNAMICCARGO self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". -- @return #SET_DYNAMICCARGO self - function SET_DYNAMICCARGO:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self - end --- Builds a set of dynamic cargo of defined dynamic cargo type names. -- @param #SET_DYNAMICCARGO self From 6f052492148e86343700d553660da39f9d506ce0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 13 Nov 2025 15:54:47 +0100 Subject: [PATCH 265/349] XX --- Moose Development/Moose/AI/.gitignore | 2 - Moose Development/Moose/Core/Event.lua | 30 - Moose Development/Moose/Core/Set.lua | 470 -------- .../Moose/Functional/Artillery.lua | 27 - .../Moose/Functional/Formation.lua | 1000 +++++++++++++++++ .../Moose/Functional/Suppression.lua | 2 +- Moose Development/Moose/Modules_local.lua | 7 + Moose Development/Moose/Ops/OpsGroup.lua | 2 +- Moose Development/Moose/Ops/RescueHelo.lua | 9 +- Moose Development/Moose/Wrapper/Client.lua | 22 - .../Moose/Wrapper/Controllable.lua | 2 +- Moose Setup/Moose.files | 1 + 12 files changed, 1013 insertions(+), 561 deletions(-) delete mode 100644 Moose Development/Moose/AI/.gitignore create mode 100644 Moose Development/Moose/Functional/Formation.lua diff --git a/Moose Development/Moose/AI/.gitignore b/Moose Development/Moose/AI/.gitignore deleted file mode 100644 index 73a72496a..000000000 --- a/Moose Development/Moose/AI/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/AI_Patrol.lua -/AI_BAI.lua diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 8e80c69c4..b99dbc771 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1063,36 +1063,6 @@ 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. diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index f9134d6a5..b21d13275 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1106,30 +1106,6 @@ do -- --self:F({ GroupObject = GroupObject:GetName() }) -- end -- - -- While this is a good example, there is a catch. - -- Imagine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. - -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. - -- See the modified example: - -- - -- -- Now we have a constructor of the class AI_CARGO_DISPATCHER, that receives the SetHelicopter as a parameter. - -- -- Within that constructor, we want to set an enclosed event handler OnAfterDead for SetHelicopter. - -- -- But within the OnAfterDead method, we want to refer to the self variable of the AI_CARGO_DISPATCHER. - -- - -- function AI_CARGO_DISPATCHER:New(SetCarrier, SetCargo, SetDeployZones) - -- - -- local self = BASE:Inherit(self, FSM:New()) -- #AI_CARGO_DISPATCHER - -- - -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. - -- -- Note the "." notation, and the explicit declaration of SetHelicopter, which would be using the ":" notation the implicit self variable declaration. - -- - -- function SetHelicopter.OnAfterDead(SetHelicopter, From, Event, To, GroupObject) - -- SetHelicopter:F({ GroupObject = GroupObject:GetName() }) - -- self.PickupCargo[GroupObject] = nil -- So here I clear the PickupCargo table entry of the self object AI_CARGO_DISPATCHER. - -- self.CarrierHome[GroupObject] = nil - -- end - -- - -- end - -- -- === -- @field #SET_GROUP SET_GROUP SET_GROUP = { @@ -2269,28 +2245,6 @@ do -- SET_UNIT -- --self:F({ UnitObject = UnitObject:GetName() }) -- end -- - -- While this is a good example, there is a catch. - -- Imagine you want to execute the code above, the the self would need to be from the object declared outside (above) the OnAfterDead method. - -- So, the self would need to contain another object. Fortunately, this can be done, but you must use then the **`.`** notation for the method. - -- See the modified example: - -- - -- -- Now we have a constructor of the class AI_CARGO_DISPATCHER, that receives the SetHelicopter as a parameter. - -- -- Within that constructor, we want to set an enclosed event handler OnAfterDead for SetHelicopter. - -- -- But within the OnAfterDead method, we want to refer to the self variable of the AI_CARGO_DISPATCHER. - -- - -- function ACLASS:New(SetCarrier, SetCargo, SetDeployZones) - -- - -- local self = BASE:Inherit(self, FSM:New()) -- #AI_CARGO_DISPATCHER - -- - -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. - -- -- Note the "." notation, and the explicit declaration of SetHelicopter, which would be using the ":" notation the implicit self variable declaration. - -- - -- function SetHelicopter.OnAfterDead(SetHelicopter, From, Event, To, UnitObject) - -- SetHelicopter:F({ UnitObject = UnitObject:GetName() }) - -- self.array[UnitObject] = nil -- So here I clear the array table entry of the self object ACLASS. - -- end - -- - -- end -- === -- @field #SET_UNIT SET_UNIT SET_UNIT = { @@ -5781,430 +5735,6 @@ do -- SET_AIRBASE end -do -- SET_CARGO - - --- - -- @type SET_CARGO - -- @extends Core.Set#SET_BASE - - --- Mission designers can use the @{Core.Set#SET_CARGO} class to build sets of cargos optionally belonging to certain: - -- - -- * Coalitions - -- * Types - -- * Name or Prefix - -- - -- ## SET_CARGO constructor - -- - -- Create a new SET_CARGO object with the @{#SET_CARGO.New} method: - -- - -- * @{#SET_CARGO.New}: Creates a new SET_CARGO object. - -- - -- ## Add or Remove CARGOs from SET_CARGO - -- - -- CARGOs can be added and removed using the @{Core.Set#SET_CARGO.AddCargosByName} and @{Core.Set#SET_CARGO.RemoveCargosByName} respectively. - -- These methods take a single CARGO name or an array of CARGO names to be added or removed from SET_CARGO. - -- - -- ## SET_CARGO filter criteria - -- - -- You can set filter criteria to automatically maintain the SET_CARGO contents. - -- Filter criteria are defined by: - -- - -- * @{#SET_CARGO.FilterCoalitions}: Builds the SET_CARGO with the cargos belonging to the coalition(s). - -- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the same string(s). **Attention!** LUA regular expression apply here, so special characters in names like minus, dot, hash (#) etc might lead to unexpected results. - -- Have a read through here to understand the application of regular expressions: [LUA regular expressions](https://riptutorial.com/lua/example/20315/lua-pattern-matching) - -- * @{#SET_CARGO.FilterTypes}: Builds the SET_CARGO with the cargos belonging to the cargo type(s). - -- * @{#SET_CARGO.FilterCountries}: Builds the SET_CARGO with the cargos belonging to the country(ies). - -- - -- Once the filter criteria have been set for the SET_CARGO, you can start filtering using: - -- - -- * @{#SET_CARGO.FilterStart}: Starts the filtering of the cargos within the SET_CARGO. - -- - -- ## SET_CARGO iterators - -- - -- Once the filters have been defined and the SET_CARGO has been built, you can iterate the SET_CARGO with the available iterator methods. - -- The iterator methods will walk the SET_CARGO set, and call for each cargo within the set a function that you provide. - -- The following iterator methods are currently available within the SET_CARGO: - -- - -- * @{#SET_CARGO.ForEachCargo}: Calls a function for each cargo it finds within the SET_CARGO. - -- - -- @field #SET_CARGO SET_CARGO - SET_CARGO = { - ClassName = "SET_CARGO", - Cargos = {}, - Filter = { - Coalitions = nil, - Types = nil, - Countries = nil, - ClientPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - }, - } - - --- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. - -- @param #SET_CARGO self - -- @return #SET_CARGO - -- @usage - -- -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos. - -- DatabaseSet = SET_CARGO:New() - function SET_CARGO:New() -- R2.1 - -- Inherits from BASE - local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.CARGOS)) -- #SET_CARGO - - return self - end - - --- Add CARGO to SET_CARGO. - -- @param Core.Set#SET_CARGO self - -- @param Cargo.Cargo#CARGO Cargo A single cargo. - -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargo(Cargo) -- R2.4 - - self:Add(Cargo:GetName(), Cargo) - - return self - end - - --- Add CARGOs to SET_CARGO. - -- @param Core.Set#SET_CARGO self - -- @param #string AddCargoNames A single name or an array of CARGO names. - -- @return Core.Set#SET_CARGO self - function SET_CARGO:AddCargosByName(AddCargoNames) -- R2.1 - - local AddCargoNamesArray = (type(AddCargoNames) == "table") and AddCargoNames or { AddCargoNames } - - for AddCargoID, AddCargoName in pairs(AddCargoNamesArray) do - self:Add(AddCargoName, CARGO:FindByName(AddCargoName)) - end - - return self - end - - --- Remove CARGOs from SET_CARGO. - -- @param Core.Set#SET_CARGO self - -- @param Cargo.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. - -- @return Core.Set#SET_CARGO self - function SET_CARGO:RemoveCargosByName(RemoveCargoNames) -- R2.1 - - local RemoveCargoNamesArray = (type(RemoveCargoNames) == "table") and RemoveCargoNames or { RemoveCargoNames } - - for RemoveCargoID, RemoveCargoName in pairs(RemoveCargoNamesArray) do - self:Remove(RemoveCargoName.CargoName) - end - - return self - end - - --- Finds a Cargo based on the Cargo Name. - -- @param #SET_CARGO self - -- @param #string CargoName - -- @return Cargo.Cargo#CARGO The found Cargo. - function SET_CARGO:FindCargo(CargoName) -- R2.1 - - local CargoFound = self.Set[CargoName] - return CargoFound - end - - --- Builds a set of cargos of coalitions. - -- Possible current coalitions are red, blue and neutral. - -- @param #SET_CARGO self - -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". - -- @return #SET_CARGO self - - --- Builds a set of cargos of defined cargo types. - -- Possible current types are those types known within DCS world. - -- @param #SET_CARGO self - -- @param #string Types Can take those type strings known within DCS world. - -- @return #SET_CARGO self - function SET_CARGO:FilterTypes(Types) -- R2.1 - if not self.Filter.Types then - self.Filter.Types = {} - end - if type(Types) ~= "table" then - Types = { Types } - end - for TypeID, Type in pairs(Types) do - self.Filter.Types[Type] = Type - end - return self - end - - --- Builds a set of cargos of defined countries. - -- Possible current countries are those known within DCS world. - -- @param #SET_CARGO self - -- @param #string Countries Can take those country strings known within DCS world. - -- @return #SET_CARGO self - function SET_CARGO:FilterCountries(Countries) -- R2.1 - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type(Countries) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs(Countries) do - self.Filter.Countries[Country] = Country - end - return self - end - - --- Builds a set of CARGOs that contain a given string in their name. - -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all cargos that **contain** the string. - -- @param #SET_CARGO self - -- @param #string Prefixes The string pattern(s) that need to be in the cargo name. Can also be passed as a `#table` of strings. - -- @return #SET_CARGO self - function SET_CARGO:FilterPrefixes(Prefixes) -- R2.1 - if not self.Filter.CargoPrefixes then - self.Filter.CargoPrefixes = {} - end - if type(Prefixes) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs(Prefixes) do - self.Filter.CargoPrefixes[Prefix] = Prefix - end - return self - end - - --- Starts the filtering. - -- @param #SET_CARGO self - -- @return #SET_CARGO self - function SET_CARGO:FilterStart() -- R2.1 - - if _DATABASE then - self:_FilterStart() - self:HandleEvent(EVENTS.NewCargo) - self:HandleEvent(EVENTS.DeleteCargo) - end - - return self - end - - --- Stops the filtering for the defined collection. - -- @param #SET_CARGO self - -- @return #SET_CARGO self - function SET_CARGO:FilterStop() - - self:UnHandleEvent(EVENTS.NewCargo) - self:UnHandleEvent(EVENTS.DeleteCargo) - - return self - end - - --- Handles the Database to check on an event (birth) that the Object was added in the Database. - -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! - -- @param #SET_CARGO self - -- @param Core.Event#EVENTDATA Event - -- @return #string The name of the CARGO - -- @return #table The CARGO - function SET_CARGO:AddInDatabase(Event) -- R2.1 - --self:F3({ Event }) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] - end - - --- Handles the Database to check on any event that Object exists in the Database. - -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! - -- @param #SET_CARGO self - -- @param Core.Event#EVENTDATA Event - -- @return #string The name of the CARGO - -- @return #table The CARGO - function SET_CARGO:FindInDatabase(Event) -- R2.1 - --self:F3({ Event }) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] - end - - --- Iterate the SET_CARGO and call an iterator function for each CARGO, providing the CARGO and optional parameters. - -- @param #SET_CARGO self - -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. - -- @return #SET_CARGO self - function SET_CARGO:ForEachCargo(IteratorFunction, ...) -- R2.1 - --self:F2(arg) - - self:ForEach(IteratorFunction, arg, self:GetSet()) - - return self - end - - --- Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#COORDINATE}. - -- @param #SET_CARGO self - -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. - -- @return Cargo.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. - function SET_CARGO:FindNearestCargoFromPointVec2(Coordinate) -- R2.1 - --self:F2(Coordinate) - - local NearestCargo = self:FindNearestObjectFromPointVec2(Coordinate) - return NearestCargo - end - - --- - -- @param #SET_CARGO self - function SET_CARGO:FirstCargoWithState(State) - - local FirstCargo = nil - - for CargoName, Cargo in pairs(self.Set) do - if Cargo:Is(State) then - FirstCargo = Cargo - break - end - end - - return FirstCargo - end - - --- - -- @param #SET_CARGO self - function SET_CARGO:FirstCargoWithStateAndNotDeployed(State) - - local FirstCargo = nil - - for CargoName, Cargo in pairs(self.Set) do - if Cargo:Is(State) and not Cargo:IsDeployed() then - FirstCargo = Cargo - break - end - end - - return FirstCargo - end - - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded. - -- @param #SET_CARGO self - -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. - function SET_CARGO:FirstCargoUnLoaded() - local FirstCargo = self:FirstCargoWithState("UnLoaded") - return FirstCargo - end - - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is UnLoaded and not Deployed. - -- @param #SET_CARGO self - -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. - function SET_CARGO:FirstCargoUnLoadedAndNotDeployed() - local FirstCargo = self:FirstCargoWithStateAndNotDeployed("UnLoaded") - return FirstCargo - end - - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Loaded. - -- @param #SET_CARGO self - -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. - function SET_CARGO:FirstCargoLoaded() - local FirstCargo = self:FirstCargoWithState("Loaded") - return FirstCargo - end - - --- Iterate the SET_CARGO while identifying the first @{Cargo.Cargo#CARGO} that is Deployed. - -- @param #SET_CARGO self - -- @return Cargo.Cargo#CARGO The first @{Cargo.Cargo#CARGO}. - function SET_CARGO:FirstCargoDeployed() - local FirstCargo = self:FirstCargoWithState("Deployed") - return FirstCargo - end - - --- - -- @param #SET_CARGO self - -- @param AI.AI_Cargo#AI_CARGO MCargo - -- @return #SET_CARGO self - function SET_CARGO:IsIncludeObject(MCargo) -- R2.1 - --self:F2(MCargo) - - local MCargoInclude = true - - if MCargo then - local MCargoName = MCargo:GetName() - - if self.Filter.Coalitions then - local MCargoCoalition = false - for CoalitionID, CoalitionName in pairs(self.Filter.Coalitions) do - local CargoCoalitionID = MCargo:GetCoalition() - --self:T(3({ "Coalition:", CargoCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName }) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == CargoCoalitionID then - MCargoCoalition = true - end - end - --self:F({ "Evaluated Coalition", MCargoCoalition }) - MCargoInclude = MCargoInclude and MCargoCoalition - end - - if self.Filter.Types then - local MCargoType = false - for TypeID, TypeName in pairs(self.Filter.Types) do - --self:T(3({ "Type:", MCargo:GetType(), TypeName }) - if TypeName == MCargo:GetType() then - MCargoType = true - end - end - --self:F({ "Evaluated Type", MCargoType }) - MCargoInclude = MCargoInclude and MCargoType - end - - if self.Filter.CargoPrefixes then - local MCargoPrefix = false - for CargoPrefixId, CargoPrefix in pairs(self.Filter.CargoPrefixes) do - --self:T(3({ "Prefix:", string.find(MCargo.Name, CargoPrefix, 1), CargoPrefix }) - if string.find(MCargo.Name, CargoPrefix, 1) then - MCargoPrefix = true - end - end - --self:F({ "Evaluated Prefix", MCargoPrefix }) - MCargoInclude = MCargoInclude and MCargoPrefix - end - end - - if self.Filter.Functions and MCargoInclude then - local MClientFunc = self:_EvalFilterFunctions(MCargo) - MCargoInclude = MCargoInclude and MClientFunc - end - - --self:T(2(MCargoInclude) - return MCargoInclude - end - - --- Handles the OnEventNewCargo event for the Set. - -- @param #SET_CARGO self - -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventNewCargo(EventData) -- R2.1 - - --self:F({ "New Cargo", EventData }) - - if EventData.Cargo then - if EventData.Cargo and self:IsIncludeObject(EventData.Cargo) then - self:Add(EventData.Cargo.Name, EventData.Cargo) - end - end - end - - --- Handles the OnDead or OnCrash event for alive units set. - -- @param #SET_CARGO self - -- @param Core.Event#EVENTDATA EventData - function SET_CARGO:OnEventDeleteCargo(EventData) -- R2.1 - --self:F3({ EventData }) - - if EventData.Cargo then - local Cargo = _DATABASE:FindCargo(EventData.Cargo.Name) - if Cargo and Cargo.Name then - - -- When cargo was deleted, it may probably be because of an S_EVENT_DEAD. - -- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call. - -- And this is a problem because it will remove all entries from the SET_CARGOs. - -- To prevent this from happening, the Cargo object has a flag NoDestroy. - -- When true, the SET_CARGO won't Remove the Cargo object from the set. - -- This flag is switched off after the event handlers have been called in the EVENT class. - --self:F({ CargoNoDestroy = Cargo.NoDestroy }) - if Cargo.NoDestroy then - else - self:Remove(Cargo.Name) - end - end - end - end - -end do -- SET_ZONE diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index a9dc78ddb..2f9e96373 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -419,14 +419,6 @@ -- arty set, battery "Mortar Bravo", rearming group "Ammo Truck M939" -- Note that the name of the rearming group has to be given in quotation marks and spelt exactly as the group name defined in the mission editor. -- --- ## Transporting --- --- ARTY groups can be transported to another location as @{Cargo.Cargo} by means of classes such as @{AI.AI_Cargo_APC}, @{AI.AI_Cargo_Dispatcher_APC}, --- @{AI.AI_Cargo_Helicopter}, @{AI.AI_Cargo_Dispatcher_Helicopter} or @{AI.AI_Cargo_Airplane}. --- --- In order to do this, one needs to define an ARTY object via the @{#ARTY.NewFromCargoGroup}(*cargogroup*, *alias*) function. --- The first argument *cargogroup* has to be a @{Cargo.CargoGroup#CARGO_GROUP} object. The second argument *alias* is a string which can be freely chosen by the user. --- -- ## Fine Tuning -- -- The mission designer has a few options to tailor the ARTY object according to his needs. @@ -511,25 +503,6 @@ -- -- Start ARTY process. -- normandy:Start() -- --- ### Transportation as Cargo --- This example demonstates how an ARTY group can be transported to another location as cargo. --- -- Define a group as CARGO_GROUP --- CargoGroupMortars=CARGO_GROUP:New(GROUP:FindByName("Mortars"), "Mortars", "Mortar Platoon Alpha", 100 , 10) --- --- -- Define the mortar CARGO GROUP as ARTY object --- mortars=ARTY:NewFromCargoGroup(CargoGroupMortars, "Mortar Platoon Alpha") --- --- -- Start ARTY process --- mortars:Start() --- --- -- Setup AI cargo dispatcher for e.g. helos --- SetHeloCarriers = SET_GROUP:New():FilterPrefixes("CH-47D"):FilterStart() --- SetCargoMortars = SET_CARGO:New():FilterTypes("Mortars"):FilterStart() --- SetZoneDepoly = SET_ZONE:New():FilterPrefixes("Deploy"):FilterStart() --- CargoHelo=AI_CARGO_DISPATCHER_HELICOPTER:New(SetHeloCarriers, SetCargoMortars, SetZoneDepoly) --- CargoHelo:Start() --- The ARTY group will be transported and resume its normal operation after it has been deployed. New targets can be assigned at any time also during the transportation process. --- -- @field #ARTY ARTY={ ClassName="ARTY", diff --git a/Moose Development/Moose/Functional/Formation.lua b/Moose Development/Moose/Functional/Formation.lua new file mode 100644 index 000000000..9467c1380 --- /dev/null +++ b/Moose Development/Moose/Functional/Formation.lua @@ -0,0 +1,1000 @@ +--- **Functional** - Build large airborne formations of aircraft. +-- +-- **Features:** +-- +-- * Build in-air formations consisting of more than 40 aircraft as one group. +-- * Build different formation types. +-- * Assign a group leader that will guide the large formation path. +-- +-- === +-- +-- ## Additional Material: +-- +-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/FORMATION) +-- * **YouTube videos:** [Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0bFIJ9jIdYM22uaWmIN4oz) +-- * **Guides:** None +-- +-- === +-- +-- ### Author: **FlightControl** +-- ### Contributions: **Applevangelist** +-- +-- === +-- +-- @module Functional.FORMATION +-- @image AI_Large_Formations.JPG + +--- FORMATION class +-- @type FORMATION +-- @extends Core.Fsm#FSM_SET +-- @field Wrapper.Unit#UNIT FollowUnit +-- @field Core.Set#SET_GROUP FollowGroupSet +-- @field #string FollowName +-- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. +-- @field #number FollowDistance The current follow distance. +-- @field DCS#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup. +-- @field DCS#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup. +-- @field #number dtFollow Time step between position updates. + + +--- Build (large) formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader. +-- +-- FORMATION makes AI @{Wrapper.Group#GROUP}s fly in formation of various compositions. +-- The FORMATION class models formations in a different manner than the internal DCS formation logic!!! +-- The purpose of the class is to: +-- +-- * Make formation building a process that can be managed while in flight, rather than a task. +-- * Human players can guide formations, consisting of larget planes. +-- * Build large formations (like a large bomber field). +-- * Form formations that DCS does not support off-the-shelve. +-- +-- A few remarks: +-- +-- * Depending on the type of plane, the change in direction by the leader may result in the formation getting disentangled while in flight and needs to be rebuild. +-- * Formations are vulnerable to collissions, but is depending on the type of plane, the distance between the planes and the speed and angle executed by the leader. +-- * Formations may take a while to build up. +-- +-- As a result, the FORMATION is not perfect, but is very useful to: +-- +-- * Model large formations when flying straight line. You can build close formations when doing this. +-- * Make humans guide a large formation, when the planes are wide from each other. +-- +-- ## FORMATION construction +-- +-- Create a new SPAWN object with the @{#FORMATION.New} method: +-- +-- * @{#FORMATION.New}(): Creates a new FORMATION object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT} or a @{Wrapper.Unit#UNIT}, with an optional briefing text. +-- +-- ## Formation methods +-- +-- The following methods can be used to set or change the formation: +-- +-- * @{#FORMATION.FormationLine}(): Form a line formation (core formation function). +-- * @{#FORMATION.FormationTrail}(): Form a trail formation. +-- * @{#FORMATION.FormationLeftLine}(): Form a left line formation. +-- * @{#FORMATION.FormationRightLine}(): Form a right line formation. +-- * @{#FORMATION.FormationRightWing}(): Form a right wing formation. +-- * @{#FORMATION.FormationLeftWing}(): Form a left wing formation. +-- * @{#FORMATION.FormationCenterWing}(): Form a center wing formation. +-- * @{#FORMATION.FormationCenterVic}(): Form a Vic formation (same as CenterWing). +-- * @{#FORMATION.FormationCenterBoxed}(): Form a center boxed formation. +-- +-- ## Randomization +-- +-- Use the method @{FORMATION#FORMATION.SetFlightRandomization}() to simulate the formation flying errors that pilots make while in formation. Is a range set in meters. +-- +-- @usage +-- local FollowGroupSet = SET_GROUP:New():FilterCategories("plane"):FilterCoalitions("blue"):FilterPrefixes("Follow"):FilterStart() +-- local LeaderUnit = UNIT:FindByName( "Leader" ) +-- local LargeFormation = FORMATION:New( LeaderUnit, FollowGroupSet, "Center Wing Formation", "Briefing" ) +-- LargeFormation:FormationCenterWing( 500, 50, 0, 250, 250 ) +-- LargeFormation:__Start( 1 ) +-- +-- @field #FORMATION +FORMATION = { + ClassName = "FORMATION", + FollowName = nil, -- The Follow Name + FollowUnit = nil, + FollowGroupSet = nil, + FollowScheduler = nil, + OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, + OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, + dtFollow = 0.5, +} + +--- @type FORMATION.Formation +-- @field #number None +-- @field #number Line +-- @field #number Trail +-- @field #number Stack +-- @field #number LeftLine +-- @field #number RightLine +-- @field #number LeftWing +-- @field #number RightWing +-- @field #number Vic +-- @field #number Box +FORMATION.Formation = { + None = 1, + Line = 2, + Trail = 3, + Stack = 4, + LeftLine = 5, + RightLine = 6, + LeftWing = 7, + RightWing = 8, + Vic = 9, + Box = 10, +} + +--- FORMATION class constructor for an AI group +-- @param #FORMATION self +-- @param Wrapper.Unit#UNIT FollowUnit The UNIT leading the FolllowGroupSet. +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string FollowName Name of the escort. +-- @return #FORMATION self +function FORMATION:New( FollowUnit, FollowGroupSet, FollowName ) + local self = BASE:Inherit( self, FSM_SET:New( FollowGroupSet ) ) + self:F( { FollowUnit, FollowGroupSet, FollowName } ) + + self.FollowUnit = FollowUnit -- Wrapper.Unit#UNIT + self.FollowGroupSet = FollowGroupSet -- Core.Set#SET_GROUP + + self:SetFlightRandomization( 2 ) + + self:SetStartState( "None" ) + + self:AddTransition( "*", "Stop", "Stopped" ) + + self:AddTransition( {"None", "Stopped"}, "Start", "Following" ) + + self:AddTransition( "*", "FormationLine", "*" ) + + --- Start Trigger for FORMATION + -- @function [parent=#FORMATION] Start + -- @param #FORMATION self + + --- Start Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __Start + -- @param #FORMATION self + -- @param #number Delay + + --- Stop Trigger for FORMATION + -- @function [parent=#FORMATION] Stop + -- @param #FORMATION self + + --- Stop Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __Stop + -- @param #FORMATION self + -- @param #number Delay + + --- FormationLine Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationLine + -- @param #FORMATION self + -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationLine Trigger for FORMATION + -- @function [parent=#FORMATION] FormationLine + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationLine Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationLine + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + self:AddTransition( "*", "FormationTrail", "*" ) + + --- FormationTrail Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationTrail + -- @param #FORMATION self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + + --- FormationTrail Trigger for FORMATION + -- @function [parent=#FORMATION] FormationTrail + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + + --- FormationTrail Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationTrail + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + + self:AddTransition( "*", "FormationStack", "*" ) + + --- FormationStack Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationStack + -- @param #FORMATION self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + + --- FormationStack Trigger for FORMATION + -- @function [parent=#FORMATION] FormationStack + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + + --- FormationStack Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationStack + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + + self:AddTransition( "*", "FormationLeftLine", "*" ) + + --- FormationLeftLine Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationLeftLine + -- @param #FORMATION self + -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationLeftLine Trigger for FORMATION + -- @function [parent=#FORMATION] FormationLeftLine + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationLeftLine Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationLeftLine + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + self:AddTransition( "*", "FormationRightLine", "*" ) + + --- FormationRightLine Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationRightLine + -- @param #FORMATION self + -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationRightLine Trigger for FORMATION + -- @function [parent=#FORMATION] FormationRightLine + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationRightLine Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationRightLine + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + self:AddTransition( "*", "FormationLeftWing", "*" ) + + --- FormationLeftWing Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationLeftWing + -- @param #FORMATION self + -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationLeftWing Trigger for FORMATION + -- @function [parent=#FORMATION] FormationLeftWing + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationLeftWing Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationLeftWing + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + self:AddTransition( "*", "FormationRightWing", "*" ) + + --- FormationRightWing Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationRightWing + -- @param #FORMATION self + -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationRightWing Trigger for FORMATION + -- @function [parent=#FORMATION] FormationRightWing + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationRightWing Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationRightWing + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + self:AddTransition( "*", "FormationCenterWing", "*" ) + + --- FormationCenterWing Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationCenterWing + -- @param #FORMATION self + -- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationCenterWing Trigger for FORMATION + -- @function [parent=#FORMATION] FormationCenterWing + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationCenterWing Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationCenterWing + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + self:AddTransition( "*", "FormationVic", "*" ) + + --- FormationVic Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationVic + -- @param #FORMATION self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationVic Trigger for FORMATION + -- @function [parent=#FORMATION] FormationVic + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + --- FormationVic Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationVic + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + + self:AddTransition( "*", "FormationBox", "*" ) + + --- FormationBox Handler OnAfter for FORMATION + -- @function [parent=#FORMATION] OnAfterFormationBox + -- @param #FORMATION self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + -- @param #number ZLevels The amount of levels on the Z-axis. + + --- FormationBox Trigger for FORMATION + -- @function [parent=#FORMATION] FormationBox + -- @param #FORMATION self + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + -- @param #number ZLevels The amount of levels on the Z-axis. + + --- FormationBox Asynchronous Trigger for FORMATION + -- @function [parent=#FORMATION] __FormationBox + -- @param #FORMATION self + -- @param #number Delay + -- @param #number XStart The start position on the X-axis in meters for the first group. + -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. + -- @param #number ZLevels The amount of levels on the Z-axis. + + self:AddTransition( "*", "Follow", "Following" ) + + self:FormationLeftLine( 500, 0, 250, 250 ) + + self.FollowName = FollowName + + self.CT1 = 0 + self.GT1 = 0 + + return self +end + + +--- Set time interval between updates of the formation. +-- @param #FORMATION self +-- @param #number dt Time step in seconds between formation updates. Default is every 0.5 seconds. +-- @return #FORMATION +function FORMATION:SetFollowTimeInterval(dt) + self.dtFollow=dt or 0.5 + return self +end + +--- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. +-- This allows to visualize where the escort is flying to. +-- @param #FORMATION self +-- @param #boolean SmokeDirection If true, then the direction vector will be smoked. +-- @return #FORMATION +function FORMATION:TestSmokeDirectionVector( SmokeDirection ) + self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false + return self +end + +--- FormationLine Handler OnAfter for FORMATION +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +-- @return #FORMATION +function FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation ) + self:F( { FollowGroupSet, From , Event ,To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation } ) + + XStart = XStart or self.XStart + XSpace = XSpace or self.XSpace + YStart = YStart or self.YStart + YSpace = YSpace or self.YSpace + ZStart = ZStart or self.ZStart + ZSpace = ZSpace or self.ZSpace + + local FollowSet = FollowGroupSet:GetSet() + + local i = 1 --FF i=0 caused first unit to have no XSpace! Probably needs further adjustments. This is just a quick work around. + + for FollowID, FollowGroup in pairs( FollowSet ) do + + local PointVec3 = COORDINATE:New() + PointVec3:SetX( XStart + i * XSpace ) + PointVec3:SetY( YStart + i * YSpace ) + PointVec3:SetZ( ZStart + i * ZSpace ) + + local Vec3 = PointVec3:GetVec3() + FollowGroup:SetState( self, "FormationVec3", Vec3 ) + i = i + 1 + + FollowGroup:SetState( FollowGroup, "Formation", Formation ) + end + + return self + +end + +--- FormationTrail Handler OnAfter for FORMATION +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @return #FORMATION +function FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) + + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0, self.Formation.Trail ) + + return self +end + + +--- FormationStack Handler OnAfter for FORMATION +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @return #FORMATION +function FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) + + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0, self.Formation.Stack ) + + return self +end + +--- FormationLeftLine Handler OnAfter for FORMATION +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +-- @return #FORMATION +function FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) + + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace, self.Formation.LeftLine ) + + return self +end + +--- FormationRightLine Handler OnAfter for FORMATION +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +-- @return #FORMATION +function FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) + + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.Formation.RightLine) + + return self +end + +--- FormationLeftWing Handler OnAfter for FORMATION +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +function FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) + + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.Formation.LeftWing) + + return self +end + +--- FormationRightWing Handler OnAfter for FORMATION +-- @function [parent=#FORMATION] OnAfterFormationRightWing +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +function FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) + + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.Formation.RightWing) + + return self +end + +--- FormationCenterWing Handler OnAfter for FORMATION +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The group AI escorting the FollowUnit. +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +function FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) + + local FollowSet = FollowGroupSet:GetSet() + + local i = 0 + + for FollowID, FollowGroup in pairs( FollowSet ) do + + local PointVec3 = COORDINATE:New() + + local Side = ( i % 2 == 0 ) and 1 or -1 + local Row = i / 2 + 1 + + PointVec3:SetX( XStart + Row * XSpace ) + PointVec3:SetY( YStart ) + PointVec3:SetZ( Side * ( ZStart + i * ZSpace ) ) + + local Vec3 = PointVec3:GetVec3() + FollowGroup:SetState( self, "FormationVec3", Vec3 ) + i = i + 1 + FollowGroup:SetState( FollowGroup, "Formation", self.Formation.Vic ) + end + + return self +end + +--- FormationVic Handle for FORMATION +-- @param #FORMATION self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +-- @return #FORMATION +function FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) + + self:onafterFormationCenterWing(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,ZStart,ZSpace) + + return self +end + +--- FormationBox Handler OnAfter for FORMATION +-- @param #FORMATION self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +-- @param #number ZLevels The amount of levels on the Z-axis. +-- @return #FORMATION +function FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) + + local FollowSet = FollowGroupSet:GetSet() + + local i = 0 + + for FollowID, FollowGroup in pairs( FollowSet ) do + + local PointVec3 = COORDINATE:New() + + local ZIndex = i % ZLevels + local XIndex = math.floor( i / ZLevels ) + local YIndex = math.floor( i / ZLevels ) + + PointVec3:SetX( XStart + XIndex * XSpace ) + PointVec3:SetY( YStart + YIndex * YSpace ) + PointVec3:SetZ( -ZStart - (ZSpace * ZLevels / 2 ) + ZSpace * ZIndex ) + + local Vec3 = PointVec3:GetVec3() + FollowGroup:SetState( self, "FormationVec3", Vec3 ) + i = i + 1 + FollowGroup:SetState( FollowGroup, "Formation", self.Formation.Box ) + end + + return self +end + +--- Use the method @{FORMATION#FORMATION.SetFlightRandomization}() to make the air units in your formation randomize their flight a bit while in formation. +-- @param #FORMATION self +-- @param #number FlightRandomization The formation flying errors that pilots can make while in formation. Is a range set in meters. +-- @return #FORMATION +function FORMATION:SetFlightRandomization( FlightRandomization ) + self.FlightRandomization = FlightRandomization + return self +end + +--- Stop function. Formation will not be updated any more. +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To The to state. +function FORMATION:onafterStop(FollowGroupSet, From, Event, To) + self:E("Stopping formation.") +end + +--- Follow event fuction. Check if coming from state "stopped". If so the transition is rejected. +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To The to state. +function FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) + if From=="Stopped" then + return false -- Deny transition. + end + return true +end + +--- Enter following state. +-- @param #FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To The to state. +function FORMATION:onenterFollowing( FollowGroupSet ) + + if self.FollowUnit:IsAlive() then + + local ClientUnit = self.FollowUnit + + local CT1, CT2, CV1, CV2 + CT1 = ClientUnit:GetState( self, "CT1" ) + + local CuVec3=ClientUnit:GetVec3() + + if CT1 == nil or CT1 == 0 then + ClientUnit:SetState( self, "CV1", CuVec3) + ClientUnit:SetState( self, "CT1", timer.getTime() ) + else + CT1 = ClientUnit:GetState( self, "CT1" ) + CT2 = timer.getTime() + CV1 = ClientUnit:GetState( self, "CV1" ) + CV2 = CuVec3 + + ClientUnit:SetState( self, "CT1", CT2 ) + ClientUnit:SetState( self, "CV1", CV2 ) + end + + --FollowGroupSet:ForEachGroupAlive( bla, self, ClientUnit, CT1, CV1, CT2, CV2) + + for _,_group in pairs(FollowGroupSet:GetSet()) do + local group=_group --Wrapper.Group#GROUP + if group and group:IsAlive() then + self:FollowMe(group, ClientUnit, CT1, CV1, CT2, CV2) + end + end + + self:__Follow( -self.dtFollow ) + end + +end + +--- Follow me. +-- @param #FORMATION self +-- @param Wrapper.Group#GROUP FollowGroup Follow group. +-- @param Wrapper.Unit#UNIT ClientUnit Client Unit. +-- @param DCS#Time CT1 Time +-- @param DCS#Vec3 CV1 Vec3 +-- @param DCS#Time CT2 Time +-- @param DCS#Vec3 CV2 Vec3 +function FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2) + + if not self:Is("Stopped") then + + self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) + + FollowGroup:OptionROTEvadeFire() + FollowGroup:OptionROEReturnFire() + + local GroupUnit = FollowGroup:GetUnit( 1 ) + + local GuVec3=GroupUnit:GetVec3() + + local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) + + if FollowFormation then + local FollowDistance = FollowFormation.x + + local GT1 = GroupUnit:GetState( self, "GT1" ) + + if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then + GroupUnit:SetState( self, "GV1", GuVec3) + GroupUnit:SetState( self, "GT1", timer.getTime() ) + else + local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 + local CT = CT2 - CT1 + + local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 + + local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } + local Ca = math.atan2( CDv.x, CDv.z ) + + local GT1 = GroupUnit:GetState( self, "GT1" ) + local GT2 = timer.getTime() + + local GV1 = GroupUnit:GetState( self, "GV1" ) + local GV2 = GuVec3 + + GV2.x=GV2.x+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + GV2.y=GV2.y+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + GV2.z=GV2.z+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + + GroupUnit:SetState( self, "GT1", GT2 ) + GroupUnit:SetState( self, "GV1", GV2 ) + + local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 + local GT = GT2 - GT1 + + -- Calculate the distance + local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } + local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) + local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T + local Position = math.cos( Alpha_R ) + local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 + local Distance = GD * Position + - CS * 0.5 + + -- Calculate the group direction vector + local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } + + -- Calculate GH2, GH2 with the same height as CV2. + local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } + + -- Calculate the angle of GV to the orthonormal plane + local alpha = math.atan2( GV.x, GV.z ) + + local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) + local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) + + -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. + -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) + local Inclination = ( Distance + FollowFormation.x ) / 10 + if Inclination < -30 then + Inclination = - 30 + end + + local CVI = { + x = CV2.x + CS * 10 * math.sin(Ca), + y = GH2.y + Inclination, -- + FollowFormation.y, + z = CV2.z + CS * 10 * math.cos(Ca), + } + + -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. + local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } + + -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. + -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. + -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... + local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } + + -- Now we can calculate the group destination vector GDV. + local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } + + local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) + local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) + + local GDV_Formation = { + x = GDV.x - GVx, + y = GDV.y, + z = GDV.z - GVz + } + + -- Debug smoke. + if self.SmokeDirectionVector == true then + trigger.action.smoke( GDV, trigger.smokeColor.Green ) + trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) + end + + local Time = 120 + + local Speed = - ( Distance + FollowFormation.x ) / Time + + if Distance > -10000 then + Speed = - ( Distance + FollowFormation.x ) / 60 + end + + if Distance > -2500 then + Speed = - ( Distance + FollowFormation.x ) / 20 + end + + local GS = Speed + CS + + --self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } ) + + -- Now route the escort to the desired point with the desired speed. + FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second) + + end + end + end +end diff --git a/Moose Development/Moose/Functional/Suppression.lua b/Moose Development/Moose/Functional/Suppression.lua index d93959990..08ff6966c 100644 --- a/Moose Development/Moose/Functional/Suppression.lua +++ b/Moose Development/Moose/Functional/Suppression.lua @@ -308,7 +308,7 @@ SUPPRESSION.version="0.9.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Creates a new AI_suppression object. +--- Creates a new suppression object. -- @param #SUPPRESSION self -- @param Wrapper.Group#GROUP group The GROUP object for which suppression should be applied. -- @return #SUPPRESSION self diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index a080a6942..44738a244 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -31,6 +31,7 @@ __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' ) @@ -76,6 +77,12 @@ __Moose.Include( 'Functional\\AmmoTruck.lua' ) __Moose.Include( 'Functional\\Tiresias.lua' ) __Moose.Include( 'Functional\\Stratego.lua' ) __Moose.Include( 'Functional\\ClientWatch.lua' ) +__Moose.Include( 'Functional\\Formation.lua' ) + +__Moose.Include( 'Navigation\\Beacons.lua' ) +__Moose.Include( 'Navigation\\Point.lua' ) +__Moose.Include( 'Navigation\\Radios.lua' ) +__Moose.Include( 'Navigation\\Towns.lua' ) __Moose.Include( 'Ops\\Airboss.lua' ) __Moose.Include( 'Ops\\RecoveryTanker.lua' ) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 08539aa5c..d3d212670 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4311,7 +4311,7 @@ function OPSGROUP:_UpdateTask(Task, Mission) local followUnit=UNIT:FindByName(param.unitname) -- Define AI Formation object. - Task.formation=AI_FORMATION:New(followUnit, followSet, AUFTRAG.SpecialTask.FORMATION, "Follow X at given parameters.") + Task.formation=FORMATION:New(followUnit, followSet, AUFTRAG.SpecialTask.FORMATION) -- Formation parameters. Task.formation:FormationCenterWing(-param.offsetX, 50, math.abs(param.altitude), 50, param.offsetZ, 50) diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 70a1e95f4..954735153 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -20,7 +20,6 @@ -- === -- -- ### Author: **funkyfranky** --- ### Contributions: Flightcontrol (@{AI.AI_Formation} class being used here) -- -- @module Ops.RescueHelo -- @image Ops_RescueHelo.png @@ -37,7 +36,7 @@ -- @field #number takeoff Takeoff type. -- @field Wrapper.Airbase#AIRBASE airbase The airbase object acting as home base of the helo. -- @field Core.Set#SET_GROUP followset Follow group set. --- @field AI.AI_Formation#AI_FORMATION formation AI_FORMATION object. +-- @field Functional.Formation#FORMATION formation FORMATION object. -- @field #number lowfuel Low fuel threshold of helo in percent. -- @field #number altitude Altitude of helo in meters. -- @field #number offsetX Offset in meters to carrier in longitudinal direction. @@ -947,17 +946,13 @@ function RESCUEHELO:onafterStart(From, Event, To) self.HeloFuel0=self.helo:GetFuel() -- Define AI Formation object. - self.formation=AI_FORMATION:New(self.carrier, self.followset, "Helo Formation with Carrier", "Follow Carrier at given parameters.") - + self.formation=FORMATION:New(self.carrier, self.followset, "Helo Formation with Carrier") -- Formation parameters. self.formation:FormationCenterWing(-self.offsetX, 50, math.abs(self.altitude), 50, self.offsetZ, 50) -- Set follow time interval. self.formation:SetFollowTimeInterval(self.dtFollow) - -- Formation mode. - self.formation:SetFlightModeFormation(self.helo) - -- Start formation FSM. self.formation:__Start(delay) diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index d39f0bb30..3fb04ab4a 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -516,28 +516,6 @@ function CLIENT:IsTransport() return self.ClientTransport end ---- Shows the @{AI.AI_Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{AI.AI_Cargo#CARGO} is shown using the @{Core.Message#MESSAGE} distribution system. --- @param #CLIENT self -function CLIENT:ShowCargo() - self:F() - - local CargoMsg = "" - - for CargoName, Cargo in pairs( CARGOS ) do - if self == Cargo:IsLoadedInClient() then - CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" - end - end - - if CargoMsg == "" then - CargoMsg = "empty" - end - - self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 ) - -end - --- The main message driver for the CLIENT. -- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. -- @param #CLIENT self diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 780dc31c8..82ab29432 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -409,7 +409,7 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) local Controller = self:_GetController() -- self:I( "Before SetTask" ) Controller:setTask( DCSTask ) - -- AI_FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace. + -- RESCUEHELO calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace. self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } ) else BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } ) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index d1263e908..f7c8be377 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -75,6 +75,7 @@ Functional/ZoneGoalCargo.lua Functional/Tiresias.lua Functional/Stratego.lua Functional/ClientWatch.lua +Functional/Formation.lua Ops/Airboss.lua Ops/RecoveryTanker.lua From 856197a5c87cb967a9a9052b23d457ae8a609005 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 14 Nov 2025 13:20:39 +0100 Subject: [PATCH 266/349] xx --- .../Moose/Navigation/Beacons.lua | 23 ++++++++++++++----- Moose Development/Moose/Navigation/Point.lua | 17 +++++++++++++- Moose Development/Moose/Navigation/Radios.lua | 4 ++-- Moose Development/Moose/Navigation/Towns.lua | 4 ++-- .../Moose/Wrapper/Controllable.lua | 4 ++-- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Navigation/Beacons.lua b/Moose Development/Moose/Navigation/Beacons.lua index 9c7acba8b..00b5cb852 100644 --- a/Moose Development/Moose/Navigation/Beacons.lua +++ b/Moose Development/Moose/Navigation/Beacons.lua @@ -39,7 +39,7 @@ -- -- # The BEACONS Concept -- --- This class is desinged to make information about beacons of a map/theatre easier accessible. The information contains location, type and frequencies of all or specific beacons of the map. +-- This class is designed to make information about beacons of a map/theatre easier accessible. The information contains location, type and frequencies of all or specific beacons of the map. -- -- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `beacons.lua` that can be -- found in the installation directory of DCS for each map or a table that the user needs to provide. @@ -104,7 +104,7 @@ BEACONS.version="0.1.0" -- Constructor(s) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new BECAONS class instance from a given table. +--- Create a new BEACONS class instance from a given table. -- @param #BEACONS self -- @param #table BeaconTable Table with beacon info. -- @return #BEACONS self @@ -159,7 +159,7 @@ function BEACONS:NewFromTable(BeaconTable) end ---- Create a new BECAONS class instance from a given file. +--- Create a new BEACONS class instance from a given file. -- @param #BEACONS self -- @param #string FileName Full path to the file containing the map beacons. -- @return #BEACONS self @@ -267,16 +267,27 @@ end --- Get table of all beacons, optionally of a given type. -- @param #BEACONS self --- @param #number TypeID (Optional) Only return specific beacon types, *e.g.* `BEACON.Type.TACAN`. +-- @param #number TypeID (Optional) Only return specific beacon types, *e.g.* `BEACON.Type.TACAN`. Can be handed in as tanle of beacon types. -- @return #table Table of beacons. Each element is of type #BEACON.Beacon. function BEACONS:GetBeacons(TypeID) - + local beacons={} + local keys = {} + if TypeID~=nil and type(TypeID) ~= "table" then + TypeID = {TypeID} + end + + for _,_typeid in pairs(TypeID or {}) do + if _typeid ~= nil then + keys[_typeid] = _typeid + end + end + for _,_beacon in pairs(self.beacons) do local bc=_beacon --#BEACONS.Beacon - if TypeID==nil or TypeID==bc.type then + if TypeID==nil or keys[bc.type] ~= nil then table.insert(beacons, bc) end diff --git a/Moose Development/Moose/Navigation/Point.lua b/Moose Development/Moose/Navigation/Point.lua index 2c8d1e378..963e8cd05 100644 --- a/Moose Development/Moose/Navigation/Point.lua +++ b/Moose Development/Moose/Navigation/Point.lua @@ -231,6 +231,21 @@ function NAVFIX:NewFromNavFix(Name, Type, NavFix, Distance, Bearing, Reciprocal) return self end +--- Create a new NAVFIX class instance from BEACONS.Beacon data. +-- @param #NAVFIX self +-- @param Navigation.Beacons#BEACONS.Beacon Beacon The beacon data. +-- @return #NAVFIX self +function NAVFIX:NewFromBeacon(Beacon) + local frequency, unit = BEACONS:_GetFrequency(Beacon.frequency) + frequency = string.format("%.3f",frequency) + if Beacon.typeName == "TACAN" then + frequency = Beacon.channel + unit = "X" + end + self = NAVFIX:NewFromVector(string.format("%s %s %s",Beacon.typeName,frequency,unit),Beacon.typeName,Beacon.vec3) + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -348,7 +363,7 @@ end --- Set whether this fix is compulsory. -- @param #NAVFIX self --- @param #boolean Compulsory If `true`, this is a compusory fix. If `false` or nil, it is non-compulsory. +-- @param #boolean Compulsory If `true`, this is a compulsory fix. If `false` or nil, it is non-compulsory. -- @return #NAVFIX self function NAVFIX:SetCompulsory(Compulsory) self.isCompulsory=Compulsory diff --git a/Moose Development/Moose/Navigation/Radios.lua b/Moose Development/Moose/Navigation/Radios.lua index 910235109..531309fc2 100644 --- a/Moose Development/Moose/Navigation/Radios.lua +++ b/Moose Development/Moose/Navigation/Radios.lua @@ -39,7 +39,7 @@ -- -- # The RADIOS Concept -- --- This class is desinged to make information about radios of a map/theatre easier accessible. The information contains mostly the frequencies of airbases of the map. +-- This class is designed to make information about radios of a map/theatre easier accessible. The information contains mostly the frequencies of airbases of the map. -- -- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `radio.lua` that can be -- found in the installation directory of DCS for each map or a table that the user needs to provide. @@ -48,7 +48,7 @@ -- -- A new `RADIOS` object can be created with the @{#RADIOS.NewFromFile}(*radio_lua_file*) function. -- --- local radios=RADIOS:NewFromFile("\Mods\terrains\\radios.lua") +-- local radios=RADIOS:NewFromFile("\Mods\terrains\\radio.lua") -- radios:MarkerShow() -- -- This will load the radios from the `` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file diff --git a/Moose Development/Moose/Navigation/Towns.lua b/Moose Development/Moose/Navigation/Towns.lua index c5f67efbd..5f8ef2619 100644 --- a/Moose Development/Moose/Navigation/Towns.lua +++ b/Moose Development/Moose/Navigation/Towns.lua @@ -39,7 +39,7 @@ -- -- # The TOWNS Concept -- --- This class is desinged to make information about towns of a map/theatre easier accessible. The information contains location and road/rail connections of the towns. +-- This class is designed to make information about towns of a map/theatre easier accessible. The information contains location and road/rail connections of the towns. -- -- **Note** that try to avoid hard coding stuff in Moose since DCS is updated frequently and things change. Therefore, the main source of information is either a file `towns.lua` that can be -- found in the installation directory of DCS for each map or a table that the user needs to provide. @@ -48,7 +48,7 @@ -- -- A new `TOWNS` object can be created with the @{#TOWNS.NewFromFile}(*towns_lua_file*) function. -- --- local towns=TOWNS:NewFromFile("\Mods\terrains\\towns.lua") +-- local towns=TOWNS:NewFromFile("\Mods\terrains\\map\towns.lua") -- towns:MarkerShow() -- -- This will load the towns from the `` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 82ab29432..14bb99183 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -2855,7 +2855,7 @@ do -- Route methods -- @param Core.Zone#ZONE Zone The zone where to route to. -- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. -- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h. - -- @param Core.Base#FORMATION Formation The formation string. + -- @param DCS#FORMATION Formation The formation string. function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) self:F2( Zone ) @@ -2915,7 +2915,7 @@ do -- Route methods -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 The Vec2 where to route to. -- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h. - -- @param Core.Base#FORMATION Formation The formation string. + -- @param DCS#FORMATION Formation The formation string. function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation ) local DCSControllable = self:GetDCSObject() From b983e9f8b4dd89c471a9af5d084d30360e1d49a3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 14 Nov 2025 17:27:26 +0100 Subject: [PATCH 267/349] SXX --- Moose Development/Moose/Functional/Scoring.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index d9427d279..042217508 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -325,6 +325,8 @@ function SCORING:New( GameName, SavePath, AutoSave ) self:OpenCSV( GameName ) end + self:I("SCORING "..tostring(GameName).." started! v"..self.version) + return self end @@ -399,6 +401,11 @@ end -- @return #SCORING function SCORING:AddStaticScore( ScoreStatic, Score ) + if ScoreStatic == nil then + BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") + return self + end + local StaticName = ScoreStatic:GetName() self.ScoringObjects[StaticName] = Score @@ -1305,7 +1312,7 @@ function SCORING:_EventOnDeadOrCrash( Event ) local Destroyed = false -- What is the player destroying? - if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered??? + if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and TargetUnit and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered??? local TargetThreatLevel = Player.Hit[TargetCategory][TargetUnitName].ThreatLevel local TargetThreatType = Player.Hit[TargetCategory][TargetUnitName].ThreatType From f93a1959b4d01a607f70ffaa31b0a049ef16ee82 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 15 Nov 2025 16:51:06 +0100 Subject: [PATCH 268/349] xx --- Moose Development/Moose/Functional/Formation.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Formation.lua b/Moose Development/Moose/Functional/Formation.lua index 9467c1380..5eac066db 100644 --- a/Moose Development/Moose/Functional/Formation.lua +++ b/Moose Development/Moose/Functional/Formation.lua @@ -11,13 +11,12 @@ -- ## Additional Material: -- -- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AI/FORMATION) --- * **YouTube videos:** [Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0bFIJ9jIdYM22uaWmIN4oz) -- * **Guides:** None -- -- === -- -- ### Author: **FlightControl** --- ### Contributions: **Applevangelist** +-- ### Contributions: **Applevangelist** (refactor) -- -- === -- From f4a2ae8cccfec778ddea0fb4eb80f56c697fd7e5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Nov 2025 16:52:41 +0100 Subject: [PATCH 269/349] xx --- Moose Development/Moose/Core/Point.lua | 168 ------------------ Moose Development/Moose/Functional/Escort.lua | 114 ++++++------ Moose Development/Moose/Ops/OpsGroup.lua | 15 +- Moose Development/Moose/Ops/RescueHelo.lua | 25 ++- 4 files changed, 93 insertions(+), 229 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 729a5116d..15050e0cc 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -3,8 +3,6 @@ -- ## Features: -- -- * Provides a COORDINATE class, which allows to manage points in 3D space and perform various operations on it. --- * Provides a POINT\_VEC2 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a Lat/Lon and Altitude perspective. --- * Provides a POINT\_VEC3 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a X, Z and Y vector perspective. -- -- === -- @@ -3876,170 +3874,4 @@ do -- COORDINATE end -do - --- The POINT_VEC3 class - -- @type POINT_VEC3 - -- @field #number x The x coordinate in 3D space. - -- @field #number y The y coordinate in 3D space. - -- @field #number z The z COORDINATE in 3D space. - -- @field Utilities.Utils#SMOKECOLOR SmokeColor - -- @field Utilities.Utils#FLARECOLOR FlareColor - -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType - -- @field #POINT_VEC3.RoutePointType RoutePointType - -- @field #POINT_VEC3.RoutePointAction RoutePointAction - -- @extends #COORDINATE - - - --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. - -- - -- **DEPRECATED - PLEASE USE COORDINATE!** - -- - -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. - -- In order to keep the credibility of the the author, - -- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors, - -- who you can find on the Eagle Dynamics Forums. - -- - -- - -- ## POINT_VEC3 constructor - -- - -- A new POINT_VEC3 object can be created with: - -- - -- * @{#POINT_VEC3.New}(): a 3D point. - -- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCS#Vec3}. - -- - -- - -- ## Manupulate the X, Y, Z coordinates of the POINT_VEC3 - -- - -- A POINT_VEC3 class works in 3D space. It contains internally an X, Y, Z coordinate. - -- Methods exist to manupulate these coordinates. - -- - -- The current X, Y, Z axis can be retrieved with the methods @{#POINT_VEC3.GetX}(), @{#POINT_VEC3.GetY}(), @{#POINT_VEC3.GetZ}() respectively. - -- The methods @{#POINT_VEC3.SetX}(), @{#POINT_VEC3.SetY}(), @{#POINT_VEC3.SetZ}() change the respective axis with a new value. - -- The current axis values can be changed by using the methods @{#POINT_VEC3.AddX}(), @{#POINT_VEC3.AddY}(), @{#POINT_VEC3.AddZ}() - -- to add or substract a value from the current respective axis value. - -- Note that the Set and Add methods return the current POINT_VEC3 object, so these manipulation methods can be chained... For example: - -- - -- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3() - -- - -- - -- ## 3D calculation methods - -- - -- Various calculation methods exist to use or manipulate 3D space. Find below a short description of each method: - -- - -- - -- ## Point Randomization - -- - -- Various methods exist to calculate random locations around a given 3D point. - -- - -- * @{#POINT_VEC3.GetRandomPointVec3InRadius}(): Provides a random 3D point around the current 3D point, in the given inner to outer band. - -- - -- - -- @field #POINT_VEC3 - POINT_VEC3 = { - ClassName = "POINT_VEC3", - Metric = true, - RoutePointAltType = { - BARO = "BARO", - }, - RoutePointType = { - TakeOffParking = "TakeOffParking", - TurningPoint = "Turning Point", - }, - RoutePointAction = { - FromParkingArea = "From Parking Area", - TurningPoint = "Turning Point", - }, - } - - --- RoutePoint AltTypes - -- @type POINT_VEC3.RoutePointAltType - -- @field BARO "BARO" - - --- RoutePoint Types - -- @type POINT_VEC3.RoutePointType - -- @field TakeOffParking "TakeOffParking" - -- @field TurningPoint "Turning Point" - - --- RoutePoint Actions - -- @type POINT_VEC3.RoutePointAction - -- @field FromParkingArea "From Parking Area" - -- @field TurningPoint "Turning Point" - - -- Constructor. - - --- Create a new POINT_VEC3 object. - -- @param #POINT_VEC3 self - -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. - -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing Upwards. - -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right. - -- @return Core.Point#POINT_VEC3 - function POINT_VEC3:New( x, y, z ) - - local self = BASE:Inherit( self, COORDINATE:New( x, y, z ) ) -- Core.Point#POINT_VEC3 - self:F2( self ) - - return self - end - -end - -do - - --- @type POINT_VEC2 - -- @field DCS#Distance x The x coordinate in meters. - -- @field DCS#Distance y the y coordinate in meters. - -- @extends Core.Point#COORDINATE - - --- Defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. - -- - -- **DEPRECATED - PLEASE USE COORDINATE!** - -- - -- ## POINT_VEC2 constructor - -- - -- A new POINT_VEC2 instance can be created with: - -- - -- * @{Core.Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter. - -- * @{Core.Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCS#Vec2}. - -- - -- ## Manupulate the X, Altitude, Y coordinates of the 2D point - -- - -- A POINT_VEC2 class works in 2D space, with an altitude setting. It contains internally an X, Altitude, Y coordinate. - -- Methods exist to manupulate these coordinates. - -- - -- The current X, Altitude, Y axis can be retrieved with the methods @{#POINT_VEC2.GetX}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetY}() respectively. - -- The methods @{#POINT_VEC2.SetX}(), @{#POINT_VEC2.SetAlt}(), @{#POINT_VEC2.SetY}() change the respective axis with a new value. - -- The current Lat(itude), Alt(itude), Lon(gitude) values can also be retrieved with the methods @{#POINT_VEC2.GetLat}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetLon}() respectively. - -- The current axis values can be changed by using the methods @{#POINT_VEC2.AddX}(), @{#POINT_VEC2.AddAlt}(), @{#POINT_VEC2.AddY}() - -- to add or substract a value from the current respective axis value. - -- Note that the Set and Add methods return the current POINT_VEC2 object, so these manipulation methods can be chained... For example: - -- - -- local Vec2 = PointVec2:AddX( 100 ):AddY( 2000 ):GetVec2() - -- - -- @field #POINT_VEC2 - POINT_VEC2 = { - ClassName = "POINT_VEC2", - } - - - - --- POINT_VEC2 constructor. - -- @param #POINT_VEC2 self - -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. - -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. - -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. - -- @return Core.Point#POINT_VEC2 - function POINT_VEC2:New( x, y, LandHeightAdd ) - - local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - local self = BASE:Inherit( self, COORDINATE:New( x, LandHeight, y ) ) -- Core.Point#POINT_VEC2 - self:F2( self ) - - return self - end - -end diff --git a/Moose Development/Moose/Functional/Escort.lua b/Moose Development/Moose/Functional/Escort.lua index ecf53a382..d7fe6a2f7 100644 --- a/Moose Development/Moose/Functional/Escort.lua +++ b/Moose Development/Moose/Functional/Escort.lua @@ -193,7 +193,7 @@ ESCORT = { function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) local self = BASE:Inherit( self, BASE:New() ) -- #ESCORT - self:F( { EscortClient, EscortGroup, EscortName } ) + --self:F( { EscortClient, EscortGroup, EscortName } ) self.EscortClient = EscortClient -- Wrapper.Client#CLIENT self.EscortGroup = EscortGroup -- Wrapper.Group#GROUP @@ -202,7 +202,7 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) self.EscortSetGroup = SET_GROUP:New() self.EscortSetGroup:AddObject( self.EscortGroup ) - self.EscortSetGroup:Flush() + --self.EscortSetGroup:Flush() self.Detection = DETECTION_UNITS:New( self.EscortSetGroup, 15000 ) self.EscortGroup.Detection = self.Detection @@ -242,12 +242,11 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) self.CT1 = 0 self.GT1 = 0 - self.FollowScheduler, self.FollowSchedule = SCHEDULER:New( self, self._FollowScheduler, {}, 1, .5, .01 ) + self.FollowScheduler, self.FollowSchedule = SCHEDULER:New( self, self._FollowScheduler, {}, 1, 2, .01 ) self.FollowScheduler:Stop( self.FollowSchedule ) self.EscortMode = ESCORT.MODE.MISSION - - + return self end @@ -273,12 +272,11 @@ function ESCORT:TestSmokeDirectionVector( SmokeDirection ) self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false end - --- Defines the default menus -- @param #ESCORT self --- @return #ESCORT +-- @return #ESCORT self function ESCORT:Menus() - self:F() + --self:F() self:MenuFollowAt( 100 ) self:MenuFollowAt( 200 ) @@ -299,19 +297,16 @@ function ESCORT:Menus() self:MenuEvasion() self:MenuResumeMission() - return self end - - --- Defines a menu slot to let the escort Join and Follow you at a certain distance. -- This menu will appear under **Navigation**. -- @param #ESCORT self -- @param DCS#Distance Distance The distance in meters that the escort needs to follow the client. --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuFollowAt( Distance ) - self:F(Distance) + --self:F(Distance) if self.EscortGroup:IsAir() then if not self.EscortMenuReportNavigation then @@ -336,10 +331,10 @@ end -- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. -- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT +-- @return #ESCORT self -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) + --self:F( { Height, Seconds, MenuTextFormat } ) if self.EscortGroup:IsAir() then @@ -390,17 +385,16 @@ function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat ) return self end - --- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Navigation**. -- @param #ESCORT self -- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. -- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT +-- @return #ESCORT self -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. function ESCORT:MenuHoldAtLeaderPosition( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) + --self:F( { Height, Seconds, MenuTextFormat } ) if self.EscortGroup:IsAir() then @@ -458,9 +452,9 @@ end -- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. -- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) + --self:F( { Height, Seconds, MenuTextFormat } ) if self.EscortGroup:IsAir() then if not self.EscortMenuScan then @@ -508,16 +502,14 @@ function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) return self end - - --- Defines a menu slot to let the escort disperse a flare in a certain color. -- This menu will appear under **Navigation**. -- The flare will be fired from the first unit in the group. -- @param #ESCORT self -- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuFlare( MenuTextFormat ) - self:F() + ----self:F() if not self.EscortMenuReportNavigation then self.EscortMenuReportNavigation = MENU_GROUP:New( self.EscortClient:GetGroup(), "Navigation", self.EscortMenu ) @@ -547,9 +539,9 @@ end -- The smoke will be fired from the first unit in the group. -- @param #ESCORT self -- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuSmoke( MenuTextFormat ) - self:F() + ----self:F() if not self.EscortGroup:IsAir() then if not self.EscortMenuReportNavigation then @@ -581,9 +573,9 @@ end -- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. -- @param #ESCORT self -- @param DCS#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuReportTargets( Seconds ) - self:F( { Seconds } ) + --self:F( { Seconds } ) if not self.EscortMenuReportNearbyTargets then self.EscortMenuReportNearbyTargets = MENU_GROUP:New( self.EscortClient:GetGroup(), "Report targets", self.EscortMenu ) @@ -601,7 +593,6 @@ function ESCORT:MenuReportTargets( Seconds ) -- Attack Targets self.EscortMenuAttackNearbyTargets = MENU_GROUP:New( self.EscortClient:GetGroup(), "Attack targets", self.EscortMenu ) - self.ReportTargetsScheduler, self.ReportTargetsSchedulerID = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, Seconds ) return self @@ -611,9 +602,9 @@ end -- This menu will appear under **Request assistance from**. -- Note that this method needs to be preceded with the method MenuReportTargets. -- @param #ESCORT self --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuAssistedAttack() - self:F() + --self:F() -- Request assistance from other escorts. -- This is very useful to let f.e. an escorting ship attack a target detected by an escorting plane... @@ -625,9 +616,9 @@ end --- Defines a menu to let the escort set its rules of engagement. -- All rules of engagement will appear under the menu **ROE**. -- @param #ESCORT self --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuROE( MenuTextFormat ) - self:F( MenuTextFormat ) + --self:F( MenuTextFormat ) if not self.EscortMenuROE then -- Rules of Engagement @@ -649,13 +640,12 @@ function ESCORT:MenuROE( MenuTextFormat ) return self end - --- Defines a menu to let the escort set its evasion when under threat. -- All rules of engagement will appear under the menu **Evasion**. -- @param #ESCORT self --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuEvasion( MenuTextFormat ) - self:F( MenuTextFormat ) + --self:F( MenuTextFormat ) if self.EscortGroup:IsAir() then if not self.EscortMenuEvasion then @@ -682,9 +672,9 @@ end --- Defines a menu to let the escort resume its mission from a waypoint on its route. -- All rules of engagement will appear under the menu **Resume mission from**. -- @param #ESCORT self --- @return #ESCORT +-- @return #ESCORT self function ESCORT:MenuResumeMission() - self:F() + --self:F() if not self.EscortMenuResumeMission then -- Mission Resume Menu Root @@ -694,7 +684,8 @@ function ESCORT:MenuResumeMission() return self end - +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_HoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) @@ -735,6 +726,8 @@ function ESCORT:_HoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) end +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_JoinUpAndFollow( Distance ) @@ -752,7 +745,7 @@ end -- @param Wrapper.Client#CLIENT EscortClient -- @param DCS#Distance Distance function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) - self:F( { EscortGroup, EscortClient, Distance } ) + --self:F( { EscortGroup, EscortClient, Distance } ) self.FollowScheduler:Stop( self.FollowSchedule ) @@ -768,6 +761,8 @@ function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) EscortGroup:MessageToClient( "Rejoining and Following at " .. Distance .. "!", 30, EscortClient ) end +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_Flare( Color, Message ) @@ -778,6 +773,8 @@ function ESCORT:_Flare( Color, Message ) EscortGroup:MessageToClient( Message, 10, EscortClient ) end +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_Smoke( Color, Message ) @@ -788,7 +785,8 @@ function ESCORT:_Smoke( Color, Message ) EscortGroup:MessageToClient( Message, 10, EscortClient ) end - +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_ReportNearbyTargetsNow() @@ -799,6 +797,8 @@ function ESCORT:_ReportNearbyTargetsNow() end +--- +-- @param #ESCORT self function ESCORT:_SwitchReportNearbyTargets( ReportTargets ) local EscortGroup = self.EscortGroup @@ -816,6 +816,8 @@ function ESCORT:_SwitchReportNearbyTargets( ReportTargets ) end end +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_ScanTargets( ScanDuration ) @@ -846,24 +848,27 @@ function ESCORT:_ScanTargets( ScanDuration ) end +--- +-- @param #ESCORT self -- @param Wrapper.Group#GROUP EscortGroup function _Resume( EscortGroup ) - env.info( '_Resume' ) + --env.info( '_Resume' ) local Escort = EscortGroup:GetState( EscortGroup, "Escort" ) - env.info( "EscortMode = " .. Escort.EscortMode ) + --env.info( "EscortMode = " .. Escort.EscortMode ) if Escort.EscortMode == ESCORT.MODE.FOLLOW then Escort:JoinUpAndFollow( EscortGroup, Escort.EscortClient, Escort.Distance ) end end +--- -- @param #ESCORT self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem function ESCORT:_AttackTarget( DetectedItem ) local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - self:F( EscortGroup ) + --self:F( EscortGroup ) local EscortClient = self.EscortClient @@ -983,6 +988,8 @@ function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItem ) end +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_ROE( EscortROEFunction, EscortROEMessage ) @@ -993,6 +1000,8 @@ function ESCORT:_ROE( EscortROEFunction, EscortROEMessage ) EscortGroup:MessageToClient( EscortROEMessage, 10, EscortClient ) end +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_ROT( EscortROTFunction, EscortROTMessage ) @@ -1003,6 +1012,8 @@ function ESCORT:_ROT( EscortROTFunction, EscortROTMessage ) EscortGroup:MessageToClient( EscortROTMessage, 10, EscortClient ) end +--- +-- @param #ESCORT self -- @param #MENUPARAM MenuParam function ESCORT:_ResumeMission( WayPoint ) @@ -1027,7 +1038,7 @@ end -- @param #ESCORT self -- @return #table function ESCORT:RegisterRoute() - self:F() + --self:F() local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP @@ -1038,9 +1049,11 @@ function ESCORT:RegisterRoute() return TaskPoints end +--- +-- @param #ESCORT self -- @param Functional.Escort#ESCORT self function ESCORT:_FollowScheduler() - self:F( { self.FollowDistance } ) + --self:F( { self.FollowDistance } ) self:T( {self.EscortClient.UnitName, self.EscortGroup.GroupName } ) if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then @@ -1146,11 +1159,10 @@ function ESCORT:_FollowScheduler() return false end - --- Report Targets Scheduler. -- @param #ESCORT self function ESCORT:_ReportTargetsScheduler() - self:F( self.EscortGroup:GetName() ) + --self:F( self.EscortGroup:GetName() ) if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then @@ -1163,7 +1175,7 @@ function ESCORT:_ReportTargetsScheduler() end local DetectedItems = self.Detection:GetDetectedItems() - self:F( DetectedItems ) + --self:F( DetectedItems ) local DetectedTargets = false @@ -1175,7 +1187,7 @@ function ESCORT:_ReportTargetsScheduler() --local EscortUnit = EscortGroupData:GetUnit( 1 ) for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do - self:F( { DetectedItemIndex, DetectedItem } ) + --self:F( { DetectedItemIndex, DetectedItem } ) -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, EscortGroupData.EscortGroup, _DATABASE:GetPlayerSettings( self.EscortClient:GetPlayerName() ) ) @@ -1216,7 +1228,7 @@ function ESCORT:_ReportTargetsScheduler() end end - self:F( DetectedMsgs ) + --self:F( DetectedMsgs ) if DetectedTargets then self.EscortGroup:MessageToClient( "Reporting detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), 20, self.EscortClient ) else diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index d3d212670..6a94c713a 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4302,7 +4302,16 @@ function OPSGROUP:_UpdateTask(Task, Mission) Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then - + + if Mission.Type == AUFTRAG.Type.RESCUEHELO then + local param=Task.dcstask.params + local followUnit=UNIT:FindByName(param.unitname) + local helogroupname = self:GetGroup():GetName() + Task.formation = RESCUEHELO:New(followUnit,helogroupname) + -- Start formation FSM. + Task.formation:Start() + else + -- Set of group(s) to follow Mother. local followSet=SET_GROUP:New():AddGroup(self.group) @@ -4324,7 +4333,9 @@ function OPSGROUP:_UpdateTask(Task, Mission) -- Start formation FSM. Task.formation:Start() - + + end + elseif Task.dcstask.id==AUFTRAG.SpecialTask.PATROLZONE then --- diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 954735153..fed02dfc8 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -870,15 +870,24 @@ function RESCUEHELO:onafterStart(From, Event, To) -- Delay before formation is started. local delay=120 - - -- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine. - local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias) - -- Set modex for spawn. - Spawn:InitModex(self.modex) - + local UsesAliveGroup=false + local Spawn = GROUP:FindByName(self.helogroupname) + if Spawn and Spawn:IsAlive() then + self.helo=Spawn + UsesAliveGroup = true + else + + -- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine. + local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias) + + -- Set modex for spawn. + Spawn:InitModex(self.modex) + + end + -- Spawn in air or at airbase. - if self.takeoff==SPAWN.Takeoff.Air then + if UsesAliveGroup==false and self.takeoff==SPAWN.Takeoff.Air then -- Carrier heading local hdg=self.carrier:GetHeading() @@ -898,7 +907,7 @@ function RESCUEHELO:onafterStart(From, Event, To) -- Start formation in 1 seconds delay=1 - else + elseif UsesAliveGroup==false then -- Check if an uncontrolled helo group was requested. if self.uncontrolledac then From 01f2b6b7837597467cee2832d1a8a2e3d270a421 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Nov 2025 15:13:43 +0100 Subject: [PATCH 270/349] #EVENT - Small fix --- Moose Development/Moose/Core/Event.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index b99dbc771..aa72e9766 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1446,12 +1446,14 @@ function EVENT:onEvent( Event ) -- SCENERY --- Event.TgtDCSUnit = Event.target - Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName() or nil + Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName = Event.TgtDCSUnitName Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) Event.TgtCategory = Event.TgtDCSUnit:getDesc().category Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + --self:I("Event Registered for Scenery Object ".. Event.TgtDCSUnitName) + --UTILS.PrintTableToLog(Event.TgtUnit) end end end From 5edffbbca481a7c4b209a466e2d8c4ccdc6e6df7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Nov 2025 15:14:57 +0100 Subject: [PATCH 271/349] #SET - Small fix --- Moose Development/Moose/Core/Set.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index b21d13275..e46bbb771 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -945,7 +945,8 @@ do -- SET_BASE function SET_BASE:IsInSet(Object) --self:F3(Object) local outcome = false - local name = Object:GetName() + if Object == nil then return false end + local name = (Object ~= nil and Object.GetName) and Object:GetName() or "none" --self:I("SET_BASE: Objectname = "..name) self:ForEach( function(object) @@ -7859,7 +7860,7 @@ do -- SET_SCENERY local AddSceneryNamesArray = (type(AddSceneryNames) == "table") and AddSceneryNames or { AddSceneryNames } - --self:T((AddSceneryNamesArray) + --UTILS.PrintTableToLog(AddSceneryNamesArray) for AddSceneryID, AddSceneryName in pairs(AddSceneryNamesArray) do self:Add(AddSceneryName, SCENERY:FindByZoneName(AddSceneryName)) end From c869d237784f7f15bce8e4590ca46da51eac08bd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Nov 2025 15:15:24 +0100 Subject: [PATCH 272/349] #ZONE - Small fix --- Moose Development/Moose/Core/Zone.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 4eb9eed6c..851ea0caf 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1165,10 +1165,10 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData.SceneryTable = {} self.ScanData.Units = {} - local ZoneCoord = self:GetCoordinate() + local ZoneCoord = self:GetCoordinate():SetAlt() local ZoneRadius = self:GetRadius() - --self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) + --self:I({x = ZoneCoord.x, y=ZoneCoord.y, z=ZoneCoord.z, ZoneRadius = ZoneRadius}) local SphereSearch = { id = world.VolumeType.SPHERE, @@ -1180,6 +1180,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local function EvaluateZone( ZoneObject ) --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 + if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then -- Get object category. @@ -3381,7 +3382,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName]) - --self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) + --self:I( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end From cbb6d2c712fca7b41e03639f53fc4a5a4330b23a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Nov 2025 15:25:02 +0100 Subject: [PATCH 273/349] fixes --- .../Moose/Functional/Scoring.lua | 247 ++++++++++++++---- Moose Development/Moose/Wrapper/Scenery.lua | 186 +++++++++++-- 2 files changed, 350 insertions(+), 83 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index 042217508..6ddd370c4 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -90,7 +90,8 @@ -- @image Scoring.JPG --- @type SCORING --- @field Players A collection of the current players that have joined the game. +-- @field #table Players A collection of the current players that have joined the game. +-- @field Core.Set#SET_SCENERY ScoringScenery -- @extends Core.Base#BASE --- SCORING class @@ -229,7 +230,8 @@ SCORING = { ClassID = 0, Players = {}, AutoSave = true, - version = "1.18.4" + version = "1.18.4", + ScoringScenery = nil, -- Core.Set#SET_SCENERY } local _SCORINGCoalition = { @@ -379,7 +381,7 @@ function SCORING:AddUnitScore( ScoreUnit, Score ) return self end ---- Removes a @{Wrapper.Unit} for additional scoring when the @{Wrapper.Unit} is destroyed. +--- Removes a @{Wrapper.Unit} for scoring when the @{Wrapper.Unit} is destroyed. -- @param #SCORING self -- @param Wrapper.Unit#UNIT ScoreUnit The @{Wrapper.Unit} for which the Score needs to be given. -- @return #SCORING @@ -396,10 +398,21 @@ end -- Note that if there was already a @{Wrapper.Static} declared within the scoring with the same name, -- then the old @{Wrapper.Static} will be replaced with the new @{Wrapper.Static}. -- @param #SCORING self --- @param Wrapper.Static#UNIT ScoreStatic The @{Wrapper.Static} for which the Score needs to be given. +-- @param Wrapper.Static#STATIC ScoreStatic The @{Wrapper.Static} for which the Score needs to be given. -- @param #number Score The Score value. -- @return #SCORING function SCORING:AddStaticScore( ScoreStatic, Score ) + return self:AddScoreStatic( ScoreStatic, Score ) +end + +--- Add a @{Wrapper.Static} for additional scoring when the @{Wrapper.Static} is destroyed. +-- Note that if there was already a @{Wrapper.Static} declared within the scoring with the same name, +-- then the old @{Wrapper.Static} will be replaced with the new @{Wrapper.Static}. +-- @param #SCORING self +-- @param Wrapper.Static#STATIC ScoreStatic The @{Wrapper.Static} for which the Score needs to be given. +-- @param #number Score The Score value. +-- @return #SCORING +function SCORING:AddScoreStatic( ScoreStatic, Score ) if ScoreStatic == nil then BASE:E("SCORING.AddStaticScore: Parameter ScoreStatic is nil!") @@ -413,7 +426,61 @@ function SCORING:AddStaticScore( ScoreStatic, Score ) return self end ---- Removes a @{Wrapper.Static} for additional scoring when the @{Wrapper.Static} is destroyed. +--- Add a @{Wrapper.Scenery} for additional scoring when the @{Wrapper.Scenery} is destroyed. +-- @param #SCORING self +-- @param Wrapper.Scenery#SCENERY ScoreScenery The @{Wrapper.Scenery} for which the Score needs to be given. +-- @param #number Score The Score value. +-- @return #SCORING +function SCORING:AddScoreScenery( ScoreScenery, Score ) + + if ScoreScenery == nil then + self:E("SCORING.ScoreScenery: Parameter ScoreScenery is nil!") + return self + end + + if not self.ScoringScenery then + self.ScoringScenery = SET_SCENERY:New() -- Core.Set#SET_SCENERY + end + + local StaticName = ScoreScenery:GetName() + self:T("Scenery name = ".. StaticName) + + self.ScoringScenery:AddScenery(ScoreScenery) + + return self +end + +--- Removes a @{Wrapper.Scenery} for scoring when the @{Wrapper.Scenery} is destroyed. +-- @param #SCORING self +-- @param Wrapper.Scenery#SCENERY ScoreStatic The @{Wrapper.Scenery} for which the Score needs to be given. +-- @return #SCORING +function SCORING:RemoveSceneryScore( ScoreScenery ) + + local StaticName = ScoreScenery:GetName() + + self.ScoringObjects[StaticName] = nil + + return self +end + +--- Specify a special additional score for a @{Core.Set#SET_SCENERY}. +-- @param #SCORING self +-- @param Core.Set#SET_SCENERY Set The @{Core.Set#SET_SCENERY} for which each @{Wrapper.Scenery} in the SET a Score is given. +-- @param #number Score The Score value. +-- @return #SCORING +function SCORING:AddScoreSetScenery(Set, Score) + local set = Set.Set + + for _,_static in pairs (set) do + if _static ~= nil then + self:AddScoreScenery(_static,Score) + end + end + + return self +end + +--- Removes a @{Wrapper.Static} for scoring when the @{Wrapper.Static} is destroyed. -- @param #SCORING self -- @param Wrapper.Static#UNIT ScoreStatic The @{Wrapper.Static} for which the Score needs to be given. -- @return #SCORING @@ -468,6 +535,31 @@ function SCORING:AddScoreSetGroup(Set, Score) return self end +--- Specify a special additional score for a @{Core.Set#SET_STATIC}. +-- @param #SCORING self +-- @param Core.Set#SET_STATIC Set The @{Core.Set#SET_STATIC} for which each @{Wrapper.Static} in the SET a Score is given. +-- @param #number Score The Score value. +-- @return #SCORING +function SCORING:AddScoreSetStatic(Set, Score) + local set = Set:GetSetObjects() + + for _,_static in pairs (set) do + if _static and _static:IsAlive() then + self:AddStaticScore(_static,Score) + end + end + + local function AddScore(static) + self:AddStaticScore(static,Score) + end + + function Set:OnAfterAdded(From,Event,To,ObjectName,Object) + AddScore(Object) + end + + return self +end + --- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone. -- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced! -- This allows for a dynamic destruction zone evolution within your mission. @@ -487,7 +579,24 @@ function SCORING:AddZoneScore( ScoreZone, Score ) return self end ---- Remove a @{Core.Zone} for additional scoring. +--- Add a @{Core.Set#SET_ZONE} to define additional scoring when any object is destroyed in that zone. +-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced! +-- This allows for a dynamic destruction zone evolution within your mission. +-- @param #SCORING self +-- @param Core.Set#SET_ZONE ScoreZoneSet The @{Core.Set#SET_ZONE} which defines the destruction score perimeters. +-- Note that a zone can be a polygon or a moving zone. +-- @param #number Score The Score value. +-- @return #SCORING +function SCORING:AddZoneScoreSet( ScoreZoneSet, Score ) + + for _,_zone in pairs(ScoreZoneSet.Set or {}) do + self:AddZoneScore(_zone,Score) + end + + return self +end + +--- Remove a @{Core.Zone} for scoring. -- The scoring will search if any @{Core.Zone} is added with the given name, and will remove that zone from the scoring. -- This allows for a dynamic destruction zone evolution within your mission. -- @param #SCORING self @@ -965,7 +1074,7 @@ end -- @param #SCORING self -- @param Core.Event#EVENTDATA Event function SCORING:_EventOnHit( Event ) - self:F( { Event } ) + --self:F( { Event } ) local InitUnit = nil local InitUNIT = nil @@ -995,6 +1104,7 @@ function SCORING:_EventOnHit( Event ) local TargetUnitCategory = nil local TargetUnitType = nil local TargetIsScenery = false + local TargetSceneryObject = nil if Event.IniDCSUnit then @@ -1016,7 +1126,7 @@ function SCORING:_EventOnHit( Event ) InitUnitCategory = _SCORINGCategory[InitCategory] InitUnitType = InitType - self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType, InitUnitCoalition, InitUnitCategory, InitUnitType } ) + --self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType, InitUnitCoalition, InitUnitCategory, InitUnitType } ) end if Event.TgtDCSUnit then @@ -1034,18 +1144,23 @@ function SCORING:_EventOnHit( Event ) -- TargetCategory = TargetUnit:getDesc().category TargetCategory = Event.TgtCategory TargetType = Event.TgtTypeName + -- Scenery hit - if (not TargetCategory) and TargetUNIT ~= nil and TargetUnit:IsInstanceOf("SCENERY") then + if TargetUNIT ~= nil and TargetUNIT:IsInstanceOf("SCENERY") then TargetCategory = Unit.Category.STRUCTURE TargetIsScenery = true + TargetType = "Scenery" + TargetSceneryObject = TargetUNIT + self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") + UTILS.PrintTableToLog(TargetSceneryObject) end TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] TargetUnitCategory = _SCORINGCategory[TargetCategory] TargetUnitType = TargetType - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType, TargetUnitCoalition, TargetUnitCategory, TargetUnitType } ) + --self:T( { TargetUnitName=TargetUnitName, TargetGroupName=TargetGroupName, TargetPlayerName=TargetPlayerName, TargetCoalition=TargetCoalition, TargetCategory=TargetCategory, TargetType=TargetType, TargetUnitCoalition=TargetUnitCoalition, TargetUnitCategory=TargetUnitCategory, TargetUnitType=TargetUnitType } ) end if InitPlayerName ~= nil then -- It is a player that is hitting something @@ -1058,7 +1173,7 @@ function SCORING:_EventOnHit( Event ) self:T( "Hitting Something" ) -- What is he hitting? - if TargetCategory then + if (TargetCategory ~=nil) and (TargetIsScenery == false) then -- A target got hit, score it. -- Player contains the score data from self.Players[InitPlayerName] @@ -1078,7 +1193,7 @@ function SCORING:_EventOnHit( Event ) PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT -- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth - if PlayerHit.UNIT.ThreatType == nil then + if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType == nil then PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() -- if this fails for some reason, set a good default value if PlayerHit.ThreatType == nil or PlayerHit.ThreatType == "" then @@ -1164,21 +1279,21 @@ function SCORING:_EventOnHit( Event ) -- It is a weapon initiated by a player, that is hitting something -- This seems to occur only with scenery and static objects. - if Event.WeaponPlayerName ~= nil then - self:_AddPlayerFromUnit( Event.WeaponUNIT ) - if self.Players[Event.WeaponPlayerName] then -- This should normally not happen, but i'll test it anyway. - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - self:_AddPlayerFromUnit( TargetUNIT ) - end + if Event.WeaponPlayerName ~= nil or TargetIsScenery == true then + + local playername = Event.WeaponPlayerName or Event.IniPlayerName or "Ghost" + + --self:_AddPlayerFromUnit( Event.WeaponUNIT ) + if self.Players[playername] then -- This should normally not happen, but i'll test it anyway. - self:T( "Hitting Scenery" ) + self:T( "Hitting Scenery or Static" ) -- What is he hitting? - if TargetCategory then + if Event.TgtObjectCategory then -- A scenery or static got hit, score it. -- Player contains the score data from self.Players[WeaponPlayerName] - local Player = self.Players[Event.WeaponPlayerName] + local Player = self.Players[playername] -- Ensure there is a hit table per TargetCategory and TargetUnitName. Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {} @@ -1186,15 +1301,17 @@ function SCORING:_EventOnHit( Event ) -- PlayerHit contains the score counters and data per unit that was hit. local PlayerHit = Player.Hit[TargetCategory][TargetUnitName] - + + -- Init player scores PlayerHit.Score = PlayerHit.Score or 0 PlayerHit.Penalty = PlayerHit.Penalty or 0 PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT + -- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth - if PlayerHit.UNIT.ThreatType == nil then + if PlayerHit.UNIT and PlayerHit.UNIT.ThreatType == nil then PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() -- if this fails for some reason, set a good default value if PlayerHit.ThreatType == nil then @@ -1202,8 +1319,8 @@ function SCORING:_EventOnHit( Event ) PlayerHit.ThreatType = "Unknown" end else - PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel - PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType + PlayerHit.ThreatLevel = PlayerHit.UNIT and PlayerHit.UNIT.ThreatLevel or 1 + PlayerHit.ThreatType = PlayerHit.UNIT and PlayerHit.UNIT.ThreatType or "Unknown" end -- Only grant hit scores if there was more than one second between the last hit. @@ -1211,50 +1328,68 @@ function SCORING:_EventOnHit( Event ) PlayerHit.TimeStamp = timer.getTime() local Score = 0 - - if InitCoalition then -- A coalition object was hit, probably a static. - if InitCoalition == TargetCoalition then - -- TODO: Penalty according scale - local Penalty = 10 - Player.Penalty = Player.Penalty + Penalty --* self.ScaleDestroyPenalty - PlayerHit.Penalty = PlayerHit.Penalty + Penalty --* self.ScaleDestroyPenalty - PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty - - MESSAGE - :NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - "Penalty: -" .. Penalty .. " = " .. Player.Score - Player.Penalty, - MESSAGE.Type.Update - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - else + + local TgtName = Event.TgtDCSUnit and Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or "Unknown" + + --if InitCoalition then -- A coalition object was hit, probably a static. + if TargetIsScenery == true and self.ScoringScenery:IsInSet(TargetSceneryObject) then Player.Score = Player.Score + self.ScoreIncrementOnHit PlayerHit.Score = PlayerHit.Score + self.ScoreIncrementOnHit PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 - MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. + MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. playername .. "' hit scenery target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - else -- A scenery object was hit. - MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit scenery object.", - MESSAGE.Type.Update ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) + self:ScoreCSV( playername, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) + elseif TargetIsScenery == false and Event.TgtObjectCategory == Object.Category.STATIC and self.ScoringObjects[TgtName] then + Player.Score = Player.Score + self.ScoreIncrementOnHit + PlayerHit.Score = PlayerHit.Score + self.ScoreIncrementOnHit + PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 + MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. playername .. "' hit static target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. + "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, + MESSAGE.Type.Update ) + :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) + :ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - self:ScoreCSV( Event.WeaponPlayerName, "", "HIT_SCORE", 1, 0, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, "", "Scenery", TargetUnitType ) - end + self:ScoreCSV( playername, TargetPlayerName, "HIT_SCORE", 1, 1, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) + + else + self:E("Hit unregistered scenery or static object - NO target! ("..TgtName..")") + end + --end end end end + + -- Check if there are Zones where the destruction happened. + for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do + self:F( { ScoringZone = ScoreZoneData } ) + local hit=Event.TgtUnit + local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE + local Score = ScoreZoneData.Score + if TargetUNIT and ScoreZone:IsVec2InZone( TargetUNIT:GetVec2() ) then + -- A scenery or static got hit, score it. + -- Player contains the score data from self.Players[WeaponPlayerName] + local PlayerName = Event.IniPlayerName or "Ghost" + local Player = self.Players[PlayerName] + + Player.Score = Player.Score + Score + Player.Score = Player.Score + self.ScoreIncrementOnHit + MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." .. + "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, + MESSAGE.Type.Information ) + :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) + :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) + + self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType ) + end + end end -end - + + end + --- Track DEAD or CRASH events for the scoring. -- @param #SCORING self -- @param Core.Event#EVENTDATA Event diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index 9c6b02516..13e523bbf 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -18,7 +18,13 @@ -- @field #string SceneryName Name of the scenery object. -- @field DCS#Object SceneryObject DCS scenery object. -- @field #number Life0 Initial life points. --- @field #table Properties +-- @field #table Properties. +-- @field #number ID. +-- @field Core.Zone#ZONE_POLYGON SceneryZone. +-- @field Core.Point#COORDINATE Coordinate. +-- @field DCS#Vec2 Vec2. +-- @field DCS#Vec3 Vec3. +-- @field Core.Vector#VECTOR Vector. -- @extends Wrapper.Positionable#POSITIONABLE @@ -35,27 +41,72 @@ SCENERY = { ClassName = "SCENERY", } +_SCENERY = {} + --- Register scenery object as POSITIONABLE. --@param #SCENERY self --@param #string SceneryName Scenery name. --@param DCS#Object SceneryObject DCS scenery object. +--@param Core.Zone#ZONE_POLYGON SceneryZone (optional) The zone object. --@return #SCENERY Scenery object. -function SCENERY:Register( SceneryName, SceneryObject ) - +function SCENERY:Register( SceneryName, SceneryObject, SceneryZone ) + + local ID = (SceneryObject and SceneryObject.getID) and SceneryObject:getID() or SceneryName + + if _SCENERY[ID] and _SCENERY[ID].SceneryObject == nil then + + _SCENERY[ID].SceneryObject=SceneryObject + SCENERY._UpdateFromDCSObject(_SCENERY[ID]) + + end + + if _SCENERY[ID] then return _SCENERY[ID] end + local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) ) self.SceneryName = tostring(SceneryName) - + self.ID = ID self.SceneryObject = SceneryObject + self.SceneryZone = SceneryZone + + if SceneryZone then + self.Vec3 = SceneryZone:GetVec3() + self.Vec2 = SceneryZone:GetVec2() + self.Vector = (self.Vec3 and VECTOR) and VECTOR:NewFromVec(self.Vec3) or nil + end if self.SceneryObject and self.SceneryObject.getLife then -- fix some objects do not have all functions - self.Life0 = self.SceneryObject:getLife() or 0 + self.Life0 = self.SceneryObject:getLife() or 1 else - self.Life0 = 0 + self.Life0 = 1 end self.Properties = {} + _SCENERY[self.ID] = self + + return self +end + + +--- [INTERNAL] Update data +-- @param Wrapper.Scenery#SCENERY Scenery The object to update. +function SCENERY._UpdateFromDCSObject(Scenery) + env.info("APPLE _UpdateFromDCSObject "..tostring(Scenery.SceneryName)) + local self=Scenery + if self.Vec2 == nil and self.SceneryObject ~= nil then + self.Vec3 = self.SceneryObject:getPoint() + if self.Vec3 then + self.Vec2 = {x=self.Vec3.x,y=self.Vec3.z} + self.Vector = VECTOR:NewFromVec(self.Vec3) + end + end + if not self.Life0 or self.Life0 == 1 then + if self.SceneryObject and self.SceneryObject.getLife() then + self.Life = self.SceneryObject:getLife() or 1 + self.Life0 = self.Life + end + end return self end @@ -99,6 +150,39 @@ function SCENERY:GetName() return self.SceneryName end +--- Obtain object coordinate. +--@param #SCENERY self +--@return Core.Point#COORDINATE Coordinate +function SCENERY:GetCoordinate() + if self.Coordinate then + return self.Coordinate + elseif self.Vec3 then + self.Coordinate = COORDINATE:NewFromVec3(self.Vec3):SetAlt() + end + return self.Coordinate +end + +--- Obtain object coordinate. +--@param #SCENERY self +--@return DCS#Vec3 Vec3 +function SCENERY:GetVec3() + return self.Vec3 +end + +--- Obtain object coordinate. +--@param #SCENERY self +--@return DCS#Vec2 Vec2 +function SCENERY:GetVec2() + return self.Vec2 +end + +--- Obtain object coordinate. +--@param #SCENERY self +--@return Core.Vector#VECTOR Vector +function SCENERY:GetVector() + return self.Vector +end + --- Obtain DCS Object from the SCENERY Object. --@param #SCENERY self --@return DCS#Object DCS scenery object. @@ -106,6 +190,13 @@ function SCENERY:GetDCSObject() return self.SceneryObject end +--- Obtain object ID. +--@param #SCENERY self +--@return #string Name +function SCENERY:GetID() + return self.ID +end + --- Get current life points from the SCENERY Object. Note - Some scenery objects always have 0 life points. -- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120% -- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success @@ -113,12 +204,14 @@ end --@param #SCENERY self --@return #number life function SCENERY:GetLife() - local life = 0 + local life = 1 if self.SceneryObject and self.SceneryObject.getLife then life = self.SceneryObject:getLife() if life > self.Life0 then self.Life0 = math.floor(life * 1.2) end + elseif self.Life then + life = self.Life end return life end @@ -176,12 +269,19 @@ end --- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first -- to find the correct object. --@param #SCENERY self ---@param #string Name The name/id of the scenery object as taken from the ME. Ex. '595785449' ---@param Core.Point#COORDINATE Coordinate Where to find the scenery object ---@param #number Radius (optional) Search radius around coordinate, defaults to 100 ---@return #SCENERY Scenery Object or `nil` if it cannot be found -function SCENERY:FindByName(Name, Coordinate, Radius, Role) - +--@param #string Name The name/id of the scenery object as taken from the ME. Ex. '595785449'. +--@param Core.Point#COORDINATE Coordinate Where to find the scenery object. +--@param #number Radius (optional) Search radius around coordinate, defaults to 100. +--@param #string Role (optional) The role if set on the zone object. +--@param Core.Zone#ZONE_POLYGON Zone (optional) The Zone where the scenery is located. +--@return #SCENERY Scenery Object or `nil` if it cannot be found. +function SCENERY:FindByName(Name, Coordinate, Radius, Role, Zone) + + --BASE:I("Coordinate x = "..Coordinate.x .. " y = "..Coordinate.y.." z = "..Coordinate.z) + + local findme = self:_FindByName(Name) + if findme then return findme end + local radius = Radius or 100 local name = Name or "unknown" local scenery = nil @@ -193,12 +293,13 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role) local function SceneryScan(scoordinate, sradius, sname) if scoordinate ~= nil then local Vec2 = scoordinate:GetVec2() - local scanzone = ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius,true) + local scanzone = ZONE_RADIUS:New("Zone-"..sname,Vec2,sradius) scanzone:Scan({Object.Category.SCENERY}) local scanned = scanzone:GetScannedSceneryObjects() local rscenery = nil -- Wrapper.Scenery#SCENERY for _,_scenery in pairs(scanned) do local scenery = _scenery -- Wrapper.Scenery#SCENERY + --BASE:I({tostring(scenery.SceneryName),tostring(sname)}) if tostring(scenery.SceneryName) == tostring(sname) then rscenery = scenery if Role then rscenery:SetProperty("ROLE",Role) end @@ -211,13 +312,36 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role) end if Coordinate then - --BASE:I("Coordinate Scenery Scan") scenery = SceneryScan(Coordinate, radius, name) end - + + if not scenery then scenery = SCENERY:Register(Name,nil,Zone) end + return scenery end +--- Find a SCENERY object that was previously registered(!) by it's ID. +-- @param #SCENERY self +-- @param #number ID +-- @return Wrapper.Scenery#SCENERY Scenery or nil if it could not be found +function SCENERY:FindByID(ID) + return _SCENERY[ID] +end + +--- Find a SCENERY object that was previously registered(!) by it's name. +-- @param #SCENERY self +-- @param #string Name +-- @return Wrapper.Scenery#SCENERY Scenery or nil if it could not be found +function SCENERY:_FindByName(Name) + for _id,_object in pairs(_SCENERY) do + if _object and _object.GetName and _object:GetName() then + local name = _object:GetName() + if Name == name then return _object end + end + end + return nil +end + --- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first -- to find the correct object. --@param #SCENERY self @@ -231,8 +355,8 @@ function SCENERY:FindByNameInZone(Name, Zone, Radius) if type(Zone) == "string" then Zone = ZONE:FindByName(Zone) end - local coordinate = Zone:GetCoordinate() - return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE")) + local coordinate = Zone:GetCoordinate():SetAlt() + return self:FindByName(Name,coordinate,Radius,Zone:GetProperty("ROLE"),Zone) end --- Find a SCENERY object from its zone name. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first @@ -241,14 +365,18 @@ end --@param #string ZoneName The name of the scenery zone as created with a right-click on the map in the mission editor and select "assigned to...". Can be handed over as ZONE object. --@return #SCENERY First found Scenery Object or `nil` if it cannot be found function SCENERY:FindByZoneName( ZoneName ) + --BASE:I(ZoneName) + local zone = ZoneName -- Core.Zone#ZONE_BASE + if type(ZoneName) == "string" then zone = ZONE:FindByName(ZoneName) -- Core.Zone#ZONE_POLYGON end + local _id = zone:GetProperty('OBJECT ID') - --local properties = zone:GetAllProperties() or {} - --BASE:I(string.format("Object ID is: %s",_id or "none")) - --BASE:T("Object ID ".._id) + + --BASE:I("Object ID ".._id) + if not _id then -- this zone has no object ID BASE:E("**** Zone without object ID: "..ZoneName.." | Type: "..tostring(zone.ClassName)) @@ -257,23 +385,27 @@ function SCENERY:FindByZoneName( ZoneName ) local scanned = zone:GetScannedSceneryObjects() for _,_scenery in (scanned) do local scenery = _scenery -- Wrapper.Scenery#SCENERY - if scenery:IsAlive() then + --if scenery:IsAlive() then local role = zone:GetProperty("ROLE") if role then scenery:SetProperty("ROLE",role) end return scenery - end + --end end return nil else - return self:FindByName(_id, zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) + local coordinate = zone:GetCoordinate() + coordinate:SetAlt() + return self:FindByName(_id, coordinate,nil,zone:GetProperty("ROLE"),zone) end else - return self:FindByName(_id, zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) + local coordinate = zone:GetCoordinate() + coordinate:SetAlt() + return self:FindByName(_id, coordinate,nil,zone:GetProperty("ROLE"),zone) end end --- Scan and find all SCENERY objects from a zone by zone-name. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first --- to find the correct object. +-- to find the correct object. Might return nil! --@param #SCENERY self --@param #string ZoneName The name of the zone, can be handed as ZONE_RADIUS or ZONE_POLYGON object --@return #table of SCENERY Objects, or `nil` if nothing found @@ -295,7 +427,7 @@ function SCENERY:FindAllByZoneName( ZoneName ) return nil end else - local obj = self:FindByName(_id, zone:GetCoordinate(),nil,zone:GetProperty("ROLE")) + local obj = self:FindByName(_id, zone:GetCoordinate():SetAlt(),nil,zone:GetProperty("ROLE"), zone) if obj then return {obj} else From c4f0440b0a703191581ee5965e09e6b92e8a45a3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Nov 2025 16:17:00 +0100 Subject: [PATCH 274/349] xx --- Moose Development/Moose/Ops/Auftrag.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 22 +++++++++++++++++++--- Moose Development/Moose/Ops/RescueHelo.lua | 15 ++++++++++----- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index cdad223f1..a3ac50a46 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2037,7 +2037,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) -- Mission options: mission.missionTask=ENUMS.MissionTask.NOTHING - mission.missionFraction=0.5 + mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 6a94c713a..3ec931d9e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4303,13 +4303,24 @@ function OPSGROUP:_UpdateTask(Task, Mission) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then - if Mission.Type == AUFTRAG.Type.RESCUEHELO then + if Mission.type == AUFTRAG.Type.RESCUEHELO then + self:I("**********") + self:I("** RESCUEHELO USED") + self:I("**********") local param=Task.dcstask.params local followUnit=UNIT:FindByName(param.unitname) local helogroupname = self:GetGroup():GetName() Task.formation = RESCUEHELO:New(followUnit,helogroupname) + Task.formation:SetRespawnOnOff(false) + Task.formation.respawninair=false + Task.formation:SetTakeoffCold() + Task.formation:SetHomeBase(followUnit) + Task.formation.helo = self:GetGroup() -- Start formation FSM. Task.formation:Start() + if self:IsFlightgroup() then + self:SetDespawnAfterLanding() + end else -- Set of group(s) to follow Mother. @@ -4329,7 +4340,7 @@ function OPSGROUP:_UpdateTask(Task, Mission) Task.formation:SetFollowTimeInterval(param.dtFollow) -- Formation mode. - Task.formation:SetFlightModeFormation(self.group) + --Task.formation:SetFlightModeFormation(self.group) -- Start formation FSM. Task.formation:Start() @@ -8031,11 +8042,16 @@ function OPSGROUP:onafterDead(From, Event, To) -- Get asset. local asset=self.legion:GetAssetByName(self.groupname) + if asset then + -- Get request. local request=self.legion:GetRequestByID(asset.rid) -- Trigger asset dead event. self.legion:AssetDead(asset, request) + + end + end -- Stop in 5 sec to give possible respawn attempts a chance. @@ -12728,7 +12744,7 @@ end -- @return #OPSGROUP self function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) - self:T(self.lid..string.format("Setting Default callsing %s-%s", tostring(CallsignName), tostring(CallsignNumber))) + self:T(self.lid..string.format("Setting Default callsign %s-%s", tostring(CallsignName), tostring(CallsignNumber))) self.callsignDefault={} --#OPSGROUP.Callsign self.callsignDefault.NumberSquad=CallsignName diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index fed02dfc8..62b54cfa8 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -234,7 +234,7 @@ _RESCUEHELOID=0 --- Class version. -- @field #string version -RESCUEHELO.version="1.1.0" +RESCUEHELO.version="1.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -247,6 +247,7 @@ RESCUEHELO.version="1.1.0" -- DONE: Possibility to add already present/spawned aircraft, e.g. for warehouse. -- DONE: Add rescue event when aircraft crashes. -- DONE: Make offset input parameter. +-- DONE: Make useable for AUFTRAG ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -336,6 +337,9 @@ function RESCUEHELO:New(carrierunit, helogroupname) self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "Stopped") + + self:I(self.lid.."Started.") + --- Triggers the FSM event "Start" that starts the rescue helo. Initializes parameters and starts event handlers. -- @function [parent=#RESCUEHELO] Start @@ -875,7 +879,8 @@ function RESCUEHELO:onafterStart(From, Event, To) local Spawn = GROUP:FindByName(self.helogroupname) if Spawn and Spawn:IsAlive() then self.helo=Spawn - UsesAliveGroup = true + UsesAliveGroup = true + delay = 1 else -- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine. @@ -907,7 +912,7 @@ function RESCUEHELO:onafterStart(From, Event, To) -- Start formation in 1 seconds delay=1 - elseif UsesAliveGroup==false then + elseif UsesAliveGroup==false and self.uncontrolledac then -- Check if an uncontrolled helo group was requested. if self.uncontrolledac then @@ -927,9 +932,9 @@ function RESCUEHELO:onafterStart(From, Event, To) -- No group of that name! self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!", self.helogroupname)) return - end + end - else + elseif UsesAliveGroup==false then -- Spawn at airbase. self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable) From 9e81e5606a54d534770201827294b9bfa754fc5b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 28 Nov 2025 08:47:37 +0100 Subject: [PATCH 275/349] xx --- Moose Development/Moose/Utilities/Utils.lua | 45 +++++++++++---------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 873b68619..980e908c5 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4222,25 +4222,27 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, local function PopulateStorage(Name,liquids,equip,airframes) local newWH = STORAGE:New(Name) - if liquids and liquids > 0 then - -- Storage fill-up - newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons - newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) - newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) - newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) - end - - if equip and equip > 0 then - for cat,nitem in pairs(ENUMS.Storage.weapons) do - for name,item in pairs(nitem) do - newWH:SetItem(item,equip) + if newWH then + if liquids and liquids > 0 then + -- Storage fill-up + newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons + newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) + newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) + newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) + end + + if equip and equip > 0 then + for cat,nitem in pairs(ENUMS.Storage.weapons) do + for name,item in pairs(nitem) do + newWH:SetItem(item,equip) + end end end - end - - if airframes and airframes > 0 then - for typename in pairs (CSAR.AircraftType) do - newWH:SetItem(typename,airframes) + + if airframes and airframes > 0 then + for typename in pairs (CSAR.AircraftType) do + newWH:SetItem(typename,airframes) + end end end end @@ -4319,8 +4321,6 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, } -- Create BIRTH event. world.onEvent(Event) - - PopulateStorage(Name.."-1",liquids,equip,airframes) else -- Spawn FARP local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" @@ -4328,10 +4328,11 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) - - PopulateStorage(Name,liquids,equip,airframes) + end - + + PopulateStorage(Name,liquids,equip,airframes) + -- Spawn Objects local FARPStaticObjectsNato = { ["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"}, From 9992361980396b4c7a1409514984dbc43ef49a2b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 28 Nov 2025 09:15:50 +0100 Subject: [PATCH 276/349] xx --- Moose Development/Moose/Ops/RescueHelo.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 62b54cfa8..65190c11d 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -884,7 +884,7 @@ function RESCUEHELO:onafterStart(From, Event, To) else -- Spawn helo. We need to introduce an alias in case this class is used twice. This would confuse the spawn routine. - local Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias) + Spawn=SPAWN:NewWithAlias(self.helogroupname, self.alias) -- Set modex for spawn. Spawn:InitModex(self.modex) @@ -933,8 +933,8 @@ function RESCUEHELO:onafterStart(From, Event, To) self:E(string.format("ERROR: No uncontrolled (alive) rescue helo group with name %s could be found!", self.helogroupname)) return end - - elseif UsesAliveGroup==false then + end + elseif UsesAliveGroup==false then -- Spawn at airbase. self.helo=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.HelicopterUsable) @@ -948,9 +948,8 @@ function RESCUEHELO:onafterStart(From, Event, To) delay=60 end - end - end + -- Set of group(s) to follow Mother. self.followset=SET_GROUP:New() From 7456489fb0e1c25481136f0fdbdd7302447ff298 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 28 Nov 2025 12:48:49 +0100 Subject: [PATCH 277/349] xx --- .../Moose/Functional/Scoring.lua | 25 ++++---- Moose Development/Moose/Ops/CTLD.lua | 62 ++++++++++++++++--- 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index 6ddd370c4..a05747933 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -1153,7 +1153,7 @@ function SCORING:_EventOnHit( Event ) TargetType = "Scenery" TargetSceneryObject = TargetUNIT self:T("***** Target is Scenery and TargetUNIT is SCENERY object!") - UTILS.PrintTableToLog(TargetSceneryObject) + --UTILS.PrintTableToLog(TargetSceneryObject) end TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] @@ -1374,17 +1374,18 @@ function SCORING:_EventOnHit( Event ) -- Player contains the score data from self.Players[WeaponPlayerName] local PlayerName = Event.IniPlayerName or "Ghost" local Player = self.Players[PlayerName] - - Player.Score = Player.Score + Score - Player.Score = Player.Score + self.ScoreIncrementOnHit - MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, - MESSAGE.Type.Information ) - :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) - - self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType ) - end + if Player then + Player.Score = Player.Score + Score + Player.Score = Player.Score + self.ScoreIncrementOnHit + MESSAGE:NewType( self.DisplayMessagePrefix .. "hit in zone '" .. ScoreZone:GetName() .. "'." .. + "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, + MESSAGE.Type.Information ) + :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) + :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) + + self:ScoreCSV( PlayerName, "", "HIT_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Zone", TargetUnitType ) + end + end end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 42e8d4e03..de13ea731 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1423,7 +1423,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.39" +CTLD.version="1.3.40" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2273,6 +2273,12 @@ function CTLD:_FindCratesCargoObject(Name) return cargo end end + for _,_cargo in pairs(self.Cargo_Statics)do + local cargo = _cargo -- #CTLD_CARGO + if cargo.Name == Name then + return cargo + end + end return nil end @@ -2840,14 +2846,16 @@ end -- @param #table stockSummary Optional pooled stock summary. -- @return #CTLD self function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSummary) + self:T("_AddCrateQuantityMenus "..cargoObj.Name) local needed = cargoObj:GetCratesNeeded() or 1 local stockEntry = self:_GetCrateStockEntry(cargoObj, stockSummary) - local stock = nil + local stock = 0 if stockEntry and type(stockEntry.Stock) == "number" then stock = stockEntry.Stock else stock = cargoObj:GetStock() end + self:T("_AddCrateQuantityMenus "..cargoObj.Name.." Stock: "..tostring(stock)) local maxQuantity = self.maxCrateMenuQuantity or 1 local availableSets = nil if type(stock) == "number" and stock >= 0 then @@ -2861,6 +2869,7 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum end end maxQuantity = math.floor(maxQuantity) + self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity) if maxQuantity < 1 then return self end @@ -2896,6 +2905,7 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum maxQuantity = 1 end end + self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) local maxMassSets = nil if Unit then local maxload = self:_GetMaxLoadableMass(Unit) @@ -2911,10 +2921,14 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum end end end + self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) if maxQuantity < 1 then return self end + if maxQuantity == 1 then + self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) + --parentMenu.MenuText MENU_GROUP_COMMAND:New(Group, "Get", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) local canLoad = (allowLoad and (not capacitySets or capacitySets >= 1) and (not maxMassSets or maxMassSets >= 1)) if canLoad then @@ -2928,10 +2942,14 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum end MENU_GROUP_COMMAND:New(Group, msg, parentMenu, self._SendMessage, self, msg, 10, false, Group) end + parentMenu:Refresh() return self end + for quantity = 1, maxQuantity do + self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") local label = tostring(quantity) + self:T("_AddCrateQuantityMenus Label "..label) local qMenu = MENU_GROUP:New(Group, label, parentMenu) MENU_GROUP_COMMAND:New(Group, "Get", qMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity) local canLoad = (allowLoad and (not capacitySets or capacitySets >= quantity) and (not maxMassSets or maxMassSets >= quantity)) @@ -5105,7 +5123,12 @@ function CTLD:_RefreshF10Menus() return end local needed = cargoObj:GetCratesNeeded() or 1 - local txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + local txt + if needed > 1 then + txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + else + txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + end if cargoObj.Location then txt = txt.."[R]" end if self.showstockinmenuitems then local suffix = self:_FormatCrateStockSuffix(cargoObj,crateStockSummary) @@ -5116,7 +5139,7 @@ function CTLD:_RefreshF10Menus() self:_AddCrateQuantityMenus(_group,_unit,mSet,cargoObj,crateStockSummary) end - if self.usesubcats then + if self.usesubcats == true then local subcatmenus = {} for catName,_ in pairs(self.subcats) do subcatmenus[catName] = MENU_GROUP:New(_group,catName,cratesmenu) @@ -5144,7 +5167,12 @@ function CTLD:_RefreshF10Menus() for _, cargoObj in pairs(self.Cargo_Crates) do if not cargoObj.DontShowInMenu then local needed = cargoObj:GetCratesNeeded() or 1 - local txt = string.format("%d crate%s %s (%dkg)", needed, needed==1 and "" or "s", cargoObj.Name, cargoObj.PerCrateMass or 0) + local txt + if needed > 1 then + txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + else + txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + end if cargoObj.Location then txt = txt.."[R]" end local stock = cargoObj:GetStock() if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end @@ -5154,7 +5182,12 @@ function CTLD:_RefreshF10Menus() for _, cargoObj in pairs(self.Cargo_Statics) do if not cargoObj.DontShowInMenu then local needed = cargoObj:GetCratesNeeded() or 1 - local txt = string.format("%d crate%s %s (%dkg)", needed, needed==1 and "" or "s", cargoObj.Name, cargoObj.PerCrateMass or 0) + local txt + if needed > 1 then + txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + else + txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + end if cargoObj.Location then txt = txt.."[R]" end local stock = cargoObj:GetStock() if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end @@ -5165,7 +5198,12 @@ function CTLD:_RefreshF10Menus() for _, cargoObj in pairs(self.Cargo_Crates) do if not cargoObj.DontShowInMenu then local needed = cargoObj:GetCratesNeeded() or 1 - local txt = string.format("%d crate%s %s (%dkg)", needed, needed==1 and "" or "s", cargoObj.Name, cargoObj.PerCrateMass or 0) + local txt + if needed > 1 then + txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + else + txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + end if cargoObj.Location then txt = txt.."[R]" end local stock = cargoObj:GetStock() if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end @@ -5175,7 +5213,12 @@ function CTLD:_RefreshF10Menus() for _, cargoObj in pairs(self.Cargo_Statics) do if not cargoObj.DontShowInMenu then local needed = cargoObj:GetCratesNeeded() or 1 - local txt = string.format("%d crate%s %s (%dkg)", needed, needed==1 and "" or "s", cargoObj.Name, cargoObj.PerCrateMass or 0) + local txt + if needed > 1 then + txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + else + txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) + end if cargoObj.Location then txt = txt.."[R]" end local stock = cargoObj:GetStock() if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end @@ -8655,10 +8698,11 @@ do -- TODO CTLD_HERCULES ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -- This script will only work for the Herculus mod by Anubis, and only for **Air Dropping** cargo from the Hercules. +-- It *DOES NOT* work with the purchaseable Hercules module from ED. -- Use the standard Moose CTLD if you want to unload on the ground. -- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file -- Except for Ammo pallets, this script will spawn whatever payload gets launched from pylons 11, 12 and 13 --- Pylons 11, 12 and 13 are moveable within the Herculus cargobay area +-- Pylons 11, 12 and 13 are moveable within the Hercules cargobay area -- Ammo pallets can only be jettisoned from these pylons with no benefit to DCS world -- To benefit DCS world, Ammo pallets need to be off/on loaded using DCS arming and refueling window -- Cargo_Container_Enclosed = true: Cargo enclosed in container with parachute, need to be dropped from 100m (300ft) or more, except when parked on ground From 7189154d4741ecf5fe5f50237c931f9c46e10be8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 7 Dec 2025 13:23:23 +0100 Subject: [PATCH 278/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 5 +++-- Moose Development/Moose/Wrapper/Group.lua | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 43df08964..d84dfb615 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1423,7 +1423,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.40" +CTLD.version="1.3.41" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2611,7 +2611,7 @@ end self:T(self.lid .. " _ExtractTroops") -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) - local hoverload = self:CanHoverLoad(Unit) + local hoverload = self:IsCorrectHover(Unit) -- correct call now for extracting troops while hovering local hassecondaries = false if not grounded and not hoverload then @@ -7007,6 +7007,7 @@ end -- @return #boolean Outcome function CTLD:IsCorrectHover(Unit) self:T(self.lid .. " IsCorrectHover") + if self:IsFixedWing(Unit) then return false end -- FW cannot hover local outcome = false -- see if we are in air and within parameters. if self:IsUnitInAir(Unit) then diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index ad7afeef1..acd1c4932 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2439,7 +2439,7 @@ end -- @return #table The mission route defined by points. function GROUP:GetTaskRoute() --self:F2( self.GroupName ) - if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then + if _DATABASE.Templates.Groups[self.GroupName] and _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) else return {} From d76e8ddc3ff68ece6a05e6a3360f966bc0a9dcab Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 7 Dec 2025 15:13:04 +0100 Subject: [PATCH 279/349] xx --- .../Moose/Functional/PseudoATC.lua | 48 ++++++++++++++++++- Moose Development/Moose/Navigation/Radios.lua | 2 +- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua index 16c530cf3..8efd45c15 100644 --- a/Moose Development/Moose/Functional/PseudoATC.lua +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -100,7 +100,7 @@ PSEUDOATC.id="PseudoATC | " --- PSEUDOATC version. -- @field #number version -PSEUDOATC.version="0.10.5" +PSEUDOATC.version="0.10.6" ----------------------------------------------------------------------------------------------------------------------------------------- @@ -579,6 +579,9 @@ function PSEUDOATC:MenuAirports(GID,UID) -- Create menu reporting commands missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name) missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name) + if self.radios then + missionCommands.addCommandForGroup(GID, "Radios", submenu, self.ReportRadios, self, GID, UID, pos, name) + end -- Debug message. self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID)) @@ -705,7 +708,30 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location) end ---- Report absolute bearing and range form player unit to airport. +--- Report airport radio information. +-- @param #PSEUDOATC self +-- @param #number GID Group id of player unit. +-- @param #number UID Unit id of player. +-- @param Core.Point#COORDINATE position Coordinate of the airport. +-- @param #string location Name of the airport. +function PSEUDOATC:ReportRadios(GID, UID, position, location) + self:F({GID=GID, UID=UID, position=position, location=location}) + if self.radios then + local Text="" + local radio = self.radios:GetClosestRadio(position,9) --Navigation.Radios#RADIOS.Radio + if radio then + Text=self.radios:_GetMarkerText(radio) + else + Text=self.group[GID].player[UID].playername..", no radio information found!" + end + -- Send message + self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, Text, self.mdur, true) + end + return self +end + + +--- Report absolute bearing and range from player unit to airport. -- @param #PSEUDOATC self -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. @@ -979,3 +1005,21 @@ function PSEUDOATC:_myname(unitname) return string.format("%s (%s)", csign, pname) end + +--- Returns a string which consits of this callsign and the player name. +-- @param #PSEUDOATC self +-- @param #string path Path to map data, e.g. `[[\Mods\terrains\\Radio.lua]]` (replace with correct path). +-- Needs `lfs` and `io` to be desanitized in the `MissionScripting.lua` in `\Scripts` +-- @return #PSEUDOATC self +-- @usage +-- +-- mypseudoatc:SetUsingRadioInformationFromMap([[C:\Program Files\Eagle Dynamics\DCS World.Openbeta\Mods\terrains\Caucasus\Radio.lua]]) +-- +function PSEUDOATC:SetUsingRadioInformationFromMap(path) + if RADIOS and lfs and io then + self.radios = RADIOS:NewFromFile(path) + else + self:E("PSEUDOATC:SetUsingRadioInformationFromMap Needs `lfs`and `io` to be desanitized in the `MissionScripting.lua` in `\Scripts`") + end + return self +end diff --git a/Moose Development/Moose/Navigation/Radios.lua b/Moose Development/Moose/Navigation/Radios.lua index 4fe1dad72..fb78e56f4 100644 --- a/Moose Development/Moose/Navigation/Radios.lua +++ b/Moose Development/Moose/Navigation/Radios.lua @@ -49,7 +49,7 @@ -- -- A new `RADIOS` object can be created with the @{#RADIOS.NewFromFile}(*radio_lua_file*) function. -- --- local radios=RADIOS:NewFromFile("\Mods\terrains\\radio.lua") +-- local radios=RADIOS:NewFromFile("\Mods\terrains\\Radio.lua") -- radios:MarkerShow() -- -- This will load the radios from the `` for the specific map and place markers on the F10 map. This is the first step you should do to ensure that the file From 65ab481432a966020b6c390c3d65065c85ecbc04 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 11 Dec 2025 09:35:17 +0100 Subject: [PATCH 280/349] xx --- .../Moose/Functional/PseudoATC.lua | 3 +- Moose Development/Moose/Ops/EasyA2G.lua | 735 ++++++++++++++++++ Moose Development/Moose/Ops/EasyGCICAP.lua | 8 +- 3 files changed, 742 insertions(+), 4 deletions(-) create mode 100644 Moose Development/Moose/Ops/EasyA2G.lua diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua index 8efd45c15..defeb0064 100644 --- a/Moose Development/Moose/Functional/PseudoATC.lua +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -1019,7 +1019,8 @@ function PSEUDOATC:SetUsingRadioInformationFromMap(path) if RADIOS and lfs and io then self.radios = RADIOS:NewFromFile(path) else - self:E("PSEUDOATC:SetUsingRadioInformationFromMap Needs `lfs`and `io` to be desanitized in the `MissionScripting.lua` in `\Scripts`") + self:E("PSEUDOATC:SetUsingRadioInformationFromMap Needs `lfs`and `io` to be desanitized in the `MissionScripting.lua` in `/Scripts`") end return self end + diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua new file mode 100644 index 000000000..b1e19349e --- /dev/null +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -0,0 +1,735 @@ +------------------------------------------------------------------------- +-- 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 A2Gdefense 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 Defense Point that it manages. Defense Points 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.75, i.e. a group of 3 intrudes will +-- be managed by 2 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 Kuitaisi 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 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("Blue A2G Operations",AIRBASE.Caucasus.Kutaisi,"blue","Blue SCOUT") +-- +-- -- Add a patrol point belonging to our airbase, we'll be at 15k ft doing 200 kn, initial direction 90 degrees (East), leg 20NM +-- mywing:AddPatrolPointA2G(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone 1"):GetCoordinate(),15000,200,90,20) +-- +-- -- Add a Squadron with template "Blue Sq1 M2000c", 20 airframes, skill good, Modex starting with 102 and skin "Vendee Jeanne" +-- mywing:AddSquadron("Blue Sq1 M2000c","A2G Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.GOOD,102,"ec1.5_Vendee_Jeanne_clean") +-- +-- -- 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 300 kn. +-- * @{#EASYA2G.SetDefaultA2GAlt}: Set at which altitude (ASL) the A2G planes will fly, default 25,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 15 NM. +-- * @{#EASYA2G.SetDefaultA2GGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2. +-- * @{#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.75, + capgrouping = 2, + airbasename = nil, + airbase = nil, + coalition = "blue", + alias = nil, + wings = {}, + Intel = nil, + resurrection = 900, + capspeed = 300, + capalt = 25000, + capdir = 45, + capleg = 15, + maxinterceptsize = 2, + missionrange = 100, + noalert5 = 4, + ManagedAW = {}, + ManagedSQ = {}, + ManagedCP = {}, + ManagedTK = {}, + ManagedEWR = {}, + ManagedREC = {}, + MaxAliveMissions = 8, + debug = false, + 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 EWRName = {EWRName} 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 = 15000 + self.capdir = 90 + self.capleg = 15 + 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 15 +-- @return #EASYA2G self +function EASYA2G:SetDefaultA2GLeg(Leg) + self:T(self.lid.."SetDefaultLeg") + self.capleg = Leg or 15 + 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 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:AddPatrolPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) + self:T(self.lid.."AddPatrolPointA2G")--..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 15 + self.ManagedCP[#self.ManagedCP+1] = EntryCAP + if self.debug then + local mark = MARKER:New(coordinate,self.lid.."Patrol 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 + +--- 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) + -- 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 + 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 Interceptors need wingsize %d", UTILS.GetCoalitionName(self.coalition), wingsize),15,"CAPGCI"):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 InterceptAuftrag = AUFTRAG:NewBAI(contact.group,capalt) + :SetMissionRange(150) + :SetPriority(1,true,1) + --:SetRequiredAssets(wingsize) + :SetRepeatOnFailure(repeats) + :SetMissionSpeed(UTILS.KnotsToAltKIAS(capspeed,capalt)) + :SetMissionAltitude(capalt) + + 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 + + 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 + + diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 8a902cad5..1fe96f3dd 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -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 @@ -1459,6 +1459,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, From 05035964147a171c72f2d518800d96f976b50a48 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 11 Dec 2025 15:41:55 +0100 Subject: [PATCH 281/349] xx --- Moose Development/Moose/Functional/Warehouse.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 53c7d95af..9ed4c030f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4112,8 +4112,8 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, -- Get name of template group. local templategroupname=group:GetName() - - local Descriptors=group:GetUnit(1):GetDesc() + local unit = group:GetUnit(1) + local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {} local Category=group:GetCategory() local TypeName=group:GetTypeName() local SpeedMax=group:GetSpeedMax() From 12bde81e67ced7b7645268023ffe1135b0265dd6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 11 Dec 2025 15:47:26 +0100 Subject: [PATCH 282/349] xx --- Moose Development/Moose/Modules_local.lua | 1 + Moose Development/Moose/Ops/Auftrag.lua | 2 +- Moose Development/Moose/Ops/EasyA2G.lua | 240 ++++++++++++++++++--- Moose Development/Moose/Ops/EasyGCICAP.lua | 24 ++- Moose Development/Moose/Ops/OpsGroup.lua | 9 + Moose Setup/Moose.files | 1 + 6 files changed, 237 insertions(+), 40 deletions(-) diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index 44738a244..cf5679f4b 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -115,6 +115,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( 'Sound\\UserSound.lua' ) __Moose.Include( 'Sound\\SoundOutput.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 index b1e19349e..e385878a1 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -88,7 +88,7 @@ -- -- # The EasyAG Concept -- --- The idea of this class is partially to make the OPS classes easier operational for an A2Gdefense network, and to replace the legacy AI_A2G_Dispatcher system - not to it's +-- 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 @@ -96,38 +96,49 @@ -- ## 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 Defense Point that it manages. Defense Points will be covered by the AirWing automatically as long as airframes are available. Detected enemy ground forces will be assigned to **one** +-- 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.75, i.e. a group of 3 intrudes will --- be managed by 2 planes from the assigned AirWing. There is an maximum missions limitation per AirWing, so we do not spam the skies. +-- 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 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 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 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. +-- 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("Blue A2G Operations",AIRBASE.Caucasus.Kutaisi,"blue","Blue SCOUT") +-- local mywing = EASYA2G:New("A2G",AIRBASE.Caucasus.Kutaisi,"blue","SCOUT") -- --- -- Add a patrol point belonging to our airbase, we'll be at 15k ft doing 200 kn, initial direction 90 degrees (East), leg 20NM --- mywing:AddPatrolPointA2G(AIRBASE.Caucasus.Kutaisi,ZONE:FindByName("Blue Zone 1"):GetCoordinate(),15000,200,90,20) +-- -- 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 Squadron with template "Blue Sq1 M2000c", 20 airframes, skill good, Modex starting with 102 and skin "Vendee Jeanne" --- mywing:AddSquadron("Blue Sq1 M2000c","A2G Kutaisi",AIRBASE.Caucasus.Kutaisi,20,AI.Skill.GOOD,102,"ec1.5_Vendee_Jeanne_clean") +-- -- 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 @@ -177,11 +188,11 @@ -- ## 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 300 kn. --- * @{#EASYA2G.SetDefaultA2GAlt}: Set at which altitude (ASL) the A2G planes will fly, default 25,000 ft. +-- * @{#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 15 NM. --- * @{#EASYA2G.SetDefaultA2GGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2. +-- * @{#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. @@ -198,8 +209,8 @@ -- @field #EASYA2G EASYA2G = { ClassName = "EASYA2G", - overhead = 0.75, - capgrouping = 2, + overhead = 0.2, + capgrouping = 1, airbasename = nil, airbase = nil, coalition = "blue", @@ -210,10 +221,10 @@ EASYA2G = { capspeed = 300, capalt = 25000, capdir = 45, - capleg = 15, + capleg = 5, maxinterceptsize = 2, missionrange = 100, - noalert5 = 4, + noalert5 = 2, ManagedAW = {}, ManagedSQ = {}, ManagedCP = {}, @@ -221,7 +232,7 @@ EASYA2G = { ManagedEWR = {}, ManagedREC = {}, MaxAliveMissions = 8, - debug = false, + debug = true, engagerange = 50, repeatsonfailure = 3, GoZoneSet = nil, @@ -304,7 +315,7 @@ function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName) 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 EWRName = {EWRName} end + if type(ScoutName) == "string" then ScoutName = {ScoutName} end self.EWRName = ScoutName --or self.coalitionname.." EWR" --self.CapZoneName = CapZoneName self.airbasename = AirbaseName @@ -314,9 +325,9 @@ function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName) self.ConflictZoneSet = SET_ZONE:New() self.resurrection = 900 self.capspeed = 225 - self.capalt = 15000 + self.capalt = 5000 self.capdir = 90 - self.capleg = 15 + self.capleg = 5 self.capgrouping = 2 self.missionrange = 100 self.noalert5 = 2 @@ -410,11 +421,11 @@ end --- Set default leg length in NM -- @param #EASYA2G self --- @param #number Leg Leg defaults to 15 +-- @param #number Leg Leg defaults to 5 -- @return #EASYA2G self function EASYA2G:SetDefaultA2GLeg(Leg) self:T(self.lid.."SetDefaultLeg") - self.capleg = Leg or 15 + self.capleg = Leg or 5 return self end @@ -449,7 +460,7 @@ function EASYA2G:SetA2GEngageTargetTypes(types) return self end ---- Add a A2G patrol point to a Wing +--- 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). @@ -458,8 +469,8 @@ end -- @param #number Heading Defaults to 90 degrees (East). -- @param #number LegLength Defaults to 15 NM. -- @return #EASYA2G self -function EASYA2G:AddPatrolPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) - self:T(self.lid.."AddPatrolPointA2G")--..Coordinate:ToStringLLDDM()) +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 @@ -472,14 +483,15 @@ function EASYA2G:AddPatrolPointA2G(AirbaseName,Coordinate,Altitude,Speed,Heading EntryCAP.Altitude = Altitude or 25000 EntryCAP.Speed = Speed or 300 EntryCAP.Heading = Heading or 90 - EntryCAP.LegLength = LegLength or 15 + EntryCAP.LegLength = LegLength or 5 self.ManagedCP[#self.ManagedCP+1] = EntryCAP if self.debug then - local mark = MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll() + 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. @@ -508,7 +520,7 @@ function EASYA2G:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, S 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) + 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 @@ -562,15 +574,39 @@ function EASYA2G:_TryAssignMission(ReadyFlightGroups,Auftrag,Group,WingSize) 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 + local capalt = self.capalt or 5000 local maxsize = self.maxinterceptsize local repeatsonfailure = self.repeatsonfailure @@ -582,7 +618,7 @@ function EASYA2G:_AssignMission(Cluster) local ReadyFlightGroups = self.ReadyFlightGroups -- Aircraft? - if Cluster.ctype ~= INTEL.Ctype.AIRCRAFT then return end + if Cluster.ctype == INTEL.Ctype.AIRCRAFT then return end -- Threatlevel 0..10 local contact = self.Intel:GetHighestThreatContact(Cluster) local name = contact.groupname --#string @@ -601,7 +637,7 @@ function EASYA2G:_AssignMission(Cluster) retrymission = false end if (retrymission) and (wingsize >= 1) then - MESSAGE:New(string.format("**** %s Interceptors need wingsize %d", UTILS.GetCoalitionName(self.coalition), wingsize),15,"CAPGCI"):ToAllIf(self.debug):ToLog() + 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 @@ -651,13 +687,26 @@ function EASYA2G:_AssignMission(Cluster) 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( @@ -682,6 +731,14 @@ function EASYA2G:_AssignMission(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:_TryAssignMission(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then @@ -732,4 +789,119 @@ function EASYA2G:_StartIntel() 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 1fe96f3dd..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". -- @@ -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) @@ -1470,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 @@ -1605,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 = {} @@ -1616,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 2eb5374b28981748b968b89da67a94a0f5019c74 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 12 Dec 2025 12:52:10 +0100 Subject: [PATCH 283/349] xx --- Moose Development/Moose/Ops/EasyA2G.lua | 34 ++++++++++++-- Moose Development/Moose/Ops/EasyGCICAP.lua | 54 +++++++++++++++++++++- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index e385878a1..7ae69954a 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 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 7fec25ddcd66f89d8dd6969bcbe7ccc18e39df6d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 14 Dec 2025 14:34:22 +0100 Subject: [PATCH 284/349] xx --- Moose Development/Moose/Core/Zone.lua | 22 +- Moose Development/Moose/Functional/Mantis.lua | 195 ++++++++++++++++-- Moose Development/Moose/Ops/Intelligence.lua | 69 ++++++- 3 files changed, 267 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 851ea0caf..e9b618cf2 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1158,7 +1158,7 @@ end -- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) -- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) - + self.ScanData = {} self.ScanData.Coalitions = {} self.ScanData.Scenery = {} @@ -1266,8 +1266,9 @@ end --- Get a set of scanned units. -- @param #ZONE_RADIUS self +-- @param #number Coalition (optional) Filter for this coalition only. -- @return Core.Set#SET_UNIT Set of units and statics inside the zone. -function ZONE_RADIUS:GetScannedSetUnit() +function ZONE_RADIUS:GetScannedSetUnit(Coalition) local SetUnit = SET_UNIT:New() @@ -1276,7 +1277,12 @@ function ZONE_RADIUS:GetScannedSetUnit() local UnitObject = UnitObject -- DCS#Unit if UnitObject:isExist() then local FoundUnit = UNIT:FindByName( UnitObject:getName() ) - if FoundUnit then + local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil + local includeoncoalition = true + if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end + if Coalition == nil then includeoncoalition = true end + --self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition))) + if FoundUnit and includeoncoalition then SetUnit:AddUnit( FoundUnit ) else local FoundStatic = STATIC:FindByName( UnitObject:getName(), false ) @@ -1293,8 +1299,9 @@ end --- Get a set of scanned groups. -- @param #ZONE_RADIUS self +-- @param #number Coalition (optional) Filter for this coalition only. -- @return Core.Set#SET_GROUP Set of groups. -function ZONE_RADIUS:GetScannedSetGroup() +function ZONE_RADIUS:GetScannedSetGroup(Coalition) self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP @@ -1307,7 +1314,12 @@ function ZONE_RADIUS:GetScannedSetGroup() if UnitObject:isExist() then local FoundUnit=UNIT:FindByName(UnitObject:getName()) - if FoundUnit then + local FoundCoalition = FoundUnit and FoundUnit:GetCoalition() or nil + local includeoncoalition = true + if Coalition ~= nil and FoundCoalition==Coalition then includeoncoalition = true else includeoncoalition = false end + if Coalition == nil then includeoncoalition = true end + --self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition))) + if FoundUnit and includeoncoalition then local group=FoundUnit:GetGroup() self.ScanSetGroup:AddGroup(group) end diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 2e1ff3b18..efa4d72aa 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: August 2025 +-- Last Update: December 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -61,10 +61,13 @@ -- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range. -- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. -- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. --- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White +-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White. -- @field #number checkcounter Counter for SAM Table refreshes. -- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay. --- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true +-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true. +-- @field #boolean DetectAccoustic Set if we can also detect units accousticly. +-- @field #number DetectAccousticRadius We can hear in this range. +-- @field #table DetectAccousticCategories We can hear these categories. -- @extends Core.Base#BASE @@ -280,7 +283,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", - version = "0.9.34", + version = "0.9.41", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -331,6 +334,9 @@ MANTIS = { checkcounter = 1, DLinkCacheTime = 120, logsamstatus = false, + DetectAccoustic = false, + DetectAccousticRadius = 2000, + DetectAccousticCategories = {Unit.Category.HELICOPTER}, } --- Advanced state enumerator @@ -561,6 +567,7 @@ do -- DONE: Treat Awacs separately, since they might be >80km off site -- DONE: Allow tables of prefixes for the setup -- DONE: Auto-Mode with range setups for various known SAM types. + -- DONE: Added reaction on HIT and UNIT LOST events. self.name = name or "mymantis" self.SAM_Templates_Prefix = samprefix or "Red SAM" @@ -716,6 +723,8 @@ do self:AddTransition("*", "SeadSuppressionStart", "*") -- SEAD has switched off one group. self:AddTransition("*", "SeadSuppressionEnd", "*") -- SEAD has switched on one group. self:AddTransition("*", "SeadSuppressionPlanned", "*") -- SEAD has planned a suppression. + self:AddTransition("*", "SAMUnitHit", "*") -- A SAM unit was hit + self:AddTransition("*", "SAMUnitLost", "*") -- A SAM Unit was lost self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. ------------------------ @@ -826,6 +835,24 @@ do -- @param Wrapper.Group#GROUP Group The suppressed GROUP object -- @param #string Name Name of the suppressed group + --- On After "SAMUnitHit" event. A SAM Unit was hit. + -- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP of the hit UNIT object + -- @param #string Name Name of the suppressed group + + --- On After "SAMUnitLoast" event. A SAM Unit was lost. + -- @function [parent=#MANTIS] OnAfterSeadSuppressionEnd + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP of the lost UNIT object + -- @param #string Name Name of the suppressed group + return self end @@ -833,6 +860,145 @@ do -- MANTIS helper functions ----------------------------------------------------------------------- + --- Set to accept accoustic detection. Set this *before* MANTIS starts! + -- @param #MANTIS self + -- @param #number Radius Radius in which we can "hear" units. Defaults to 2000 meters. + -- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.HELICOPTER}` + -- @return #MANTIS self + function MANTIS:SetAccousticDetectionOn(Radius,UnitCategories) + self.DetectAccoustic = true + self.DetectAccousticRadius = Radius or 2000 + self.DetectAccousticCategories = UnitCategories or {Unit.Category.HELICOPTER} + return self + end + + --- Switch off accoustic detection. + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:SetAccousticDetectionOff() + self.DetectAccoustic = false + return self + end + + --- [Internal] Function to manage hits on SAM units + -- @param #MANTIS self + -- @param Core.Event#EVENTDATA EventData The EVENT data + -- @return #MANTIS self + function MANTIS:_EventHandler(EventData) + self:T(self.lid .. "_EventHandler") + + local function IsOneOfOurs(name) + for _,_name in pairs(self.ewr_templates) do + if string.find(name,_name,1,true) then + return true + end + end + return false + end + + local function SwitchSAMOn(Name,Group) + local suppressed = self.SuppressedGroups[Name] or false + if not suppressed and self.SamStateTracker[Name] == "GREEN" then + self.SamStateTracker[Name] = "RED" + if self.UseEmOnOff then + -- DONE: add emissions on/off + Group:EnableEmission(true) + elseif (not self.UseEmOnOff) then + Group:OptionAlarmStateRed() + end + self:__RedState(1,Group) + if self.SmokeDecoy == true then --shortsam == true and + self:T("Smoking") + local units = Group:GetUnits() or {} + local smoke = self.SmokeDecoyColor or SMOKECOLOR.White + for _,unit in pairs(units) do + if unit and unit:IsAlive() then + unit:GetCoordinate():Smoke(smoke) + end + end + end + end + end + + local coordinate -- Core.Point#COORDINATE + local Name -- #string + local Group -- Wrapper.Group#GROUP + local lasthit = 0 + local firsthit = false + local alerton = false + + -- Check if we can get a location + + local data = EventData -- Core.Event#EVENTDATA + if data.id == EVENTS.Hit then + -- Unit hit, one of ours? + if data.TgtGroupName and IsOneOfOurs(data.TgtGroupName) then + self:T("Unit hit in group: "..data.TgtGroupName) + if data.TgtGroup then + lasthit = data.TgtGroup:GetProperty("MANTIS_LASTHIT") + firsthit = (lasthit==nil) and true or false + if firsthit == true then alerton = true end + if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end + coordinate = data.TgtGroup:GetCoordinate() + Name = data.TgtGroupName + Group = data.TgtGroup + if alerton == true then + self:__SAMUnitHit(1,Group,Name) + SwitchSAMOn(Name,Group) + end + if coordinate and self.debug then + local text = coordinate:ToStringMGRS() + self:I("Location: "..text) + end + end + end + end + + if data.id == EVENTS.UnitLost then + if data.IniGroupName and IsOneOfOurs(data.IniGroupName) then + self:T("Unit lost in group: "..data.IniGroupName) + if data.IniGroup then + lasthit = data.IniGroup:GetProperty("MANTIS_LASTHIT") + firsthit = (lasthit==nil) and true or false + if firsthit == true then alerton = true end + if lasthit ~= nil and timer.getTime()-lasthit > self.ShoradTime then alerton = true end + coordinate = data.IniGroup:GetCoordinate() + Name = data.IniGroupName + Group = data.IniGroup + alerton = true + SwitchSAMOn(Name,Group) + self:__SAMUnitLost(1,Group,Name) + if coordinate and self.debug then + local text = coordinate:ToStringMGRS() + self:I("Location: "..text) + end + end + end + end + + if firsthit == true or alerton == true then + Group:SetProperty("MANTIS_LASTHIT",timer.getTime()) + end + + + if coordinate ~= nil and Name ~= nil and Group ~=nil and alerton == true then + if self.ShoradLink then + self:T("Shorad activated for: "..Name) + local Shorad = self.Shorad -- Functional.Shorad#SHORAD + local radius = self.checkradius + local ontime = self.ShoradTime + Shorad:WakeUpShorad(Name, radius, ontime, nil, true) + self:__ShoradActivated(1,Name, radius, ontime) + end + if self.autorelocate and Group then + Group:RelocateGroundRandomInRadius(20,500,true,true,nil,true) + end + end + + return self + end + + --- [Internal] Function to get the self.SAM_Table -- @param #MANTIS self -- @return #table table @@ -1445,13 +1611,15 @@ do self.intelset = {} local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne") - --IntelOne:SetClusterAnalysis(true,true) - --IntelOne:SetClusterRadius(5000) + IntelOne.DetectAccoustic = self.DetectAccoustic + IntelOne.DetectAccousticRadius = self.DetectAccousticRadius or 2000 + IntelOne.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} IntelOne:Start() local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo") - --IntelTwo:SetClusterAnalysis(true,true) - --IntelTwo:SetClusterRadius(5000) + IntelTwo.DetectAccoustic = self.DetectAccoustic + IntelTwo.DetectAccousticRadius = self.DetectAccousticRadius or 2000 + IntelTwo.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} IntelTwo:Start() local CacheTime = self.DLinkCacheTime or 120 @@ -1807,7 +1975,7 @@ do local radius = _data[3] local height = _data[4] local blind = _data[5] * 1.25 + 1 - local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false + local shortsam = (_data[6] ~= MANTIS.SamType.LONG) and true or false if not shortsam then shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false end @@ -1834,10 +2002,9 @@ do end if self.SamStateTracker[name] ~= "RED" and switch then self:__RedState(1,samgroup) - self.SamStateTracker[name] = "RED" end - -- TODO doesn't work - if shortsam == true and self.SmokeDecoy == true then + -- TODO Check doesn't work + if shortsam == true and self.SmokeDecoy == true then self:T("Smoking") local units = samgroup:GetUnits() or {} local smoke = self.SmokeDecoyColor or SMOKECOLOR.White @@ -2045,6 +2212,10 @@ do if self.shootandscoot and self.SkateZones and self.Shorad then self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation) end + + self:HandleEvent(EVENTS.Hit,self._EventHandler) + self:HandleEvent(EVENTS.UnitLost,self._EventHandler) + self:__Status(-math.random(1,10)) return self end diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index f582f0675..73e89134a 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -39,6 +39,9 @@ -- @field #number prediction Seconds default to be used with CalcClusterFuturePosition. -- @field #boolean detectStatics If `true`, detect STATIC objects. Default `false`. -- @field #number statusupdate Time interval in seconds after which the status is refreshed. Default 60 sec. Should be negative. +-- @field #boolean DetectAccoustic If true, also detect by sound (ie proximity). +-- @field #number DetectAccousticRadius Radius dfor accoustic detection, defaults to 2000 meters. +-- @field #table DetectAccousticUnitTypes Types of units we can detect accousticly. Defaults to {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER} -- @extends Core.Fsm#FSM --- Top Secret! @@ -102,6 +105,9 @@ INTEL = { clusterarrows = false, prediction = 300, detectStatics = false, + DetectAccoustic = false, + DetectAccousticRadius = 1000, + DetectAccousticUnitTypes = {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER}, } --- Detected item info. @@ -160,7 +166,7 @@ INTEL.Ctype={ --- INTEL class version. -- @field #string version -INTEL.version="0.3.6" +INTEL.version="0.3.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -398,6 +404,26 @@ function INTEL:SetAcceptZones(AcceptZoneSet) return self end +--- Set to accept accoustic detection. +-- @param #INTEL self +-- @param #number Radius Radius in which we can "hear" units. Defaults to 1000 meters. +-- @param #table UnitCategories Set what Unit Categories we can "hear". Defaults to `{Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER}` +-- @return #INTEL self +function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) + self.DetectAccoustic = true + self.DetectAccousticRadius = Radius or 1000 + self.DetectAccousticUnitTypes = UnitCategories or {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER} + return self +end + +--- Switch off accoustic detection. +-- @param #INTEL self +-- @return #INTEL self +function INTEL:SetAccousticDetectionOff() + self.DetectAccoustic = false + return self +end + --- Add an accept zone. Only contacts detected in this zone are considered. -- @param #INTEL self -- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. @@ -831,6 +857,18 @@ function INTEL:UpdateIntel() self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK) end + + if self.DetectAccoustic then + local recce = group:GetFirstUnitAlive() + local detectionzone = group:GetProperty("INTEL_DETECT_ACCZONE") + if not detectionzone then + detectionzone = ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) + group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) + end + if recce then + self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) + end + end end end @@ -1106,7 +1144,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, DetectedStatics, RecceDetecti return self end ---- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}. +--- (Internal) Return the detected target groups of the controllable as a table. -- The optional parameters specify the detection methods that can be applied. -- If no detection method is given, the detection will use all the available methods by default. -- @param #INTEL self @@ -1202,6 +1240,33 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisua end end +--- (Internal) Return the detected target groups of the controllable as a @{Core.Set#SET_GROUP}. +-- @param #INTEL self +-- @param Wrapper.Unit#UNIT Recce The unit detecting. +-- @param #table DetectedUnits Table of detected units to be filled. +-- @param #table RecceDetecting Table of recce per unit to be filled. +-- @param Core.Zone#ZONE_GROUP detectionzone The zone where to look. +function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,detectionzone) + local othercoalition = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE + self:T("Other coalition = "..othercoalition) + if detectionzone then + -- Get detected units + local reccename = Recce:GetName() + detectionzone:Scan({Object.Category.UNIT},self.DetectAccousticUnitTypes) + local unitset = detectionzone:GetScannedSetUnit(othercoalition) -- Core.Set#SET_UNIT + self:T("Accoustic detection found #Units "..unitset:CountAlive()) + for _,_unit in pairs(unitset.Set or {}) do + if _unit and _unit:IsAlive() then + local name = _unit:GetName() or "none" + DetectedUnits[name]=_unit + RecceDetecting[name]=reccename + self:T("Unit name = "..name) + end + end + unitset = nil + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 1d0a273ffb6e5ca8a6e5e8f25e57447a9d5c572f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 14 Dec 2025 16:04:07 +0100 Subject: [PATCH 285/349] xx --- Moose Development/Moose/Wrapper/Group.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index d4c93960f..e63c84fed 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2944,6 +2944,16 @@ function GROUP:EnableEmission(switch) return self end + +--- Set a formation for this group. +-- @param #GROUP self +-- @param #number Formation See. ENUMS.Formation or [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) for options. +-- @return #GROUP self +function GROUP:SetFormation(Formation) + self:SetOption(AI.Option.Air.id.FORMATION,Formation) + return self +end + --- Switch on/off invisible flag for the group. -- @param #GROUP self -- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled. From 79654a99d9c92275be25e9ec4a7aeb49dcbd3b81 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 15 Dec 2025 09:40:08 +0100 Subject: [PATCH 286/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index efa4d72aa..3d5dde62b 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -907,15 +907,8 @@ do Group:OptionAlarmStateRed() end self:__RedState(1,Group) - if self.SmokeDecoy == true then --shortsam == true and - self:T("Smoking") - local units = Group:GetUnits() or {} - local smoke = self.SmokeDecoyColor or SMOKECOLOR.White - for _,unit in pairs(units) do - if unit and unit:IsAlive() then - unit:GetCoordinate():Smoke(smoke) - end - end + if self.SmokeDecoy == true then + self:_SmokeUnits(Group) end end end @@ -1491,11 +1484,12 @@ do return inzone end - --- [Internal] Function to prefilter height based + --- [Internal] Function to prefilter height based and check for Helo activity. -- @param #MANTIS self -- @param #number height + -- @param Core.Point#COORDINATE SamCoordinate -- @return #table set - function MANTIS:_PreFilterHeight(height) + function MANTIS:_PreFilterHeight(height,SamCoordinate) self:T(self.lid.."_PreFilterHeight") local set = {} local dlink = self.Detection -- Ops.Intel#INTEL_DLINK @@ -1504,8 +1498,14 @@ do local contact = _contact -- Ops.Intel#INTEL.Contact local grp = contact.group -- Wrapper.Group#GROUP if grp:IsAlive() then - if grp:GetHeight(true) < height then - local coord = grp:GetCoordinate() + local coord = grp:GetCoordinate() + local dist = 0 + local include = true + if coord and SamCoordinate and grp:IsHelicopter() then + dist = coord:Get2DDistance(SamCoordinate) or 0 + if dist > self.ShoradActDistance then include = false end -- we do not want long range shooting at helos + end + if grp:GetHeight(true) < height and include == true then table.insert(set,coord) end end @@ -1529,7 +1529,7 @@ do local set = dectset if dlink then -- DEBUG - set = self:_PreFilterHeight(height) + set = self:_PreFilterHeight(height,samcoordinate) end --self.friendlyset -- Core.Set#SET_GROUP if self.checkforfriendlies == true and self.friendlyset == nil then @@ -1948,6 +1948,27 @@ do self.ShoradLink = false return self end + + --- [Internal] Function to smoke a group in decoy. + -- @param #MANTIS self + -- @param Wrapper.Group#GROUP Group + -- @return #MANTIS self + function MANTIS:_SmokeUnits(Group) + self:T("Smoking") + local LastSmoketime=Group:GetProperty("MANTIS_LASTSMOKE_TIME") or 0 + local TNow = timer.getTime() + if TNow - LastSmoketime > 290 then -- Smoking lasts 5 minutes + Group:SetProperty("MANTIS_LASTSMOKE_TIME",TNow) + local units = Group:GetUnits() or {} + local smoke = self.SmokeDecoyColor or SMOKECOLOR.White + for _,unit in pairs(units) do + if unit and unit:IsAlive() then + unit:GetCoordinate():Smoke(smoke) + end + end + end + return self + end ----------------------------------------------------------------------- -- MANTIS main functions @@ -2003,16 +2024,10 @@ do if self.SamStateTracker[name] ~= "RED" and switch then self:__RedState(1,samgroup) end - -- TODO Check doesn't work - if shortsam == true and self.SmokeDecoy == true then + -- DONE Restrict on Distance + if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*2 then self:T("Smoking") - local units = samgroup:GetUnits() or {} - local smoke = self.SmokeDecoyColor or SMOKECOLOR.White - for _,unit in pairs(units) do - if unit and unit:IsAlive() then - unit:GetCoordinate():Smoke(smoke) - end - end + self:_SmokeUnits(samgroup) end -- link in to SHORAD if available -- DONE: Test integration fully From 7605c2f4f5976b4ce2a21242619ef0247925f3db Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 15 Dec 2025 12:12:20 +0100 Subject: [PATCH 287/349] xx --- Moose Development/Moose/Core/Zone.lua | 12 +++-- Moose Development/Moose/Functional/Mantis.lua | 29 +++++++---- Moose Development/Moose/Functional/Shorad.lua | 51 +++++++++++++++++-- Moose Development/Moose/Ops/Intelligence.lua | 14 ++--- 4 files changed, 80 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index e9b618cf2..f88b7c2f6 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1270,8 +1270,10 @@ end -- @return Core.Set#SET_UNIT Set of units and statics inside the zone. function ZONE_RADIUS:GetScannedSetUnit(Coalition) - local SetUnit = SET_UNIT:New() - + self.SetUnit = self.SetUnit or SET_UNIT:New() + self.SetUnit:Clear(false) + self.SetUnit.Set={} + if self.ScanData then for ObjectID, UnitObject in pairs( self.ScanData.Units ) do local UnitObject = UnitObject -- DCS#Unit @@ -1283,18 +1285,18 @@ function ZONE_RADIUS:GetScannedSetUnit(Coalition) if Coalition == nil then includeoncoalition = true end --self:I(string.format("Unit name %s coalition %s filter coalition = %s include = %s",FoundUnit:GetName(),tostring(FoundCoalition),tostring(Coalition),tostring(includeoncoalition))) if FoundUnit and includeoncoalition then - SetUnit:AddUnit( FoundUnit ) + self.SetUnit:AddUnit( FoundUnit ) else local FoundStatic = STATIC:FindByName( UnitObject:getName(), false ) if FoundStatic then - SetUnit:AddUnit( FoundStatic ) + self.SetUnit:AddUnit( FoundStatic ) end end end end end - return SetUnit + return self.SetUnit end --- Get a set of scanned groups. diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 3d5dde62b..7128e7afd 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -404,7 +404,7 @@ MANTIS.SamData = { ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, - ["NIKE"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" }, + ["Nike"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" }, ["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" }, -- CH Added to DCS core 2.9.19.x ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" }, @@ -530,7 +530,7 @@ do --@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..." --@param #string ewrprefix Prefixes for the EWR groups from the ME, e.g. all groups starting with "Red EWR..." --@param #string hq Group name of your HQ (optional) - --@param #string coalition Coalition side of your setup, e.g. "blue", "red" or "neutral" + --@param #string Coalition Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional) @@ -554,7 +554,7 @@ do -- mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") -- mybluemantis:Start() -- - function MANTIS:New(name,samprefix,ewrprefix,hq,coalition,dynamic,awacs, EmOnOff, Padding, Zones) + function MANTIS:New(name,samprefix,ewrprefix,hq,Coalition,dynamic,awacs, EmOnOff, Padding, Zones) -- Inherit everything from BASE class. @@ -573,7 +573,8 @@ do self.SAM_Templates_Prefix = samprefix or "Red SAM" self.EWR_Templates_Prefix = ewrprefix or "Red EWR" self.HQ_Template_CC = hq or nil - self.Coalition = coalition or "red" + self.Coalition = Coalition or "red" + self.coalition = Coalition == "blue" and coalition.side.BLUE or coalition.side.RED self.SAM_Table = {} self.SAM_Table_Long = {} self.SAM_Table_Medium = {} @@ -1501,11 +1502,19 @@ do local coord = grp:GetCoordinate() local dist = 0 local include = true + if grp:IsGround() then include = false end + if grp:GetCoalition() == self.coalition then include = false end if coord and SamCoordinate and grp:IsHelicopter() then dist = coord:Get2DDistance(SamCoordinate) or 0 if dist > self.ShoradActDistance then include = false end -- we do not want long range shooting at helos end - if grp:GetHeight(true) < height and include == true then + if self.debug then + local text = "Looking at Group: "..grp:GetName() or "N/A" + text = text .. " Include = "..tostring(include) + MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog() + end + local grpalt = grp:GetHeight(true) + if grpalt < height and grpalt > 10 and include == true then table.insert(set,coord) end end @@ -1610,16 +1619,18 @@ do self.intelset = {} - local IntelOne = INTEL:New(groupset,self.Coalition,self.name.." IntelOne") + local IntelOne = INTEL:New(groupset,self.coalition,self.name.." IntelOne") IntelOne.DetectAccoustic = self.DetectAccoustic IntelOne.DetectAccousticRadius = self.DetectAccousticRadius or 2000 IntelOne.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} + --IntelOne:SetClusterAnalysis(true,true,true) IntelOne:Start() - local IntelTwo = INTEL:New(samset,self.Coalition,self.name.." IntelTwo") + local IntelTwo = INTEL:New(samset,self.coalition,self.name.." IntelTwo") IntelTwo.DetectAccoustic = self.DetectAccoustic IntelTwo.DetectAccousticRadius = self.DetectAccousticRadius or 2000 IntelTwo.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} + --IntelTwo:SetClusterAnalysis(true,true,true) IntelTwo:Start() local CacheTime = self.DLinkCacheTime or 120 @@ -2025,7 +2036,7 @@ do self:__RedState(1,samgroup) end -- DONE Restrict on Distance - if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*2 then + if shortsam == true and self.SmokeDecoy == true and Distance < self.DetectAccousticRadius*1.5 then self:T("Smoking") self:_SmokeUnits(samgroup) end @@ -2218,7 +2229,7 @@ do end --]] if self.autoshorad then - self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff) + self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.Coalition,self.UseEmOnOff,self.SmokeDecoy,self.SmokeDecoyColor) self.Shorad:SetDefenseLimits(80,95) self.ShoradLink = true self.Shorad.Groupset=self.ShoradGroupSet diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index bc5156943..9b8ff7428 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -48,7 +48,9 @@ -- @field #number minscootdist Min distance of the next zone -- @field #number maxscootdist Max distance of the next zone -- @field #boolean scootrandomcoord If true, use a random coordinate in the zone and not the center --- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone" +-- @field #string scootformation Formation to take for scooting, e.g. "Vee" or "Cone" +-- @field #boolean SmokeDecoy = false, +-- @field #number SmokeDecoyColor = SMOKECOLOR.White -- @extends Core.Base#BASE @@ -114,7 +116,9 @@ SHORAD = { SkateZones = nil, minscootdist = 100, maxscootdist = 3000, - scootrandomcoord = false, + scootrandomcoord = false, + SmokeDecoy = false, + SmokeDecoyColor = SMOKECOLOR.White } ----------------------------------------------------------------------- @@ -161,8 +165,10 @@ do -- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" -- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch) + -- @param #boolean SmokeDecoy Throw smoke decoy when getting activated. Defaults to false. + -- @param #number SmokeDecoyColor SMOLECOLOR to use. Defaults to SMOLECOLOR.White -- @return #SHORAD self - function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff) + function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff, SmokeDecoy, SmokeDecoyColor) local self = BASE:Inherit( self, FSM:New() ) self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) @@ -171,6 +177,7 @@ do self.name = Name or "MyShorad" self.Prefixes = ShoradPrefix or "SAM SHORAD" self.Radius = Radius or 20000 + if type(Coalition) == "number" then Coalition = string.lower(UTILS.GetCoalitionName(Coalition)) end self.Coalition = Coalition or "blue" self.Samset = Samset or GroupSet self.ActiveTimer = ActiveTimer or 600 @@ -181,8 +188,15 @@ do self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green + if UseEmOnOff == false then self.UseEmOnOff = UseEmOnOff end - self:I("*** SHORAD - Started Version 0.3.4") + + if SmokeDecoy then + self.SmokeDecoy = SmokeDecoy + self.SmokeDecoyColor = SmokeDecoyColor or SMOKECOLOR.White + end + + self:I("*** SHORAD - Started Version 0.3.5") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() @@ -451,6 +465,32 @@ do return returnname end + --- Smoke a SHORAD Group + -- @param #SHORAD self + -- @param Wrapper.Group#GROUP Group The Shorad Group to Smoke + -- @return self + function SHORAD:_SmokeUnits(Group) + if self.SmokeDecoy == true then + if Group and Group:IsAlive() then + local units = Group:GetUnits() + for _,_unit in pairs(units) do + local unit = _unit -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + local coordinate = unit:GetCoordinate() + if coordinate then + coordinate:SwitchSmokeOffsetOn() + coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,1,20) + coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,180,20) + coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,270,20) + coordinate:Smoke(self.SmokeDecoyColor,Duration,nil,Name,true,90,20) + end + end + end + end + end + return self + end + --- Calculate if the missile shot is detected -- @param #SHORAD self -- @return #boolean Returns true for a detection, else false @@ -537,7 +577,7 @@ do local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName()) self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) - + self:_SmokeUnits(_group) --Shoot and Scoot if self.shootandscoot then self:__ShootAndScoot(1,_group) @@ -552,6 +592,7 @@ do _group:EnableEmission(true) end _group:OptionAlarmStateRed() + self:_SmokeUnits(_group) if self.ActiveGroups[groupname] == nil then -- no timer yet for this group self.ActiveGroups[groupname] = { Timing = ActiveTimer } local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 73e89134a..c47b35421 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -41,7 +41,7 @@ -- @field #number statusupdate Time interval in seconds after which the status is refreshed. Default 60 sec. Should be negative. -- @field #boolean DetectAccoustic If true, also detect by sound (ie proximity). -- @field #number DetectAccousticRadius Radius dfor accoustic detection, defaults to 2000 meters. --- @field #table DetectAccousticUnitTypes Types of units we can detect accousticly. Defaults to {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER} +-- @field #table DetectAccousticUnitTypes Types of units we can detect accousticly. Defaults to {Unit.Category.HELICOPTER} -- @extends Core.Fsm#FSM --- Top Secret! @@ -107,7 +107,7 @@ INTEL = { detectStatics = false, DetectAccoustic = false, DetectAccousticRadius = 1000, - DetectAccousticUnitTypes = {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER}, + DetectAccousticUnitTypes = {Unit.Category.HELICOPTER}, } --- Detected item info. @@ -412,7 +412,7 @@ end function INTEL:SetAccousticDetectionOn(Radius,UnitCategories) self.DetectAccoustic = true self.DetectAccousticRadius = Radius or 1000 - self.DetectAccousticUnitTypes = UnitCategories or {Unit.Category.GROUND_UNIT,Unit.Category.HELICOPTER} + self.DetectAccousticUnitTypes = UnitCategories or {Unit.Category.HELICOPTER} return self end @@ -865,7 +865,7 @@ function INTEL:UpdateIntel() detectionzone = ZONE_GROUP:New(group.IdentifiableName.."INTEL_DETECT_ACCZONE",group,self.DetectAccousticRadius or 2000) group:SetProperty("INTEL_DETECT_ACCZONE",detectionzone) end - if recce then + if recce and recce:IsGround() then self:GetDetectedUnitsAccoustic(recce,DetectedUnits,RecceDetecting,detectionzone) end end @@ -1252,18 +1252,18 @@ function INTEL:GetDetectedUnitsAccoustic(Recce,DetectedUnits,RecceDetecting,dete if detectionzone then -- Get detected units local reccename = Recce:GetName() - detectionzone:Scan({Object.Category.UNIT},self.DetectAccousticUnitTypes) + local DetectAccousticUnitTypes = self.DetectAccousticUnitTypes or {Unit.Category.HELICOPTER} + detectionzone:Scan({Object.Category.UNIT},DetectAccousticUnitTypes) local unitset = detectionzone:GetScannedSetUnit(othercoalition) -- Core.Set#SET_UNIT self:T("Accoustic detection found #Units "..unitset:CountAlive()) for _,_unit in pairs(unitset.Set or {}) do - if _unit and _unit:IsAlive() then + if _unit and _unit:IsAlive() and _unit:GetCoalition() ~= self.coalition then local name = _unit:GetName() or "none" DetectedUnits[name]=_unit RecceDetecting[name]=reccename self:T("Unit name = "..name) end end - unitset = nil end end From 49097649de36b2c4417d701f37117b862bbc444f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 17 Dec 2025 11:31:07 +0100 Subject: [PATCH 288/349] xx --- .../Moose/Core/MarkerOps_Base.lua | 39 ++++++++++++------- Moose Development/Moose/Utilities/Utils.lua | 10 ++--- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 222866321..ae124bacf 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -50,7 +50,7 @@ MARKEROPS_BASE = { ClassName = "MARKEROPS", Tag = "mytag", Keywords = {}, - version = "0.1.4", + version = "0.1.5", debug = false, Casesensitive = true, } @@ -59,9 +59,8 @@ MARKEROPS_BASE = { -- @param #MARKEROPS_BASE self -- @param #string Tagname Name to identify us from the event text. -- @param #table Keywords Table of keywords recognized from the event text. --- @param #boolean Casesensitive (Optional) Switch case sensitive identification of Tagname. Defaults to true. -- @return #MARKEROPS_BASE self -function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) +function MARKEROPS_BASE:New(Tagname,Keywords) -- Inherit FSM local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE @@ -72,11 +71,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too self.debug = false self.Casesensitive = true - - if Casesensitive and Casesensitive == false then - self.Casesensitive = false - end - + ----------------------- --- FSM Transitions --- ----------------------- @@ -145,7 +140,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) end ---- (internal) Handle events. +--- (Internal) Handle events. -- @param #MARKEROPS_BASE self -- @param Core.Event#EVENTDATA Event function MARKEROPS_BASE:OnEventMark(Event) @@ -201,15 +196,17 @@ function MARKEROPS_BASE:OnEventMark(Event) end end ---- (internal) Match tag. +--- (Internal) Match tag. -- @param #MARKEROPS_BASE self -- @param #string Eventtext Text added to the marker. -- @return #boolean function MARKEROPS_BASE:_MatchTag(Eventtext) local matches = false - if not self.Casesensitive then + self:I(self.lid .. "Casesensitive "..tostring(self.Casesensitive)) + if self.Casesensitive == false then + self:I(self.lid .. "Marker non-casesensitive "..Eventtext) local type = string.lower(self.Tag) -- #string - if string.find(string.lower(Eventtext),type) then + if string.find(string.lower(Eventtext),type,1,true) then matches = true --event text contains tag end else @@ -221,7 +218,7 @@ function MARKEROPS_BASE:_MatchTag(Eventtext) return matches end ---- (internal) Match keywords table. +--- (Internal) Match keywords table. -- @param #MARKEROPS_BASE self -- @param #string Eventtext Text added to the marker. -- @return #table @@ -286,6 +283,22 @@ function MARKEROPS_BASE:onenterStopped(From,Event,To) self:UnHandleEvent(EVENTS.MarkRemoved) end +--- Switch off case sensitive matching + -- @param #MARKEROPS_BASE self + -- @return self +function MARKEROPS_BASE:SwitchCaseSensitiveOff() + self.Casesensitive = false + return self +end + +--- Switch on case sensitive matching + -- @param #MARKEROPS_BASE self + -- @return self +function MARKEROPS_BASE:SwitchCaseSensitiveOn() + self.Casesensitive = true + return self +end + -------------------------------------------------------------------------- -- MARKEROPS_BASE Class Definition End. -------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index aa5d3a39d..e5f54b667 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4747,7 +4747,7 @@ function UTILS.DoStringIn(State,DoString) end --- Show a picture on the screen to all --- @param #string FileName File name of the picture +-- @param #string FilePath File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4770,7 +4770,7 @@ end --- Show a picture on the screen to Coalition -- @param #number Coalition Coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL --- @param #string FileName File name of the picture +-- @param #string FilePath File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4795,7 +4795,7 @@ end --- Show a picture on the screen to Country -- @param #number Country Country ID, can be country.id.USA, country.id.RUSSIA, etc. --- @param #string FileName File name of the picture +-- @param #string FilePath File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4818,7 +4818,7 @@ end --- Show a picture on the screen to Group -- @param Wrapper.Group#GROUP Group Group to show the picture to --- @param #string FileName File name of the picture +-- @param #string FilePath File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4841,7 +4841,7 @@ end --- Show a picture on the screen to Unit -- @param Wrapper.Unit#UNIT Unit Unit to show the picture to --- @param #string FileName File name of the picture +-- @param #string FilePath File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 From 8a958df5f5847af505d0bfaa041ae001092ebd95 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 20 Dec 2025 12:07:31 +0100 Subject: [PATCH 289/349] xx --- Moose Development/Moose/Modules_local.lua | 126 ++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 Moose Development/Moose/Modules_local.lua diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua new file mode 100644 index 000000000..cd206f68d --- /dev/null +++ b/Moose Development/Moose/Modules_local.lua @@ -0,0 +1,126 @@ +__Moose.Include( 'Utilities\\Enums.lua' ) +__Moose.Include( 'Utilities\\Utils.lua' ) +__Moose.Include( 'Utilities\\Profiler.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( '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( '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( 'Navigation\\Point.lua' ) +__Moose.Include( 'Navigation\\Beacons.lua' ) +__Moose.Include( 'Navigation\\Radios.lua' ) +__Moose.Include( 'Navigation\\Towns.lua' ) + +__Moose.Include( 'Globals.lua' ) From 3aa39429987177715c7e6e76ae85b1863c0978a1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 21 Dec 2025 11:44:19 +0100 Subject: [PATCH 290/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 8 +- Moose Development/Moose/Ops/Cohort.lua | 2 +- Moose Development/Moose/Ops/PlayerTask.lua | 237 ++++++++++++++++-- 3 files changed, 222 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 7128e7afd..45af655e9 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -116,7 +116,7 @@ -- * TOR M2 -- * C-RAM -- * Silkworm (though strictly speaking this is a surface to ship missile) --- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19 +-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19, SA-21, S-300VM, S-300V4, S-400 -- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!) -- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2 -- * Other Mods: Nike @@ -283,7 +283,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", - version = "0.9.41", + version = "0.9.42", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -401,6 +401,10 @@ MANTIS.SamData = { ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" }, ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, + ["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"}, + ["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M"}, + ["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M"}, + ["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E"}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 48f63112c..3bb75c370 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -1474,7 +1474,7 @@ function COHORT:_CheckAmmo() -- Descriptors. local Desc=weapon["desc"] - + -- Warhead. local Warhead=Desc["warhead"] diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 1f5269ce9..7edda117b 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -61,6 +61,7 @@ do -- @field #number PreviousCount -- @field #boolean CanSmoke -- @field #boolean ShowThreatDetails +-- @field #boolean PersistMe -- @extends Core.Fsm#FSM @@ -98,11 +99,12 @@ PLAYERTASK = { PreviousCount = 0, CanSmoke = true, ShowThreatDetails = true, + PersistMe = false, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.29" +PLAYERTASK.version="0.1.30" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -401,8 +403,17 @@ function PLAYERTASK:CanJoinTask(Group, Client) return true end +--- [User] Set this task for persistance, if persistance is enabled on the PLAYERTASKCONTROLLER instance. +-- @param #PLAYERTASK self +-- @return #PLAYERTASK self +function PLAYERTASK:EnablePersistance() + self.PersistMe = true + return self +end + --- [Internal] Add a PLAYERTASKCONTROLLER for this task -- @param #PLAYERTASK self +-- -- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller -- @return #PLAYERTASK self function PLAYERTASK:_SetController(Controller) @@ -1088,7 +1099,6 @@ function PLAYERTASK:onafterStatus(From, Event, To) return self end - --- [Internal] On after progress call -- @param #PLAYERTASK self -- @param #string From @@ -1363,6 +1373,12 @@ do -- @field Core.ClientMenu#CLIENTMENU MenuNoTask -- @field #boolean InformationMenu Show Radio Info Menu -- @field #number TaskInfoDuration How long to show the briefing info on the screen +-- @field #table TaskPersistance Table for persistance data +-- @field #boolean TaskPersistanceSwitch Switch for persisting tasks +-- @field #string TaskPersistancePath File path for persisting tasks +-- @field #string TaskPersistanceFilename File name for persisting tasks +-- @field #table TasksPersistable List of persistable tasks +-- @field #number SceneryExplosivesAmount Kgs of TNT to explode scenery on task persistance loading -- @extends Core.Fsm#FSM --- @@ -1720,6 +1736,12 @@ PLAYERTASKCONTROLLER = { MenuNoTask = nil, InformationMenu = false, TaskInfoDuration = 30, + TaskPersistance = {}, + TaskPersistanceSwitch = false, + TaskPersistancePath = nil, + TaskPersistanceFilename = nil, + TasksPersistable = {}, + SceneryExplosivesAmount = 300, } --- @@ -1740,6 +1762,7 @@ AUFTRAG.Type.PRECISIONBOMBING = "Precision Bombing" AUFTRAG.Type.CTLD = "Combat Transport" AUFTRAG.Type.CSAR = "Combat Rescue" AUFTRAG.Type.CONQUER = "Conquer" + --- -- @type Scores PLAYERTASKCONTROLLER.Scores = { @@ -1759,6 +1782,23 @@ PLAYERTASKCONTROLLER.Scores = { [AUFTRAG.Type.CAP] = 100, [AUFTRAG.Type.CAPTUREZONE] = 100, } + +--- +-- @type TasksPersistable +PLAYERTASKCONTROLLER.TasksPersistable = { + [AUFTRAG.Type.PRECISIONBOMBING] = true, + [AUFTRAG.Type.BOMBING] = true, + [AUFTRAG.Type.ARTY] = true, +} + +--- +-- @type PersistenceData +-- @field #number ID +-- @field #string Name +-- @field #string Type +-- @field #number InitialTargets +-- @field #number Targetsleft +-- @field #boolean updated --- -- @type SeadAttributes @@ -2174,6 +2214,30 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) end +--- [User] Enable Task persistance (for specific gound target tasks) +-- @param #PLAYERTASKCONTROLLER self +-- @param #string Path Path where to save the task data +-- @param #string Filename File name under which to save the task data +-- @param #number KgsOfTNT (Optional) Explosives kgs used to remove scenery for persistence, defaults to 300 +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:EnableTaskPersistance(Path,Filename,KgsOfTNT) + self.TaskPersistanceSwitch = true + self.TaskPersistancePath = Path + self.TaskPersistanceFilename = Filename + self.SceneryExplosivesAmount = KgsOfTNT or 300 + return self +end + +--- [User] Disable Task persistance (for specific gound target tasks) +-- @param #PLAYERTASKCONTROLLER self +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:DisableTaskPersistance() + self.TaskPersistanceSwitch = false + self.TaskPersistancePath = nil + self.TaskPersistanceFilename = nil + return self +end + --- [User] Set or create a SCORING object for this taskcontroller -- @param #PLAYERTASKCONTROLLER self -- @param Functional.Scoring#SCORING Scoring (optional) the Scoring object @@ -2861,15 +2925,6 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType() end end - --[[ - for _type,_data in pairs(tasktypes) do - self:I("Task Type: ".._type) - for _id,_task in pairs(_data) do - self:I("Task Name: ".._task.Target:GetName()) - end - end - --]] - return tasktypes end @@ -3566,9 +3621,9 @@ function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter) PlayerTask:_SetController(self) PlayerTask:SetCoalition(self.Coalition) self.TaskQueue:Push(PlayerTask) - if not Silent then - self:__TaskAdded(10,PlayerTask) - end + --if not Silent then + self:__TaskAdded(10,PlayerTask,Silent) + --end else self:E(self.lid.."***** NO valid PAYERTASK object sent!") end @@ -4765,6 +4820,128 @@ function PLAYERTASKCONTROLLER:SetSRSBroadcast(Frequency,Modulation) return self end + +--- +-- @param #PLAYERTASKCONTROLLER self +-- @param Ops.PlayerTask#PLASERTASK Task +-- @param #number TargetsLeft +function PLAYERTASKCONTROLLER:_UpdateTargetsAlive(Task,TargetsLeft) + self:T(self.lid.."_UpdateTargetsAlive") + local delta = Task.Target:CountTargets() - TargetsLeft + if delta > 0 then + self:T("Delta targets to be removed: "..delta) + local count = 0 + local targets = Task.Target:GetObjects() + for _,_object in pairs(targets or {}) do + if _object and _object.ClassName and (_object:IsInstanceOf("GROUP") or _object:IsInstanceOf("UNIT") or _object:IsInstanceOf("STATIC") or _object:IsInstanceOf("SCENERY")) then + if count < delta then + count = count + 1 + if not _object:IsInstanceOf("SCENERY") then + _object:Destroy(true) + else + _object:Explode(self.SceneryExplosivesAmount) + end + end + end + end + end + return self +end + +--- +-- @param #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:_LoadTasksPersisted() + self:T(self.lid.."_LoadTasksPersisted") + + local function MatchTask(Type,Name) + local foundtask + self.TaskQueue:ForEach( + function(_task) + local task = _task -- #PLAYERTASK + if task.Type == Type and task.Target.name and task.Target.name == Name then + foundtask = task + end + end + ) + return foundtask + end + + if lfs and io then + local ok,data = UTILS.LoadFromFile(self.TaskPersistancePath,self.TaskPersistanceFilename) + if ok == true then + table.remove(data, 1) + for _,_entry in pairs(data) do + -- "--ID;;Name;;InitialTargets;;Targetsleft;;Type\n" + local dataset = UTILS.Split(_entry,";;") + local Taskdata = {} -- #PersistenceData + Taskdata.ID = tonumber(dataset[1]) + Taskdata.Name = tostring(dataset[2]) + Taskdata.InitialTargets = tonumber(dataset[3]) + Taskdata.Targetsleft = tonumber(dataset[4]) + Taskdata.Type = tostring(dataset[5]) + Taskdata.Task = MatchTask(Taskdata.Type,Taskdata.Name) + if Taskdata.Task == nil then + self:E(self.lid.."No actual task found for "..Taskdata.Name) + else + self:T(self.lid.."Task loaded and match found for "..Taskdata.Name) + end + Taskdata.updated = Taskdata.InitialTargets == Taskdata.Targetsleft and true or false + if Taskdata.Task and Taskdata.updated == false then + self:_UpdateTargetsAlive(Taskdata.Task,Taskdata.Targetsleft) + Taskdata.updated = true + end + self.TaskPersistance[Taskdata.ID] = Taskdata + end + end + end + return self +end + +--- +-- @param #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:ClearPersistedData() + if lfs and io then + local text = "-- Data Cleared\n" + UTILS.SaveToFile(self.TaskPersistancePath,self.TaskPersistanceFilename,text) + end + return self +end + +--- +-- @param #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:_SaveTasksPersisted() + if lfs and io then + local text = "--ID;;Name;;InitialTargets;;Targetsleft;;Type\n" + for _,_data in pairs(self.TaskPersistance) do + local data = _data -- #PersistenceData + data.Targetsleft = data.Task.Target:CountTargets() -- recount + if data.Task and data.Task:IsDone() then data.Targetsleft = 0 end + local tasktext = string.format("%d;;%s;;%d;;%d;;%s\n",data.ID,data.Name,data.InitialTargets,data.Targetsleft,data.Type) + text = text..tasktext + end + UTILS.SaveToFile(self.TaskPersistancePath,self.TaskPersistanceFilename,text) + end + return self +end + +--- +-- @param #PLAYERTASKCONTROLLER self +-- @param #PLAYERTASK Task +function PLAYERTASKCONTROLLER:_AddPersistenceData(Task) + local Taskdata = {} -- #PersistenceData + if not self.TaskPersistance[Task.PlayerTaskNr] then + Taskdata.ID = Task.PlayerTaskNr + Taskdata.Name = Task.Target.name or "none" + Taskdata.InitialTargets = Task.Target:CountTargets() + Taskdata.Targetsleft = Taskdata.InitialTargets + Taskdata.Type = Task.Type + Taskdata.updated = true + Taskdata.Task = Task + self.TaskPersistance[Task.PlayerTaskNr] = Taskdata + end + return self +end + ------------------------------------------------------------------------------------------------------------------- -- FSM Functions PLAYERTASKCONTROLLER -- TODO: FSM Functions PLAYERTASKCONTROLLER @@ -4788,7 +4965,11 @@ function PLAYERTASKCONTROLLER:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.UnitLost, self._EventHandler) - self:SetEventPriority(5) + self:SetEventPriority(5) + -- Persistence + if self.TaskPersistanceSwitch == true then + self:_LoadTasksPersisted() + end return self end @@ -4828,6 +5009,11 @@ function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To) self:I(text) end + -- Persistence + if self.TaskPersistanceSwitch == true then + self:_SaveTasksPersisted() + end + if self:GetState() ~= "Stopped" then self:__Status(-30) end @@ -4983,19 +5169,26 @@ end -- @param #string Event -- @param #string To -- @param Ops.PlayerTask#PLAYERTASK Task +-- @param #boolean Silent -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task) +function PLAYERTASKCONTROLLER:onafterTaskAdded(From, Event, To, Task, Silent) self:T({From, Event, To}) self:T(self.lid.."TaskAdded") local addtxt = self.gettext:GetEntry("TASKADDED",self.locale) local taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.Type)) - if not self.NoScreenOutput then - self:_SendMessageToClients(taskname,15) - --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + if not Silent then + if not self.NoScreenOutput then + self:_SendMessageToClients(taskname,15) + --local m = MESSAGE:New(taskname,15,"Tasking"):ToCoalition(self.Coalition) + end + if self.UseSRS then + taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType)) + self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) + end end - if self.UseSRS then - taskname = string.format(addtxt, self.MenuName or self.Name, tostring(Task.TTSType)) - self.SRSQueue:NewTransmission(taskname,nil,self.SRS,nil,2) + self:T(self.lid..string.format("Pers = %s | Type = %s | TypePers = %s | TaskFlag = %s",tostring(self.TaskPersistanceSwitch),tostring(Task.Type),tostring(self.TasksPersistable[Task.Type]),tostring(Task.PersistMe))) + if self.TaskPersistanceSwitch == true and self.TasksPersistable[Task.Type] == true and Task.PersistMe == true then + self:_AddPersistenceData(Task) end return self end From fc9e31199ee1aab179a7b19204042d778ff89eab Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 21 Dec 2025 12:49:00 +0100 Subject: [PATCH 291/349] #CSAR - added option for Smoke Closest MASH, adjusted logic for airbase rescues --- Moose Development/Moose/Ops/CSAR.lua | 74 +++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ddfb67043..fcf9e0b6c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Oct 2025 +-- Last Update Dec 2025 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -81,7 +81,7 @@ -- -- mycsar.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- mycsar.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! --- mycsar.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. +-- mycsar.FARPRescueDistance = 500 -- you need to be this close to a FARP or Airport for the pilot to be rescued. -- mycsar.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. -- mycsar.autosmokedistance = 1000 -- distance for autosmoke -- mycsar.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. @@ -119,6 +119,7 @@ -- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each -- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it). -- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins). +-- mycsar.EnableMenuSmokeMASH = true -- Allow a menu item to request smoke at the closest MASH/AFB for rescue. -- -- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups -- @@ -272,6 +273,8 @@ CSAR = { UserSetGroup = nil, AllowIRStrobe = false, IRStrobeRuntime = 300, + FARPRescueDistance = 500, + EnableMenuSmokeMASH = true, } --- Downed pilots info. @@ -431,12 +434,13 @@ function CSAR:New(Coalition, Template, Alias) self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! self.beaconRefresher = 29 -- seconds self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase - self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. + self.FARPRescueDistance = 500 -- you need to be this close to a FARP or Airport for the pilot to be rescued. self.max_units = 6 --max number of pilots that can be carried self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot self.mashprefix = {"MASH"} -- prefixes used to find MASHes + self.EnableMenuSmokeMASH = true self.autosmoke = false -- automatically smoke location when heli is near self.autosmokedistance = 2000 -- distance for autosmoke @@ -1761,7 +1765,14 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule, I end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) - + + if self.verbose>0 then + local debugtext = string.format("Distance %dm | Rescuedist %dm | IsAirport %s | IsInAir %s | IsHeloBase %s\n",_dist,self.FARPRescueDistance,tostring(isairport),tostring(_heliUnit:InAir()),tostring(IsHeloBase)) + self:T("*******************************") + self:T(debugtext) + self:T("*******************************") + end + if ( _dist < self.FARPRescueDistance or isairport ) and ((_heliUnit:InAir() == false) or (IsHeloBase == true)) then self:T(self.lid.."[Drop off debug] Distance ok, door check") if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then @@ -2138,11 +2149,47 @@ function CSAR:_Reqsmoke( _unitName ) return self end +---(Internal) Request smoke at closest MASH/AFB. +--@param #CSAR self +--@param #string _unitName Name of the helicopter +function CSAR:_ReqsmokeMash( _unitName ) + self:T(self.lid .. " _ReqsmokeMash") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + local smokedist = 8000 + if smokedist < self.approachdist_far then smokedist = self.approachdist_far end + local distance, name, coordinate = self:_GetClosestMASH(_heli) + if coordinate and distance then + local disttext + if _SETTINGS:IsImperial() then + disttext = string.format("%.1fnm",UTILS.MetersToNM(distance)) + else + disttext = string.format("%.1fkm",distance/1000) + end + local _msg = string.format("%s - Popping smoke at the closest rescue point: %s", self:_GetCustomCallSign(_unitName), disttext) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) + local color = self.smokecolor + coordinate:Smoke(color) + else + local _distance = string.format("%.1fkm",smokedist/1000) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No rescue point within %s",_distance), self.messageTime, false, false, true) + end + return self +end + --- (Internal) Determine distance to closest MASH. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @return #number Distance in meters -- @return #string MASH Name as string +-- @return Core.Point#COORDINATE Coordinate The MASH/AFB Coordinate (for smoke) function CSAR:_GetClosestMASH(_heli) self:T(self.lid .. " _GetClosestMASH") local _mashset = self.mash -- Core.Set#SET_GROUP @@ -2155,12 +2202,23 @@ function CSAR:_GetClosestMASH(_heli) local _distance = 0 local _helicoord = _heli:GetCoordinate() local MashName = nil + local Coordinate = nil -- Core.Point#COORDINATE if self.allowFARPRescue then local position = _heli:GetCoordinate() local afb,distance = position:GetClosestAirbase(nil,self.coalition) _shortestDistance = distance MashName = (afb ~= nil) and afb:GetName() or "Unknown" + Coordinate = (afb ~= nil) and afb:GetCoordinate() + if afb then + local afbzone = afb:GetZone() + if afbzone then + --afbzone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,6) + if afbzone:IsCoordinateInZone(Coordinate) and distance > self.FARPRescueDistance then + distance = 100 + end + end + end end for _,_mashes in pairs(MashSets) do @@ -2175,12 +2233,13 @@ function CSAR:_GetClosestMASH(_heli) if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then _shortestDistance = _distance MashName = _mashUnit:GetName() or "Unknown" + Coordinate = _mashcoord end end end if _shortestDistance ~= -1 then - return _shortestDistance, MashName + return _shortestDistance, MashName, Coordinate else return -1 end @@ -2249,6 +2308,9 @@ function CSAR:_AddMedevacMenuItem() local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh() + end + if self.EnableMenuSmokeMASH then + local _rootMenu6 = MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath, self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -2979,7 +3041,7 @@ function CSAR:onafterLoad(From, Event, To, path, filename) -- Info message. local text=string.format("Loading CSAR state from file %s", filename) - MESSAGE:New(text,10):ToAllIf(self.Debug) + MESSAGE:New(text,10):ToAllIf(self.verbose>0) self:I(self.lid..text) local file=assert(io.open(filename, "rb")) From 17a3f59bdc8e0aedffa6914fe5dd4b387fbe3de4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 21 Dec 2025 15:34:17 +0100 Subject: [PATCH 292/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 120 +++++++++++++++++- Moose Development/Moose/Ops/Awacs.lua | 59 ++++++++- Moose Development/Moose/Ops/Chief.lua | 4 +- Moose Development/Moose/Ops/EasyA2G.lua | 10 +- Moose Development/Moose/Ops/EasyGCICAP.lua | 58 ++++++++- Moose Development/Moose/Ops/Intelligence.lua | 90 ++++++++++++- Moose Development/Moose/Ops/PlayerTask.lua | 97 +++++++++++++- 7 files changed, 421 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 45af655e9..dc706f543 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1067,13 +1067,111 @@ do return self end + --- Add a single reject zone to MANTIS. + -- @param #MANTIS self + -- @param Core.Zone#ZONE Zone The zone to be added + -- @return #MANTIS self + function MANTIS:AddRejectZone(Zone) + if Zone and Zone:IsInstanceOf("ZONE_BASE") then + table.insert(self.RejectZones,Zone) + self.usezones = true + end + return self + end + + --- Add a single accept zone to MANTIS. + -- @param #MANTIS self + -- @param Core.Zone#ZONE Zone The zone to be added + -- @return #MANTIS self + function MANTIS:AddAcceptZone(Zone) + if Zone and Zone:IsInstanceOf("ZONE_BASE") then + table.insert(self.AcceptZones,Zone) + self.usezones = true + end + return self + end + + --- Add a single conflict zone to MANTIS. + -- @param #MANTIS self + -- @param Core.Zone#ZONE Zone The zone to be added + -- @return #MANTIS self + function MANTIS:AddConflictZone(Zone) + if Zone and Zone:IsInstanceOf("ZONE_BASE") then + table.insert(self.ConflictZones,Zone) + self.usezones = true + end + return self + end + + --- Function to set corridor zones. + -- @param #MANTIS self + -- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object. + -- @return #MANTIS self + function MANTIS:SetCorridorZones(CorridorZones) + self:T(self.lid .. "SetCorridorZones") + if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then + self.corridorzones = CorridorZones + self.usecorridors = true + elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then + if not self.corridorzones then self.corridorzones = SET_ZONE:New() end + self.corridorzones:AddZone(CorridorZones) + self.usecorridors = true + end + if self.intelset then + for _,_intel in pairs(self.intelset) do + _intel:SetCorridorZones(self.corridorzones) + end + end + return self + end + + --- Function to add one corridor zone. + -- @param #MANTIS self + -- @param Core.Zone#ZONE CorridorZone The ZONE object to be added. + -- @return #MANTIS self + function MANTIS:AddCorridorZone(CorridorZone) + self:T(self.lid .. "AddCorridorZone") + self:SetCorridorZones(CorridorZone) + return self + end + + --- Function to set corridor zone floor and ceiling in FEET. + -- @param #MANTIS self + -- @param #number Floor Floor altitude ASL in feet. + -- @param #number Ceiling Ceiling altitude ASL in feet. + -- @return #MANTIS self + function MANTIS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) + self.corridorfloor = UTILS.FeetToMeters(Floor) + self.corridorceiling = UTILS.FeetToMeters(Ceiling) + if self.intelset then + for _,_intel in pairs(self.intelset) do + _intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) + end + end + return self + end + + --- Function to set corridor zone floor and ceiling in METERS. + -- @param #MANTIS self + -- @param #number Floor Floor altitude ASL in meters. + -- @param #number Ceiling Ceiling altitude ASL in meters. + -- @return #MANTIS self + function MANTIS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) + self.corridorfloor = Floor + self.corridorceiling = Ceiling + if self.intelset then + for _,_intel in pairs(self.intelset) do + _intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) + end + end + return self + end + --- Function to set the detection radius of the EWR in meters. (Deprecated, SAM range is used) -- @param #MANTIS self -- @param #number radius Radius of the EWR detection zone function MANTIS:SetEWRRange(radius) self:T(self.lid .. "SetEWRRange") - --local radius = radius or 80000 - -- self.acceptrange = radius return self end @@ -1628,6 +1726,12 @@ do IntelOne.DetectAccousticRadius = self.DetectAccousticRadius or 2000 IntelOne.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} --IntelOne:SetClusterAnalysis(true,true,true) + if self.usecorridors == true then + IntelOne:SetCorrdidorZones(self.corridorzones) + if self.corridorfloor or self.corridorceiling then + IntelOne:SetCooridorLimits(self.corridorfloor,self.corridorceiling) + end + end IntelOne:Start() local IntelTwo = INTEL:New(samset,self.coalition,self.name.." IntelTwo") @@ -1635,6 +1739,12 @@ do IntelTwo.DetectAccousticRadius = self.DetectAccousticRadius or 2000 IntelTwo.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} --IntelTwo:SetClusterAnalysis(true,true,true) + if self.usecorridors == true then + IntelTwo:SetCorrdidorZones(self.corridorzones) + if self.corridorfloor or self.corridorceiling then + IntelTwo:SetCooridorLimits(self.corridorfloor,self.corridorceiling) + end + end IntelTwo:Start() local CacheTime = self.DLinkCacheTime or 120 @@ -2227,11 +2337,7 @@ do else self.Detection = self:StartIntelDetection() end - --[[ - if self.advAwacs and not self.automode then - self.AWACS_Detection = self:StartAwacsDetection() - end - --]] + if self.autoshorad then self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.Coalition,self.UseEmOnOff,self.SmokeDecoy,self.SmokeDecoyColor) self.Shorad:SetDefenseLimits(80,95) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 062f32d7b..61c100bc3 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.73", -- #string + version = "0.2.74", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -2060,6 +2060,55 @@ function AWACS:SetRejectionZone(Zone,Draw) return self end +--- Function to set corridor zones. +-- @param #AWACS self +-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object. +-- @return #AWACS self +function AWACS:SetCorridorZones(CorridorZones) + self:T(self.lid .. "SetCorridorZones") + if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then + self.corridorzones = CorridorZones + self.usecorridors = true + elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then + if not self.corridorzones then self.corridorzones = SET_ZONE:New() end + self.corridorzones:AddZone(CorridorZones) + self.usecorridors = true + end + return self +end + +--- Function to add one corridor zone. +-- @param #AWACS self +-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added. +-- @return #AWACS self +function AWACS:AddCorridorZone(CorridorZone) + self:T(self.lid .. "AddCorridorZone") + self:SetCorridorZones(CorridorZone) + return self +end + +--- Function to set corridor zone floor and ceiling in FEET. +-- @param #AWACS self +-- @param #number Floor Floor altitude ASL in feet. +-- @param #number Ceiling Ceiling altitude ASL in feet. +-- @return #AWACS self +function AWACS:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) + self.corridorfloor = UTILS.FeetToMeters(Floor) + self.corridorceiling = UTILS.FeetToMeters(Ceiling) + return self +end + +--- Function to set corridor zone floor and ceiling in METERS. +-- @param #AWACS self +-- @param #number Floor Floor altitude ASL in meters. +-- @param #number Ceiling Ceiling altitude ASL in meters. +-- @return #AWACS self +function AWACS:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) + self.corridorfloor = Floor + self.corridorceiling = Ceiling + return self +end + --- [User] Draw a line around the FEZ on the F10 map. -- @param #AWACS self -- @return #AWACS self @@ -4336,6 +4385,14 @@ function AWACS:_StartIntel(awacs) intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER}) end + -- Corridors + if self.usecorridors == true then + intel:SetCorridorZones(self.corridorzones) + if self.corridorceiling or self.corridorfloor then + intel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) + end + end + -- Callbacks local function NewCluster(Cluster) self:__NewCluster(5,Cluster) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 9500ae313..ce7d102d9 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -332,7 +332,7 @@ CHIEF.Strategy = { --- CHIEF class version. -- @field #string version -CHIEF.version="0.7.0" +CHIEF.version="0.7.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -388,6 +388,8 @@ function CHIEF:New(Coalition, AgentSet, Alias) self:SetBorderZones() self:SetConflictZones() self:SetAttackZones() + self:SetCorridorZones() + self:SetRejectZones() self:SetThreatLevelRange() -- Init stuff. diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index c5678dd60..2cc1cc8c1 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -293,7 +293,7 @@ EASYA2G = { --- EASYA2G class version. -- @field #string version -EASYA2G.version="0.0.2" +EASYA2G.version="0.1.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -806,6 +806,14 @@ function EASYA2G:_StartIntel() BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) + + if self.usecorridors == true then + BlueIntel:SetCorridorZones(self.corridorzones) + if self.corridorfloor or self.corridorceiling then + BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) + end + end + BlueIntel:Start() if self.debug then diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index fff814be0..02a48aa1a 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -284,7 +284,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.32" +EASYGCICAP.version="0.1.33" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- @@ -1364,6 +1364,54 @@ function EASYGCICAP:AddConflictZone(Zone) return self end +--- Function to set corridor zones. +-- @param #EASYGCICAP self +-- @param Core.Set#SET_ZONE CorridorZones Can be handed in as SET\_ZONE or single ZONE object. +-- @return #EASYGCICAP self +function EASYGCICAP:SetCorridorZones(CorridorZones) + self:T(self.lid .. "SetCorridorZones") + if CorridorZones and CorridorZones:IsInstanceOf("SET_ZONE") then + self.corridorzones = CorridorZones + self.usecorridors = true + elseif CorridorZones and CorridorZones:IsInstanceOf("ZONE_BASE") then + if not self.corridorzones then self.corridorzones = SET_ZONE:New() end + self.corridorzones:AddZone(CorridorZones) + self.usecorridors = true + end + return self +end + +--- Function to add one corridor zone. +-- @param #EASYGCICAP self +-- @param Core.Zone#ZONE CorridorZone The ZONE object to be added. +-- @return #EASYGCICAP self +function EASYGCICAP:AddCorridorZone(CorridorZone) + self:T(self.lid .. "AddCorridorZone") + self:SetCorridorZones(CorridorZone) + return self +end + +--- Function to set corridor zone floor and ceiling in FEET. +-- @param #EASYGCICAP self +-- @param #number Floor Floor altitude ASL in feet. +-- @param #number Ceiling Ceiling altitude ASL in feet. +-- @return #EASYGCICAP self +function EASYGCICAP:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) + self.corridorfloor = UTILS.FeetToMeters(Floor) + self.corridorceiling = UTILS.FeetToMeters(Ceiling) + return self +end + +--- Function to set corridor zone floor and ceiling in METERS. +-- @param #EASYGCICAP self +-- @param #number Floor Floor altitude ASL in meters. +-- @param #number Ceiling Ceiling altitude ASL in meters. +-- @return #EASYGCICAP self +function EASYGCICAP:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) + self.corridorfloor = Floor + self.corridorceiling = Ceiling + return self +end --- (Internal) Try to assign the intercept to a FlightGroup already in air and ready. -- @param #EASYGCICAP self @@ -1574,6 +1622,14 @@ function EASYGCICAP:_StartIntel() BlueIntel:SetRejectZones(self.NoGoZoneSet) BlueIntel:SetConflictZones(self.ConflictZoneSet) BlueIntel:SetVerbosity(0) + + if self.usecorridors == true then + BlueIntel:SetCorridorZones(self.corridorzones) + if self.corridorfloor or self.corridorceiling then + BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) + end + end + BlueIntel:Start() if self.debug then diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index c47b35421..ee07fba91 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -27,6 +27,9 @@ -- @field Core.Set#SET_ZONE acceptzoneset Set of accept zones. If defined, only contacts in these zones are considered. -- @field Core.Set#SET_ZONE rejectzoneset Set of reject zones. Contacts in these zones are not considered, even if they are in accept zones. -- @field Core.Set#SET_ZONE conflictzoneset Set of conflict zones. Contacts in these zones are considered, even if they are not in accept zones or if they are in reject zones. +-- @field Core.Set#SET_ZONE corridorzoneset Set of corridor zones. Contacts in these zones are never considered. Also see corridorfloorheight and corridorfloorceiling. +-- @field #number corridorfloor [Air] Contacts below this height (ASL!) are considered, even if they are in a corridor zone. +-- @field #number corridorceiling [Air] Contacts above this height (ASL!) are considered, even if they are in a corridor zone. -- @field #table Contacts Table of detected items. -- @field #table ContactsLost Table of lost detected items. -- @field #table ContactsUnknown Table of new detected items. @@ -166,7 +169,7 @@ INTEL.Ctype={ --- INTEL class version. -- @field #string version -INTEL.version="0.3.7" +INTEL.version="0.3.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -181,6 +184,7 @@ INTEL.version="0.3.7" -- NOGO: SetAttributeZone --> return groups of generalized attributes in a zone. -- DONE: Loose units only if they remain undetected for a given time interval. We want to avoid fast oscillation between detected/lost states. Maybe 1-5 min would be a good time interval?! -- DONE: Combine units to groups for all, new and lost. +-- DONE: Add corridor zones. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -272,6 +276,7 @@ function INTEL:New(DetectionSet, Coalition, Alias) self:SetForgetTime() self:SetAcceptZones() self:SetRejectZones() + self:SetCorridorZones() self:SetConflictZones() ------------------------ @@ -501,7 +506,63 @@ function INTEL:RemoveConflictZone(ConflictZone) return self end ---- **OBSOLETE, will be removed in next version!** Set forget contacts time interval. +--- Set corrdidor zones. Contacts detected in this/these zone(s) are never reported by the detection. +-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights. +-- @param #INTEL self +-- @param Core.Set#SET_ZONE CorridorZoneSet Set of corrdidor zone(s). +-- @return #INTEL self +function INTEL:SetCorridorZones(CorridorZoneSet) + self.corridorzoneset=CorridorZoneSet or SET_ZONE:New() + return self +end + +--- Add a corrdidor zone. Contacts detected in this zone are corrdidored and not reported by the detection. +-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights. +-- @param #INTEL self +-- @param Core.Zone#ZONE CorridorZone Add a zone to the corrdidor zone set. +-- @return #INTEL self +function INTEL:AddCorridorZone(CorridorZone) + self.corridorzoneset:AddZone(CorridorZone) + return self +end + +--- Remove a corrdidor zone from the corrdidor zone set. +-- Note that corrdidor zones overrule all other zones, for exceptions see corridor floor and corridor ceiling heights. +-- @param #INTEL self +-- @param Core.Zone#ZONE CorridorZone Remove a zone from the corrdidor zone set. +-- @return #INTEL self +function INTEL:RemoveCorridorZone(CorridorZone) + self.corridorzoneset:Remove(CorridorZone:GetName(), true) + return self +end + +--- [Air] Add corrdidor zone floor and height. Are considered as ASL (above sea level or barometric) values. +-- Overrides corridor exception for objects flying outside this limitations. +-- @param #INTEL self +-- @param #number Floor Floor altitude in meters. +-- @param #number Ceiling Ceiling altitude in meters. +-- @return #INTEL self +function INTEL:SetCorridorLimits(Floor,Ceiling) + self.corridorceiling = Ceiling or 10000 + self.corridorfloor = Floor or 1 + return self +end + +--- [Air] Add corrdidor zone floor and height. Are considered as ASL (above sea level or barometric) values. +-- Overrides corridor exception for objects flying outside this limitations. +-- @param #INTEL self +-- @param #number Floor Floor altitude in feet. +-- @param #number Ceiling Ceiling altitude in feet. +-- @return #INTEL self +function INTEL:SetCorridorLimitsFeet(Floor,Ceiling) + local Ceiling = Ceiling or 25000 + local Floor = Floor or 15000 + self.corridorceiling = UTILS.FeetToMeters(Ceiling) + self.corridorfloor = UTILS.FeetToMeters(Floor) + return self +end + +--- **OBSOLETE, not functional!** Set forget contacts time interval. -- Previously known contacts that are not detected any more, are "lost" after this time. -- This avoids fast oscillations between a contact being detected and undetected. -- @param #INTEL self @@ -922,6 +983,29 @@ function INTEL:UpdateIntel() table.insert(remove, unitname) end end + + -- Check if unit is in any of the corridor zones. + if self.corridorzoneset:Count()>0 then + local inzone = false + for _,_zone in pairs(self.corridorzoneset.Set) do + local zone=_zone --Core.Zone#ZONE + if unit:IsInZone(zone) then + if unit:IsAir() and (self.corridorfloor ~= nil or self.corridorceiling ~= nil) then + local alt = unit:GetAltitude() + if self.corridorfloor and alt > self.corridorfloor then inzone = true end + if self.corridorceiling and alt < self.corridorceiling then inzone = true end + if inzone == true then break end + else + inzone=true + break + end + end + end + -- Unit is inside a corridor zone ==> remove! + if inzone then + table.insert(remove, unitname) + end + end -- Filter unit categories. Added check that we have a UNIT and not a STATIC object because :GetUnitCategory() is only available for units. if #self.filterCategory>0 and unit:IsInstanceOf("UNIT") then @@ -2209,7 +2293,7 @@ function INTEL:GetClusterCoordinate(Cluster, Update) return Cluster.coordinate end ---- Check if the coorindate of the cluster changed. +--- Check if the coordindate of the cluster changed. -- @param #INTEL self -- @param #INTEL.Cluster Cluster The cluster. -- @param #number Threshold in meters. Default 100 m. diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 7edda117b..ac306e404 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -104,7 +104,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.30" +PLAYERTASK.version="0.1.31" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -1685,8 +1685,28 @@ do -- -- Set a marker on the map and add the following text to create targets from it: "TARGET". This is effectively the same as adding a COORDINATE object as target. -- The marker can be deleted any time. --- --- ## 9 Discussion +-- +-- ## 9 Single Task Persistence for mission designer added tasks +-- +-- The class can persist the state of single tasks of type BOMBING, PRECISIONBOMBING, ARTY and SEAD, i.e. tasks which have a GROUND(!) GROUP, UNIT, STATIC or SCENERY as target. +-- This requires the task to have a unique(!) menu name set, a TARGET which already exists on the map at mission start(!), and a flag that this task is actually to be persisted. +-- Also, you need to desanitize the mission scripting environment, i.e. "lfs" and "io" must be available so we can write to disk. +-- +-- -- First, we need to enable on the PLAYERTASKCONTROLLER itself +-- taskmanager:EnableTaskPersistance([[C:\Users\myname\Saved Games\DCS\Missions\MyMisionFolder\]],"Mission Tasks.csv") -- Path and Filename +-- +-- -- Then, we can design a task marking mission progress that we want to persist +-- local RussianRadios = SET_STATIC:New():FilterPrefixes("Comms Tower Russia"):FilterOnce() +-- +-- local RadioTask = PLAYERTASK:New(AUFTRAG.Type.BOMBING,RussianRadios,true,5,"Bombing") +-- RadioTask:SetMenuName("Neutralize Comms Towers") -- UNIQUE menu name so we can find the task later! +-- RadioTask:AddFreetext("Find and neutralize the two communication towers near NB70 East of Fulda on Streufelsberg!") +-- RadioTask:AddFreetextTTS("Find and neutralize the two communication towers naer N;B;7;zero; East of Fulda on Streufelsberg!") +-- RadioTask:EnablePersistance() -- Enable persistence for this task +-- +-- taskmanager:AddPlayerTaskToQueue(RadioTask,true,false) +-- +-- ## 10 Discussion -- -- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) #ops-playertask channel. -- @@ -1789,6 +1809,7 @@ PLAYERTASKCONTROLLER.TasksPersistable = { [AUFTRAG.Type.PRECISIONBOMBING] = true, [AUFTRAG.Type.BOMBING] = true, [AUFTRAG.Type.ARTY] = true, + [AUFTRAG.Type.SEAD] = true, } --- @@ -4639,6 +4660,76 @@ function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone) return self end +--- [User] Add an corridor zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Zone#ZONE CorridorZone Add a zone to the corridor zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddCorridorZone(CorridorZone) + self:T(self.lid.."AddCorridorZone") + if self.Intel then + self.Intel:AddCorridorZone(CorridorZone) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + +--- [User] Add an corridor SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Set#SET_ZONE CorridorZoneSet Add a SET_ZONE to the corridor zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddCorridorZoneSet(CorridorZoneSet) + self:T(self.lid.."AddCorridorZoneSet") + if self.Intel then + self.Intel.corridorzoneset:AddSet(CorridorZoneSet) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + +--- [User] Remove an corridor zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Zone#ZONE CorridorZone Remove this zone from the corridor zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:RemoveCorridorZone(CorridorZone) + self:T(self.lid.."RemoveCorridorZone") + if self.Intel then + self.Intel:RemoveCorridorZone(CorridorZone) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + +--- Function to set corridor zone floor and ceiling in FEET. +-- @param #PLAYERTASKCONTROLLER self +-- @param #number Floor Floor altitude ASL in feet. +-- @param #number Ceiling Ceiling altitude ASL in feet. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeiling(Floor,Ceiling) + if self.Intel then + self.Intel:SetCorridorLimitsFeet(Floor,Ceiling) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + +--- Function to set corridor zone floor and ceiling in METERS. +-- @param #PLAYERTASKCONTROLLER self +-- @param #number Floor Floor altitude ASL in meters. +-- @param #number Ceiling Ceiling altitude ASL in meters. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetCorridorZoneFloorAndCeilingMeters(Floor,Ceiling) + if self.Intel then + self.Intel:SetCorridorLimits(Floor,Ceiling) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + --- [User] Set the top menu name to a custom string. -- @param #PLAYERTASKCONTROLLER self -- @param #string Name The name to use as the top menu designation. From 12fa333907b0bbecc872f952d9442db516dfedeb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 21 Dec 2025 18:46:08 +0100 Subject: [PATCH 293/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 8 ++++---- Moose Development/Moose/Ops/EasyA2G.lua | 2 +- Moose Development/Moose/Ops/Intelligence.lua | 6 +++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index dc706f543..10086cda1 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1727,9 +1727,9 @@ do IntelOne.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} --IntelOne:SetClusterAnalysis(true,true,true) if self.usecorridors == true then - IntelOne:SetCorrdidorZones(self.corridorzones) + IntelOne:SetCorridorZones(self.corridorzones) if self.corridorfloor or self.corridorceiling then - IntelOne:SetCooridorLimits(self.corridorfloor,self.corridorceiling) + IntelOne:SetCorridorLimits(self.corridorfloor,self.corridorceiling) end end IntelOne:Start() @@ -1740,9 +1740,9 @@ do IntelTwo.DetectAccousticUnitTypes = self.DetectAccousticCategories or {Unit.Category.HELICOPTER} --IntelTwo:SetClusterAnalysis(true,true,true) if self.usecorridors == true then - IntelTwo:SetCorrdidorZones(self.corridorzones) + IntelTwo:SetCorridorZones(self.corridorzones) if self.corridorfloor or self.corridorceiling then - IntelTwo:SetCooridorLimits(self.corridorfloor,self.corridorceiling) + IntelTwo:SetCorridorLimits(self.corridorfloor,self.corridorceiling) end end IntelTwo:Start() diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index 2cc1cc8c1..5a5f05823 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -239,7 +239,7 @@ EASYA2G = { ManagedEWR = {}, ManagedREC = {}, MaxAliveMissions = 8, - debug = true, + debug = false, engagerange = 50, repeatsonfailure = 3, GoZoneSet = nil, diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index ee07fba91..8a85de5cf 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -986,14 +986,18 @@ function INTEL:UpdateIntel() -- Check if unit is in any of the corridor zones. if self.corridorzoneset:Count()>0 then + self:I("Corridorzone Check for unit "..unit:GetName()) local inzone = false for _,_zone in pairs(self.corridorzoneset.Set) do local zone=_zone --Core.Zone#ZONE if unit:IsInZone(zone) then + local debugtext = "Corridorzone Check for unit "..unit:GetName().."\n" + debugtext = debugtext .. string.format("IsAir %s | Alt %dm | Floor %dm | Ceil %dm",tostring(unit:IsAir()),tonumber(unit:GetAltitude()),tonumber(self.corridorfloor),tonumber(self.corridorceiling)) + MESSAGE:New(debugtext,15,"INTEL"):ToAll():ToLog() if unit:IsAir() and (self.corridorfloor ~= nil or self.corridorceiling ~= nil) then local alt = unit:GetAltitude() if self.corridorfloor and alt > self.corridorfloor then inzone = true end - if self.corridorceiling and alt < self.corridorceiling then inzone = true end + if self.corridorceiling and (inzone == true or self.corridorfloor == nil) and alt < self.corridorceiling then inzone = true else inzone = false end if inzone == true then break end else inzone=true From 79a2e905a89e8c78124b90a2830bd9506d25a76f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 22 Dec 2025 14:08:32 +0100 Subject: [PATCH 294/349] xx --- Moose Development/Moose/Ops/Intelligence.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 278793f07..14882c67e 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -992,7 +992,8 @@ function INTEL:UpdateIntel() local zone=_zone --Core.Zone#ZONE if unit:IsInZone(zone) then local debugtext = "Corridorzone Check for unit "..unit:GetName().."\n" - debugtext = debugtext .. string.format("IsAir %s | Alt %dm | Floor %dm | Ceil %dm",tostring(unit:IsAir()),tonumber(unit:GetAltitude()),tonumber(self.corridorfloor),tonumber(self.corridorceiling)) + debugtext = debugtext .. string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), + tonumber(UTILS.MetersToFeet(self.corridorfloor)),tonumber(UTILS.MetersToFeet(self.corridorceiling))) MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if unit:IsAir() and (self.corridorfloor ~= nil or self.corridorceiling ~= nil) then local alt = unit:GetAltitude() From 5b88ad29674564c561382f520cf74ffc956a52e4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 23 Dec 2025 17:14:44 +0100 Subject: [PATCH 295/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index a9744719c..b0cc4d27f 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1930,7 +1930,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo crate. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew loaded! + -- @param #table Cargotable Table of #CTLD_CARGO cargo crates. Can be a Wrapper.DynamicCargo#DYNAMICCARGO objects, if ground crew loaded! -- @return #CTLD self --- FSM Function OnAfterTroopsDeployed. @@ -8924,10 +8924,36 @@ end -- @return #CTLD self function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) self:T({From, Event, To}) + if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then + local playername = Unit:GetPlayerName() + for _,_cargo in pairs(Cargotable) do + local Vehicle = _cargo.Positionable + if Vehicle then + local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0) + local dropvec2 = dropcoord:GetVec2() + self.PlayerTaskQueue:ForEach( + function (Task) + local task = Task -- Ops.PlayerTask#PLAYERTASK + local subtype = task:GetSubType() + -- right subtype? + if Event == subtype and not task:IsDone() then + local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2) then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end + end + end + end + ) + end + end + end return self end - --- (Internal) FSM Function onafterGetCrates. + --- (Internal) FSM Function OnAfterGetCrates. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -8936,12 +8962,12 @@ end -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #table Cargotable Table of #CTLD_CARGO objects spawned via "Get". -- @return #CTLD self - function CTLD:onaftergetcrates(From, Event, To, Group, Unit, Cargotable) + function CTLD:OnAfterGetCrates(From, Event, To, Group, Unit, Cargotable) self:T({From, Event, To}) return self end - --- (Internal) FSM Function onafterGetCrates. + --- (Internal) FSM Function OnAfterRemoveCratesNearby. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -8950,7 +8976,7 @@ end -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #table Cargotable Table of #CTLD_CARGO objects spawned via "Get". -- @return #CTLD self - function CTLD:onafterremovecratesnearby(From, Event, To, Group, Unit, Cargotable) + function CTLD:OnAfterRemoveCratesNearby(From, Event, To, Group, Unit, Cargotable) self:T({From, Event, To}) return self end From ab14deb2cf562f7d710209518f96d88956b9a074 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 28 Dec 2025 13:33:16 +0100 Subject: [PATCH 296/349] xx --- Moose Development/Moose/Modules_local.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index cd206f68d..7d11e2b67 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -77,6 +77,7 @@ __Moose.Include( 'Functional\\AmmoTruck.lua' ) __Moose.Include( 'Functional\\Tiresias.lua' ) __Moose.Include( 'Functional\\Stratego.lua' ) __Moose.Include( 'Functional\\ClientWatch.lua' ) +__Moose.Include( 'Functional\\Formation.lua' ) __Moose.Include( 'Ops\\Airboss.lua' ) __Moose.Include( 'Ops\\RecoveryTanker.lua' ) From 2cb58da7b75df46b2c08713c7cbdb529180ec6d6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 28 Dec 2025 13:50:43 +0100 Subject: [PATCH 297/349] xx --- .../Moose/Functional/Scoring.lua | 181 ++++++++++++++---- 1 file changed, 148 insertions(+), 33 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index a05747933..adaaaf58d 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -230,8 +230,10 @@ SCORING = { ClassID = 0, Players = {}, AutoSave = true, - version = "1.18.4", + version = "1.18.5", ScoringScenery = nil, -- Core.Set#SET_SCENERY + SceneryHitsInZone = false, + LoadSave = false, } local _SCORINGCoalition = { @@ -250,15 +252,16 @@ local _SCORINGCategory = { --- Creates a new SCORING object to administer the scoring achieved by players. -- @param #SCORING self -- @param #string GameName The name of the game. This name is also logged in the CSV score file. --- @param #string SavePath (Optional) Path where to save the CSV file, defaults to your **\\Saved Games\\DCS\\Logs** folder. --- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off. +-- @param #string SavePath (Optional) Path where to save the CSV files, defaults to your **\\Saved Games\\DCS\\Logs** folder. See next two options. +-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off. This stores a detailed table which will never be loaded again by SCORING (for e.g. Discord purposes). +-- @param #boolean LoadSave (Optional) If passed as `true` save summary scores per player, and load at restart of the mission. -- @return #SCORING self -- @usage -- -- -- Define a new scoring object for the mission Gori Valley. -- ScoringObject = SCORING:New( "Gori Valley" ) -- -function SCORING:New( GameName, SavePath, AutoSave ) +function SCORING:New( GameName, SavePath, AutoSave, LoadSave ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) -- #SCORING @@ -266,7 +269,7 @@ function SCORING:New( GameName, SavePath, AutoSave ) if GameName then self.GameName = GameName else - error( "A game name must be given to register the scoring results" ) + error( "A game name must be given to register the scoring results!" ) end -- Additional Object scores @@ -301,6 +304,12 @@ function SCORING:New( GameName, SavePath, AutoSave ) self.penaltyoncoalitionchange = true self:SetDisplayMessagePrefix() + + self.SceneryHitsInZone = false + + if LoadSave then + self.LoadSave = LoadSave + end -- Event handlers self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) @@ -328,11 +337,61 @@ function SCORING:New( GameName, SavePath, AutoSave ) end self:I("SCORING "..tostring(GameName).." started! v"..self.version) - + + if LoadSave == true then + self:_LoadPlayerSummaryScore() + end + return self end +--- [Internal] Helper to load scores from disk at scoring start +-- @param #SCORING self +-- @return #SCORING self +function SCORING:_LoadPlayerSummaryScore() + + if lfs and io and self.LoadSave == true then + local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]] + local filename = self.GameName or "PlayerScoresSummary" + filename = filename..".csv" + if UTILS.CheckFileExists(path,filename) then + local ok, data = UTILS.LoadFromFile(path,filename) + -- Playername;;Score;;Penalty + table.remove(data,1) + for _,_data in pairs(data) do + local line = UTILS.Split(_data,";;") + local playername = tostring(line[1]) + local score = tonumber(line[2]) + local penalty = tonumber(line[3]) + self:I(string.format("Player %s Score %d Penalty %d",playername,score,penalty)) + local PlayerData = self.Players[playername] + if not PlayerData then + PlayerData = {} + PlayerData.Hit = {} + PlayerData.Destroy = {} + PlayerData.Goals = {} + PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty} + PlayerData.Mission = {} + PlayerData.HitPlayers = {} + PlayerData.Score = score + PlayerData.Penalty = penalty + PlayerData.PenaltyCoalition = 0 + PlayerData.PenaltyWarning = 0 + self.Players[playername] = PlayerData + else + PlayerData.Score = score + PlayerData.Penalty =penalty + self.Players[playername] = PlayerData + PlayerData.Goals[self.GameName] = {Score = score, Penalty = penalty} + end + end + end + end + + return self +end + --- Set a prefix string that will be displayed at each scoring message sent. -- @param #SCORING self -- @param #string DisplayMessagePrefix (Default="Scoring: ") The scoring prefix string. @@ -579,6 +638,22 @@ function SCORING:AddZoneScore( ScoreZone, Score ) return self end +--- Allow Scenery hits in Zones to count (no specific(!) scenery targets). NOTE - Allowing this can spam your scoring display! +-- @param #SCORING self +-- @return #SCORING self +function SCORING:EnableSceneryHitsinZones() + self.SceneryHitsInZone = true + return self +end + +--- Disallow Scenery hits in Zones to count (no specific(!) scenery targets). +-- @param #SCORING self +-- @return #SCORING self +function SCORING:DisableSceneryHitsinZones() + self.SceneryHitsInZone = false + return self +end + --- Add a @{Core.Set#SET_ZONE} to define additional scoring when any object is destroyed in that zone. -- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced! -- This allows for a dynamic destruction zone evolution within your mission. @@ -877,6 +952,20 @@ function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score ) -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. if PlayerName then local PlayerData = self.Players[PlayerName] + if not PlayerData then + PlayerData = {} + PlayerData.Goals = {} + PlayerData.Hit = {} + PlayerData.Destroy = {} + PlayerData.Goals = {} + PlayerData.Mission = {} + PlayerData.HitPlayers = {} + PlayerData.Score = 0 + PlayerData.Penalty = 0 + PlayerData.PenaltyCoalition = 0 + PlayerData.PenaltyWarning = 0 + self.Players[PlayerName] = PlayerData + end PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 } PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score @@ -1568,22 +1657,24 @@ function SCORING:_EventOnDeadOrCrash( Event ) end else - -- Check if there are Zones where the destruction happened. - for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do - self:F( { ScoringZone = ScoreZoneData } ) - local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE - local Score = ScoreZoneData.Score - if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then - Player.Score = Player.Score + Score - TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, - MESSAGE.Type.Information ) - :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) - - self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) - Destroyed = true + if self.SceneryHitsInZone == true then + -- Check if there are Zones where the destruction happened. + for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do + self:F( { ScoringZone = ScoreZoneData } ) + local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE + local Score = ScoreZoneData.Score + if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then + Player.Score = Player.Score + Score + TargetDestroy.Score = TargetDestroy.Score + Score + MESSAGE:NewType( self.DisplayMessagePrefix .. "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." .. + "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. "Total: " .. Player.Score - Player.Penalty, + MESSAGE.Type.Information ) + :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) + :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) + + self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) + Destroyed = true + end end end end @@ -1927,9 +2018,12 @@ end --- Report all players score -- @param #SCORING self -- @param Wrapper.Group#GROUP PlayerGroup The player group. -function SCORING:ReportScoreAllSummary( PlayerGroup ) +-- @param #boolean JustScore If this is true, return just a table with playernames and overall scores. +-- @return #table ReportTable Table returned if JustScore is true. +function SCORING:ReportScoreAllSummary( PlayerGroup, JustScore ) local PlayerMessage = "" + local ReportTable = {} self:T( { "Summary Score Report of All Players", Players = self.Players } ) @@ -1961,20 +2055,28 @@ function SCORING:ReportScoreAllSummary( PlayerGroup ) local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty - ) - MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup ) + + if JustScore~=true then + PlayerMessage = + string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", + PlayerName, + PlayerScore - PlayerPenalty, + PlayerScore, + PlayerPenalty + ) + MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup ) + else + ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} + end end end - + return ReportTable end +--- Opens a score CSV file to log the scores. +-- @param #SCORING self +-- @param #number sSeconds +-- @return #string ClockString function SCORING:SecondsToClock( sSeconds ) local nSeconds = sSeconds if nSeconds == 0 then @@ -2102,6 +2204,19 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, self.CSVFile:write( "\n" ) end + + if lfs and io and self.LoadSave == true then + local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]] + local filename = self.GameName or "PlayerScoresSummary" + filename = filename..".csv" + local data = self:ReportScoreAllSummary("",true) + local text = "-- Playername;;Score;;Penalty\n" + for _playername,_data in pairs(data or {}) do + text = text..string.format("%s;;%d;;%d\n") + end + UTILS.SaveToFile(path,filename,text) + end + end --- Close CSV file From b3005010e7224cf32c969c874b15e78b2650b25a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 28 Dec 2025 13:51:23 +0100 Subject: [PATCH 298/349] xx --- Moose Development/Moose/Core/Zone.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index f88b7c2f6..a6b507182 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -783,7 +783,7 @@ end -- @param #ZONE_BASE self -- @param #string From -- @param #string Event --- @param #string to +-- @param #string To -- @return #ZONE_BASE self function ZONE_BASE:onafterTriggerRunCheck(From,Event,To) if self:GetState() ~= "TriggerStopped" then From c119e359013ab5c3ee108934930af91df16c8008 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 28 Dec 2025 14:02:03 +0100 Subject: [PATCH 299/349] xx --- Moose Development/Moose/Ops/Intelligence.lua | 24 +++++++++++++------- Moose Development/Moose/Ops/PlayerTask.lua | 9 ++++---- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 14882c67e..a20365af8 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -169,14 +169,14 @@ INTEL.Ctype={ --- INTEL class version. -- @field #string version -INTEL.version="0.3.9" +INTEL.version="0.3.10" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add min cluster size. Only create new clusters if they have a certain group size. --- TODO: process detected set asynchroniously for better performance. +-- NODO: process detected set asynchroniously for better performance. -- DONE: Add statics. -- DONE: Filter detection methods. -- DONE: Accept zones. @@ -536,8 +536,11 @@ function INTEL:RemoveCorridorZone(CorridorZone) return self end ---- [Air] Add corrdidor zone floor and height. Are considered as ASL (above sea level or barometric) values. +--- [Air] Add corrdidor zone floor and height. This is generally applicable to all(!) corridor zones. Considered as ASL (above sea level or barometric) values. -- Overrides corridor exception for objects flying outside this limitations. +-- To set an individual ceiling/floor on any Core.Zone#ZONE you wish to use, set these properties on the Core.Zone#ZONE object: +-- `mycorridorzone:SetProperty("CorridorFloor",500)` -- meters, case sensitivity matters! +-- `mycorridorzone:SetProperty("CorridorCeiling",10000)` -- meters, case sensitivity matters! -- @param #INTEL self -- @param #number Floor Floor altitude in meters. -- @param #number Ceiling Ceiling altitude in meters. @@ -548,8 +551,11 @@ function INTEL:SetCorridorLimits(Floor,Ceiling) return self end ---- [Air] Add corrdidor zone floor and height. Are considered as ASL (above sea level or barometric) values. +--- [Air] Add corrdidor zone floor and height. This is generally applicable to all(!) corridor zones. Considered as ASL (above sea level or barometric) values. -- Overrides corridor exception for objects flying outside this limitations. +-- To set an individual ceiling/floor on any Core.Zone#ZONE you wish to use, set these properties on the Core.Zone#ZONE object: +-- `mycorridorzone:SetProperty("CorridorFloor",UTILS.FeetToMeters(5000))` -- feet, case sensitivity matters! +-- `mycorridorzone:SetProperty("CorridorCeiling",UTILS.FeetToMeters(20000))` -- feet, case sensitivity matters! -- @param #INTEL self -- @param #number Floor Floor altitude in feet. -- @param #number Ceiling Ceiling altitude in feet. @@ -991,14 +997,16 @@ function INTEL:UpdateIntel() for _,_zone in pairs(self.corridorzoneset.Set) do local zone=_zone --Core.Zone#ZONE if unit:IsInZone(zone) then + local corridorfloor = zone:GetProperty("CorridorFloor") or self.corridorfloor + local corridorceiling = zone:GetProperty("CorridorCeiling") or self.corridorceiling local debugtext = "Corridorzone Check for unit "..unit:GetName().."\n" debugtext = debugtext .. string.format("IsAir %s | Alt %dft | Floor %dft | Ceil %dft",tostring(unit:IsAir()),tonumber(UTILS.MetersToFeet(unit:GetAltitude())), - tonumber(UTILS.MetersToFeet(self.corridorfloor)),tonumber(UTILS.MetersToFeet(self.corridorceiling))) + tonumber(UTILS.MetersToFeet(corridorfloor)),tonumber(UTILS.MetersToFeet(corridorceiling))) MESSAGE:New(debugtext,15,"INTEL"):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) - if unit:IsAir() and (self.corridorfloor ~= nil or self.corridorceiling ~= nil) then + if unit:IsAir() and (corridorfloor ~= nil or corridorceiling ~= nil) then local alt = unit:GetAltitude() - if self.corridorfloor and alt > self.corridorfloor then inzone = true end - if self.corridorceiling and (inzone == true or self.corridorfloor == nil) and alt < self.corridorceiling then inzone = true else inzone = false end + if corridorfloor and alt > corridorfloor then inzone = true end + if corridorceiling and (inzone == true or corridorfloor == nil) and alt < corridorceiling then inzone = true else inzone = false end if inzone == true then break end else inzone=true diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 0800ddc77..d52e7347a 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -2120,7 +2120,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self:AddTransition("*", "Stop", "Stopped") self:__Start(2) - local starttime = math.random(5,10) + local starttime = math.random(10,15) self:__Status(starttime) self:I(self.lid..self.version.." Started.") @@ -3787,7 +3787,7 @@ function PLAYERTASKCONTROLLER:_FlashInfo() local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK local Coordinate = task.Target:GetCoordinate() local CoordText = "" - if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then + if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText = Coordinate:ToStringA2G(_client, nil, self.ShowMagnetic) else CoordText = Coordinate:ToStringA2A(_client, nil, self.ShowMagnetic) @@ -3846,7 +3846,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local CoordTextLLDM = nil local ShowThreatInfo = task.ShowThreatDetails local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) - if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then + if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) @@ -5059,7 +5059,8 @@ function PLAYERTASKCONTROLLER:onafterStart(From, Event, To) self:SetEventPriority(5) -- Persistence if self.TaskPersistanceSwitch == true then - self:_LoadTasksPersisted() + self:ScheduleOnce(5,self._LoadTasksPersisted,self) + --self:_LoadTasksPersisted() end return self end From fb8cc4bfebb388b3a939da8a96b4059d89a7fb95 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 28 Dec 2025 16:51:52 +0100 Subject: [PATCH 300/349] xx --- Moose Development/Moose/Functional/Warehouse.lua | 2 +- Moose Development/Moose/Ops/EasyA2G.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 9ed4c030f..308abe9d4 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4115,7 +4115,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, local unit = group:GetUnit(1) local Descriptors= (unit and unit:IsAlive()) and unit:GetDesc() or {} local Category=group:GetCategory() - local TypeName=group:GetTypeName() + local TypeName=group:GetTypeName() or "none" local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(Descriptors) diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index 5a5f05823..9092e970d 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -918,7 +918,7 @@ function EASYA2G:onafterStatus(From,Event,To) local name = FG:GetName() local engage = FG:IsEngaging() local hasmissiles = FG:CanAirToGround() - self:T("Is Alert5? "..tostring(FG:GetMissionCurrent().type)) + --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))) From e3b405ad2dec7bcdff8143a4ba68001409408224 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 28 Dec 2025 17:51:25 +0100 Subject: [PATCH 301/349] xx --- Moose Development/Moose/Ops/AirWing.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 1e11793e3..3da21d6c3 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -326,6 +326,27 @@ function AIRWING:AddSquadron(Squadron) if Squadron:IsStopped() then Squadron:Start() end + + -- if storage is limited, add the amount of aircraft needed + local airbasename = self:GetAirbaseName() + + if airbasename then + local group = GROUP:FindByName(Squadron.templategroup) + local Nunits = 1 + local units + if group then units = group:GetUnits() end + if units then Nunits = #units end + local typename = Squadron.aircrafttype or "none" + local NAssets = Squadron.Ngroups * Nunits + local storage = STORAGE:New(airbasename) + --self:T(self.lid.."Adding "..typename.." #"..NAssets) + if storage and storage:IsLimitedAircraft() and typename ~= "none" then + local NInStore = storage:GetItemAmount(typename) or 0 + if NAssets > NInStore then + storage:AddItem(typename,NAssets) + end + end + end return self end From ca19352acec9e68672386335a2d8d74bba11a633 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 29 Dec 2025 10:47:06 +0100 Subject: [PATCH 302/349] xx --- Moose Development/Moose/Ops/CSAR.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index fcf9e0b6c..bd8d37552 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -2193,7 +2193,8 @@ end function CSAR:_GetClosestMASH(_heli) self:T(self.lid .. " _GetClosestMASH") local _mashset = self.mash -- Core.Set#SET_GROUP - local MashSets = {} + local MashSets = {} + --local _mashes = _mashset.Set-- #table table.insert(MashSets,_mashset.Set) table.insert(MashSets,self.zonemashes.Set) @@ -2213,9 +2214,13 @@ function CSAR:_GetClosestMASH(_heli) if afb then local afbzone = afb:GetZone() if afbzone then - --afbzone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,6) + local zoneradius = afbzone:GetRadius() + if zoneradius < 2000 then afbzone:SetRadius(2000) end + if self.verbose > 1 then + afbzone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,6) + end if afbzone:IsCoordinateInZone(Coordinate) and distance > self.FARPRescueDistance then - distance = 100 + _shortestDistance = 100 end end end @@ -2331,6 +2336,7 @@ function CSAR:_GetDistance(_point1, _point2) if _point1 and _point2 then local distance1 = _point1:Get2DDistance(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2) + MESSAGE:New(string.format("_GetDistance: d1 = %dm | d2 = %dm",distance1,distance2)):ToAllIf(self.verbose>1):ToLogIf(self.verbose>1) if distance1 and type(distance1) == "number" then return distance1 elseif distance2 and type(distance2) == "number" then From a3754b7031ce59915c376aee9259b689895b12e9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 29 Dec 2025 12:34:42 +0100 Subject: [PATCH 303/349] xx --- Moose Development/Moose/Ops/CSAR.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index bd8d37552..77265f521 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -318,7 +318,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.34" +CSAR.version="1.0.35" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -677,7 +677,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.alive = true DownedPilot.wetfeet = Wetfeet or false DownedPilot.BeaconName = BeaconName - + -- Add Pilot local PilotTable = self.downedPilots local counter = self.downedpilotcounter @@ -855,6 +855,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla else BeaconName = "Ghost-1-1"..math.random(1,10000) end + + if _playerName == nil or _playerName == "" then + _playerName = "AI MIA" + end if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName) @@ -2922,6 +2926,7 @@ function CSAR:onafterSave(From, Event, To, path, filename) if DownedPilot and DownedPilot.alive then -- get downed pilot data for saving local playerName = DownedPilot.player + if playerName == nil or playerName == "" then playerName = "AI MIA" end local group = DownedPilot.group local coalition = group:GetCoalition() local country = group:GetCountry() From 9df694e12e95cd2e2fcad3778ce472f2f29017c0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 29 Dec 2025 12:48:35 +0100 Subject: [PATCH 304/349] xx --- Moose Development/Moose/Utilities/Utils.lua | 178 +++++++++++++++++++- 1 file changed, 172 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index e5f54b667..3639f5ab8 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -249,7 +249,23 @@ CALLSIGN={ Stetson = 22, Wrath = 23, }, - + Intruder = { + Raygun = 4, + Heartless = 5, + Viceroy = 6, + Cupcake = 7, + ["Flying Tiger"] = 8, + ["Flying Ace"] = 9, + Buckeye = 10, + Goldplate = 11, + Phoenix = 12, + Electron = 13, + Rustler = 14, + Vixen = 15, + Jackal = 16, + Milestone = 17, + Devil = 18, + }, } --#CALLSIGN --- Utilities static class. @@ -2014,6 +2030,12 @@ function UTILS.GetCallsignName(Callsign) end end + for name, value in pairs(CALLSIGN.Intruder) do + if value==Callsign then + return name + end + end + return "Ghostrider" end @@ -4747,7 +4769,7 @@ function UTILS.DoStringIn(State,DoString) end --- Show a picture on the screen to all --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4770,7 +4792,7 @@ end --- Show a picture on the screen to Coalition -- @param #number Coalition Coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4795,7 +4817,7 @@ end --- Show a picture on the screen to Country -- @param #number Country Country ID, can be country.id.USA, country.id.RUSSIA, etc. --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4818,7 +4840,7 @@ end --- Show a picture on the screen to Group -- @param Wrapper.Group#GROUP Group Group to show the picture to --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -4841,7 +4863,7 @@ end --- Show a picture on the screen to Unit -- @param Wrapper.Unit#UNIT Unit Unit to show the picture to --- @param #string FilePath File name of the picture +-- @param #string FileName File name of the picture -- @param #number Duration Duration in seconds, defaults to 10 -- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false -- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 @@ -5290,3 +5312,147 @@ function UTILS.CreateAirbaseEnum() _savefile(string.format("%s-enums.txt", env.mission.theatre), text) --env.info(text) end + +--- Calculate then center and radius of a circle enclosing a list if DCS#Vec2 points. +-- @param #table points Table of DCS#Vec2 entries +-- @return DCS#Vec2 center DCS#Vec2 +-- @return #number radius +function UTILS.GetCenterAndRadius(points) + if #points == 0 then + return nil, nil + end + + -- Calculate centroid (average of all points) + local sumX, sumY = 0, 0 + for _, p in ipairs(points) do + sumX = sumX + p.x + sumY = sumY + p.y + end + + local center = { + x = sumX / #points, + y = sumY / #points + } + + -- Find maximum distance from center to any point + local maxDist = 0 + for _, p in ipairs(points) do + local dx = p.x - center.x + local dy = p.y - center.y + local dist = math.sqrt(dx*dx + dy*dy) + if dist > maxDist then + maxDist = dist + end + end + + return center, maxDist +end + +--- More accurate: Minimum bounding circle (Welzl's algorithm), calculate then center and radius of a circle enclosing a list if DCS#Vec2 points. +-- @param #table points Table of DCS#Vec2 entries +-- @return DCS#Vec2 center DCS#Vec2 +-- @return #number radius +function UTILS.GetMinimumBoundingCircle(points) + if #points == 0 then + return nil, nil + end + + -- Calculate distance between two points + local function distance(p1, p2) + local dx = p2.x - p1.x + local dy = p2.y - p1.y + return math.sqrt(dx*dx + dy*dy) + end + + -- Circle from 2 points (diameter) + local function circleFrom2Points(p1, p2) + local center = { + x = (p1.x + p2.x) / 2, + y = (p1.y + p2.y) / 2 + } + local radius = distance(p1, p2) / 2 + return center, radius + end + + -- Circle from 3 points (circumcircle) + local function circleFrom3Points(p1, p2, p3) + local ax, ay = p1.x, p1.y + local bx, by = p2.x, p2.y + local cx, cy = p3.x, p3.y + + local d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) + + if math.abs(d) < 0.0001 then + -- Points are collinear, use 2-point circle + return circleFrom2Points(p1, p3) + end + + local aSq = ax*ax + ay*ay + local bSq = bx*bx + by*by + local cSq = cx*cx + cy*cy + + local ux = (aSq * (by - cy) + bSq * (cy - ay) + cSq * (ay - by)) / d + local uy = (aSq * (cx - bx) + bSq * (ax - cx) + cSq * (bx - ax)) / d + + local center = {x = ux, y = uy} + local radius = distance(center, p1) + + return center, radius + end + + -- Check if point is inside circle + local function isInside(center, radius, point, tolerance) + tolerance = tolerance or 0.0001 + return distance(center, point) <= radius + tolerance + end + + -- Welzl's algorithm (recursive) + local function welzlHelper(pts, n, boundary) + -- Base cases + if n == 0 or #boundary == 3 then + if #boundary == 0 then + return {x = 0, y = 0}, 0 + elseif #boundary == 1 then + return {x = boundary[1].x, y = boundary[1].y}, 0 + elseif #boundary == 2 then + return circleFrom2Points(boundary[1], boundary[2]) + else + return circleFrom3Points(boundary[1], boundary[2], boundary[3]) + end + end + + -- Pick a random point + local p = pts[n] + + -- Get circle without this point + local center, radius = welzlHelper(pts, n - 1, boundary) + + -- If point is inside, we're done + if isInside(center, radius, p) then + return center, radius + end + + -- Otherwise, point must be on the boundary + local newBoundary = {} + for i = 1, #boundary do + newBoundary[i] = boundary[i] + end + table.insert(newBoundary, p) + + return welzlHelper(pts, n - 1, newBoundary) + end + + -- Shuffle points for better average performance + local pts = {} + for i, p in ipairs(points) do + pts[i] = {x = p.x, y = p.y} + end + + -- Simple shuffle + for i = #pts, 2, -1 do + local j = math.random(1, i) + pts[i], pts[j] = pts[j], pts[i] + end + + return welzlHelper(pts, #pts, {}) +end From 3eef63b75cd727f01e86a92a5631c19d67deb3d7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 29 Dec 2025 13:59:23 +0100 Subject: [PATCH 305/349] xx --- Moose Development/Moose/Ops/CSAR.lua | 38 ++++----------- Moose Development/Moose/Wrapper/Airbase.lua | 51 +++++++++++++++++++++ 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 77265f521..bd2d83bb5 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1275,7 +1275,7 @@ function CSAR:_EventHandler(EventData) self:T("Country = ".._country.." Coalition = ".._coalition) if _coalition == self.coalition then local _freq = self:_GenerateADFFrequency() - self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) + self:T({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, self.suppressmessages, "none")--shagrat add CSAR at Parachute location. Unit.destroy(_event.initiator) -- shagrat remove static Pilot model @@ -2207,23 +2207,17 @@ function CSAR:_GetClosestMASH(_heli) local _distance = 0 local _helicoord = _heli:GetCoordinate() local MashName = nil - local Coordinate = nil -- Core.Point#COORDINATE + local Coordinate = nil -- Core.Point#COORDINATE if self.allowFARPRescue then - local position = _heli:GetCoordinate() - local afb,distance = position:GetClosestAirbase(nil,self.coalition) + local afb,distance = _helicoord:GetClosestAirbase(nil,self.coalition) _shortestDistance = distance MashName = (afb ~= nil) and afb:GetName() or "Unknown" Coordinate = (afb ~= nil) and afb:GetCoordinate() if afb then - local afbzone = afb:GetZone() + local afbzone = afb:GetMinimumBoundingCircleFromParkingSpots() if afbzone then - local zoneradius = afbzone:GetRadius() - if zoneradius < 2000 then afbzone:SetRadius(2000) end - if self.verbose > 1 then - afbzone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,6) - end - if afbzone:IsCoordinateInZone(Coordinate) and distance > self.FARPRescueDistance then + if afbzone:IsCoordinateInZone(_helicoord) and distance > self.FARPRescueDistance*1.1 then _shortestDistance = 100 end end @@ -2440,7 +2434,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName) --local name = _radioUnit:GetName() local Sound = "l10n/DEFAULT/"..self.radioSound local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} - self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) + self:T(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight end end @@ -2568,22 +2562,6 @@ function CSAR:onafterStart(From, Event, To) self.staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() self.zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart() - --[[ - if staticmashes:Count() > 0 then - for _,_mash in pairs(staticmashes.Set) do - self.mash:AddObject(_mash) - end - end - - if zonemashes:Count() > 0 then - self:T("Adding zones to self.mash SET") - for _,_mash in pairs(zonemashes.Set) do - self.mash:AddObject(_mash) - end - self:T("Objects in SET: "..self.mash:Count()) - end - --]] - if not self.coordinate then local csarhq = self.mash:GetRandom() if csarhq then @@ -2937,7 +2915,7 @@ function CSAR:onafterSave(From, Event, To, path, filename) local unitName = DownedPilot.originalUnit local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq) - self:I(self.lid.."Saving to CSAR File: " .. txt) + self:T(self.lid.."Saving to CSAR File: " .. txt) data = data .. txt end @@ -3053,7 +3031,7 @@ function CSAR:onafterLoad(From, Event, To, path, filename) -- Info message. local text=string.format("Loading CSAR state from file %s", filename) MESSAGE:New(text,10):ToAllIf(self.verbose>0) - self:I(self.lid..text) + self:T(self.lid..text) local file=assert(io.open(filename, "rb")) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 9e8638bba..89c90b5bb 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -27,6 +27,7 @@ -- @field #table parkingByID Parking spot data table with ID as key. -- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning. -- @field #table parkingBlacklist List of parking spot terminal IDs **not** considered for spawning. +-- @field Core.Zone#ZONE_RADIUS parkingCircle Minimum bounding circle enclosing all parking spots. -- @field #table runways Runways of airdromes. -- @field #AIRBASE.Runway runwayLanding Runway used for landing. -- @field #AIRBASE.Runway runwayTakeoff Runway used for takeoff. @@ -1662,6 +1663,8 @@ end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) end + + self:GetMinimumBoundingCircleFromParkingSpots( ) -- Debug info. self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName))) @@ -2191,6 +2194,54 @@ function AIRBASE:GetParkingSpotsCoordinates(termtype) return spots end +--- Get the DCS#Vec2s of all parking spots at an airbase. Optionally only those of a specific terminal type. Spots on runways are excluded if not explicitly requested by terminal type. +-- @param #AIRBASE self +-- @param #AIRBASE.TerminalType termtype (Optional) Terminal type. Default all. +-- @return #table Table of DCS#Vec2 of parking spots. +function AIRBASE:GetParkingSpotsVec2s(termtype) + + -- Get all parking spots data. + local parkingdata=self:GetParkingData(false) + + -- Put coordinates of free spots into table. + local spots={} + for _,parkingspot in ipairs(parkingdata) do + + -- Coordinates on runway are not returned unless explicitly requested. + if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then + + -- Get coordinate from Vec3 terminal position. + local vec2 = { x = parkingspot.vTerminalPos.x, y = parkingspot.vTerminalPos.z } + + -- Add to table. + table.insert(spots, vec2) + end + + end + + return spots +end + +--- Get the the bounding circular zone around all parking spots of an airbase. +-- @param #AIRBASE self +-- @param #boolean mark (Optional) Draw zone on map on first call of this function. +-- @return Core.Zone#ZONE_RADIUS BoundingZone +function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark) + if self.isAirdrome then + if not self.parkingCircle then + local spots = self:GetParkingSpotsVec2s() + local center, radius = UTILS.GetMinimumBoundingCircle(spots) + self.parkingCircle = ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50) + if mark == true then + self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) + end + end + return self.parkingCircle + else + return self.AirbaseZone + end +end + --- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. -- @param #AIRBASE self -- @return#AIRBASE self From 07714d160c8a88327cb2e4bec14066aa070f2970 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 3 Jan 2026 17:25:45 +0100 Subject: [PATCH 306/349] xx --- Moose Development/Moose/Functional/Scoring.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index adaaaf58d..5d67f389e 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -2212,7 +2212,11 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, local data = self:ReportScoreAllSummary("",true) local text = "-- Playername;;Score;;Penalty\n" for _playername,_data in pairs(data or {}) do - text = text..string.format("%s;;%d;;%d\n") + -- ReportTable[PlayerName] = {["Score"]=PlayerScore,["Penalty"]=PlayerPenalty} + local Playername = _playername or "Ghost" + local Score = _data.Score or 0 + local Penalty = _data.Penalty or 0 + text = text..string.format("%s;;%d;;%d\n",Playername,Score,Penalty) end UTILS.SaveToFile(path,filename,text) end From e821fa110030ebd065513fe9b876a72bf75a6eaf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Jan 2026 17:51:36 +0100 Subject: [PATCH 307/349] xx --- Moose Development/Moose/Ops/AirWing.lua | 41 ++++--- Moose Development/Moose/Utilities/Utils.lua | 117 ++++++++++++++++++++ 2 files changed, 145 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 3152de856..b00f9957d 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -331,19 +331,34 @@ function AIRWING:AddSquadron(Squadron) local airbasename = self:GetAirbaseName() if airbasename then - local group = GROUP:FindByName(Squadron.templategroup) - local Nunits = 1 - local units - if group then units = group:GetUnits() end - if units then Nunits = #units end - local typename = Squadron.aircrafttype or "none" - local NAssets = Squadron.Ngroups * Nunits - local storage = STORAGE:New(airbasename) - --self:T(self.lid.."Adding "..typename.." #"..NAssets) - if storage and storage.warehouse and storage:IsLimitedAircraft() and typename ~= "none" then - local NInStore = storage:GetItemAmount(typename) or 0 - if NAssets > NInStore then - storage:AddItem(typename,NAssets) + local group = Squadron.templategroup + if group then + local Nunits = 1 + local units + if group then units = group:GetUnits() end + if units then Nunits = #units end + local typename = Squadron.aircrafttype or "none" + local NAssets = Squadron.Ngroups * Nunits + local storage = STORAGE:New(airbasename) + + self:T(self.lid.."Adding "..typename.." #"..NAssets) + if storage and storage.warehouse and storage:IsLimitedAircraft() and typename ~= "none" then + local NInStore = storage:GetItemAmount(typename) or 0 + if NAssets > NInStore then + storage:AddItem(typename,NAssets) + end + end + + local unit = group:GetUnit(1) + -- if storage is limited, add the amount of liquids needed + if unit and storage and storage.warehouse and storage:IsLimitedLiquids() and typename ~= "none" then + local fuel = unit:GetFuelMassMax() + local neededfuel = (fuel*NAssets) -- kgs of fuel + local NInStore = storage:GetLiquidAmount(STORAGE.Liquid.JETFUEL) or 0 + self:T(string.format(self.lid.."Fuel Needed: %dt | Fuel in store: %dt",neededfuel/1000,NInStore/1000)) + if neededfuel > NInStore then + storage:AddLiquid(STORAGE.Liquid.JETFUEL,neededfuel) + end end end end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 3639f5ab8..1b58765c8 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1507,6 +1507,14 @@ function UTILS.VecSubstract(a, b) return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z} end +--- Scale a 3D vectors by multiplication with s. +-- @param DCS#Vec3 v A Vector in 3D with x, y, z components. +-- @param #number s Scale +-- @return DCS#Vec3 Vec3 +function UTILS.VecScale(v, s) + return {x = v.x * s, y = v.y * s, z = v.z * s} +end + --- Substract is not a word, don't want to rename the original function because it's been around since forever function UTILS.VecSubtract(a, b) return UTILS.VecSubstract(a, b) @@ -1520,6 +1528,14 @@ function UTILS.Vec2Substract(a, b) return {x=a.x-b.x, y=a.y-b.y} end +--- Calculate the difference between two 3D vectors by substracting the x,y,z components from each other. +-- @param DCS#Vec3 a Vector in 3D with x, y, z components. +-- @param DCS#Vec3 b Vector in 3D with x, y, z components. +-- @return DCS#Vec3 Vector in 3D. +function UTILS.Vec3Substract(a, b) + return {x = a.x - b.x, y = a.y - b.y, z = a.z - b.z} +end + --- Substract is not a word, don't want to rename the original function because it's been around since forever function UTILS.Vec2Subtract(a, b) return UTILS.Vec2Substract(a, b) @@ -1649,6 +1665,13 @@ function UTILS.Vec2Translate(a, distance, angle) return {x=TX, y=TY} end +--- Calculate the lenght of a 3D vector +-- @param DCS#Vec3 v A Vector in 3D with x, y, z components. +-- @return #number length +function UTILS.Vec3Length(v) + return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) +end + --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees. @@ -5456,3 +5479,97 @@ function UTILS.GetMinimumBoundingCircle(points) return welzlHelper(pts, #pts, {}) end + +--- Calculated optimal intercepting course (Bearing) +-- @param DCS#Vec3 A1 Position of flight one +-- @param DCS#Vec3 V1 VelocityVector of flight one +-- @param DCS#Vec3 A2 Position of flight two +-- @param #number V2_speed Speed of A2 in m/s +-- @return #number Bearing Bearing course for A2 to take for an intercept of A1 or nil if aspect is hot/cold. +function UTILS.CalculateInterceptBearing(A1, V1, A2, V2_speed) + + local function berechne_bearing(richtung) + local bearing = math.deg(math.atan2(richtung.x, richtung.y)) + if bearing < 0 then + bearing = bearing + 360 + end + return bearing + end + + local function vec_normalize(v) + local len = UTILS.Vec3Length(v) + if len == 0 then return {x = 0, y = 0, z = 0} end + return {x = v.x / len, y = v.y / len, z = v.z / len} + end + + + -- Relative Position von F1 zu F2 + local rel_pos = UTILS.Vec3Substract(A1, A2) + local distance = UTILS.Vec3Length(rel_pos) + + if distance == 0 then + return nil -- Bereits am gleichen Ort + end + + -- Richtungsvektor von F2 zu F1 (normalisiert) + local richtung_zu_f1 = vec_normalize(rel_pos) + + -- Prüfe HOT: F1 fliegt direkt auf F2 zu + -- Das ist der Fall wenn V1 in die entgegengesetzte Richtung von rel_pos zeigt + local v1_normalisiert = vec_normalize(V1) + local annaeherung = UTILS.VecDot(v1_normalisiert, richtung_zu_f1) + + if annaeherung < -0.95 then -- F1 fliegt fast direkt auf F2 zu (Winkel > ~162°) + return nil -- Hot + end + + -- Prüfe COLD: F1 fliegt parallel weg von F2 + -- Berechne die Geschwindigkeitsdifferenz + local rel_velocity = UTILS.VecSubstract(V1, {x=0, y=0, z=0}) -- V1 relativ zu F2 (falls F2 stillsteht) + local flucht_komponente = UTILS.VecDot(vec_normalize(rel_velocity), richtung_zu_f1) + + if flucht_komponente > 0.95 then -- F1 fliegt fast parallel weg (Winkel < ~18°) + return nil -- Cold + end + + -- Geschwindigkeiten + local v1 = UTILS.Vec3Length(V1) + local v2 = V2_speed + + -- Löse quadratische Gleichung für Abfangzeit t + local a = UTILS.VecDot(V1, V1) - v2 * v2 + local b = 2 * UTILS.VecDot(rel_pos, V1) + local c = UTILS.VecDot(rel_pos, rel_pos) + + local discriminant = b * b - 4 * a * c + + if discriminant < 0 then + return nil -- Keine Lösung möglich + end + + -- Wähle die positive, kleinste Lösung + local t1 = (-b + math.sqrt(discriminant)) / (2 * a) + local t2 = (-b - math.sqrt(discriminant)) / (2 * a) + + local t = nil + if t1 > 0 and t2 > 0 then + t = math.min(t1, t2) + elseif t1 > 0 then + t = t1 + elseif t2 > 0 then + t = t2 + else + return nil -- Keine positive Lösung + end + + -- Berechne Treffpunkt + local treffpunkt = UTILS.VecAdd(A1, UTILS.VecScale(V1, t)) + + -- Berechne Richtung zum Treffpunkt + local richtung = UTILS.VecSubstract(treffpunkt, A2) + + -- Berechne Bearing + local bearing = berechne_bearing(richtung) + + return UTILS.Round(bearing,0) +end From b29f485e15940da618fdbbee77849e5c053460ee Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Jan 2026 17:51:48 +0100 Subject: [PATCH 308/349] xx --- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 6aa9639cc..1ca25ca08 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1954,7 +1954,7 @@ function UNIT:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits = Enabled end ---- Get the max kgs of fuel this unit can hold in its *internal* tank(s) and overall (with ext. tanks) in kgs. +--- Get the max kgs of fuel this unit can hold in its *internal* tank(s) and overall (with external tanks) in kgs. -- @param #UNIT self -- @return #number InternalFuel Max internal fuel in kgs. Zero if it cannot be determined. -- @return #number Overall Fuel Overall max in case there are external tanks in kgs. Zero if it cannot be determined. From c2b8a71000c21e5362f5a80ece57b61eadb4b2b8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 8 Jan 2026 13:03:15 +0100 Subject: [PATCH 309/349] xx --- Moose Development/Moose/Core/Point.lua | 15 ++++- Moose Development/Moose/Ops/PlayerTask.lua | 73 +++++++++++++++++++--- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 15050e0cc..d50a9fe6b 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -3281,7 +3281,6 @@ do -- COORDINATE return delta/60 end - --- Return a BR string from a COORDINATE to the COORDINATE. -- @param #COORDINATE self -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. @@ -3295,6 +3294,20 @@ do -- COORDINATE local Distance = self:Get2DDistance( FromCoordinate ) return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision ) end + + --- Return a Bearing string from a COORDINATE to the (self) COORDINATE. + -- @param #COORDINATE self + -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. + -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. + -- @param #boolean MagVar If true, also get angle in MagVar for BR/BRA + -- @param #number Precision Rounding precision, currently full km as default (=0) + -- @return #string The BR text. + function COORDINATE:ToStringBearing( FromCoordinate, Settings, MagVar, Precision ) + local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) + local AngleRadians = self:GetAngleRadians( DirectionVec3 ) + --local Distance = self:Get2DDistance( FromCoordinate ) + return self:GetBearingText(AngleRadians,Precision,Settings,MagVar) + end --- Return a BRA string from a COORDINATE to the COORDINATE. -- @param #COORDINATE self diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index d52e7347a..63ec31a7f 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -237,7 +237,7 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #boolen Silent If true, suppress message output on cancel. + -- @param #boolean Silent If true, suppress message output on cancel. --- On After "Planned" event. Task has been planned. -- @function [parent=#PLAYERTASK] OnAfterPilotPlanned @@ -1921,6 +1921,7 @@ PLAYERTASKCONTROLLER.Messages = { DESTROYER = "Destroyer", CARRIER = "Aircraft Carrier", RADIOS = "Radios", + INTERCEPTCOURSE = "Intercept course", }, DE = { TASKABORT = "Auftrag abgebrochen!", @@ -2008,12 +2009,13 @@ PLAYERTASKCONTROLLER.Messages = { DESTROYER = "Zerstörer", CARRIER = "Flugzeugträger", RADIOS = "Frequenzen", + INTERCEPTCOURSE = "Abfangkurs", }, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.71" +PLAYERTASKCONTROLLER.version="0.1.73" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -3776,6 +3778,41 @@ function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client) return self end +--- Calculate group future position after given seconds. +-- @param #PLAYERTASKCONTROLLER self +-- @param Wrapper.Group#GROUP group The group to calculate for. +-- @param #number seconds Time interval in seconds. Default is `self.prediction`. +-- @return Core.Point#COORDINATE Calculated future position of the cluster. +function PLAYERTASKCONTROLLER:_CalcGroupFuturePosition(group, seconds) + + -- Get current position of the cluster. + local p=group:GetCoordinate() + + -- Velocity vector in m/s. + local v=group:GetVelocityVec3() + + -- Time in seconds. + local t=seconds or self.prediction + + -- Extrapolated vec3. + local Vec3={x=p.x+v.x*t, y=p.y+v.y*t, z=p.z+v.z*t} + + -- Future position. + local futureposition=COORDINATE:NewFromVec3(Vec3) + + -- Create an arrow pointing in the direction of the movement. + if self.verbose == true then + local markerID = group:GetProperty("PLAYERTASK_ARROW") + if markerID then + COORDINATE:RemoveMark(markerID) + end + markerID = p:ArrowToAll(futureposition, self.coalition, {1,0,0}, 1, {1,1,0}, 0.5, 2, true, "Position Calc") + group:SetProperty("PLAYERTASK_ARROW",markerID) + end + + return futureposition +end + --- [Internal] Flashing directional info for a client -- @param #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self @@ -3785,16 +3822,34 @@ function PLAYERTASKCONTROLLER:_FlashInfo() if _client and _client:IsAlive() then if self.TasksPerPlayer:HasUniqueID(_playername) then local task = self.TasksPerPlayer:ReadByID(_playername) -- Ops.PlayerTask#PLAYERTASK - local Coordinate = task.Target:GetCoordinate() + local Coordinate = task.Target:GetCoordinate() -- Core.Point#COORDINATE local CoordText = "" if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A and task.Type~=AUFTRAG.Type.INTERCEPT then CoordText = Coordinate:ToStringA2G(_client, nil, self.ShowMagnetic) + local targettxt = self.gettext:GetEntry("TARGET",self.locale) + local text = targettxt..": "..CoordText + local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client) else CoordText = Coordinate:ToStringA2A(_client, nil, self.ShowMagnetic) + local targettxt = self.gettext:GetEntry("TARGET",self.locale) + local text = targettxt..": "..CoordText + -- calc intercept position + local name=task.Target:GetName() + local group = GROUP:FindByName(name) + local clientcoord = _client:GetCoordinate() + if group and clientcoord and group:IsAlive() and task.Type==AUFTRAG.Type.INTERCEPT then + local speed = math.max(UTILS.KnotsToMps(350) or _client:GetVelocityMPS()) + local dist = Coordinate:Get3DDistance(clientcoord) + local iTime = math.floor(dist/speed)+5 + if iTime < 10 then iTime = 10 + elseif iTime > 600 then iTime = 600 end + local npos = self:_CalcGroupFuturePosition(group,iTime) + local BR = npos:ToStringBearing(clientcoord,nil,self.ShowMagnetic,0 ) + local Intercepttext = self.gettext:GetEntry("INTERCEPTCOURSE",self.locale) + text = text .. "\n"..Intercepttext.." "..BR + end + local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client) end - local targettxt = self.gettext:GetEntry("TARGET",self.locale) - local text = "Target: "..CoordText - local m = MESSAGE:New(text,10,"Tasking"):ToClient(_client) end end end @@ -3883,8 +3938,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) Elevation = math.floor(UTILS.MetersToFeet(Elevation)) end -- ELEVATION = "\nTarget Elevation: %s %s", - local elev = self.gettext:GetEntry("ELEVATION",self.locale) - text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure) + if task.Type ~= AUFTRAG.Type.INTERCEPT then + local elev = self.gettext:GetEntry("ELEVATION",self.locale) + text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure) + end -- Prec bombing if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then if LasingDrone and LasingDrone.playertask then From e5fc887d4a6046d032c6d41b9f3270a68f8ee14e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 8 Jan 2026 13:12:12 +0100 Subject: [PATCH 310/349] xx --- Moose Development/Moose/Core/Point.lua | 2 -- Moose Development/Moose/Ops/PlayerTask.lua | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index d50a9fe6b..ee2b0c6ce 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -3886,5 +3886,3 @@ do -- COORDINATE end end - - diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 63ec31a7f..a43909dfb 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -1599,6 +1599,7 @@ do -- ELEVATION = "\nTarget Elevation: %s %s", -- METER = "meter", -- FEET = "feet", +-- INTERCEPTCOURSE = "Intercept course", -- }, -- -- e.g. From 6b3148ec31bfdc756aa704b8595490c9f838e3b5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 8 Jan 2026 18:50:00 +0100 Subject: [PATCH 311/349] xx --- .../Moose/Ops/RecoveryTanker.lua | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index 02e0828a7..59c38e9b2 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -310,7 +310,7 @@ _RECOVERYTANKERID=0 --- Class version. -- @field #string version -RECOVERYTANKER.version="1.0.10" +RECOVERYTANKER.version="1.0.11" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -923,6 +923,22 @@ function RECOVERYTANKER:onafterStart(From, Event, To) Spawn:InitRadioCommsOnOff(true) Spawn:InitRadioFrequency(self.RadioFreq) Spawn:InitRadioModulation(self.RadioModu) + + if self.callsignname and self.callsignnumber then + local grp = GROUP:FindByName(self.tankergroupname) + if grp then + local typename = grp:GetTypeName() or "" + --self:I(self.lid.."Typename: "..typename) + local Name + local enumerator = CALLSIGN.Tanker + if typename == "A6E" then + enumerator = CALLSIGN.Intruder + end + Name = self:_GetCallsignName(self.callsignname,enumerator) + --self:I(self.lid.."CallsignName: "..Name) + Spawn:InitCallSign(self.callsignname,Name,self.callsignnumber,self.callsignnumber) + end + end Spawn:InitModex(self.modex) -- Spawn on carrier. @@ -1190,9 +1206,9 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To) -- Task combo. -- Be a tanker or be an AWACS. - local taskroll = self.tanker:EnRouteTaskTanker() + local taskrole = self.tanker:EnRouteTaskTanker() if self.awacs then - taskroll=self.tanker:EnRouteTaskAWACS() + taskrole=self.tanker:EnRouteTaskAWACS() end --local taskeplrs=self.tanker:TaskEPLRS(true, 2) @@ -1201,7 +1217,7 @@ function RECOVERYTANKER:onafterPatternUpdate(From, Event, To) local taskroute = self.tanker:TaskRoute(wp) -- Note that the order is important here! tasktanker has to come first. Otherwise it does not work. - local taskcombo = self.tanker:TaskCombo({taskroll, taskroute}) + local taskcombo = self.tanker:TaskCombo({taskrole, taskroute}) -- Set task. self.tanker:SetTask(taskcombo, 1) @@ -1450,6 +1466,20 @@ function RECOVERYTANKER:_RefuelingStop(EventData) end +--- Get clear name callsign for spawn from enumerator +-- @param #RECOVERYTANKER self +-- @param #number Callsign +-- @param #table Enumerator The table of callsigns, e.g. `CALLSIGN.Tanker` +-- @return #string Name Name or "" if not found +function RECOVERYTANKER:_GetCallsignName(Callsign, Enumerator) + for name, value in pairs(Enumerator or {}) do + if value==Callsign then + return name + end + end + return "" +end + --- A unit crashed or died. -- @param #RECOVERYTANKER self -- @param Core.Event#EVENTDATA EventData Event data. From ce711a3d0cb5c7717db011a12878c37b23b13cee Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 9 Jan 2026 15:35:19 +0100 Subject: [PATCH 312/349] #EASYGCICAP - Fix trying to set corridor limits in feet when they are meters internally --- Moose Development/Moose/Ops/EasyGCICAP.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 02a48aa1a..68804a66b 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -1626,7 +1626,7 @@ function EASYGCICAP:_StartIntel() if self.usecorridors == true then BlueIntel:SetCorridorZones(self.corridorzones) if self.corridorfloor or self.corridorceiling then - BlueIntel:SetCorridorLimitsFeet(self.corridorfloor,self.corridorceiling) + BlueIntel:SetCorridorLimits(self.corridorfloor,self.corridorceiling) end end From 33483ae37b928de8e8a995d18ad1b9cc23b2cf3f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Jan 2026 13:14:32 +0100 Subject: [PATCH 313/349] xx --- Moose Development/Moose/Wrapper/Airbase.lua | 172 +++++++++++++------- 1 file changed, 116 insertions(+), 56 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index fa1551363..a6c597c93 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -718,62 +718,62 @@ AIRBASE.SouthAtlantic = { --- Airbases of the Sinai map -- --- * `AIRBASE.SinaiMap.Abu_Rudeis` Abu Rudeis --- * `AIRBASE.SinaiMap.Abu_Suwayr` Abu Suwayr --- * `AIRBASE.SinaiMap.Al_Bahr_al_Ahmar` Al Bahr al Ahmar --- * `AIRBASE.SinaiMap.Al_Ismailiyah` Al Ismailiyah --- * `AIRBASE.SinaiMap.Al_Khatatbah` Al Khatatbah --- * `AIRBASE.SinaiMap.Al_Mansurah` Al Mansurah --- * `AIRBASE.SinaiMap.Al_Rahmaniyah_Air_Base` Al Rahmaniyah Air Base --- * `AIRBASE.SinaiMap.As_Salihiyah` As Salihiyah --- * `AIRBASE.SinaiMap.AzZaqaziq` AzZaqaziq --- * `AIRBASE.SinaiMap.Baluza` Baluza --- * `AIRBASE.SinaiMap.Ben_Gurion` Ben-Gurion --- * `AIRBASE.SinaiMap.Beni_Suef` Beni Suef --- * `AIRBASE.SinaiMap.Bilbeis_Air_Base` Bilbeis Air Base --- * `AIRBASE.SinaiMap.Bir_Hasanah` Bir Hasanah --- * `AIRBASE.SinaiMap.Birma_Air_Base` Birma Air Base --- * `AIRBASE.SinaiMap.Borg_El_Arab_International_Airport` Borg El Arab International Airport --- * `AIRBASE.SinaiMap.Cairo_International_Airport` Cairo International Airport --- * `AIRBASE.SinaiMap.Cairo_West` Cairo West --- * `AIRBASE.SinaiMap.Damascus_Intl` Damascus Intl --- * `AIRBASE.SinaiMap.Difarsuwar_Airfield` Difarsuwar Airfield --- * `AIRBASE.SinaiMap.Ein_Shamer` Ein Shamer --- * `AIRBASE.SinaiMap.El_Arish` El Arish --- * `AIRBASE.SinaiMap.El_Gora` El Gora --- * `AIRBASE.SinaiMap.El_Minya` El Minya --- * `AIRBASE.SinaiMap.Fayed` Fayed --- * `AIRBASE.SinaiMap.Gebel_El_Basur_Air_Base` Gebel El Basur Air Base --- * `AIRBASE.SinaiMap.Hatzerim` Hatzerim --- * `AIRBASE.SinaiMap.Hatzor` Hatzor --- * `AIRBASE.SinaiMap.Hurghada_International_Airport` Hurghada International Airport --- * `AIRBASE.SinaiMap.Inshas_Airbase` Inshas Airbase --- * `AIRBASE.SinaiMap.Jiyanklis_Air_Base` Jiyanklis Air Base --- * `AIRBASE.SinaiMap.Kedem` Kedem --- * `AIRBASE.SinaiMap.Khalkhalah_Air_Base` Khalkhalah Air Base --- * `AIRBASE.SinaiMap.Kibrit_Air_Base` Kibrit Air Base --- * `AIRBASE.SinaiMap.King_Feisal_Air_Base` King Feisal Air Base --- * `AIRBASE.SinaiMap.Kom_Awshim` Kom Awshim --- * `AIRBASE.SinaiMap.Megiddo` Megiddo --- * `AIRBASE.SinaiMap.Melez` Melez --- * `AIRBASE.SinaiMap.Mezzeh_Air_Base` Mezzeh Air Base --- * `AIRBASE.SinaiMap.Nevatim` Nevatim --- * `AIRBASE.SinaiMap.Ovda` Ovda --- * `AIRBASE.SinaiMap.Palmachim` Palmachim --- * `AIRBASE.SinaiMap.Quwaysina` Quwaysina --- * `AIRBASE.SinaiMap.Rafic_Hariri_Intl` Rafic Hariri Intl --- * `AIRBASE.SinaiMap.Ramat_David` Ramat David --- * `AIRBASE.SinaiMap.Ramon_Airbase` Ramon Airbase --- * `AIRBASE.SinaiMap.Ramon_International_Airport` Ramon International Airport --- * `AIRBASE.SinaiMap.Sde_Dov` Sde Dov --- * `AIRBASE.SinaiMap.Sharm_El_Sheikh_International_Airport` Sharm El Sheikh International Airport --- * `AIRBASE.SinaiMap.St_Catherine` St Catherine --- * `AIRBASE.SinaiMap.Taba_International_Airport` Taba International Airport --- * `AIRBASE.SinaiMap.Tabuk` Tabuk --- * `AIRBASE.SinaiMap.TabukHeliBase` TabukHeliBase --- * `AIRBASE.SinaiMap.Tel_Nof` Tel Nof --- * `AIRBASE.SinaiMap.Wadi_Abu_Rish` Wadi Abu Rish --- * `AIRBASE.SinaiMap.Wadi_al_Jandali` Wadi al Jandali +-- * `AIRBASE.Sinai.Abu_Rudeis` Abu Rudeis +-- * `AIRBASE.Sinai.Abu_Suwayr` Abu Suwayr +-- * `AIRBASE.Sinai.Al_Bahr_al_Ahmar` Al Bahr al Ahmar +-- * `AIRBASE.Sinai.Al_Ismailiyah` Al Ismailiyah +-- * `AIRBASE.Sinai.Al_Khatatbah` Al Khatatbah +-- * `AIRBASE.Sinai.Al_Mansurah` Al Mansurah +-- * `AIRBASE.Sinai.Al_Rahmaniyah_Air_Base` Al Rahmaniyah Air Base +-- * `AIRBASE.Sinai.As_Salihiyah` As Salihiyah +-- * `AIRBASE.Sinai.AzZaqaziq` AzZaqaziq +-- * `AIRBASE.Sinai.Baluza` Baluza +-- * `AIRBASE.Sinai.Ben_Gurion` Ben-Gurion +-- * `AIRBASE.Sinai.Beni_Suef` Beni Suef +-- * `AIRBASE.Sinai.Bilbeis_Air_Base` Bilbeis Air Base +-- * `AIRBASE.Sinai.Bir_Hasanah` Bir Hasanah +-- * `AIRBASE.Sinai.Birma_Air_Base` Birma Air Base +-- * `AIRBASE.Sinai.Borg_El_Arab_International_Airport` Borg El Arab International Airport +-- * `AIRBASE.Sinai.Cairo_International_Airport` Cairo International Airport +-- * `AIRBASE.Sinai.Cairo_West` Cairo West +-- * `AIRBASE.Sinai.Damascus_Intl` Damascus Intl +-- * `AIRBASE.Sinai.Difarsuwar_Airfield` Difarsuwar Airfield +-- * `AIRBASE.Sinai.Ein_Shamer` Ein Shamer +-- * `AIRBASE.Sinai.El_Arish` El Arish +-- * `AIRBASE.Sinai.El_Gora` El Gora +-- * `AIRBASE.Sinai.El_Minya` El Minya +-- * `AIRBASE.Sinai.Fayed` Fayed +-- * `AIRBASE.Sinai.Gebel_El_Basur_Air_Base` Gebel El Basur Air Base +-- * `AIRBASE.Sinai.Hatzerim` Hatzerim +-- * `AIRBASE.Sinai.Hatzor` Hatzor +-- * `AIRBASE.Sinai.Hurghada_International_Airport` Hurghada International Airport +-- * `AIRBASE.Sinai.Inshas_Airbase` Inshas Airbase +-- * `AIRBASE.Sinai.Jiyanklis_Air_Base` Jiyanklis Air Base +-- * `AIRBASE.Sinai.Kedem` Kedem +-- * `AIRBASE.Sinai.Khalkhalah_Air_Base` Khalkhalah Air Base +-- * `AIRBASE.Sinai.Kibrit_Air_Base` Kibrit Air Base +-- * `AIRBASE.Sinai.King_Feisal_Air_Base` King Feisal Air Base +-- * `AIRBASE.Sinai.Kom_Awshim` Kom Awshim +-- * `AIRBASE.Sinai.Megiddo` Megiddo +-- * `AIRBASE.Sinai.Melez` Melez +-- * `AIRBASE.Sinai.Mezzeh_Air_Base` Mezzeh Air Base +-- * `AIRBASE.Sinai.Nevatim` Nevatim +-- * `AIRBASE.Sinai.Ovda` Ovda +-- * `AIRBASE.Sinai.Palmachim` Palmachim +-- * `AIRBASE.Sinai.Quwaysina` Quwaysina +-- * `AIRBASE.Sinai.Rafic_Hariri_Intl` Rafic Hariri Intl +-- * `AIRBASE.Sinai.Ramat_David` Ramat David +-- * `AIRBASE.Sinai.Ramon_Airbase` Ramon Airbase +-- * `AIRBASE.Sinai.Ramon_International_Airport` Ramon International Airport +-- * `AIRBASE.Sinai.Sde_Dov` Sde Dov +-- * `AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport` Sharm El Sheikh International Airport +-- * `AIRBASE.Sinai.St_Catherine` St Catherine +-- * `AIRBASE.Sinai.Taba_International_Airport` Taba International Airport +-- * `AIRBASE.Sinai.Tabuk` Tabuk +-- * `AIRBASE.Sinai.TabukHeliBase` TabukHeliBase +-- * `AIRBASE.Sinai.Tel_Nof` Tel Nof +-- * `AIRBASE.Sinai.Wadi_Abu_Rish` Wadi Abu Rish +-- * `AIRBASE.Sinai.Wadi_al_Jandali` Wadi al Jandali -- -- @field Sinai AIRBASE.Sinai = { @@ -834,6 +834,66 @@ AIRBASE.Sinai = { ["Wadi_Abu_Rish"] = "Wadi Abu Rish", ["Wadi_al_Jandali"] = "Wadi al Jandali", } +--- +-- @field SinaiMap +AIRBASE.SinaiMap = { + ["Abu_Rudeis"] = "Abu Rudeis", + ["Abu_Suwayr"] = "Abu Suwayr", + ["Al_Bahr_al_Ahmar"] = "Al Bahr al Ahmar", + ["Al_Ismailiyah"] = "Al Ismailiyah", + ["Al_Khatatbah"] = "Al Khatatbah", + ["Al_Mansurah"] = "Al Mansurah", + ["Al_Rahmaniyah_Air_Base"] = "Al Rahmaniyah Air Base", + ["As_Salihiyah"] = "As Salihiyah", + ["AzZaqaziq"] = "AzZaqaziq", + ["Baluza"] = "Baluza", + ["Ben_Gurion"] = "Ben-Gurion", + ["Beni_Suef"] = "Beni Suef", + ["Bilbeis_Air_Base"] = "Bilbeis Air Base", + ["Bir_Hasanah"] = "Bir Hasanah", + ["Birma_Air_Base"] = "Birma Air Base", + ["Borg_El_Arab_International_Airport"] = "Borg El Arab International Airport", + ["Cairo_International_Airport"] = "Cairo International Airport", + ["Cairo_West"] = "Cairo West", + ["Damascus_Intl"] = "Damascus Intl", + ["Difarsuwar_Airfield"] = "Difarsuwar Airfield", + ["Ein_Shamer"] = "Ein Shamer", + ["El_Arish"] = "El Arish", + ["El_Gora"] = "El Gora", + ["El_Minya"] = "El Minya", + ["Fayed"] = "Fayed", + ["Gebel_El_Basur_Air_Base"] = "Gebel El Basur Air Base", + ["Hatzerim"] = "Hatzerim", + ["Hatzor"] = "Hatzor", + ["Hurghada_International_Airport"] = "Hurghada International Airport", + ["Inshas_Airbase"] = "Inshas Airbase", + ["Jiyanklis_Air_Base"] = "Jiyanklis Air Base", + ["Kedem"] = "Kedem", + ["Khalkhalah_Air_Base"] = "Khalkhalah Air Base", + ["Kibrit_Air_Base"] = "Kibrit Air Base", + ["King_Feisal_Air_Base"] = "King Feisal Air Base", + ["Kom_Awshim"] = "Kom Awshim", + ["Megiddo"] = "Megiddo", + ["Melez"] = "Melez", + ["Mezzeh_Air_Base"] = "Mezzeh Air Base", + ["Nevatim"] = "Nevatim", + ["Ovda"] = "Ovda", + ["Palmachim"] = "Palmachim", + ["Quwaysina"] = "Quwaysina", + ["Rafic_Hariri_Intl"] = "Rafic Hariri Intl", + ["Ramat_David"] = "Ramat David", + ["Ramon_Airbase"] = "Ramon Airbase", + ["Ramon_International_Airport"] = "Ramon International Airport", + ["Sde_Dov"] = "Sde Dov", + ["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport", + ["St_Catherine"] = "St Catherine", + ["Taba_International_Airport"] = "Taba International Airport", + ["Tabuk"] = "Tabuk", + ["TabukHeliBase"] = "TabukHeliBase", + ["Tel_Nof"] = "Tel Nof", + ["Wadi_Abu_Rish"] = "Wadi Abu Rish", + ["Wadi_al_Jandali"] = "Wadi al Jandali", +} --- Airbases of the Kola map -- From af67829a41d280aeccbe16300e277cb7ace1e7a3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 22 Jan 2026 12:30:54 +0100 Subject: [PATCH 314/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 116 +++++++++++++++--- Moose Development/Moose/Functional/Sead.lua | 22 +++- Moose Development/Moose/Ops/CSAR.lua | 6 +- Moose Development/Moose/Ops/CTLD.lua | 6 +- 4 files changed, 126 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 10086cda1..3d526aea1 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: December 2025 +-- Last Update: January 2026 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -283,7 +283,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", - version = "0.9.42", + version = "0.9.43", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -337,6 +337,8 @@ MANTIS = { DetectAccoustic = false, DetectAccousticRadius = 2000, DetectAccousticCategories = {Unit.Category.HELICOPTER}, + ARMWeaponSeen = {}, + InboundARMs = {}, } --- Advanced state enumerator @@ -372,24 +374,25 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3 -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) -- @field #string Point Point defense capable +-- @field ARMCapacit ARMCapacity ie how many (H)ARMs the system can defend at the same time MANTIS.SamData = { ["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km - ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B + ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS", ARMCapacity=1 }, -- AIM 120B ["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" }, ["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" }, ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" }, ["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" }, ["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" }, ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" }, - ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"}, + ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4", ARMCapacity=4}, ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, - ["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, + ["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland", ARMCapacity=1 }, ["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" }, ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" }, ["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" }, - ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true" }, + ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true", ARMCapacity=2 }, ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" }, ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, @@ -398,13 +401,13 @@ MANTIS.SamData = { ["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, - ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" }, + ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=3 }, ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"}, - ["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M"}, - ["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M"}, - ["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E"}, + ["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4}, + ["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4}, + ["S-400"] = { Range=250, Blindspot=5, Height=27, Type="Long" , Radar="92N6E", ARMCapacity=4}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, @@ -412,7 +415,7 @@ MANTIS.SamData = { ["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" }, -- CH Added to DCS core 2.9.19.x ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" }, - ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, + ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=4 }, ["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, } @@ -430,7 +433,7 @@ MANTIS.SamDataHDS = { ["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" }, ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" }, ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD - ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD + ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln", ARMCapacity=3}, -- V55RUD ["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " }, ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"}, ["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, @@ -479,7 +482,7 @@ MANTIS.SamDataCH = { -- https://www.currenthill.com/ -- group name MUST contain CHM to ID launcher type correctly! ["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" }, - ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true" }, + ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true", ARMCapacity=3 }, ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, ["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" }, ["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, @@ -625,6 +628,8 @@ do self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP self.FilterZones = Zones + self.ARMWeaponSeen = {} + self.InboundARMs = {} self.SkateZones = nil self.SkateNumber = 3 @@ -1868,6 +1873,7 @@ do local HDSmod = false local SMAMod = false local CHMod = false + local ARMCapacity = 0 if string.find(grpname,"HDS",1,true) then HDSmod = true elseif string.find(grpname,"SMA",1,true) then @@ -1885,6 +1891,7 @@ do range = _entry.Range * 1000 * radiusscale -- max firing range height = _entry.Height * 1000 -- max firing height blind = _entry.Blindspot + ARMCapacity = _entry.ARMCapacity or 0 self:T("Matching Groupname = " .. grpname .. " Range= " .. range) found = true break @@ -1911,7 +1918,7 @@ do if found and string.find(grpname,"SHORAD",1,true) then type = MANTIS.SamType.POINT -- force short on match end - return range, height, type, blind + return range, height, type, blind, ARMCapacity end --- [Internal] Function to set the SAM start state @@ -1946,8 +1953,8 @@ do group:OptionEngageRange(engagerange) --default engagement will be 95% of firing range local grpname = group:GetName() local grpcoord = group:GetCoordinate() - local grprange,grpheight,type,blind = self:_GetSAMRange(grpname) - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) + local grprange,grpheight,type,blind,ARMCapacity = self:_GetSAMRange(grpname) + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) --table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) @@ -2011,11 +2018,11 @@ do if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() - local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) + local grprange, grpheight,type,blind, ARMCapacity = self:_GetSAMRange(grpname) -- TODO the below might stop working at some point after some hours, needs testing --local radaralive = group:IsSAM() local radaralive = true - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG and radaralive then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) @@ -2098,6 +2105,77 @@ do ----------------------------------------------------------------------- -- MANTIS main functions ----------------------------------------------------------------------- + +--- [Internal] Check if a system can and should defend for HARMs itself +-- @param #MANTIS self +-- @param Wrapper.Group#GROUP targetGroup +-- @param #string targetName +-- @param Wrapper.Group#GROUPattackerGroup +-- @param #string weaponName +-- @param Wrapper.Weapon#WEAPON weaponWrapper +-- @param #number tti +-- @param #number delay +-- @return #boolean Outcome +function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, weaponName, weaponWrapper, tti, delay) + + -- Init per-SAM weapon tracking + self.ARMWeaponSeen = self.ARMWeaponSeen or {} + self.ARMWeaponSeen[targetName] = self.ARMWeaponSeen[targetName] or {} + + -- Extract weapon ID + local wid = nil + if weaponWrapper and weaponWrapper.GetDCSObject then + local wpn = weaponWrapper:GetDCSObject() + if wpn then + wid = wpn:getID() + end + end + + -- Ignore duplicate evaluations of the same missile + if wid and self.ARMWeaponSeen[targetName][wid] then + self:T(string.format( + "MANTIS: Duplicate ARM ignored for %s (weapon %d)", + targetName, wid + )) + return false + end + + -- Mark weapon as seen + if wid then + self.ARMWeaponSeen[targetName][wid] = true + end + + -- NOW increment ARM counter (once per weapon) + self.InboundARMs[targetName] = (self.InboundARMs[targetName] or 0) + 1 + + -- Lookup ARM capacity + local samdata + for _, sam in pairs(self.SAM_Table or {}) do + if sam[1] == targetName then + samdata = sam + break + end + end + + local armcap = samdata and samdata[7] + + if not armcap or armcap == 0 then + return true + end + + if targetGroup and targetGroup:IsAlive() then + local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition() + if AmmoM and AmmoM == 0 then + return true + end + end + + if self.InboundARMs[targetName] > armcap then + return true + end + + return false + end --- [Internal] Check detection function -- @param #MANTIS self @@ -2549,6 +2627,8 @@ do function MANTIS:onafterSeadSuppressionEnd(From, Event, To, Group, Name) self:T({From, Event, To, Name}) self.SuppressedGroups[Name] = false + self.InboundARMs[Name] = 0 + self.ARMWeaponSeen[Name] = nil return self end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 34b5e7d13..2bc819169 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -19,7 +19,7 @@ -- -- ### Authors: **applevangelist**, **FlightControl** -- --- Last Update: Dec 2024 +-- Last Update: January 2026 -- -- === -- @@ -157,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) self:AddTransition("*", "ManageEvasion", "*") self:AddTransition("*", "CalculateHitZone", "*") - self:I("*** SEAD - Started Version 0.4.9") + self:I("*** SEAD - Started Version 0.4.10") return self end @@ -443,6 +443,24 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay local _targetgroupname = _targetgroup:GetName() if not self.SuppressedGroups[_targetgroupname] then + -- TODO: ask callback if suppression is allowed BEFORE scheduling timers + local allow = true + if self.UseCallBack and self.CallBack and self.CallBack.SeadAllowSuppression then + allow = self.CallBack:SeadAllowSuppression( + _targetgroup, + _targetgroupname, + SEADGroup, + SEADWeaponName, + Weapon, + _tti, + delay + ) + end + if not allow then + self:T(string.format("*** SEAD - %s | Suppression vetoed by callback", _targetgroupname)) + -- Important: do NOT schedule SuppressionStart/Stop and do NOT flip SuppressedGroups true. + return self + end self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay)) timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime) timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index bd2d83bb5..b416051af 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Dec 2025 +-- Last Update Jan 2026 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -315,10 +315,12 @@ CSAR.AircraftType["MH-60R"] = 10 CSAR.AircraftType["OH-6A"] = 2 CSAR.AircraftType["OH58D"] = 2 CSAR.AircraftType["CH-47Fbl1"] = 31 +CSAR.AircraftType["AH-6J"] = 2 +CSAR.AircraftType["MH-6J"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.35" +CSAR.version="1.0.36" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f9b48a3db..a6e4c7179 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -25,7 +25,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Dec 2025 +-- Last Update Jan 2026 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -1496,6 +1496,8 @@ CTLD.UnitTypeCapabilities = { ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, + ["AH-6J"] = {type="AH-6J", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, + ["MH-6J"] = {type="MH-6J", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 10800}, @@ -1514,7 +1516,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.42" +CTLD.version="1.3.43" --- Instantiate a new CTLD. -- @param #CTLD self From 829860e4f0660f4de7296ba6db62b9358b53a635 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 22 Jan 2026 17:21:04 +0100 Subject: [PATCH 315/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 145 +++++++++++------- Moose Development/Moose/Functional/Sead.lua | 4 +- Moose Development/Moose/Wrapper/Weapon.lua | 2 +- 3 files changed, 94 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 3d526aea1..a737c8fd6 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -628,7 +628,7 @@ do self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP self.FilterZones = Zones - self.ARMWeaponSeen = {} + self.LastThreatEval = {} self.InboundARMs = {} self.SkateZones = nil @@ -2117,64 +2117,82 @@ do -- @param #number delay -- @return #boolean Outcome function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, weaponName, weaponWrapper, tti, delay) + self:T(self.lid.."SeadAllowSuppression") - -- Init per-SAM weapon tracking - self.ARMWeaponSeen = self.ARMWeaponSeen or {} - self.ARMWeaponSeen[targetName] = self.ARMWeaponSeen[targetName] or {} - - -- Extract weapon ID - local wid = nil - if weaponWrapper and weaponWrapper.GetDCSObject then - local wpn = weaponWrapper:GetDCSObject() - if wpn then - wid = wpn:getID() - end + --- Thanks to @Goon Jan 2026 + ---------------------------------------------------------------- + -- LOG INCOMING REQUEST + ---------------------------------------------------------------- + self:T(string.format("MANTIS:SeadAllowSuppression REQUEST | target=%s | weapon=%s | tti=%s | delay=%s",tostring(targetName), + tostring(weaponName),tostring(tti),tostring(delay))) + + ---------------------------------------------------------------- + -- LOOK UP ARM CAPACITY FOR THIS SAM + ---------------------------------------------------------------- + local armcap = nil + + for _, sam in pairs(self.SAM_Table or {}) do + if sam[1] == targetName then + armcap = sam[7] -- ARMCapacity + break end + end + + self:T(string.format("MANTIS:SeadAllowSuppression SAM DATA | target=%s | ARMCapacity=%s",tostring(targetName),armcap and tostring(armcap) or "nil")) + + ---------------------------------------------------------------- + -- TRACK SEAD THREATS (PER TARGET) + ---------------------------------------------------------------- + local THREAT_WINDOW = 0.1 -- seconds - -- Ignore duplicate evaluations of the same missile - if wid and self.ARMWeaponSeen[targetName][wid] then - self:T(string.format( - "MANTIS: Duplicate ARM ignored for %s (weapon %d)", - targetName, wid - )) - return false - end + self.LastThreatEval = self.LastThreatEval or {} + self.InboundARMs = self.InboundARMs or {} - -- Mark weapon as seen - if wid then - self.ARMWeaponSeen[targetName][wid] = true - end + local now = timer.getTime() + local last = self.LastThreatEval[targetName] or 0 - -- NOW increment ARM counter (once per weapon) + if (now - last) >= THREAT_WINDOW then self.InboundARMs[targetName] = (self.InboundARMs[targetName] or 0) + 1 + self.LastThreatEval[targetName] = now + self:T(string.format("MANTIS:SeadAllowSuppression NEW threat accepted | Δt=%.3f",now - last)) + else + self:T(string.format("MANTIS:SeadAllowSuppression duplicate evaluation ignored | Δt=%.3f",now - last)) + end + + local inbound = self.InboundARMs[targetName] or 0 + + self:T(string.format("MANTIS:SeadAllowSuppression THREAT COUNT | target=%s | inboundThreats=%d",tostring(targetName),inbound)) + + ---------------------------------------------------------------- + -- DECISION GATE + ---------------------------------------------------------------- - -- Lookup ARM capacity - local samdata - for _, sam in pairs(self.SAM_Table or {}) do - if sam[1] == targetName then - samdata = sam - break - end - end - - local armcap = samdata and samdata[7] - - if not armcap or armcap == 0 then + -- No missiles left over → legacy behavior + if targetGroup and targetGroup:IsAlive() then + local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition() + -- TODO Check C-RAM probably needs an exception as it is a gun, need to check its effectiveness + if AmmoM and AmmoM == 0 then + self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no MISSILES) | target=%s",tostring(targetName))) return true end - - if targetGroup and targetGroup:IsAlive() then - local AmmotT, AmmoS, _, _,AmmoM = targetGroup:GetAmmunition() - if AmmoM and AmmoM == 0 then - return true - end - end + end - if self.InboundARMs[targetName] > armcap then - return true - end - - return false + -- No ARM capacity defined → legacy behavior + if (not armcap) or armcap == 0 then + self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (no ARMCAP) | target=%s",tostring(targetName))) + return true + end + + -- Suppress only once enough threats accumulated + if inbound >= armcap then + self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> APPROVED (inbound %d >= cap %d) | target=%s",inbound,armcap,tostring(targetName))) + return true + end + + self:T(string.format("MANTIS:SeadAllowSuppression DECISION -> DENIED (inbound %d < cap %d) | target=%s",inbound,armcap,tostring(targetName))) + + return false + end --- [Internal] Check detection function @@ -2225,6 +2243,7 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea switch = true end if self.SamStateTracker[name] ~= "RED" and switch then + self.SamStateTracker[name] = "RED" self:__RedState(1,samgroup) end -- DONE Restrict on Distance @@ -2257,8 +2276,8 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea samgroup:OptionAlarmStateGreen() end if self.SamStateTracker[name] ~= "GREEN" then - self:__GreenState(1,samgroup) self.SamStateTracker[name] = "GREEN" + self:__GreenState(1,samgroup) end if self.debug or self.verbose then local text = string.format("SAM %s in alarm state GREEN!", name) @@ -2268,12 +2287,13 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea end --end alive end --end check end --for loop + --[[ if self.debug or self.verbose or self.logsamstatus then for _,_status in pairs(self.SamStateTracker) do if _status == "GREEN" then instatusgreen=instatusgreen+1 elseif _status == "RED" then - instatusred=instatusred+1 + instatusred=instatusred+1 end end if self.Shorad then @@ -2282,6 +2302,8 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea end end end + self:T(self.lid..string.format("SAM State Count: GREEN %d | RED %d | SHORAD %d",instatusred, instatusgreen, activeshorads)) + --]] return instatusred, instatusgreen, activeshorads end @@ -2313,13 +2335,29 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange) local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange) + local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange) else local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) + local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) end local function GetReport() + + if self.debug or self.verbose or self.logsamstatus then + for _,_status in pairs(self.SamStateTracker) do + if _status == "GREEN" then + instatusgreen=instatusgreen+1 + elseif _status == "RED" then + instatusred=instatusred+1 + end + end + if self.Shorad then + for _,_name in pairs(self.Shorad.ActiveGroups or {}) do + activeshorads=activeshorads+1 + end + end + end + local statusreport = REPORT:New("\nMANTIS Status "..self.name) statusreport:Add("+-----------------------------+") statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) @@ -2628,7 +2666,6 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea self:T({From, Event, To, Name}) self.SuppressedGroups[Name] = false self.InboundARMs[Name] = 0 - self.ARMWeaponSeen[Name] = nil return self end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 2bc819169..88e94fa59 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -318,7 +318,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG elseif height <= 12500 then Ropt = Ropt * 0.98 end - + local WeaponWrapper = WEAPON:New(SEADWeapon) -- look at a couple of zones across the trajectory for n=1,3 do local dist = Ropt - ((n-1)*20000) @@ -342,7 +342,7 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG _targetgroupname = tgtgrp:GetName() -- group name _targetskill = tgtgrp:GetUnit(1):GetSkill() self:T("*** Found Target = ".. _targetgroupname) - self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20) + self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20, WeaponWrapper) end --end end diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua index 0ba988748..86b4cea03 100644 --- a/Moose Development/Moose/Wrapper/Weapon.lua +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -350,7 +350,7 @@ end -- myweapon:SetFuncImpact(OnImpact) -- -- -- Start tracking. --- myweapon:Track() +-- myweapon:StartTrack() -- function WEAPON:SetFuncImpact(FuncImpact, ...) self.impactFunc=FuncImpact From bff150a931f8aaa492f6df6264175273ce47ed1c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 23 Jan 2026 18:21:21 +0100 Subject: [PATCH 316/349] xx --- Moose Development/Moose/Ops/EasyA2G.lua | 7 +++--- Moose Development/Moose/Ops/EasyGCICAP.lua | 27 +++++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index 9092e970d..1e97ea5c4 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -21,7 +21,7 @@ -- ------------------------------------------------------------------------- -- Date: Dec 2025 --- Last Update: Dec 2025 +-- Last Update: Jan 2026 ------------------------------------------------------------------------- -- -- === @@ -293,7 +293,7 @@ EASYA2G = { --- EASYA2G class version. -- @field #string version -EASYA2G.version="0.1.3" +EASYA2G.version="0.1.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -352,6 +352,7 @@ function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName) self.FuelCriticalThreshold = 10 self.showpatrolpointmarks = false self.EngageTargetTypes = {"Ground"} + self:SetDefaultTurnoverTime() -- Set some string id for output to DCS.log file. self.lid=string.format("EASYA2G %s | ", self.alias) @@ -546,7 +547,7 @@ function EASYA2G:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames, S 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:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 68804a66b..2c557773a 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -21,7 +21,7 @@ -- ------------------------------------------------------------------------- -- Date: September 2023 --- Last Update: Aug 2025 +-- Last Update: Jan 2026 ------------------------------------------------------------------------- -- --- **Ops** - Easy GCI & CAP Manager @@ -89,6 +89,8 @@ -- @field #number FuelCriticalThreshold -- @field #boolean showpatrolpointmarks -- @field #table EngageTargetTypes +-- @field #number maintenancetime +-- @field #number repairtime -- @extends Core.Fsm#FSM --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. @@ -284,7 +286,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.33" +EASYGCICAP.version="0.1.34" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- @@ -343,6 +345,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.FuelCriticalThreshold = 10 self.showpatrolpointmarks = false self.EngageTargetTypes = {"Air"} + self:SetDefaultTurnoverTime() -- Set some string id for output to DCS.log file. self.lid=string.format("EASYGCICAP %s | ", self.alias) @@ -608,6 +611,18 @@ function EASYGCICAP:SetDefaultMissionRange(Range) return self end +--- Set default turnover times for squadrons in minutes +-- @param #EASYGCICAP self +-- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 5 min. +-- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 10 min. +-- @return #EASYGCICAP self +function EASYGCICAP:SetDefaultTurnoverTime(MaintenanceTime,RepairTime) + self:T(self.lid.."SetDefaultTurnoverTime") + self.maintenancetime=MaintenanceTime or 5 + self.repairtime=RepairTime or 10 + return self +end + --- Set default number of airframes standing by for intercept tasks (visible on the airfield) -- @param #EASYGCICAP self -- @param #number Airframes defaults to 2 @@ -1217,7 +1232,7 @@ function EASYGCICAP:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames Squadron_One:AddMissionCapability({AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) @@ -1248,7 +1263,7 @@ function EASYGCICAP:_AddReconSquadron(TemplateName, SquadName, AirbaseName, AirF Squadron_One:AddMissionCapability({AUFTRAG.Type.RECON}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) @@ -1282,7 +1297,7 @@ function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, Air Squadron_One:AddMissionCapability({AUFTRAG.Type.TANKER}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) @@ -1319,7 +1334,7 @@ function EASYGCICAP:_AddAWACSSquadron(TemplateName, SquadName, AirbaseName, AirF Squadron_One:AddMissionCapability({AUFTRAG.Type.AWACS}) --Squadron_One:SetFuelLowRefuel(true) Squadron_One:SetFuelLowThreshold(0.3) - Squadron_One:SetTurnoverTime(10,20) + Squadron_One:SetTurnoverTime(self.maintenancetime,self.repairtime) Squadron_One:SetModex(Modex) Squadron_One:SetLivery(Livery) Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) From 34d2336030bb2b974ab572bb5790677f12b4b5c4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 24 Jan 2026 16:17:44 +0100 Subject: [PATCH 317/349] xx --- Moose Development/Moose/Ops/EasyA2G.lua | 7 +++--- Moose Development/Moose/Ops/EasyGCICAP.lua | 7 +++--- Moose Development/Moose/Utilities/Utils.lua | 28 ++++++++++++++++++--- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyA2G.lua b/Moose Development/Moose/Ops/EasyA2G.lua index 1e97ea5c4..96f3e6a0d 100644 --- a/Moose Development/Moose/Ops/EasyA2G.lua +++ b/Moose Development/Moose/Ops/EasyA2G.lua @@ -319,6 +319,10 @@ function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName) -- defaults self.alias = Alias or AirbaseName.." A2G Wing" + + -- Set some string id for output to DCS.log file. + self.lid=string.format("EASYA2G %s | ", self.alias) + self.coalitionname = string.lower(Coalition) or "blue" self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED self.wings = {} @@ -354,9 +358,6 @@ function EASYA2G:New(Alias, AirbaseName, Coalition, ScoutName) self.EngageTargetTypes = {"Ground"} self:SetDefaultTurnoverTime() - -- 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") diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 2c557773a..a1433f052 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -312,6 +312,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) -- defaults self.alias = Alias or AirbaseName.." CAP Wing" + + -- Set some string id for output to DCS.log file. + self.lid=string.format("EASYGCICAP %s | ", self.alias) + self.coalitionname = string.lower(Coalition) or "blue" self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED self.wings = {} @@ -347,9 +351,6 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.EngageTargetTypes = {"Air"} self:SetDefaultTurnoverTime() - -- Set some string id for output to DCS.log file. - self.lid=string.format("EASYGCICAP %s | ", self.alias) - -- Add FSM transitions. -- From State --> Event --> To State self:SetStartState("Stopped") diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f5694b295..53f5e8e04 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2923,16 +2923,27 @@ end -- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position. -- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke. -- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5. +-- @param #boolean Resurrection If true, dead units can be restored on load. Defaults to false. +-- @param #number ResurrectPercentage Use this percentage of probability to resurrect a unit. [0..100], defaults to 25. +-- @param #number Healmin If set, life points of a resurrected unit will be randomly restored to min this percentage. [0..100], defaults to 25. +-- @param #number Healmax If set, life points of a resurrected unit will be randomly restored to max this percentage. [0..100], defaults to 75. -- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read. -- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` -function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density) - +function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density,Resurrection,ResurrectPercentage,Healmin,Healmax) + + local healmin = Healmin or 25 + local healmax = Healmax or 75 + local resurrection = (Resurrection == true) and true or false + local resurrectpercentage = ResurrectPercentage or 25 + local fires = {} + --- + -- @param Core.Point#COORDINATE coord local function Smokers(name,coord,effect,density) local eff = math.random(8) if type(effect) == "number" then eff = effect end - coord:BigSmokeAndFire(eff,density,name) + coord:BigSmokeAndFire( eff, Density, 300, 1, name ) table.insert(fires,name) end @@ -2947,7 +2958,16 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema local name = _unit:GetName() Smokers(name,coordinate,Effect,Density) end - _unit:Destroy(false) + -- TODO Resurrection logic + local resurectok = math.random(1,100) + BASE:E(string.format("Load Group | Resurrection | Resurrect %s | Thresh %d | Random %d",tostring(resurrection),resurrectpercentage,resurectok)) + if resurrection == true and (resurectok < resurrectpercentage) then + local heallife = math.random(healmin,healmax) + BASE:E("Load Group | Resurrection | Life "..heallife) + _unit:SetLife(heallife) + else + _unit:Destroy(false) + end reduced = reduced + 1 if reduced == anzahl then break end end From 802c2065ee2c667a00e9f4da56d1b544c3c6ba61 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 25 Jan 2026 13:15:32 +0100 Subject: [PATCH 318/349] xx --- Moose Development/Moose/Functional/Mantis.lua | 86 ++++++----- Moose Development/Moose/Functional/Sead.lua | 15 +- Moose Development/Moose/Functional/Shorad.lua | 145 +++++++++++------- 3 files changed, 140 insertions(+), 106 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index a737c8fd6..05b8aca46 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -283,7 +283,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", - version = "0.9.43", + version = "0.9.44", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -400,10 +400,10 @@ MANTIS.SamData = { ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, ["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, -- units from HDS Mod, multi launcher options is tricky - ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, - ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=3 }, - ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, - ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, + ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B", ARMCapacity=4}, + ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 }, + ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1", ARMCapacity=16}, + ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2", ARMCapacity=18}, ["SA-21"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="92N6E"}, ["S-300VM"] = { Range=200, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4}, ["S-300V4"] = { Range=380, Blindspot=5, Height=30, Type="Long" , Radar="9S32M", ARMCapacity=4}, @@ -414,9 +414,9 @@ MANTIS.SamData = { ["Nike"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" }, ["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" }, -- CH Added to DCS core 2.9.19.x - ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" }, + ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true", ARMCapacity=3 }, ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=4 }, - ["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, + ["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM", ARMCapacity=12 }, -- 4 per starter, usually 3 starters in a battery } --- SAM data HDS @@ -432,13 +432,13 @@ MANTIS.SamDataHDS = { -- group name MUST contain HDS to ID launcher type correctly! ["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" }, ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" }, - ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD + ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln", ARMCapacity=8}, -- V55RUD ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln", ARMCapacity=3}, -- V55RUD - ["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " }, - ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"}, - ["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, - ["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" }, - ["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" }, + ["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17", ARMCapacity=4 }, + ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l", ARMCapacity=12}, + ["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l", ARMCapacity=12}, + ["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME", ARMCapacity=14 }, + ["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME", ARMCapacity=14 }, ["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher ["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher @@ -483,20 +483,20 @@ MANTIS.SamDataCH = { -- group name MUST contain CHM to ID launcher type correctly! ["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" }, ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true", ARMCapacity=3 }, - ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, + ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2", ARMCapacity=4 }, ["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" }, ["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, ["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, ["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, - ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, - ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true" }, - ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true" }, + ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true", ARMCapacity=3 }, + ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true", ARMCapacity=4 }, + ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true", ARMCapacity=4 }, ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, ["PGZ-09 CHM"] = { Range=4, Blindspot=0.5, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" }, - ["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, - ["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" }, + ["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100", ARMCapacity=20 }, + ["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D", ARMCapacity=20 }, ["LAV-AD CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_LAVAD" }, ["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" }, ["PGZ-95 CHM"] = { Range=2.5, Blindspot=0.5, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" }, @@ -508,8 +508,8 @@ MANTIS.SamDataCH = { ["Skynex CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" }, ["Skyshield CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" }, ["WieselOzelot CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" }, - ["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" }, - ["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" }, + ["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M", ARMCapacity=20 }, + ["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA", ARMCapacity=20 }, ["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" }, ["Stormer CHM"] = { Range=7.5, Blindspot=0.3, Height=7, Type="Short", Radar="CH_StormerHVM" }, ["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" }, @@ -1809,6 +1809,7 @@ do local group = GROUP:FindByName(grpname) -- Wrapper.Group#GROUP local units = group:GetUnits() local SAMData = self.SamData + local ARMCapacity if mod then SAMData = self.SamDataHDS elseif sma then @@ -1816,22 +1817,23 @@ do elseif chm then SAMData = self.SamDataCH end - --self:I("Looking to auto-match for "..grpname) + self:T("Looking to auto-match for "..grpname) for _,_unit in pairs(units) do local unit = _unit -- Wrapper.Unit#UNIT - local type = string.lower(unit:GetTypeName()) - --self:I(string.format("Matching typename: %s",type)) + local typename = string.lower(unit:GetTypeName()) + self:T(string.format("Matching typename: %s",typename)) for idx,entry in pairs(SAMData) do local _entry = entry -- #MANTIS.SamData local _radar = string.lower(_entry.Radar) - --self:I(string.format("Trying typename: %s",_radar)) - if string.find(type,_radar,1,true) then + self:T(string.format("Trying typename: %s",_radar)) + if string.find(typename,_radar,1,true) then type = _entry.Type radiusscale = self.radiusscale[type] range = _entry.Range * 1000 * radiusscale -- max firing range used as switch-on height = _entry.Height * 1000 -- max firing height - blind = _entry.Blindspot * 100 -- blind spot range - --self:I(string.format("Match: %s - %s",_radar,type)) + blind = _entry.Blindspot * 100 -- blind spot range + ARMCapacity = _entry.ARMCapacity + self:T(string.format("Match: %s - %s",_radar,type)) found = true break end @@ -1852,7 +1854,7 @@ do if not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end - return range, height, type, blind + return range, height, type, blind, ARMCapacity end --- [Internal] Function to get SAM firing data @@ -1911,7 +1913,7 @@ do end --- Tertiary filter if not found if (not found) or HDSmod or SMAMod or CHMod then - range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod) + range, height, type, blind, ARMCapacity = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod) elseif not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end @@ -1954,6 +1956,7 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange,grpheight,type,blind,ARMCapacity = self:_GetSAMRange(grpname) + if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) --table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG then @@ -2021,9 +2024,12 @@ do local grprange, grpheight,type,blind, ARMCapacity = self:_GetSAMRange(grpname) -- TODO the below might stop working at some point after some hours, needs testing --local radaralive = group:IsSAM() + if ARMCapacity and ARMCapacity>0 then _group:SetProperty("ARMCapacity",ARMCapacity) end local radaralive = true table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type, ARMCapacity}) -- make the table lighter, as I don't really use the zone here - table.insert( SEAD_Grps, grpname ) + if type ~= MANTIS.SamType.POINT then + table.insert( SEAD_Grps, grpname ) + end if type == MANTIS.SamType.LONG and radaralive then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) self:T({grpname,grprange, grpheight}) @@ -2110,7 +2116,7 @@ do -- @param #MANTIS self -- @param Wrapper.Group#GROUP targetGroup -- @param #string targetName --- @param Wrapper.Group#GROUPattackerGroup +-- @param Wrapper.Group#GROUP attackerGroup -- @param #string weaponName -- @param Wrapper.Weapon#WEAPON weaponWrapper -- @param #number tti @@ -2129,12 +2135,14 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea ---------------------------------------------------------------- -- LOOK UP ARM CAPACITY FOR THIS SAM ---------------------------------------------------------------- - local armcap = nil - - for _, sam in pairs(self.SAM_Table or {}) do - if sam[1] == targetName then - armcap = sam[7] -- ARMCapacity - break + local armcap = targetGroup:GetProperty("ARMCapacity") + + if not armcap then + for _, sam in pairs(self.SAM_Table or {}) do + if sam[1] == targetName then + armcap = sam[7] -- ARMCapacity + break + end end end @@ -2228,6 +2236,7 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then activeshorad = true end + if samgroup:GetProperty("SHORAD_ACTIVE") == true and activeshorad == false then activeshorad = true end if IsInZone and (not suppressed) and (not activeshorad) then --check any target in zone and not currently managed by SEAD if samgroup:IsAlive() then -- switch on SAM @@ -2460,6 +2469,7 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea self.ShoradLink = true self.Shorad.Groupset=self.ShoradGroupSet self.Shorad.debug = self.debug + self.Shorad:AddCallBack(self) end if self.shootandscoot and self.SkateZones and self.Shorad then self.Shorad:AddScootZones(self.SkateZones,self.SkateNumber or 3,self.ScootRandom,self.ScootFormation) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 88e94fa59..f7227bf2e 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -157,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) self:AddTransition("*", "ManageEvasion", "*") self:AddTransition("*", "CalculateHitZone", "*") - self:I("*** SEAD - Started Version 0.4.10") + self:I("*** SEAD - Started Version 0.4.11") return self end @@ -442,19 +442,12 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP local SuppressionStartTime = timer.getTime() + delay local SuppressionEndTime = timer.getTime() + delay + _tti + self.Padding + delay local _targetgroupname = _targetgroup:GetName() - if not self.SuppressedGroups[_targetgroupname] then + local shoradactive = _targetgroup:GetProperty("SHORAD_ACTIVE") + if not self.SuppressedGroups[_targetgroupname] and shoradactive ~= true then -- TODO: ask callback if suppression is allowed BEFORE scheduling timers local allow = true if self.UseCallBack and self.CallBack and self.CallBack.SeadAllowSuppression then - allow = self.CallBack:SeadAllowSuppression( - _targetgroup, - _targetgroupname, - SEADGroup, - SEADWeaponName, - Weapon, - _tti, - delay - ) + allow = self.CallBack:SeadAllowSuppression(_targetgroup,_targetgroupname,SEADGroup,SEADWeaponName,Weapon,_tti,delay) end if not allow then self:T(string.format("*** SEAD - %s | Suppression vetoed by callback", _targetgroupname)) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 9b8ff7428..1b7475baa 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -21,7 +21,7 @@ -- @image Functional.Shorad.jpg -- -- Date: Nov 2021 --- Last Update: Jan 2025 +-- Last Update: Jan 2026 ------------------------------------------------------------------------- --- **SHORAD** class, extends Core.Base#BASE @@ -196,7 +196,7 @@ do self.SmokeDecoyColor = SmokeDecoyColor or SMOKECOLOR.White end - self:I("*** SHORAD - Started Version 0.3.5") + self:I("*** SHORAD - Started Version 0.3.6") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() @@ -465,6 +465,17 @@ do return returnname end + --- Set an object to call back when going evasive. + -- @param #SHORAD self + -- @param #table Object The object to call. + -- @return #SHORAD self + function SHORAD:AddCallBack(Object) + self:T({Class=Object.ClassName}) + self.CallBack = Object + self.UseCallBack = true + return self + end + --- Smoke a SHORAD Group -- @param #SHORAD self -- @param Wrapper.Group#GROUP Group The Shorad Group to Smoke @@ -524,7 +535,56 @@ do -- mymantis:Start() function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt) self:T(self.lid .. " WakeUpShorad") - self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) + --self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) + + local TDiff = 4 + + -- local function to switch off shorad again + local function SleepShorad(group) + if group and group:IsAlive() then + local groupname = group:GetName() + self.ActiveGroups[groupname] = nil + if self.UseEmOnOff then + group:EnableEmission(false) + else + group:OptionAlarmStateGreen() + end + group:SetProperty("SHORAD_ACTIVE",false) + local text = string.format("Sleeping SHORAD %s", group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + --Shoot and Scoot + if self.shootandscoot then + self:__ShootAndScoot(1,group) + else + --group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true) + end + end + end + + local function WakeUp(_group,groupname) + -- shot at a group we protect + local text = string.format("Waking up SHORAD %s", _group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + if self.UseEmOnOff then + _group:EnableEmission(true) + end + _group:OptionAlarmStateRed() + _group:SetProperty("SHORAD_ACTIVE",true) + self:_SmokeUnits(_group) + if self.ActiveGroups[groupname] == nil then -- no timer yet for this group + self.ActiveGroups[groupname] = { Timing = ActiveTimer } + local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit + self.ActiveGroups[groupname].Timer = TIMER:New(SleepShorad,_group):Start(endtime) + --Shoot and Scoot + if self.shootandscoot then + self:__ShootAndScoot(TDiff,_group) + TDiff=TDiff+1 + end + end + end + local targetcat = TargetCat or Object.Category.UNIT local targetgroup = TargetGroup local targetvec2 = nil @@ -541,70 +601,41 @@ do local groupset = self.Groupset --Core.Set#SET_GROUP local shoradset = groupset:GetAliveSet() --#table - -- local function to switch off shorad again - local function SleepShorad(group) - if group and group:IsAlive() then - local groupname = group:GetName() - self.ActiveGroups[groupname] = nil - if self.UseEmOnOff then - group:EnableEmission(false) - else - group:OptionAlarmStateGreen() - end - local text = string.format("Sleeping SHORAD %s", group:GetName()) - self:T(text) - local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) - --Shoot and Scoot - if self.shootandscoot then - self:__ShootAndScoot(1,group) - end - end - end - -- go through set and find the one(s) to activate - local TDiff = 4 + for _,_group in pairs (shoradset) do local groupname = _group:GetName() if groupname == TargetGroup and ShotAt==true then -- Shot at a SHORAD group - if self.UseEmOnOff then - _group:EnableEmission(false) + local allow = false + if self.CallBack and self.UseCallBack == true then + allow = self.CallBack:SeadAllowSuppression(_group,groupname) end - _group:OptionAlarmStateGreen() - self.ActiveGroups[groupname] = nil - local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName()) - self:T(text) - local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) - self:_SmokeUnits(_group) - --Shoot and Scoot - if self.shootandscoot then - self:__ShootAndScoot(1,_group) - end - - elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then - -- shot at a group we protect - local text = string.format("Waking up SHORAD %s", _group:GetName()) - self:T(text) - local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) - if self.UseEmOnOff then - _group:EnableEmission(true) - end - _group:OptionAlarmStateRed() - self:_SmokeUnits(_group) - if self.ActiveGroups[groupname] == nil then -- no timer yet for this group - self.ActiveGroups[groupname] = { Timing = ActiveTimer } - local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit - self.ActiveGroups[groupname].Timer = TIMER:New(SleepShorad,_group):Start(endtime) + if allow == true then + if self.UseEmOnOff then + _group:EnableEmission(false) + end + _group:OptionAlarmStateGreen() + self.ActiveGroups[groupname] = nil + local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + self:_SmokeUnits(_group) --Shoot and Scoot if self.shootandscoot then - self:__ShootAndScoot(TDiff,_group) - TDiff=TDiff+1 + self:__ShootAndScoot(1,_group) + else + _group:RelocateGroundRandomInRadius(30,500,false,true,"Diamond",true) end + else + WakeUp(_group,groupname) end - end - end + elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then + WakeUp(_group,groupname) + end -- end if + end -- end in pairs return self end @@ -720,7 +751,7 @@ do -- @param Core.Event#EVENTDATA EventData The event details table data set -- @return #SHORAD self function SHORAD:HandleEventShot( EventData ) - self:T( { EventData } ) + --self:T( { EventData.id } ) self:T(self.lid .. " HandleEventShot") local ShootingWeapon = EventData.Weapon -- Identify the weapon fired local ShootingWeaponName = EventData.WeaponName -- return weapon type @@ -745,7 +776,7 @@ do -- Is there target data? if not targetdata or self.debug then if string.find(ShootingWeaponName,"AGM_88",1,true) then - self:I("**** Tracking AGM-88 with no target data.") + self:T("**** Tracking AGM-88 with no target data.") local pos0 = EventData.IniUnit:GetCoordinate() local fheight = EventData.IniUnit:GetHeight() self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup) From d8dc953252b623f134fdba71771481cb073fae30 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 Feb 2026 12:20:34 +0100 Subject: [PATCH 319/349] xx --- Moose Development/Moose/Core/Event.lua | 15 +- Moose Development/Moose/Core/Point.lua | 19 -- .../Moose/Functional/Warehouse.lua | 233 +++++++----------- .../Moose/Functional/ZoneGoalCargo.lua | 2 +- Moose Development/Moose/Ops/OpsTransport.lua | 2 +- 5 files changed, 106 insertions(+), 165 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index aa72e9766..9b3d96f15 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -184,6 +184,7 @@ EVENT = { ClassName = "EVENT", ClassID = 0, MissionEnd = false, + CreateMarkCoordinateOnEvent = false, } world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000 @@ -1345,7 +1346,9 @@ function EVENT:onEvent( Event ) Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = ( Event.IniDCSUnit and Event.IniDCSUnit.getName ) and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000) Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator ) + local ID = (Event.IniDCSUnit and Event.IniDCSUnit.getID) and Event.IniDCSUnit:getID() or Event.IniDCSUnitName + Event.IniUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil + --Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator ) Event.IniCategory = (Event.IniDCSUnit and Event.IniDCSUnit.getDesc ) and Event.IniDCSUnit:getDesc().category Event.IniTypeName = (Event.initiator and Event.initiator.isExist and Event.initiator:isExist() and Event.IniDCSUnit and Event.IniDCSUnit.getTypeName) and Event.IniDCSUnit:getTypeName() or "SCENERY" @@ -1449,11 +1452,11 @@ function EVENT:onEvent( Event ) Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit:getName() or nil if Event.TgtDCSUnitName~=nil then Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) + local ID = (Event.TgtDCSUnit and Event.TgtDCSUnit.getID) and Event.TgtDCSUnit:getID() or Event.TgtDCSUnitName + --Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) + Event.TgtUnit = (_SCENERY ~= nil) and _SCENERY[ID] or nil Event.TgtCategory = Event.TgtDCSUnit:getDesc().category Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - --self:I("Event Registered for Scenery Object ".. Event.TgtDCSUnitName) - --UTILS.PrintTableToLog(Event.TgtUnit) end end end @@ -1492,7 +1495,9 @@ function EVENT:onEvent( Event ) if Event.idx then Event.MarkID=Event.idx Event.MarkVec3=Event.pos - Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) + if self.CreateMarkCoordinateOnEvent == true then + Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) + end Event.MarkText=Event.text Event.MarkCoalition=Event.coalition Event.IniCoalition=Event.coalition diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index ee2b0c6ce..8d4a3189c 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -3725,25 +3725,6 @@ do -- COORDINATE local ModeA2A = nil - --[[ - if Task then - if Task:IsInstanceOf( TASK_A2A ) then - ModeA2A = true - else - if Task:IsInstanceOf( TASK_A2G ) then - ModeA2A = false - else - if Task:IsInstanceOf( TASK_CARGO ) then - ModeA2A = false - end - if Task:IsInstanceOf( TASK_CAPTURE_ZONE ) then - ModeA2A = false - end - end - end - end - --]] - if ModeA2A == nil then local IsAir = Controllable and ( Controllable:IsAirPlane() or Controllable:IsHelicopter() ) or false if IsAir then diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 6db991fd6..be917fb0f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4605,6 +4605,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end +function WAREHOUSE:_CallbackLoaded() + MESSAGE:New("TaskEmbarking",30,"Task"):ToAll() +end + +function WAREHOUSE:_CallbackUnloaded() + MESSAGE:New("TaskDisembarking",30,"Task"):ToAll() +end + --- On after "RequestSpawned" event. Initiates the transport of the assets to the requesting warehouse. -- @param #WAREHOUSE self -- @param #string From From state. @@ -4616,7 +4624,7 @@ end function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet, TransportGroupSet) -- General type and category. - local _cargotype=Request.cargoattribute --#WAREHOUSE.Attribute + local _cargotype=Request.cargoattribute --#WAREHOUSE.Attribute local _cargocategory=Request.cargocategory --DCS#Group.Category -- Add groups to pending item. @@ -4712,57 +4720,35 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet 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 + local CargoTransport --Ops.Auftrag#AUFTRAG - if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE or Request.transporttype==WAREHOUSE.TransportType.HELICOPTER 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())) + local PickupZone = ZONE_AIRBASE:New(self.airbase:GetName()) + local DeployZone = ZONE_AIRBASE:New(Request.airbase:GetName()) + + local DropoffCoordinate = DeployZone:GetRandomCoordinate(50,200,{land.SurfaceType.LAND, land.SurfaceType.ROAD}) + local PickupCoordinate = PickupZone:GetRandomCoordinate(50,200,{land.SurfaceType.LAND, land.SurfaceType.ROAD}) + + local Auftrag = AUFTRAG:NewTROOPTRANSPORT(CargoGroupSet,DropoffCoordinate,PickupCoordinate,500) - -- 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())) - - 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) + CargoGroupSet:SetProperty("WAREHOUSE_REQUEST",Request) + for _,_Group in pairs (TransportGroupSet.Set) do + local FlightGroup = FLIGHTGROUP:New(_Group) + FlightGroup:AddMission(Auftrag) + Auftrag:AddOpsGroup(FlightGroup) + FlightGroup:SetHomebase(self.airbase) + end + CargoTransport=Auftrag + elseif Request.transporttype==WAREHOUSE.TransportType.APC then -- Pickup and deploy zones. @@ -4824,8 +4810,6 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet 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! @@ -4851,107 +4835,77 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Dispatcher Event Functions -- -------------------------------- - --- Function called after carrier picked up something. - function CargoTransport:OnAfterPickedUp(From, Event, To, Carrier, PickupZone) - + --- Function called when cargo is loading. + function CargoTransport:OnBeforeExecuting(From,Event,To) + -- Cargo loaded -- 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) - + local opsgroups = CargoTransport:GetOpsGroups() + + for _,_group in pairs(opsgroups) do + + local Carrier = _group:GetGroup() + local Element = Carrier:GetUnit(1) + local warehouse=Carrier:GetProperty("WAREHOUSE") --#WAREHOUSE + + for _,Cargo in pairs(CargoGroupSet.Set) do + + -- Debug message. + local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone", Carrier:GetName(), Cargo:GetName(), Element.UnitName) + warehouse:T(warehouse.lid..text) + + -- Get cargo group object. + local group=Cargo --Wrapper.Group#GROUP + + -- Get request. + local request = CargoGroupSet:GetProperty("WAREHOUSE_REQUEST") + + -- Add cargo group to this carrier. + table.insert(request.carriercargo[Element.UnitName], warehouse:_GetNameWithOut(Cargo:GetName())) + + end + end 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 - - --- Function called when a carrier unit has loaded a cargo group. - function CargoTransport:OnAfterLoaded(From, Event, To, Carrier, Cargo, CarrierUnit, PickupZone) - - -- 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) - - -- Get cargo group object. - local group=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())) - - end - + --- Function called when cargo has arrived and was unloaded. - function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo, CarrierUnit, DeployZone) - + function CargoTransport:OnBeforeDone(From,Event,To) + + -- Cargo unloading -- Get warehouse state. - local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + local opsgroups = CargoTransport:GetOpsGroups() + + for _,_group in pairs(opsgroups) do + + local Carrier = _group:GetGroup() + local Element = Carrier:GetUnit(1) + local warehouse=Carrier:GetProperty("WAREHOUSE") --#WAREHOUSE + + for _,Cargo in pairs(CargoGroupSet.Set) do - -- Get group obejet. - local group=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())) - warehouse:T(warehouse.lid..text) - - -- Load the cargo in the warehouse. - --Cargo:Load(warehouse.warehouse) - - -- Trigger Arrived event. - warehouse:Arrived(group) + -- Debug message. + local text=string.format("Cargo group %s was unloaded from carrier unit", tostring(Element:GetName())) + warehouse:T(warehouse.lid..text) + + -- Trigger Arrived event. + warehouse:Arrived(Cargo) + + end + + function _group:OnAfterLanded(From,Event,To,airbase) + local mybase = warehouse.airbasename + if airbase:GetName() == mybase then + -- 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) + end + end + + end end - --- 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) - - end - - -- Start dispatcher. - CargoTransport:__Start(5) - end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -5453,7 +5407,8 @@ function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) end -- Set warehouse state. - group:SetState(group, "WAREHOUSE", self) + --group:SetState(group, "WAREHOUSE", self) + group:SetProperty("WAREHOUSE",self) -- Check if all assets groups are spawned and trigger events. local n=0 diff --git a/Moose Development/Moose/Functional/ZoneGoalCargo.lua b/Moose Development/Moose/Functional/ZoneGoalCargo.lua index 10b91e12f..367202261 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCargo.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCargo.lua @@ -23,7 +23,7 @@ do -- ZoneGoal - -- @type ZONE_GOAL_CARGO + --- @type ZONE_GOAL_CARGO -- @extends Functional.ZoneGoal#ZONE_GOAL diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index f5c8e84f2..4a280a9e4 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -511,7 +511,7 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. - --TODO: Psydofunctions + --TODO: Pseudofunctions -- Call status update. self:__StatusUpdate(-1) From 486891d86197173ac2eeb482026e9889b84c5f5f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 6 Feb 2026 16:25:42 +0100 Subject: [PATCH 320/349] xx --- Moose Development/Moose/DCS.lua | 3 ++ Moose Development/Moose/Ops/CTLD.lua | 1 - .../Moose/Wrapper/Controllable.lua | 40 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index ca7ec0a95..cebedcfd6 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -1672,6 +1672,9 @@ do -- AI -- @field OPTION_RADIO_USAGE_KILL -- @field JETT_TANKS_IF_EMPTY -- @field FORCED_ATTACK + -- @field PREFER_VERTICAL + -- @field ALLOW_FORMATION_SIDE_SWAP + -- @field AI_RUNWAY_LINE_UP --- -- @type AI.Option.Air.val diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 2809c7048..9f43c8ef1 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -5053,7 +5053,6 @@ end --- (Internal) Housekeeping - Cleanup crates when build -- @param #CTLD self --- -- @param #table Crates Table of #CTLD_CARGO objects near the unit. -- @param #CTLD.Buildable Build Table build object. -- @param #number Number Number of objects in Crates (found) to limit search. diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index e9e629a0b..dc8582ec6 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3658,6 +3658,46 @@ function CONTROLLABLE:OptionPreferVerticalLanding() return nil end +--- Air - Allow formation side swap +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAllowFormationSideSwap() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ALLOW_FORMATION_SIDE_SWAP, true ) + end + + return self + end + + return nil +end + +--- Air - Allow formation takeoff, if enough space +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAIRunwayLineUp() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( 37, true ) + end + + return self + end + + return nil +end + --- Can the CONTROLLABLE evade on enemy fire? -- @param #CONTROLLABLE self -- @return #boolean From 5a9277de90e923f5066eda8099cb47e3c03e0b80 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 8 Feb 2026 12:08:00 +0100 Subject: [PATCH 321/349] xx --- .../Moose/Functional/Warehouse.lua | 277 ++++++------------ Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 11 +- Moose Development/Moose/Ops/OpsTransport.lua | 8 +- 4 files changed, 99 insertions(+), 199 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index be917fb0f..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. @@ -4605,14 +4589,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end -function WAREHOUSE:_CallbackLoaded() - MESSAGE:New("TaskEmbarking",30,"Task"):ToAll() -end - -function WAREHOUSE:_CallbackUnloaded() - MESSAGE:New("TaskDisembarking",30,"Task"):ToAll() -end - --- On after "RequestSpawned" event. Initiates the transport of the assets to the requesting warehouse. -- @param #WAREHOUSE self -- @param #string From From state. @@ -4624,7 +4600,7 @@ end function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet, TransportGroupSet) -- General type and category. - local _cargotype=Request.cargoattribute --#WAREHOUSE.Attribute + local _cargotype=Request.cargoattribute --#WAREHOUSE.Attribute local _cargocategory=Request.cargocategory --DCS#Group.Category -- Add groups to pending item. @@ -4705,112 +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 -- + ------------------------- + + -- OPSTRANSPORT + local CargoTransport --Ops.OpsTransport#OPSTRANSPORT if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then - _boardradius=5000 + + 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 - --_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 - - ------------------------ - -- Create Dispatchers -- - ------------------------ - - -- Cargo dispatcher. - local CargoTransport --Ops.Auftrag#AUFTRAG - - if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE or Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - - -- Pickup and deploy zones. - local PickupZone = ZONE_AIRBASE:New(self.airbase:GetName()) - local DeployZone = ZONE_AIRBASE:New(Request.airbase:GetName()) - local DropoffCoordinate = DeployZone:GetRandomCoordinate(50,200,{land.SurfaceType.LAND, land.SurfaceType.ROAD}) - local PickupCoordinate = PickupZone:GetRandomCoordinate(50,200,{land.SurfaceType.LAND, land.SurfaceType.ROAD}) - - local Auftrag = AUFTRAG:NewTROOPTRANSPORT(CargoGroupSet,DropoffCoordinate,PickupCoordinate,500) + CargoTransport = OPSTRANSPORT:New(CargoGroupSet, self.spawnzone, Request.warehouse.spawnzone) - CargoGroupSet:SetProperty("WAREHOUSE_REQUEST",Request) - - for _,_Group in pairs (TransportGroupSet.Set) do - local FlightGroup = FLIGHTGROUP:New(_Group) - FlightGroup:AddMission(Auftrag) - Auftrag:AddOpsGroup(FlightGroup) - FlightGroup:SetHomebase(self.airbase) - end - CargoTransport=Auftrag - 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 - -- Adjust carrier units. This has to come AFTER the dispatchers have been defined because they set the cargobay free weight! Request.carriercargo={} @@ -4831,79 +4743,61 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet end end - -------------------------------- - -- Dispatcher Event Functions -- - -------------------------------- + ---------------------------------- + -- Opstransport Event Functions -- + ---------------------------------- + + CargoTransport.warehouse = self + + --- Function called when a carrier unit has loaded a cargo group. + function CargoTransport:OnAfterLoaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier, CarrierElement) - --- Function called when cargo is loading. - function CargoTransport:OnBeforeExecuting(From,Event,To) - -- Cargo loaded -- Get warehouse state. - local opsgroups = CargoTransport:GetOpsGroups() - - for _,_group in pairs(opsgroups) do - - local Carrier = _group:GetGroup() - local Element = Carrier:GetUnit(1) - local warehouse=Carrier:GetProperty("WAREHOUSE") --#WAREHOUSE - - for _,Cargo in pairs(CargoGroupSet.Set) do - - -- Debug message. - local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone", Carrier:GetName(), Cargo:GetName(), Element.UnitName) - warehouse:T(warehouse.lid..text) - - -- Get cargo group object. - local group=Cargo --Wrapper.Group#GROUP - - -- Get request. - local request = CargoGroupSet:GetProperty("WAREHOUSE_REQUEST") - - -- Add cargo group to this carrier. - table.insert(request.carriercargo[Element.UnitName], warehouse:_GetNameWithOut(Cargo:GetName())) - - end - end + local warehouse=CargoTransport.warehouse --#WAREHOUSE + + -- Get cargo group object. + 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[CarrierElement.name], warehouse:_GetNameWithOut(group:GetName())) + + end + + --- Function called when cargo has arrived and was unloaded. + function CargoTransport:OnAfterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier) + + -- Get warehouse state. + local warehouse=CargoTransport.warehouse --Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + + -- Get group obejet. + local group=OpsGroupCargo:GetGroup() --Cargo:GetObject() --Wrapper.Group#GROUP + + -- Debug message. + local text=string.format("Cargo group %s was unloaded from carrier group %s.", tostring(group:GetName()), tostring(OpsGroupCarrier:GetName())) + warehouse:T(warehouse.lid..text) + + -- Trigger Arrived event. + warehouse:Arrived(group) end - --- Function called when cargo has arrived and was unloaded. - function CargoTransport:OnBeforeDone(From,Event,To) - - -- Cargo unloading - -- Get warehouse state. - local opsgroups = CargoTransport:GetOpsGroups() - - for _,_group in pairs(opsgroups) do - - local Carrier = _group:GetGroup() - local Element = Carrier:GetUnit(1) - local warehouse=Carrier:GetProperty("WAREHOUSE") --#WAREHOUSE - - for _,Cargo in pairs(CargoGroupSet.Set) do + -- TODO: Probably can also add some cargo/carrier dead functions here to simplify things at other places - -- Debug message. - local text=string.format("Cargo group %s was unloaded from carrier unit", tostring(Element:GetName())) - warehouse:T(warehouse.lid..text) - - -- Trigger Arrived event. - warehouse:Arrived(Cargo) - - end - - function _group:OnAfterLanded(From,Event,To,airbase) - local mybase = warehouse.airbasename - if airbase:GetName() == mybase then - -- 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) - end - end - + + -- 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 end @@ -5407,8 +5301,7 @@ function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) end -- Set warehouse state. - --group:SetState(group, "WAREHOUSE", self) - group:SetProperty("WAREHOUSE",self) + group:SetState(group, "WAREHOUSE", self) -- Check if all assets groups are spawned and trigger events. local n=0 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 4a280a9e4..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 @@ -511,7 +511,7 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. - --TODO: Pseudofunctions + --TODO: Psydofunctions -- Call status update. self:__StatusUpdate(-1) @@ -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 9e2a57a935a92f888acc899fb4ce8c4d9e518703 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 8 Feb 2026 14:17:16 +0100 Subject: [PATCH 322/349] xx --- Moose Development/Moose/Wrapper/Controllable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 379412b8c..d575a8c3f 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -2184,8 +2184,8 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... ) local ArgumentKey = '_' .. tostring( arg ):match( "table: (.*)" ) self:SetState( self, ArgumentKey, arg ) DCSScript[#DCSScript + 1] = "local Arguments = MissionControllable:GetState( MissionControllable, '" .. ArgumentKey .. "' ) " - --DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" - DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable, ((type(Arguments)=='table') and unpack(Arguments) or nil))" + DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" + --DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable, ((type(Arguments)=='table') and unpack(Arguments) or nil))" else DCSScript[#DCSScript + 1] = FunctionString .. "( MissionControllable )" end From e0487280e50870d9e71667f7c002203f589e9865 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 19 Feb 2026 09:22:36 +0100 Subject: [PATCH 323/349] xx --- Moose Development/Moose/Wrapper/Airbase.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index a6c597c93..26f6558e6 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -606,7 +606,7 @@ AIRBASE.Syria = { --- Airbases of the Mariana Islands map -- -- * `AIRBASE.MarianaIslands.Andersen_AFB` Andersen AFB --- * `AIRBASE.MarianaIslands.Antonio_B._Won_Pat_Intl` Antonio B. Won Pat Intl +-- * `AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl` Antonio B. Won Pat Intl -- * `AIRBASE.MarianaIslands.North_West_Field` North West Field -- * `AIRBASE.MarianaIslands.Olf_Orote` Olf Orote -- * `AIRBASE.MarianaIslands.Pagan_Airstrip` Pagan Airstrip @@ -617,7 +617,7 @@ AIRBASE.Syria = { -- @field MarianaIslands AIRBASE.MarianaIslands = { ["Andersen_AFB"] = "Andersen AFB", - ["Antonio_B._Won_Pat_Intl"] = "Antonio B. Won Pat Intl", + ["Antonio_B_Won_Pat_Intl"] = "Antonio B. Won Pat Intl", ["North_West_Field"] = "North West Field", ["Olf_Orote"] = "Olf Orote", ["Pagan_Airstrip"] = "Pagan Airstrip", From 12e71f44546f9535e8f82e4f729dda3a0dad510f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 23 Feb 2026 18:11:28 +0100 Subject: [PATCH 324/349] #MSRS - Added Hound TTS as backend #AIRBOSS - fixed an issue with `EnableSRS()` if no voice is given and no config file exists for MSRS. --- Moose Development/Moose/Ops/Airboss.lua | 5 +- Moose Development/Moose/Sound/SRS.lua | 331 +++++++++++++++--- Moose Development/Moose/Sound/SoundOutput.lua | 18 +- 3 files changed, 301 insertions(+), 53 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index bc42633a6..8b9f3d985 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3143,7 +3143,10 @@ function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volum self.SRS:SetVoice(Voice) end if (not Voice) and self.SRS and self.SRS:GetProvider() == MSRS.Provider.GOOGLE then - self.SRS.voice = MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B + self.SRS.voice = MSRS.Voices.Google.Standard.en_US_Standard_B + if MSRS.poptions and MSRS.poptions["gcloud"] and MSRS.poptions["gcloud"].voice then + self.SRS.voice = MSRS.poptions["gcloud"].voice + end end --self.SRS:SetVolume(Volume or 1.0) -- SRSQUEUE diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 3432e0f0c..ff1aeb491 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -50,8 +50,8 @@ -- @field #string ConfigFilePath Path to the standard config file. -- @field #boolean ConfigLoaded If `true` if config file was loaded. -- @field #table poptions Provider options. Each element is a data structure of type `MSRS.ProvierOptions`. --- @field #string provider Provider of TTS (win, gcloud, azure, amazon). --- @field #string backend Backend used as interface to SRS (MSRS.Backend.SRSEXE or MSRS.Backend.GRPC). +-- @field #string provider Provider of TTS (win, gcloud, azure, amazon, ...). +-- @field #string backend Backend used as interface to SRS (MSRS.Backend.SRSEXE, MSRS.Backend.HOUND or MSRS.Backend.GRPC). -- @field #boolean UsePowerShell Use PowerShell to execute the command and not cmd.exe -- @extends Core.Base#BASE @@ -67,7 +67,8 @@ -- -- * This script needs SRS version >= 1.9.6 -- * You need to de-sanitize os, io and lfs in the missionscripting.lua --- * Optional: DCS-gRPC as backend to communicate with SRS (vide infra) +-- * Optional: DCS-gRPC as backend to communicate with SRS (via infra) +-- * Optional: HOUNDTTS as backend to communicate with SRS (via infra) -- -- ## Knwon Issues -- @@ -77,7 +78,7 @@ -- expecially in VR but unavoidable (if you have a solution, please feel free to share!). -- -- NOTE that this is not an issue if the mission is running on a server. --- Also NOTE that using DCS-gRPC as backend will avoid the pop-up window. +-- Also NOTE that using DCS-gRPC and Hound-TTS as backend create no pop-up window. -- -- # Play Sound Files -- @@ -188,14 +189,31 @@ -- The default interface to SRS is via calling the 'DCS-SR-ExternalAudio.exe'. As noted above, this has the unavoidable drawback that a pop-up briefly appears -- and DCS might be put out of focus. -- +-- ## Hound TTS as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS +-- +-- An alternative interface to SRS is [Hound-TTS](https://github.com/uriba107/HoundTTS/releases). This does not call an exe file and therefore avoids the annoying pop-up window. +-- In addition to Windows and Google cloud, it also offers Piper local voice creation and others as providers for TTS. +-- +-- Use @{#MSRS.SetDefaultBackendHound} to enable [Hound-TTS](https://github.com/uriba107/HoundTTS/releases) as an alternate backend. +-- This can be useful if the popup should be avoided or to use Piper or others for TTS. Please note, only text-to-speech is supported and it it cannot be used to transmit audio files. +-- +-- Hound TTS must be installed and configured per the [Hound TTS](https://github.com/uriba107/HoundTTS#installation) prior to use. +-- If a cloud TTS provider is being used, the API key(s) must be set as per the documentation. +-- Hound TTS can be used both with DCS dedicated server and regular DCS installations. +-- +-- To use the default local Windows TTS with Hound TTS, Windows 2019 Server (or newer) or Windows 10/11 are required. Voices for non-local languages and dialects may need to +-- be explicitly installed. +-- +-- To set the MSRS class to use the Hound TTS backend for all future instances, call the function `MSRS.SetDefaultBackendHound()`. +-- -- ## DCS-gRPC as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS -- -- Another interface to SRS is [DCS-gRPC](https://github.com/DCS-gRPC/rust-server). This does not call an exe file and therefore avoids the annoying pop-up window. -- In addition to Windows and Google cloud, it also offers Microsoft Azure and Amazon Web Service as providers for TTS. -- --- Use @{#MSRS.SetDefaultBackendGRPC} to enable [DCS-gRPC](https://github.com/DCS-gRPC/rust-server) as an alternate backend for transmitting text-to-speech over SRS. --- This can be useful if 'DCS-SR-ExternalAudio.exe' cannot be used in the environment or to use Azure or AWS clouds for TTS. Note that DCS-gRPC does not (yet?) support --- all of the features and options available with 'DCS-SR-ExternalAudio.exe'. Of note, only text-to-speech is supported and it it cannot be used to transmit audio files. +-- Use @{#MSRS.SetDefaultBackendGRPC} to enable [DCS-gRPC](https://github.com/DCS-gRPC/rust-server) as an alternate backend. +-- This can be useful if 'DCS-SR-ExternalAudio.exe' cannot be used in the environment or to use Azure or AWS clouds for TTS. Note that DCS-gRPC does not support +-- all of the features and options available with 'DCS-SR-ExternalAudio.exe'. Also note, only text-to-speech is supported and it it cannot be used to transmit audio files. -- -- DCS-gRPC must be installed and configured per the [DCS-gRPC documentation](https://github.com/DCS-gRPC/rust-server) and already running via either the 'autostart' mechanism -- or a Lua call to 'GRPC.load()' prior to use of the alternate DCS-gRPC backend. If a cloud TTS provider is being used, the API key must be set via the 'Config\dcs-grpc.lua' @@ -212,20 +230,20 @@ -- Basic Play Text-To-Speech example using alternate DCS-gRPC backend (DCS-gRPC not previously started): -- -- -- Start DCS-gRPC --- GRPC.load() +-- GRPC.load() -- not needed if you set gRPC to auto-start -- -- Select the alternate DCS-gRPC backend for new MSRS instances -- MSRS.SetDefaultBackendGRPC() -- -- Create a SOUNDTEXT object. -- local text=SOUNDTEXT:New("All Enemies destroyed") -- -- MOOSE SRS --- local msrs=MSRS:New('', 305.0) +-- local msrs=MSRS:New('ExampleInstance', 305.0) -- -- Text-to speech with default voice after 30 seconds. -- msrs:PlaySoundText(text, 30) -- -- Basic example of using another class (ATIS) with SRS and the DCS-gRPC backend (DCS-gRPC not previously started): -- -- -- Start DCS-gRPC --- GRPC.load() +-- GRPC.load() -- not needed if you set gRPC to auto-start -- -- Select the alternate DCS-gRPC backend for new MSRS instances -- MSRS.SetDefaultBackendGRPC() -- -- Create new ATIS as usual @@ -262,7 +280,7 @@ MSRS = { --- MSRS class version. -- @field #string version -MSRS.version="0.3.3" +MSRS.version="0.3.4" --- Voices -- @type MSRS.Voices @@ -699,9 +717,11 @@ MSRS.Voices = { -- @type MSRS.Backend -- @field #string SRSEXE Use `DCS-SR-ExternalAudio.exe`. -- @field #string GRPC Use DCS-gRPC. +-- @field #string GRPC Use Hound-TTS. MSRS.Backend = { SRSEXE = "srsexe", GRPC = "grpc", + HOUND = "hound", } --- Text-to-speech providers. These are compatible with the DCS-gRPC conventions. @@ -710,11 +730,13 @@ MSRS.Backend = { -- @field #string GOOGLE Google (`gcloud`). -- @field #string AZURE Microsoft Azure (`azure`). Only possible with DCS-gRPC backend. -- @field #string AMAZON Amazon Web Service (`aws`). Only possible with DCS-gRPC backend. +-- @field #string PIPER Piper local voice service. Only possible with Hound-TTS backend. MSRS.Provider = { WINDOWS = "win", GOOGLE = "gcloud", AZURE = "azure", AMAZON = "aws", + PIPER = "piper", } --- Function for UUID. @@ -760,6 +782,7 @@ end -- DONE: Add google. -- DONE: Add gRPC google options -- DONE: Add loading default config file +-- DONE: Add Hound TTS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -874,6 +897,16 @@ function MSRS:SetBackendGRPC() return self end +--- Set Hound-TTS as backend to communicate with SRS. +-- @param #MSRS self +-- @return #MSRS self +function MSRS:SetBackendHound() + self:F() + self:SetBackend(MSRS.Backend.HOUND) + + return self +end + --- Set `DCS-SR-ExternalAudio.exe` as backend to communicate with SRS. -- @param #MSRS self -- @return #MSRS self @@ -896,6 +929,12 @@ function MSRS.SetDefaultBackendGRPC() MSRS.backend=MSRS.Backend.GRPC end +--- Set Hound-TTS to be the default backend. +-- @param #MSRS self +function MSRS.SetDefaultBackendHound() + MSRS.backend=MSRS.Backend.HOUND +end + --- Get currently set backend. -- @param #MSRS self -- @return #string Backend. @@ -1147,6 +1186,16 @@ function MSRS:SetVoiceGoogle(Voice) return self end +--- Set to use a specific voice if Piper is used as provider (only Hound-TTS backend). Note that this will override any gender and culture settings. +-- @param #MSRS self +-- @param #string Voice [Piper Voices](https://rhasspy.github.io/piper-samples/). Default `"en_US-ryan-low"`. +-- @return #MSRS self +function MSRS:SetVoicePiper(Voice) + self:F( {Voice=Voice} ) + self:SetVoiceProvider(Voice or "en_US-ryan-low", MSRS.Provider.PIPER) + + return self +end --- Set to use a specific voice if Microsoft Azure is use as provider (only DCS-gRPC backend). Note that this will override any gender and culture settings. -- @param #MSRS self @@ -1241,9 +1290,10 @@ end -- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default) -- - `MSRS.Provider.GOOGLE`: Google Cloud -- - `MSRS.Provider.AZURE`: Microsoft Azure (only with DCS-gRPC backend) --- - `MSRS.Provier.AMAZON`: Amazon Web Service (only with DCS-gRPC backend) +-- - `MSRS.Provider.AMAZON`: Amazon Web Service (only with DCS-gRPC backend) +-- - `MSRS.Provider.PIPER`: Piper Voices (only with Hound-TTS backend) -- --- Note that all providers except Microsoft Windows need as additonal information the credentials of your account. +-- Note that all providers except Microsoft Windows and Piper need as additonal information the credentials of your account. -- -- @param #MSRS self -- @param #string Provider @@ -1390,6 +1440,15 @@ function MSRS:SetTTSProviderAmazon() return self end +--- Use Piper to provide text-to-speech. Only supported if used in combination with Hound-TTS as backend. +-- @param #MSRS self +-- @return #MSRS self +function MSRS:SetTTSProviderPiper() + self:F() + self:SetProvider(MSRS.Provider.PIPER) + return self +end + --- Print SRS help to DCS log file. -- @param #MSRS self @@ -1472,9 +1531,12 @@ function MSRS:PlaySoundText(SoundText, Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0) else - + + -- TODO Insert HOUND option if self.backend==MSRS.Backend.GRPC then self:_DCSgRPCtts(SoundText.text, nil, SoundText.gender, SoundText.culture, SoundText.voice, SoundText.volume, SoundText.label, SoundText.coordinate) + elseif self.backend == MSRS.Backend.HOUND then + self:_HoundTextToSpeech(SoundText.text,nil,nil,SoundText.volume,SoundText.label,self.coalition,SoundText.coordinate,SoundText.Speed,SoundText.gender,SoundText.culture,SoundText.voice) else -- Get command. @@ -1498,8 +1560,9 @@ end -- @param #string Text Text message. -- @param #number Delay Delay in seconds, before the message is played. -- @param Core.Point#COORDINATE Coordinate Coordinate. +-- @param #number Speed -- @return #MSRS self -function MSRS:PlayText(Text, Delay, Coordinate) +function MSRS:PlayText(Text, Delay, Coordinate, Speed) self:F( {Text, Delay, Coordinate} ) if Delay and Delay>0 then @@ -1509,8 +1572,10 @@ function MSRS:PlayText(Text, Delay, Coordinate) if self.backend==MSRS.Backend.GRPC then self:T(self.lid.."Transmitting") self:_DCSgRPCtts(Text, nil, nil , nil, nil, nil, nil, Coordinate) + elseif self.backend==MSRS.Backend.HOUND then + self:_HoundTextToSpeech(Text,nil,nil,nil,nil,nil,Coordinate,Speed) else - self:PlayTextExt(Text, Delay, nil, nil, nil, nil, nil, nil, nil, Coordinate) + self:PlayTextExt(Text, Delay, nil, nil, nil, nil, nil, nil, nil, Coordinate, Speed) end end @@ -1530,12 +1595,13 @@ end -- @param #number Volume Volume. -- @param #string Label Label. -- @param Core.Point#COORDINATE Coordinate Coordinate. +-- @param #number Speed Speed. -- @return #MSRS self -function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate) - self:T({Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate} ) +function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate,Speed) + self:T({Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate, Speed} ) if Delay and Delay>0 then - self:ScheduleOnce(Delay, self.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate) + self:ScheduleOnce(Delay, self.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate, Speed) else Frequencies = Frequencies or self:GetFrequencies() @@ -1556,7 +1622,14 @@ function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture --BASE:I("MSRS.Backend.GRPC") self:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate) - + + elseif self.backend==MSRS.Backend.HOUND then + -- BASE:I("MSRS.Backend.HOUND") + + local UseGoogle = (self.provider == MSRS.Provider.GOOGLE) and true or nil + + self:_HoundTextToSpeech(Text,Frequencies,Modulations,Volume,Label,self.coalition,Coordinate,Speed,Gender,Culture,Voice,UseGoogle) + end end @@ -1871,7 +1944,7 @@ end function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate) -- Debug info. - self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()") + self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts") self:T({Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate}) local options = {} -- #MSRS.GRPCOptions @@ -1881,7 +1954,7 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab -- Get frequenceies. Frequencies = UTILS.EnsureTable(Frequencies, true) or self:GetFrequencies() - -- Plain text (not really used. + -- Plain text - not really used. options.plaintext=Text -- Name shows as sender. @@ -1938,6 +2011,156 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Hound-TTS Backend Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Hound TTS Function Wrapper +-- @param #MSRS self +-- @param #string Message The text to speak. +-- @param #table Frequencies The table of frequencies to use. +-- @param #table Modulations The table of modulations to use. +-- @param #number Volume The volume to use, defaults to 1.0. +-- @param #string Label The label to use, defaults to "MSRS". +-- @param #number Coalition The coalition to use. +-- @param Core.Point#COORDINATE Point (Optional) The point from which the voice is sent. +-- @param #number Speed (Optional) How fast to speak, defaults to 1.0. +-- @param #string Gender (Optional) Gender to use. +-- @param #string Culture (Optional) Culture to use. +-- @param #string Voice (Optional) Voice to use. +-- @param #boolean UseGoogle (Optional) If to use Google TTS. +-- @return SpeechTime Speech time in seconds. +function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Coalition,Point,Speed,Gender,Culture,Voice,UseGoogle) + self:I(self.lid.."_HoundTextToSpeech") + + local ffs = {} + for _,_f in pairs(Frequencies or self.frequencies) do + table.insert(ffs,string.format("%.1f",_f)) + end + + local freqs = table.concat(ffs, ",") + local modus = table.concat(Modulations or self.modulations, ",") + + local coal=Coalition or self.coalition + local gender=Gender or self.gender + local voice=Voice or self:GetVoice(self.provider) or self.voice + local culture=Culture or self.culture + local volume=Volume or self.volume or 1.0 + local speed=Speed or self.speed or 1.0 + local label=Label or self.Label or "MSRS" + local coordinate=Point or self.coordinate + local point = (coordinate ~= nil) and coordinate:GetVec3() or nil + local port = self.port or 5002 + + -- Replace modulation + modus=modus:gsub("0", "AM") + modus=modus:gsub("1", "FM") + + self:I({T=Message,F=freqs,M=modus,V=voice,Vx=volume,L=label,C=coal,GGL=tostring(UseGoogle)}) + + if (UseGoogle ~= true) and self.provider == MSRS.Provider.GOOGLE then + UseGoogle = true + end + + local provider = self.provider + provider=provider:gsub("gcloud", "google") + provider=provider:gsub("win", "sapi") + + local TransmissionP = { + freqs = freqs, + modulations = modus, + coalition = coal, + name = label, + point = point, + volume = volume, + port = port, + } + local ProviderP = { + provider = provider, + voice = voice, + speed = speed, + culture = culture, + gender = gender, + } + + local speechtime = HoundTTS.Transmit(Message, TransmissionP, ProviderP) + + return speechtime +end + +--- Hound Transmit Function Wrapper +-- @param #MSRS self +-- @param #string Message The message to speak +-- @param #table Transmission_params Transmission parameter table, see below +-- @param #table Provider_params Provider parameter table, see below +-- @return SpeechTime Speech time in seconds. +-- @usage +-- -- #table Transmission_params +-- | Field | Type | Default | Description | +-- | ----------- | -------- | ------------------- | ---------------------------------------- | +-- | transmitter | string | `"srs"` | Transmitter type. Currently only `"srs"` | +-- | freqs | string | `"251.0"` | Frequency in MHz, comma-separated | +-- | modulations | string | `"AM"` | `AM` or `FM`, comma-separated | +-- | coalition | number | `0` | 0=spectator, 1=red, 2=blue | +-- | name | string | `"HoundTTS"` | Client name shown in SRS | +-- | point | Vec3/nil | `nil` | DCS position for geo-location | +-- | volume | number | `1.0` | 0.0 – 1.0 | +-- | encrypt | boolean | `false` | Enable SRS encryption | +-- | encKey | number | `0` | Encryption key (0–255, must match SRS) | +-- | host | string | `HoundTTS.SRS_HOST` | SRS server IP | +-- | port | number | `HoundTTS.SRS_PORT` | SRS server port | +-- +-- -- #table Provider_params +-- | Field | Type | Default | Description | +-- | -------- | ------ | --------------------------- | ---------------------------------------------------------------------------------- | +-- | provider | string | `HoundTTS.DEFAULT_PROVIDER` | `"piper"` / `"sapi"` / `"azure"` / `"google"` / `"elevenlabs"` | +-- | voice | string | `HoundTTS.DEFAULT_VOICE` | Piper model name, SAPI voice name, Azure/Google voice name, or ElevenLabs voice ID | +-- | culture | string | `HoundTTS.DEFAULT_CULTURE` | BCP-47 locale e.g. `"en-US"`, `"en-GB"` (used by SAPI, Azure, Google) | +-- | gender | string | `HoundTTS.DEFAULT_GENDER` | `"male"` / `"female"` (used by SAPI, Google) | +-- | speed | number | `1.0` | Speech rate (0.5 = half speed, 1.0 = normal, 2.0 = double speed) | +-- +function MSRS:_HoundTransmit(Message, Transmission_params, Provider_params) + self:I(self.lid.."_HoundTransmit") + self:I({Message,Transmission_params,Provider_params}) + local speechtime = HoundTTS.Transmit(Message, Transmission_params, Provider_params) + return speechtime +end + +--- Hound Test Tone function, sends a 2-second 440 Hz sine wave tone directly over SRS, bypassing the TTS engine entirely. +-- Use this to verify the SRS connection is working before debugging TTS issues. +-- @param MSRS self +-- @param #table Frequencies The table of frequencies to use. +-- @param #table Modulations The table of modulations to use. +-- @param #number Coalition The coalition to use. +function MSRS:_HoundTestTone(Frequencies, Modulations, Coalition) + self:I(self.lid.."_HoundTestTone") + + local ffs = {} + for _,_f in pairs(Frequencies or self.frequencies) do + table.insert(ffs,string.format("%.1f",_f)) + end + + local freqs = table.concat(ffs, ",") + local modus = table.concat(Modulations or self.modulations, ",") + -- Replace modulation + modus=modus:gsub("0", "AM") + modus=modus:gsub("1", "FM") + local coal=Coalition or self.coalition + HoundTTS.TestTone(freqs, modus, coal) + return self +end + +--- Hound speech time calculator. Use to determine how long it takes to speak something out. +-- @param MSRS self +-- @param #string Message The message to measure. Can also be handed as string lenght. +-- @param #number Speed The speed to use, defaults to 1.0. +-- @param #boolean UseGoogle If to use google. Default: no. +function MSRS:_HoundSpeechTime(Message,Speed,UseGoogle) + local speed = Speed or 1.0 + local speechtime = HoundTTS.getSpeechTime(Message, speed, UseGoogle) + return speechtime +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Config File ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1957,7 +2180,7 @@ end -- MSRS_Config = { -- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio", -- Path to SRS install directory. -- Port = 5002, -- Port of SRS server. Default 5002. --- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc". +-- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc" or "hound". -- Frequency = {127, 243}, -- Default frequences. Must be a table 1..n entries! -- Modulation = {0,0}, -- Default modulations. Must be a table, 1..n entries, one for each frequency! -- Volume = 1.0, -- Default volume [0,1]. @@ -1967,7 +2190,7 @@ end -- Gender = "male", -- Voice = "Microsoft Hazel Desktop", -- Voice that is used if no explicit provider voice is specified. -- Label = "MSRS", --- Provider = "win", --Provider for generating TTS (win, gcloud, azure, aws). +-- Provider = "win", --Provider for generating TTS (win, gcloud, azure, aws, piper). -- -- Windows -- win = { -- voice = "Microsoft Hazel Desktop", @@ -2092,31 +2315,38 @@ end -- @param #boolean isGoogle We're using Google TTS function MSRS.getSpeechTime(length,speed,isGoogle) - local maxRateRatio = 3 - - speed = speed or 1.0 - isGoogle = isGoogle or false - - local speedFactor = 1.0 - if isGoogle then - speedFactor = speed + if MSRS.backend == MSRS.Backend.HOUND then + local speechtime = HoundTTS.getSpeechTime(length, speed, isGoogle) + return speechtime else - if speed ~= 0 then - speedFactor = math.abs( speed ) * (maxRateRatio - 1) / 10 + 1 + + local maxRateRatio = 3 + + speed = speed or 1.0 + isGoogle = isGoogle or false + + local speedFactor = 1.0 + if isGoogle then + speedFactor = speed + else + if speed ~= 0 then + speedFactor = math.abs( speed ) * (maxRateRatio - 1) / 10 + 1 + end + if speed < 0 then + speedFactor = 1 / speedFactor + end end - if speed < 0 then - speedFactor = 1 / speedFactor + + local wpm = math.ceil( 100 * speedFactor ) + local cps = math.floor( (wpm * 5) / 60 ) + + if type( length ) == "string" then + length = string.len( length ) end + + return length/cps --math.ceil(length/cps) + end - - local wpm = math.ceil( 100 * speedFactor ) - local cps = math.floor( (wpm * 5) / 60 ) - - if type( length ) == "string" then - length = string.len( length ) - end - - return length/cps --math.ceil(length/cps) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2168,6 +2398,7 @@ MSRSQUEUE = { -- @field #number volume Volume -- @field #string label Label to be used -- @field Core.Point#COORDINATE coordinate Coordinate for this transmission +-- @field #number speed Speed to be used --- Create a new MSRSQUEUE object for a given radio frequency/modulation. -- @param #MSRSQUEUE self @@ -2252,9 +2483,10 @@ end -- @param #number volume Volume setting -- @param #string label Label to be used -- @param Core.Point#COORDINATE coordinate Coordinate to be used +-- @param #number speed Speed to be used -- @return #MSRSQUEUE.Transmission Radio transmission table. -function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate) - self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label}) +function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate,speed) + self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label, S=speed}) if self.TransmitOnlyWithPlayers then if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then return self @@ -2294,6 +2526,7 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr transmission.volume = volume or msrs.volume transmission.label = label or msrs.Label transmission.coordinate = coordinate or msrs.coordinate + transmission.speed = speed or 1.0 -- Add transmission to queue. self:AddTransmission(transmission) @@ -2308,9 +2541,9 @@ function MSRSQUEUE:Broadcast(transmission) self:T(self.lid.."Broadcast") if transmission.frequency then - transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label, transmission.coordinate) + transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label, transmission.coordinate, transmission.speed) else - transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate) + transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate,transmission.speed) end local function texttogroup(gid) diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index c13970482..7692b63fa 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -24,7 +24,7 @@ do -- Sound Base - -- @type SOUNDBASE + --- @type SOUNDBASE -- @field #string ClassName Name of the class. -- @extends Core.Base#BASE @@ -100,7 +100,7 @@ end do -- Sound File - -- @type SOUNDFILE + --- @type SOUNDFILE -- @field #string ClassName Name of the class -- @field #string filename Name of the flag. -- @field #string path Directory path, where the sound file is located. This includes the final slash "/". @@ -300,6 +300,7 @@ do -- Text-To-Speech -- @field #string gender Gender: "male", "female". -- @field #string culture Culture, e.g. "en-GB". -- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings. + -- @field #number speed Specific speed to be used. -- @extends Core.Base#BASE @@ -363,7 +364,7 @@ do -- Text-To-Speech self:SetDuration(Duration or MSRS.getSpeechTime(Text)) --self:SetGender() --self:SetCulture() - + self:SetSpeed() -- Debug info: self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) @@ -426,4 +427,15 @@ do -- Text-To-Speech return self end + --- Set to use a specific speed. + -- @param #SOUNDTEXT self + -- @param #number Speed + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetSpeed(Speed) + + self.speed = Speed or 1.0 + + return self + end + end \ No newline at end of file From e96c26513559ac3ea6fc1bd28222ca45c1ebdd6a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 26 Feb 2026 15:39:27 +0100 Subject: [PATCH 325/349] #MSRS - some chamges for HOUND backend --- Moose Development/Moose/Ops/Awacs.lua | 8 +- Moose Development/Moose/Ops/CSAR.lua | 343 +++++++++++++++++--- Moose Development/Moose/Sound/SRS.lua | 25 +- Moose Development/Moose/Utilities/Utils.lua | 7 +- 4 files changed, 320 insertions(+), 63 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 61c100bc3..9ec9049b4 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -17,7 +17,7 @@ -- === -- -- ### Author: **applevangelist** --- @date Last Update July 2025 +-- @date Last Update Feb 2026 -- @module Ops.AWACS -- @image OPS_AWACS.jpg @@ -6855,7 +6855,7 @@ function AWACS:onafterCheckTacticalQueue(From,Event,To) end -- AI AWACS Speaking local gtext = RadioEntry.TextTTS - if self.PathToGoogleKey then + if self.PathToGoogleKey and self.Backend ~= MSRS.Backend.HOUND then gtext = string.format("%s",gtext) end self.TacticalSRSQ:NewTransmission(gtext,nil,self.TacticalSRS,nil,0.5,nil,nil,nil,frequency,self.TacticalModulation) @@ -6914,7 +6914,7 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) if not RadioEntry.FromAI then -- AI AWACS Speaking - if self.PathToGoogleKey then + if self.PathToGoogleKey and self.Backend ~= MSRS.Backend.HOUND then local gtext = RadioEntry.TextTTS gtext = string.format("%s",gtext) self.AwacsSRS:PlayTextExt(gtext,nil,self.MultiFrequency,self.MultiModulation,self.Gender,self.Culture,self.Voice,self.Volume,"AWACS") @@ -6927,7 +6927,7 @@ function AWACS:onafterCheckRadioQueue(From,Event,To) if RadioEntry.GroupID and RadioEntry.GroupID ~= 0 then local managedgroup = self.ManagedGrps[RadioEntry.GroupID] -- #AWACS.ManagedGroup if managedgroup and managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive() then - if self.PathToGoogleKey then + if self.PathToGoogleKey and self.Backend ~= MSRS.Backend.HOUND then local gtext = RadioEntry.TextTTS gtext = string.format("%s",gtext) managedgroup.FlightGroup:RadioTransmission(gtext,1,false) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index b416051af..e66d40ae1 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Jan 2026 +-- Last Update Feb 2026 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -137,7 +137,10 @@ -- mycsar.SRSport = 5002 -- and SRS Server port -- mycsar.SRSCulture = "en-GB" -- SRS voice culture -- mycsar.SRSVoice = nil -- SRS voice for downed pilot, relevant for Google TTS --- mycsar.SRSGPathToCredentials = nil -- Path to your Google credentials json file, set this if you want to use Google TTS +-- mycsar.SRSGPathToCredentials = nil -- Path to your Google credentials json file, set this if you want to use Google TTS as provider +-- mycsar.SRSBackend = MSRS.Backend.SRSEXE -- default backend is windows +-- mycsar.SRSProvider = MSRS.Provider.WINDOWS -- default TTS provider is windows +-- mycsar.SRSSpeed = 1.0 -- default speech speed - does not work with all providers -- mycsar.SRSVolume = 1 -- Volume, between 0 and 1 -- mycsar.SRSGender = "male" -- male or female voice -- mycsar.CSARVoice = MSRS.Voices.Google.Standard.en_US_Standard_A -- SRS voice for CSAR Controller, relevant for Google TTS @@ -231,6 +234,63 @@ -- **Caveat:** -- Dropped troop noMessage and forcedesc parameters aren't saved. -- +-- ## 6. Message localization +-- +-- Use the following option to use one of the build-in translations,"en" is the default one: +-- +-- mycsar.locale = "fr" -- available are "en", "de" and "fr" (English, German, French) +-- +-- To add your own text, you can do this by adding your locale and translations for these texts before calling `CSAR:New()`: +-- (NOTE - the lua placeholders like %s, %d must remain the same in type and number!) +-- +-- CSAR.Messages.EN = { +-- HEARYOULONG = "%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", +-- HEARYOUSHORT = "%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", +-- WEARECRAMMED = "%s, %s. We\'re already crammed with %d guys! Sorry!", +-- IAMINHELO = "%s: %s I\'m in! Get to the MASH ASAP! ", +-- YOUARECLOSE = "%s: %s. You\'re close now! Land or hover at the smoke.", +-- YOUARECLOSELONG = "%s: %s. You\'re close now! Land in a safe place, I will go there ", +-- WAITMORE = "Wait till %s gets in. \nETA %d more seconds.", +-- OPENTHEDOORIN = "Open the door to let me in!", +-- HOVERABOVE = "Hovering above %s. \n\nHold hover for %d seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", +-- TOOHIGHWINCH = "Too high to winch %s \nReduce height and hover for 10 seconds!", +-- OPENTHEDOOROUT = "Open the door to let me out!", +-- TAKECLINIC = "%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", +-- KILOMETERS = " kilometer", +-- NAUTMILES = " nautical miles", +-- FIRINGFLARE = "%s - Firing signal flare at your %s o\'clock. Distance %s", +-- NOPILOTSINRANGE = "No Pilots within %s", +-- IRSTROBE = "%s - IR Strobe active at your %s o\'clock. Distance %s", +-- POPPINGSMOKE = "%s - Popping smoke at your %s o\'clock. Distance %s", +-- POPPINGSMOKEMASH = "%s - Popping smoke at the closest rescue point: %s", +-- NORESCUEPOINTWITHIN = "No rescue point within %s", +-- NOPILOTSONBOARD = "No Rescued Pilots onboard", +-- MENUTOP = "CSAR", +-- MENUACTIVE = "List Active CSAR", +-- MENUCHECK = "Check Onboard", +-- MENUFLARE = "Request Signal Flare", +-- MENUSMOKE = "Request Smoke", +-- MENUSTROBE = "Request IR Strobe", +-- MENUMASH = "Smoke Closest MASH", +-- BOARDED = "Onboard - RTB to FARP/Airfield or MASH: ", +-- MAYDAY = "MAYDAY MAYDAY! %s is down. ", +-- CONTACT = "Troops In Contact. %s requests CASEVAC. ", +-- PICKUPZONE = "Pickup Zone at %s.", +-- REQUESTSAR = "%s requests SAR at %s, beacon at %.2f KHz!", +-- REQUESTSARBEACON = "%s requests SAR at %s, beacon at %.2f KHz!", +-- KHZ = "kilo hertz", +-- }, +-- +-- e.g. for Spanish: +-- +-- CSAR.Messages.ES = { +-- HEARYOULONG = "%s: %s. ¡Te escucho! ¡Por fin, eso es música para mis oídos!\nLanzaré una señal de humo cuando estés a %s de distancia.\nAterrice o quédese en vuelo estacionario junto al humo.", +-- HEARYOUSHORT = "%s: %s. ¡Te escucho! ¡Por fin, eso es música para mis oídos!\nSolicite una bengala o humo si lo necesita.", +-- WEARECRAMMED = "%s, %s. ¡Ya estamos abarrotados con %d personas! ¡Lo siento!", +-- IAMINHELO = "%s: %s ¡Estoy dentro! ¡Dirígete al MASH lo antes posible!", +-- ... +-- } +-- -- @field #CSAR CSAR = { ClassName = "CSAR", @@ -275,6 +335,123 @@ CSAR = { IRStrobeRuntime = 300, FARPRescueDistance = 500, EnableMenuSmokeMASH = true, + locale = "en", +} + +--- +-- @field Messages +CSAR.Messages = { + EN = { + HEARYOULONG = "%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", + HEARYOUSHORT = "%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", + WEARECRAMMED = "%s, %s. We\'re already crammed with %d guys! Sorry!", + IAMINHELO = "%s: %s I\'m in! Get to the MASH ASAP! ", + YOUARECLOSE = "%s: %s. You\'re close now! Land or hover at the smoke.", + YOUARECLOSELONG = "%s: %s. You\'re close now! Land in a safe place, I will go there ", + WAITMORE = "Wait till %s gets in. \nETA %d more seconds.", + OPENTHEDOORIN = "Open the door to let me in!", + HOVERABOVE = "Hovering above %s. \n\nHold hover for %d seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", + TOOHIGHWINCH = "Too high to winch %s \nReduce height and hover for 10 seconds!", + OPENTHEDOOROUT = "Open the door to let me out!", + TAKECLINIC = "%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", + KILOMETERS = " kilometer", + NAUTMILES = " nautical miles", + FIRINGFLARE = "%s - Firing signal flare at your %s o\'clock. Distance %s", + NOPILOTSINRANGE = "No Pilots within %s", + IRSTROBE = "%s - IR Strobe active at your %s o\'clock. Distance %s", + POPPINGSMOKE = "%s - Popping smoke at your %s o\'clock. Distance %s", + POPPINGSMOKEMASH = "%s - Popping smoke at the closest rescue point: %s", + NORESCUEPOINTWITHIN = "No rescue point within %s", + NOPILOTSONBOARD = "No Rescued Pilots onboard", + MENUTOP = "CSAR", + MENUACTIVE = "List Active CSAR", + MENUCHECK = "Check Onboard", + MENUFLARE = "Request Signal Flare", + MENUSMOKE = "Request Smoke", + MENUSTROBE = "Request IR Strobe", + MENUMASH = "Smoke Closest MASH", + BOARDED = "Onboard - RTB to FARP/Airfield or MASH: ", + MAYDAY = "MAYDAY MAYDAY! %s is down. ", + CONTACT = "Troops In Contact. %s requests CASEVAC. ", + PICKUPZONE = "Pickup Zone at %s.", + REQUESTSAR = "%s requests SAR at %s, beacon at %.2f KHz!", + REQUESTSARBEACON = "%s requests SAR at %s, beacon at %.2f KHz!", + KHZ = "kilo hertz", + }, + DE = { + HEARYOULONG = "%s: %s. Ich höre Sie! Endlich, das ist Musik in meinen Ohren!\nIch zünde eine Rauchgranate, wenn Sie %s entfernt sind.\nLanden Sie oder hovern Sie beim Rauch.", + HEARYOUSHORT = "%s: %s. Ich höre Sie! Endlich, das ist Musik in meinen Ohren!\nFordern Sie eine Leuchtrakete oder Rauch an, falls nötig.", + IAMINHELO = "%s: %s Ich bin drin! Jetzt sofort zum Lazarett! ", + YOUARECLOSE = "%s: %s. Sie sind jetzt nah dran! Landen Sie oder hovern Sie beim Rauch.", + YOUARECLOSELONG = "%s: %s. Sie sind jetzt nah dran! Landen Sie an einem sicheren Ort, ich komme dorthin.", + WAITMORE = "Warten Sie, bis %s eingestiegen ist. \nNoch %d Sekunden.", + OPENTHEDOORIN = "Öffnen Sie die Tür, damit ich einsteigen kann!", + HOVERABOVE = "Hovere über %s. \n\nPositon für %d Sekunden halten, um zu winschen. \n\nWenn der Countdown stoppt, sind Sie zu weit entfernt!", + TOOHIGHWINCH = "Zu hoch, um %s zu winschen. \nHöhe reduzieren und %d Sekunden halten!", + OPENTHEDOOROUT = "Öffnen Sie die Tür, damit ich aussteigen kann!", + FIRINGFLARE = "%s - Feuere Signalrakete auf Ihrer %s-Uhr-Position ab. Entfernung %s", + NOPILOTSINRANGE = "Keine Piloten in %s Reichweite", + IRSTROBE = "%s - IR-Blinklicht aktiv auf Ihrer %s-Uhr-Position. Entfernung %s", + POPPINGSMOKE = "%s - Zünde Rauchgranate auf Ihrer %s-Uhr-Position. Entfernung %s", + POPPINGSMOKEMASH = "%s - Zünde Rauchgranate am nächsten Rettungspunkt: %s", + NORESCUEPOINTWITHIN = "Kein Rettungspunkt innerhalb von %s", + NOPILOTSONBOARD = "Keine geretteten Piloten an Bord", + MENUTOP = "CSAR", + MENUACTIVE = "Aktive CSAR", + MENUCHECK = "Ladung prüfen", + MENUFLARE = "Signalrakete anfordern", + MENUSMOKE = "Rauch anfordern", + MENUSTROBE = "IR-Blinklicht anfordern", + MENUMASH = "Nächstes MASH markieren", + WEARECRAMMED = "%s, %s. Wir sind bereits voll mit %d Mann! Tut mir leid!", + TAKECLINIC = "%s: Die %d Pilot(en) wurden ins\nLazarett gebracht. Gute Arbeit!", + KILOMETERS = " Kilometer", + NAUTMILES = " Meilen", + BOARDED = "An Bord - RTB zu FARP/Flugplatz oder Lazarett: ", + MAYDAY = "MAYDAY MAYDAY! %s ist abgestürzt. ", + CONTACT = "Truppen im Kontakt. %s fordern CASEVAC an. ", + PICKUPZONE = "Aufnahmezone bei %s.", + REQUESTSAR = "%s fordert SAR bei %s an, ADF %.2f KHz!", + REQUESTSARBEACON = "%s fordert SAR bei %s an, ADF %.2f KHz!", + KHZ = "Kilohertz", + }, + FR = { + HEARYOULONG = "%s: %s. Je vous entends! Enfin, c'est de la musique dans mes oreilles!\nJe lancerai une fumée quand vous serez à %s.\nAtterrissez ou survolez la fumée.", + HEARYOUSHORT = "%s: %s. Je vous entends! Enfin, c'est de la musique dans mes oreilles!\nDemandez une fusée éclairante ou de la fumée si nécessaire.", + IAMINHELO = "%s: %s Je suis à bord! Direction le MASH immédiatement! ", + YOUARECLOSE = "%s: %s. Vous êtes proche maintenant! Atterrissez ou survolez la fumée.", + YOUARECLOSELONG = "%s: %s. Vous êtes proche maintenant! Atterrissez dans un endroit sûr, j'y vais.", + WAITMORE = "Attendez que %s monte à bord. \nEncore %d secondes.", + OPENTHEDOORIN = "Ouvrez la porte pour me laisser entrer!", + HOVERABOVE = "En vol stationnaire au-dessus de %s. \n\nMaintenir la position pendant %d secondes pour hélitreuiller. \n\nSi le compte à rebours s'arrête, vous êtes trop loin!", + TOOHIGHWINCH = "Trop haut pour hélitreuiller %s. \nRéduisez l'altitude et maintenez la position pendant %d secondes!", + OPENTHEDOOROUT = "Ouvrez la porte pour me laisser sortir!", + FIRINGFLARE = "%s - Tir d'une fusée éclairante à vos %s heures. Distance %s", + NOPILOTSINRANGE = "Aucun pilote dans un rayon de %s", + IRSTROBE = "%s - Stroboscope IR actif à vos %s heures. Distance %s", + POPPINGSMOKE = "%s - Lancement de fumigène à vos %s heures. Distance %s", + POPPINGSMOKEMASH = "%s - Lancement de fumigène au point de sauvetage le plus proche: %s", + NORESCUEPOINTWITHIN = "Aucun point de sauvetage dans un rayon de %s", + NOPILOTSONBOARD = "Aucun pilote secouru à bord", + MENUTOP = "CSAR", + MENUACTIVE = "Lister les CSAR actifs", + MENUCHECK = "Vérifier qui est à bord", + MENUFLARE = "Demander une fusée éclairante", + MENUSMOKE = "Demander un fumigène", + MENUSTROBE = "Demander un stroboscope IR", + MENUMASH = "Fumée au MASH le plus proche", + WEARECRAMMED = "%s, %s. Nous sommes déjà pleins avec %d hommes! Désolé!", + TAKECLINIC = "%s: Les %d pilote(s) ont été transportés à \n l'hôpital. Bon travail!", + KILOMETERS = " kilomètres", + NAUTMILES = " milles nautiques", + BOARDED = "À bord - RTB vers FARP/Aérodrome ou MASH: ", + MAYDAY = "MAYDAY MAYDAY ! %s est à terre. ", + CONTACT = "Troupes au contact. %s demande un CASEVAC. ", + PICKUPZONE = "Zone de ramassage à %s.", + REQUESTSAR = "%s demande un SAR à %s, balise à %.2f KHz!", + REQUESTSARBEACON = "%s demande un SAR à %s, balise à %.2f KHz!", + KHZ = "kilohertz", + }, } --- Downed pilots info. @@ -320,7 +497,7 @@ CSAR.AircraftType["MH-6J"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.36" +CSAR.version="1.1.38" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -329,6 +506,7 @@ CSAR.version="1.0.36" -- DONE: SRS Integration (to be tested) -- TODO: Maybe - add option to smoke/flare closest MASH -- DONE: shagrat Add cargoWeight to helicopter when pilot boarded +-- DONE: Localization ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -499,7 +677,11 @@ function CSAR:New(Coalition, Template, Alias) self.SRSGender = "male" -- male or female self.CSARVoice = MSRS.Voices.Google.Standard.en_US_Standard_A self.CSARVoiceMS = MSRS.Voices.Microsoft.Hedda + self.SRSBackend = MSRS.Backend.SRSEXE + self.SRSProvider = MSRS.Provider.WINDOWS + self.SRSSpeed = 1.0 self.coordinate = nil -- Core.Point#COORDINATE + self.locale = "en" local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CSAR_%s_Persist.csv",AliaS) @@ -649,6 +831,23 @@ end --- Helper Functions --- ------------------------ +--- [Internal] Init localization +-- @param #CSAR self +-- @return #CSAR self +function CSAR:_InitLocalization() + self:T(self.lid.."_InitLocalization") + self.gettext = TEXTANDSOUND:New("CSAR","en") -- Core.TextAndSound#TEXTANDSOUND + for locale,table in pairs(self.Messages) do + local Locale = string.lower(tostring(locale)) + self:T("**** Adding locale: "..Locale) + for ID,Text in pairs(table) do + self:T(string.format('Adding ID %s',tostring(ID))) + self.gettext:AddEntry(Locale,tostring(ID),Text) + end + end + return self +end + --- (Internal) Function to insert downed pilot tracker object. -- @param #CSAR self -- @param Wrapper.Group#GROUP Group The #GROUP object @@ -842,9 +1041,13 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla if not noMessage then if _freq ~= 0 then --shagrat different CASEVAC msg - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) + local text = self.gettext:GetEntry("MAYDAY",self.locale) + text = string.format(text,_typeName) + self:_DisplayToAllSAR(text, self.coalition, self.messageTime) else - self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) + local text = self.gettext:GetEntry("CONTACT",self.locale) + text = string.format(text,_typeName) + self:_DisplayToAllSAR(text, self.coalition, self.messageTime) end end @@ -1304,23 +1507,26 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage, _pla if not _nomessage then if _freq ~= 0 then --shagrat - local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' + local request = self.gettext:GetEntry("REQUESTSAR",self.locale) + local _text = string.format(request, _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' if self.coordtype ~= 2 then --not MGRS self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) else self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) - local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) - local _text = string.format("%s requests SAR at %s, beacon at %.2f kilo hertz", _groupName, coordtext, _freqk) - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) + local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true,self.SRSBackend) + local request = self.gettext:GetEntry("REQUESTSARBEACON",self.locale) + local _text = string.format(request, _groupName, coordtext, _freqk) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false,self.SRSBackend) end else --shagrat CASEVAC msg - local _text = string.format("Pickup Zone at %s.", _coordinatesText ) + local request = self.gettext:GetEntry("PICKUPZONE",self.locale) + local _text = string.format(request, _coordinatesText ) if self.coordtype ~= 2 then --not MGRS self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) else self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) - local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) - local _text = string.format("Pickup Zone at %s.", coordtext ) + local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true,self.SRSBackend) + local _text = string.format(request, coordtext ) self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) end end @@ -1459,9 +1665,11 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local dist = UTILS.MetersToNM(self.autosmokedistance) disttext = string.format("%.0fnm",dist) end - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", self:_GetCustomCallSign(_heliName), _pilotName, disttext), self.messageTime,false,true) + local text = self.gettext:GetEntry("HEARYOULONG",self.locale) + self:_DisplayMessageToSAR(_heliUnit, string.format(text, self:_GetCustomCallSign(_heliName), _pilotName, disttext), self.messageTime,false,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) + local text = self.gettext:GetEntry("HEARYOUSHORT",self.locale) + self:_DisplayMessageToSAR(_heliUnit, string.format(text, self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) end --mark as shown for THIS heli and THIS group self.heliVisibleMessage[_lookupKeyHeli] = true @@ -1525,7 +1733,8 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, self:_GetCustomCallSign(_heliName), _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true) + local text = self.gettext:GetEntry("WEARECRAMMED",self.locale) + self:_DisplayMessageToSAR(_heliUnit, string.format(text, _pilotName, self:_GetCustomCallSign(_heliName), _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true) return self end @@ -1542,8 +1751,9 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _woundedGroup:Destroy(false) self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,true,true) + + local text = self.gettext:GetEntry("IAMINHELO",self.locale) + self:_DisplayMessageToSAR(_heliUnit, string.format(text, self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,true,true) self:_UpdateUnitCargoMass(_heliName) @@ -1613,9 +1823,11 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self:T(self.lid .. "[Pickup Debug] Helo closer than 500m: ".._lookupKeyHeli) if self.heliCloseMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) + local text = self.gettext:GetEntry("YOUARECLOSE",self.locale) + self:_DisplayMessageToSAR(_heliUnit, string.format(text, self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) + local text = self.gettext:GetEntry("YOUARECLOSELONG",self.locale) + self:_DisplayMessageToSAR(_heliUnit, string.format(text, self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) end self.heliCloseMessage[_lookupKeyHeli] = true end @@ -1633,7 +1845,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG _time = self.landedStatus[_lookupKeyHeli] _woundedGroup:OptionAlarmStateGreen() self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) - self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false) + local text = self.gettext:GetEntry("WAITMORE",self.locale) + text = string.format(text,_pilotName, _time) + self:_DisplayMessageToSAR(_heliUnit, text, self.messageTime, false) else _time = self.landedStatus[_lookupKeyHeli] - 10 self.landedStatus[_lookupKeyHeli] = _time @@ -1643,7 +1857,8 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if _distance < self.loadDistance + 5 or _distance <= 13 then self:T(self.lid .. "[Pickup Debug] Pilot close enough - YES ".._lookupKeyHeli) if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) + local text = self.gettext:GetEntry("OPENTHEDOORIN",self.locale) + self:_DisplayMessageToSAR(_heliUnit, text, self.messageTime, true, true) self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) return false else @@ -1660,7 +1875,8 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self:T(self.lid .. "[Pickup Debug] Helo close enough, door check ".._lookupKeyHeli) if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) + local text = self.gettext:GetEntry("OPENTHEDOORIN",self.locale) + self:_DisplayMessageToSAR(_heliUnit, text, self.messageTime, true, true) return false else self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) @@ -1701,11 +1917,14 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self:T(self.lid .. "[Pickup Debug] Check hover timer ".._lookupKeyHeli) if _time > 0 then self:T(self.lid .. "[Pickup Debug] Helo hovering not long enough ".._lookupKeyHeli) - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) + local text = self.gettext:GetEntry("HOVERABOVE",self.locale) + text = string.format(text,_pilotName,_time) + self:_DisplayMessageToSAR(_heliUnit, text, self.messageTime, true) else self:T(self.lid .. "[Pickup Debug] Helo hovering long enough - door check ".._lookupKeyHeli) if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) + local text = self.gettext:GetEntry("OPENTHEDOORIN",self.locale) + self:_DisplayMessageToSAR(_heliUnit, text, self.messageTime, true, true) self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) return false else @@ -1718,7 +1937,8 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG _reset = false else self:T(self.lid .. "[Pickup Debug] Helo hovering too high ".._lookupKeyHeli) - self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) + local text = self.gettext:GetEntry("TOOHIGHWINCH",self.locale) + self:_DisplayMessageToSAR(_heliUnit,string.format(text,_pilotName), self.messageTime, true,true) self:T(self.lid .. "[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli) return false end @@ -1782,7 +2002,8 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule, I if ( _dist < self.FARPRescueDistance or isairport ) and ((_heliUnit:InAir() == false) or (IsHeloBase == true)) then self:T(self.lid.."[Drop off debug] Distance ok, door check") if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true, true) + local text = self.gettext:GetEntry("OPENTHEDOOROUT",self.locale) + self:_DisplayMessageToSAR(_heliUnit, text, self.messageTime, true, true) self:T(self.lid.."[Drop off debug] Door closed, try again next loop") else self:T(self.lid.."[Drop off debug] Rescued!") @@ -1815,8 +2036,9 @@ function CSAR:_RescuePilots(_heliUnit) local PilotsSaved = self:_PilotsOnboard(_heliName) self.inTransitGroups[_heliName] = nil - - local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", self:_GetCustomCallSign(_heliName), PilotsSaved) + + local text = self.gettext:GetEntry("OPENTHEDOOROUT",self.locale) + local _txt = string.format(text, self:_GetCustomCallSign(_heliName), PilotsSaved) self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) @@ -1863,8 +2085,10 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid if coord then self.msrs:SetCoordinate(coord) end - _text = string.gsub(_text,"km"," kilometer") - _text = string.gsub(_text,"nm"," nautical miles") + local km = self.gettext:GetEntry("KILOMETERS",self.locale) + local nm = self.gettext:GetEntry("NAUTMILES",self.locale) + _text = string.gsub(_text,"km",km) + _text = string.gsub(_text,"nm",nm) self.SRSQueue:NewTransmission(_text,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,self.SRSVoice,volume,label,coord) end return self @@ -2035,7 +2259,8 @@ function CSAR:_SignalFlare(_unitName) else _distance = string.format("%.1fkm",_closest.distance/1000) end - local _msg = string.format("%s - Firing signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + local text = self.gettext:GetEntry("FIRINGFLARE",self.locale) + local _msg = string.format(text, self:_GetCustomCallSign(_unitName), _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local _coord = _closest.pilot:GetCoordinate() @@ -2048,7 +2273,8 @@ function CSAR:_SignalFlare(_unitName) else dtext = string.format("%.1fkm",smokedist/1000) end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",dtext), self.messageTime, false, false, true) + local text = self.gettext:GetEntry("NOPILOTSINRANGE",self.locale) + self:_DisplayMessageToSAR(_heli, string.format(text,dtext), self.messageTime, false, false, true) end return self end @@ -2069,6 +2295,8 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen) if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda end + local kilohertz = self.gettext:GetEntry("KHZ",self.locale) + _message = string.gsub(_message,"KHz",kilohertz) --self:F("Voice = "..voice) self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate) end @@ -2103,7 +2331,8 @@ function CSAR:_ReqIRStrobe( _unitName ) else _distance = string.format("%.1fkm",_closest.distance/1000) end - local _msg = string.format("%s - IR Strobe active at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + local text = self.gettext:GetEntry("IRSTROBE",self.locale) + local _msg = string.format(text, self:_GetCustomCallSign(_unitName), _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) _closest.pilot:NewIRMarker(true,self.IRStrobeRuntime or 300) else @@ -2113,7 +2342,8 @@ function CSAR:_ReqIRStrobe( _unitName ) else _distance = string.format("%.1fkm",smokedist/1000) end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) + local text = self.gettext:GetEntry("NOPILOTSINRANGE",self.locale) + self:_DisplayMessageToSAR(_heli, string.format(text,_distance), self.messageTime, false, false, true) end return self end @@ -2138,7 +2368,8 @@ function CSAR:_Reqsmoke( _unitName ) else _distance = string.format("%.1fkm",_closest.distance/1000) end - local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + local text = self.gettext:GetEntry("POPPINGSMOKE",self.locale) + local _msg = string.format(text, self:_GetCustomCallSign(_unitName), _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local _coord = _closest.pilot:GetCoordinate() local color = self.smokecolor @@ -2150,7 +2381,8 @@ function CSAR:_Reqsmoke( _unitName ) else _distance = string.format("%.1fkm",smokedist/1000) end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) + local text = self.gettext:GetEntry("NOPILOTSINRANGE",self.locale) + self:_DisplayMessageToSAR(_heli, string.format(text,_distance), self.messageTime, false, false, true) end return self end @@ -2174,7 +2406,8 @@ function CSAR:_ReqsmokeMash( _unitName ) else disttext = string.format("%.1fkm",distance/1000) end - local _msg = string.format("%s - Popping smoke at the closest rescue point: %s", self:_GetCustomCallSign(_unitName), disttext) + local text = self.gettext:GetEntry("POPPINGSMOKEMASH",self.locale) + local _msg = string.format(text, self:_GetCustomCallSign(_unitName), disttext) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local color = self.smokecolor coordinate:Smoke(color) @@ -2185,7 +2418,8 @@ function CSAR:_ReqsmokeMash( _unitName ) else _distance = string.format("%.1fkm",smokedist/1000) end - self:_DisplayMessageToSAR(_heli, string.format("No rescue point within %s",_distance), self.messageTime, false, false, true) + local text = self.gettext:GetEntry("NORESCUEPOINTWITHIN",self.locale) + self:_DisplayMessageToSAR(_heli, string.format(text,_distance), self.messageTime, false, false, true) end return self end @@ -2263,9 +2497,10 @@ function CSAR:_CheckOnboard(_unitName) --list onboard pilots local _inTransit = self.inTransitGroups[_unitName] if _inTransit == nil then - self:_DisplayMessageToSAR(_unit, "No Rescued Pilots onboard", self.messageTime, false, false, true) + local text = self.gettext:GetEntry("NOPILOTSONBOARD",self.locale) + self:_DisplayMessageToSAR(_unit, text, self.messageTime, false, false, true) else - local _text = "Onboard - RTB to FARP/Airfield or MASH: " + local _text = self.gettext:GetEntry("BOARDED",self.locale) for _, _onboard in pairs(self.inTransitGroups[_unitName]) do _text = _text .. "\n" .. _onboard.desc end @@ -2305,17 +2540,24 @@ function CSAR:_AddMedevacMenuItem() local groupname = _group:GetName() if self.addedTo[groupname] == nil then self.addedTo[groupname] = true - local menuname = self.topmenuname or "CSAR" + local menuname = self.gettext:GetEntry("MENUTOP",self.locale) + menuname = self.topmenuname or menuname + local Menu1T = self.gettext:GetEntry("MENUACTIVE",self.locale) + local Menu2T = self.gettext:GetEntry("MENUCHECK",self.locale) + local Menu3T = self.gettext:GetEntry("MENUFLARE",self.locale) + local Menu4T = self.gettext:GetEntry("MENUSMOKE",self.locale) + local Menu5T = self.gettext:GetEntry("MENUSTROBE",self.locale) + local Menu6T = self.gettext:GetEntry("MENUMASH",self.locale) local _rootPath = MENU_GROUP:New(_group,menuname) - local _rootMenu1 = MENU_GROUP_COMMAND:New(_group,"List Active CSAR",_rootPath, self._DisplayActiveSAR,self,_unitName) - local _rootMenu2 = MENU_GROUP_COMMAND:New(_group,"Check Onboard",_rootPath, self._CheckOnboard,self,_unitName) - local _rootMenu3 = MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, self._SignalFlare,self,_unitName) - local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName) + local _rootMenu1 = MENU_GROUP_COMMAND:New(_group,Menu1T,_rootPath, self._DisplayActiveSAR,self,_unitName) + local _rootMenu2 = MENU_GROUP_COMMAND:New(_group,Menu2T,_rootPath, self._CheckOnboard,self,_unitName) + local _rootMenu3 = MENU_GROUP_COMMAND:New(_group,Menu3T,_rootPath, self._SignalFlare,self,_unitName) + local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,Menu4T,_rootPath, self._Reqsmoke,self,_unitName) if self.AllowIRStrobe then - local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh() + local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,Menu5T,_rootPath, self._ReqIRStrobe,self,_unitName):Refresh() end if self.EnableMenuSmokeMASH then - local _rootMenu6 = MENU_GROUP_COMMAND:New(_group,"Smoke Closest MASH",_rootPath, self._ReqsmokeMash,self,_unitName) + local _rootMenu6 = MENU_GROUP_COMMAND:New(_group,Menu6T,_rootPath, self._ReqsmokeMash,self,_unitName) else _rootMenu4:Refresh() end @@ -2580,6 +2822,9 @@ function CSAR:onafterStart(From, Event, To) self.msrs = MSRS:New(path,channel,modulation) -- Sound.SRS#MSRS self.msrs:SetPort(self.SRSport) self.msrs:SetLabel("CSAR") + self.msrs:SetBackend(self.SRSBackend) + self.msrs:SetProvider(self.SRSProvider) + self.msrs.speed = self.SRSSpeed self.msrs:SetCulture(self.SRSCulture) self.msrs:SetCoalition(self.coalition) self.msrs:SetVoice(self.SRSVoice) @@ -2601,7 +2846,9 @@ function CSAR:onafterStart(From, Event, To) local filepath = self.filepath self:__Save(interval,filepath,filename) end - + + self:_InitLocalization(self.locale) + return self end diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index ff1aeb491..f3fb51bd4 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -559,14 +559,14 @@ MSRS.Voices = { ["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male ["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female -- 2025 catalog changes - ["de_DE_Wavenet_A"] = 'de-DE-Wavenet-A', -- Female - ["de_DE_Wavenet_B"] = 'de-DE-Wavenet-B', -- Male - ["de_DE_Wavenet_C"] = 'de-DE-Wavenet-C', -- Female - ["de_DE_Wavenet_D"] = 'de-DE-Wavenet-D', -- Male - ["de_DE_Wavenet_E"] = 'de-DE-Wavenet-E', -- Male - ["de_DE_Wavenet_F"] = 'de-DE-Wavenet-F', -- Female - ["de_DE_Wavenet_G"] = 'de-DE-Wavenet-G', -- Female - ["de_DE_Wavenet_H"] = 'de-DE-Wavenet-H', -- Male + ["de_DE_Wavenet_A"] = 'de-DE-Wavenet-A', -- Female + ["de_DE_Wavenet_B"] = 'de-DE-Wavenet-B', -- Male + ["de_DE_Wavenet_C"] = 'de-DE-Wavenet-C', -- Female + ["de_DE_Wavenet_D"] = 'de-DE-Wavenet-D', -- Male + ["de_DE_Wavenet_E"] = 'de-DE-Wavenet-E', -- Male + ["de_DE_Wavenet_F"] = 'de-DE-Wavenet-F', -- Female + ["de_DE_Wavenet_G"] = 'de-DE-Wavenet-G', -- Female + ["de_DE_Wavenet_H"] = 'de-DE-Wavenet-H', -- Male -- ES ["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male ["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female @@ -2033,12 +2033,18 @@ end function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Coalition,Point,Speed,Gender,Culture,Voice,UseGoogle) self:I(self.lid.."_HoundTextToSpeech") + Frequencies = UTILS.EnsureTable(Frequencies) + Modulations = UTILS.EnsureTable(Modulations) + local ffs = {} for _,_f in pairs(Frequencies or self.frequencies) do table.insert(ffs,string.format("%.1f",_f)) end local freqs = table.concat(ffs, ",") + + + local modus = table.concat(Modulations or self.modulations, ",") local coal=Coalition or self.coalition @@ -2135,6 +2141,9 @@ end function MSRS:_HoundTestTone(Frequencies, Modulations, Coalition) self:I(self.lid.."_HoundTestTone") + Frequencies = UTILS.EnsureTable(Frequencies) + Modulations = UTILS.EnsureTable(Modulations) + local ffs = {} for _,_f in pairs(Frequencies or self.frequencies) do table.insert(ffs,string.format("%.1f",_f)) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 02a344198..8a489e082 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4112,9 +4112,10 @@ end --- Get a NATO abbreviated MGRS text for SRS use, optionally with prosody slow tag -- @param #string Text The input string, e.g. "MGRS 4Q FJ 12345 67890" --- @param #boolean Slow Optional - add slow tags +-- @param #boolean Slow Optional - add slow tags, if the backend is not Hound +-- @param #string Backend The TTS Backend used -- @return #string Output for (Slow) spelling in SRS TTS e.g. "MGRS;4;Quebec;Foxtrot;Juliett;1;2;3;4;5;6;7;8;niner;zero;" -function UTILS.MGRSStringToSRSFriendly(Text,Slow) +function UTILS.MGRSStringToSRSFriendly(Text,Slow,Backend) local Text = string.gsub(Text,"MGRS ","") Text = string.gsub(Text,"%s+","") Text = string.gsub(Text,"([%a%d])","%1;") -- "0;5;1;" @@ -4146,7 +4147,7 @@ function UTILS.MGRSStringToSRSFriendly(Text,Slow) Text = string.gsub(Text,"Z","Zulu") Text = string.gsub(Text,"0","zero") Text = string.gsub(Text,"9","niner") - if Slow then + if Slow and Backend ~= nil and Backend ~= MSRS.Backend.HOUND then Text = ''..Text..'' end Text = "MGRS;"..Text From 26f55b0bb53582daf2ef1b3c0eb6b3ae7b083c50 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 27 Feb 2026 16:12:47 +0100 Subject: [PATCH 326/349] #CTLD Localization --- Moose Development/Moose/Ops/CTLD.lua | 2112 ++++------------- Moose Development/Moose/Ops/CTLD_CARGO.lua | 720 ++++++ Moose Development/Moose/Ops/CTLD_Hercules.lua | 669 ++++++ .../Moose/Ops/CTLD_Localization.lua | 752 ++++++ 4 files changed, 2663 insertions(+), 1590 deletions(-) create mode 100644 Moose Development/Moose/Ops/CTLD_CARGO.lua create mode 100644 Moose Development/Moose/Ops/CTLD_Hercules.lua create mode 100644 Moose Development/Moose/Ops/CTLD_Localization.lua diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index ca75e77db..2943a6b02 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -21,725 +21,12 @@ -- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), bbirchnz (additional code!!) -- ### Repack addition for crates: **Raiden** -- ### Additional cool features: **Lekaa** +-- ### Localization: **Applevangelist** and Claude AI -- -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Jan 2026 - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO CTLD_CARGO ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -do - ------------------------------------------------------- ---- **CTLD_CARGO** class, extends Core.Base#BASE --- @type CTLD_CARGO --- @field #string ClassName Class name. --- @field #number ID ID of this cargo. --- @field #string Name Name for menu. --- @field #string DisplayName Display name for menu/messages. --- @field #table Templates Table of #POSITIONABLE objects. --- @field #string CargoType Enumerator of Type. --- @field #boolean HasBeenMoved Flag for moving. --- @field #boolean LoadDirectly Flag for direct loading. --- @field #number CratesNeeded Crates needed to build. --- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. --- @field #boolean HasBeenDropped True if dropped from heli. --- @field #number PerCrateMass Mass in kg. --- @field #number Stock Number of builds available, -1 for unlimited. --- @field #string Subcategory Sub-category name. --- @field #boolean DontShowInMenu Show this item in menu or not. --- @field Core.Zone#ZONE Location Location (if set) where to get this cargo item. --- @field #table ResourceMap Resource Map information table if it has been set for static cargo items. --- @field #string StaticShape Individual shape if set. --- @field #string StaticType Individual type if set. --- @field #string StaticCategory Individual static category if set. --- @field #list<#string> TypeNames Table of unit types able to pick this cargo up. --- @field #number Stock0 Initial stock, if any given. --- @extends Core.Base#BASE - ---- --- @field #CTLD_CARGO CTLD_CARGO -CTLD_CARGO = { - ClassName = "CTLD_CARGO", - ID = 0, - Name = "none", - DisplayName = "none", - Templates = {}, - CargoType = "none", - HasBeenMoved = false, - LoadDirectly = false, - CratesNeeded = 0, - Positionable = nil, - HasBeenDropped = false, - PerCrateMass = 0, - Stock = nil, - Stock0 = nil, - Mark = nil, - DontShowInMenu = false, - Location = nil, - } - - --- Define cargo types. - -- @type CTLD_CARGO.Enum - -- @field #string VEHICLE - -- @field #string TROOPS - -- @field #string FOB - -- @field #string CRATE - -- @field #string REPAIR - -- @field #string ENGINEERS - -- @field #string STATIC - -- @field #string GCLOADABLE - CTLD_CARGO.Enum = { - VEHICLE = "Vehicle", -- #string vehicles - TROOPS = "Troops", -- #string troops - FOB = "FOB", -- #string FOB - CRATE = "Crate", -- #string crate - REPAIR = "Repair", -- #string repair - ENGINEERS = "Engineers", -- #string engineers - STATIC = "Static", -- #string statics - GCLOADABLE = "GC_Loadable", -- #string dynamiccargo - } - - --- Function to create new CTLD_CARGO object. - -- @param #CTLD_CARGO self - -- @param #number ID ID of this #CTLD_CARGO - -- @param #string Name Name for menu. - -- @param #table Templates Table of #POSITIONABLE objects. - -- @param #CTLD_CARGO.Enum Sorte Enumerator of Type. - -- @param #boolean HasBeenMoved Flag for moving. - -- @param #boolean LoadDirectly Flag for direct loading. - -- @param #number CratesNeeded Crates needed to build. - -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. - -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. - -- @param #number PerCrateMass Mass in kg - -- @param #number Stock Number of builds available, nil for unlimited - -- @param #string Subcategory Name of subcategory, handy if using > 10 types to load. - -- @param #boolean DontShowInMenu Show this item in menu or not (default: false == show it). - -- @param Core.Zone#ZONE Location (optional) Where the cargo is available (one location only). - -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory, DontShowInMenu, Location) - -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO - self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) - self.ID = ID or math.random(100000,1000000) - self.Name = Name or "none" -- #string - self.DisplayName = Name or "none" -- #string - self.Templates = Templates or {} -- #table - self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum - self.HasBeenMoved = HasBeenMoved or false -- #boolean - self.LoadDirectly = LoadDirectly or false -- #boolean - self.CratesNeeded = CratesNeeded or 0 -- #number - self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE - self.HasBeenDropped = Dropped or false --#boolean - self.PerCrateMass = PerCrateMass or 0 -- #number - self.Stock = Stock or nil --#number - self.Stock0 = Stock or nil --#number - self.Mark = nil - self.Subcategory = Subcategory or "Other" - self.DontShowInMenu = DontShowInMenu or false - self.ResourceMap = nil - self.StaticType = "container_cargo" -- "container_cargo" - if self:IsStatic() then - self.StaticType = self.Templates - end - self.StaticShape = nil - self.TypeNames = nil - self.StaticCategory = "Cargos" - if type(Location) == "string" then - Location = ZONE:New(Location) - end - self.Location = Location - self.NoMoveToZone = false - return self - end - - --- Add specific static type and shape to this CARGO. - -- @param #CTLD_CARGO self - -- @param #string TypeName - -- @param #string ShapeName - -- @return #CTLD_CARGO self - function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) - self.StaticCategory = Category or "Cargos" - self.StaticType = TypeName or "container_cargo" - self.StaticShape = ShapeName - return self - end - - --- Get the specific static type and shape from this CARGO if set. - -- @param #CTLD_CARGO self - -- @return #string Category - -- @return #string TypeName - -- @return #string ShapeName - function CTLD_CARGO:GetStaticTypeAndShape() - return self.StaticCategory, self.StaticType, self.StaticShape - end - - --- Add specific unit types to this CARGO (restrict what types can pick this up). - -- @param #CTLD_CARGO self - -- @param #string UnitTypes Unit type name, can also be a #list<#string> table of unit type names. - -- @return #CTLD_CARGO self - function CTLD_CARGO:AddUnitTypeName(UnitTypes) - if not self.TypeNames then self.TypeNames = {} end - if type(UnitTypes) ~= "table" then UnitTypes = {UnitTypes} end - for _,_singletype in pairs(UnitTypes or {}) do - self.TypeNames[_singletype]=_singletype - end - return self - end - - --- Check if a specific unit can carry this CARGO (restrict what types can pick this up). - -- @param #CTLD_CARGO self - -- @param Wrapper.Unit#UNIT Unit - -- @return #boolean Outcome - function CTLD_CARGO:UnitCanCarry(Unit) - if not Unit then return false end - if self.TypeNames == nil then return true end - local typename = Unit:GetTypeName() or "none" - if self.TypeNames[typename] then - return true - else - return false - end - end - - --- Add Resource Map information table - -- @param #CTLD_CARGO self - -- @param #table ResourceMap - -- @return #CTLD_CARGO self - function CTLD_CARGO:SetStaticResourceMap(ResourceMap) - self.ResourceMap = ResourceMap - return self - end - - --- Get Resource Map information table - -- @param #CTLD_CARGO self - -- @return #table ResourceMap - function CTLD_CARGO:GetStaticResourceMap() - return self.ResourceMap - end - - --- Query Location. - -- @param #CTLD_CARGO self - -- @return Core.Zone#ZONE location or `nil` if not set - function CTLD_CARGO:GetLocation() - return self.Location - end - - --- Query ID. - -- @param #CTLD_CARGO self - -- @return #number ID - function CTLD_CARGO:GetID() - return self.ID - end - - --- Query Subcategory - -- @param #CTLD_CARGO self - -- @return #string SubCategory - function CTLD_CARGO:GetSubCat() - return self.Subcategory - end - - --- Query Mass. - -- @param #CTLD_CARGO self - -- @return #number Mass in kg - function CTLD_CARGO:GetMass() - return self.PerCrateMass - end - - --- Query Name. - -- @param #CTLD_CARGO self - -- @return #string Name - function CTLD_CARGO:GetName() - return self.Name - end - - --- Set display name. - -- @param #CTLD_CARGO self - -- @param #string DisplayName Display label used in menus/messages (optional). - -- @return #CTLD_CARGO self - function CTLD_CARGO:SetDisplayName(DisplayName) - if type(DisplayName) == "string" and DisplayName ~= "" then - self.DisplayName = DisplayName - else - self.DisplayName = self.Name - end - return self - end - - --- Query display name. - -- @param #CTLD_CARGO self - -- @return #string Display name, or Name if not set - function CTLD_CARGO:GetDisplayName() - return self.DisplayName or self.Name - end - - --- Query Templates. - -- @param #CTLD_CARGO self - -- @return #table Templates - function CTLD_CARGO:GetTemplates() - return self.Templates - end - - --- Query has moved. - -- @param #CTLD_CARGO self - -- @return #boolean Has moved - function CTLD_CARGO:HasMoved() - return self.HasBeenMoved - end - - --- Query was dropped. - -- @param #CTLD_CARGO self - -- @param #boolean hercOnly If true, only treat Herc drops as 'dropped'. - -- @return #boolean Has been dropped. - function CTLD_CARGO:WasDropped(hercOnly) - if hercOnly then - return self.HasBeenDropped and self.IsHercDrop==true - end - return self.HasBeenDropped - end - - --- Query directly loadable. - -- @param #CTLD_CARGO self - -- @return #boolean loadable - function CTLD_CARGO:CanLoadDirectly() - return self.LoadDirectly - end - - --- Query number of crates or troopsize. - -- @param #CTLD_CARGO self - -- @return #number Crates or size of troops. - function CTLD_CARGO:GetCratesNeeded() - return self.CratesNeeded - end - - --- Query type. - -- @param #CTLD_CARGO self - -- @return #CTLD_CARGO.Enum Type - function CTLD_CARGO:GetType() - return self.CargoType - end - - --- Query type. - -- @param #CTLD_CARGO self - -- @return Wrapper.Positionable#POSITIONABLE Positionable - function CTLD_CARGO:GetPositionable() - return self.Positionable - end - - --- Set HasMoved. - -- @param #CTLD_CARGO self - -- @param #boolean moved - function CTLD_CARGO:SetHasMoved(moved) - self.HasBeenMoved = moved or false - end - - --- Query if cargo has been loaded. - -- @param #CTLD_CARGO self - -- @param #boolean loaded - function CTLD_CARGO:Isloaded() - if self.HasBeenMoved and not self:WasDropped() then - return true - else - return false - end - end - - --- Set WasDropped. - -- @param #CTLD_CARGO self - -- @param #boolean dropped - -- @param #boolean isHercDrop set when _GetCrates is used by the herc - function CTLD_CARGO:SetWasDropped(dropped, isHercDrop) - self.HasBeenDropped = dropped or false - self.IsHercDrop = isHercDrop or false - end - - --- Get Stock. - -- @param #CTLD_CARGO self - -- @return #number Stock or -1 if unlimited. - function CTLD_CARGO:GetStock() - if self.Stock then - return self.Stock - else - return -1 - end - end - - --- Get Stock0. - -- @param #CTLD_CARGO self - -- @return #number Stock0 or -1 if unlimited. - function CTLD_CARGO:GetStock0() - if self.Stock0 then - return self.Stock0 - else - return -1 - end - end - - --- Get relative Stock. - -- @param #CTLD_CARGO self - -- @return #number Stock Percentage like 75, or -1 if unlimited. - function CTLD_CARGO:GetRelativeStock() - if self.Stock and self.Stock0 then - return math.floor((self.Stock/self.Stock0)*100) - else - return -1 - end - end - - --- Add Stock. - -- @param #CTLD_CARGO self - -- @param #number Number to add, none if nil. - -- @return #CTLD_CARGO self - function CTLD_CARGO:AddStock(Number) - if self.Stock then -- Stock nil? - local number = Number or 1 - self.Stock = self.Stock + number - end - return self - end - - --- Remove Stock. - -- @param #CTLD_CARGO self - -- @param #number Number to reduce, none if nil. - -- @return #CTLD_CARGO self - function CTLD_CARGO:RemoveStock(Number) - if self.Stock then -- Stock nil? - local number = Number or 1 - self.Stock = self.Stock - number - if self.Stock < 0 then self.Stock = 0 end - end - return self - end - - --- Set Stock. - -- @param #CTLD_CARGO self - -- @param #number Number to set, nil means unlimited. - -- @return #CTLD_CARGO self - function CTLD_CARGO:SetStock(Number) - self.Stock = Number - return self - end - - --- Query crate type for REPAIR - -- @param #CTLD_CARGO self - -- @param #boolean - function CTLD_CARGO:IsRepair() - if self.CargoType == "Repair" then - return true - else - return false - end - end - - --- Query crate type for STATIC - -- @param #CTLD_CARGO self - -- @return #boolean - function CTLD_CARGO:IsStatic() - if self.CargoType == "Static" then - return true - else - return false - end - end - - --- Add mark - -- @param #CTLD_CARGO self - -- @return #CTLD_CARGO self - function CTLD_CARGO:AddMark(Mark) - self.Mark = Mark - return self - end - - --- Get mark - -- @param #CTLD_CARGO self - -- @return #string Mark - function CTLD_CARGO:GetMark(Mark) - return self.Mark - end - - --- Wipe mark - -- @param #CTLD_CARGO self - -- @return #CTLD_CARGO self - function CTLD_CARGO:WipeMark() - self.Mark = nil - return self - end - - --- Get overall mass of a cargo object, i.e. crates needed x mass per crate - -- @param #CTLD_CARGO self - -- @return #number mass - function CTLD_CARGO:GetNetMass() - return self.CratesNeeded * self.PerCrateMass - end - -end - -do - ------------------------------------------------------- ---- **CTLD_ENGINEERING** class, extends Core.Base#BASE --- @type CTLD_ENGINEERING --- @field #string ClassName --- @field #string lid --- @field #string Name --- @field Wrapper.Group#GROUP Group --- @field Wrapper.Unit#UNIT Unit --- @field Wrapper.Group#GROUP HeliGroup --- @field Wrapper.Unit#UNIT HeliUnit --- @field #string State --- @extends Core.Base#BASE - ---- --- @field #CTLD_ENGINEERING CTLD_ENGINEERING -CTLD_ENGINEERING = { - ClassName = "CTLD_ENGINEERING", - lid = "", - Name = "none", - Group = nil, - Unit = nil, - --C_Ops = nil, - HeliGroup = nil, - HeliUnit = nil, - State = "", - } - - --- CTLD_ENGINEERING class version. - -- @field #string version - CTLD_ENGINEERING.Version = "0.0.3" - - --- Create a new instance. - -- @param #CTLD_ENGINEERING self - -- @param #string Name - -- @param #string GroupName Name of Engineering #GROUP object - -- @param Wrapper.Group#GROUP HeliGroup HeliGroup - -- @param Wrapper.Unit#UNIT HeliUnit HeliUnit - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:New(Name, GroupName, HeliGroup, HeliUnit) - - -- Inherit everything from BASE class. - local self=BASE:Inherit(self, BASE:New()) -- #CTLD_ENGINEERING - - --BASE:I({Name, GroupName}) - - self.Name = Name or "Engineer Squad" -- #string - self.Group = GROUP:FindByName(GroupName) -- Wrapper.Group#GROUP - self.Unit = self.Group:GetUnit(1) -- Wrapper.Unit#UNIT - self.HeliGroup = HeliGroup -- Wrapper.Group#GROUP - self.HeliUnit = HeliUnit -- Wrapper.Unit#UNIT - self.currwpt = nil -- Core.Point#COORDINATE - self.lid = string.format("%s (%s) | ",self.Name, self.Version) - -- Start State. - self.State = "Stopped" - self.marktimer = 300 -- wait this many secs before trying a crate again - self:Start() - local parent = self:GetParent(self) - return self - end - - --- (Internal) Set the status - -- @param #CTLD_ENGINEERING self - -- @param #string State - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:SetStatus(State) - self.State = State - return self - end - - --- (Internal) Get the status - -- @param #CTLD_ENGINEERING self - -- @return #string State - function CTLD_ENGINEERING:GetStatus() - return self.State - end - - --- (Internal) Check the status - -- @param #CTLD_ENGINEERING self - -- @param #string State - -- @return #boolean Outcome - function CTLD_ENGINEERING:IsStatus(State) - return self.State == State - end - - --- (Internal) Check the negative status - -- @param #CTLD_ENGINEERING self - -- @param #string State - -- @return #boolean Outcome - function CTLD_ENGINEERING:IsNotStatus(State) - return self.State ~= State - end - - --- (Internal) Set start status. - -- @param #CTLD_ENGINEERING self - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Start() - self:T(self.lid.."Start") - self:SetStatus("Running") - return self - end - - --- (Internal) Set stop status. - -- @param #CTLD_ENGINEERING self - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Stop() - self:T(self.lid.."Stop") - self:SetStatus("Stopped") - return self - end - - --- (Internal) Set build status. - -- @param #CTLD_ENGINEERING self - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Build() - self:T(self.lid.."Build") - self:SetStatus("Building") - return self - end - - --- (Internal) Set done status. - -- @param #CTLD_ENGINEERING self - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Done() - self:T(self.lid.."Done") - local grp = self.Group -- Wrapper.Group#GROUP - grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond") - self:SetStatus("Running") - return self - end - - --- (Internal) Search for crates in reach. - -- @param #CTLD_ENGINEERING self - -- @param #table crates Table of found crate Ops.CTLD#CTLD_CARGO objects. - -- @param #number number Number of crates found. - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Search(crates,number) - self:T(self.lid.."Search") - self:SetStatus("Searching") - -- find crates close by - --local COps = self.C_Ops -- Ops.CTLD#CTLD - local dist = self.distance -- #number - local group = self.Group -- Wrapper.Group#GROUP - --local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table - local ctable = {} - local ind = 0 - if number > 0 then - -- get set of dropped only - for _,_cargo in pairs (crates) do - local cgotype = _cargo:GetType() - if _cargo:WasDropped() and cgotype ~= CTLD_CARGO.Enum.STATIC then - local ok = false - local chalk = _cargo:GetMark() - if chalk == nil then - ok = true - else - -- have we tried this cargo recently? - local tag = chalk.tag or "none" - local timestamp = chalk.timestamp or 0 - -- enough time gone? - local gone = timer.getAbsTime() - timestamp - if gone >= self.marktimer then - ok = true - _cargo:WipeMark() - end -- end time check - end -- end chalk - if ok then - local chalk = {} - chalk.tag = "Engineers" - chalk.timestamp = timer.getAbsTime() - _cargo:AddMark(chalk) - ind = ind + 1 - table.insert(ctable,ind,_cargo) - end - end -- end dropped - end -- end for - end -- end number - - if ind > 0 then - local crate = ctable[1] -- Ops.CTLD#CTLD_CARGO - local static = crate:GetPositionable() -- Wrapper.Static#STATIC - local crate_pos = static:GetCoordinate() -- Core.Point#COORDINATE - local gpos = group:GetCoord() -- Core.Point#COORDINATE - -- see how far we are from the crate - local distance = self:_GetDistance(gpos,crate_pos) - self:T(string.format("%s Distance to crate: %d", self.lid, distance)) - -- move there - if distance > 30 and distance ~= -1 and self:IsStatus("Searching") then - group:RouteGroundTo(crate_pos,15,"Line abreast",1) - self.currwpt = crate_pos -- Core.Point#COORDINATE - self:Move() - elseif distance <= 30 and distance ~= -1 then - -- arrived - self:Arrive() - end - else - self:T(self.lid.."No crates in reach!") - end - return self - end - - --- (Internal) Move towards crates in reach. - -- @param #CTLD_ENGINEERING self - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Move() - self:T(self.lid.."Move") - self:SetStatus("Moving") - -- check if we arrived on target - --local COps = self.C_Ops -- Ops.CTLD#CTLD - local group = self.Group -- Wrapper.Group#GROUP - local tgtpos = self.currwpt -- Core.Point#COORDINATE - local gpos = group:GetCoord() -- Core.Point#COORDINATE - -- see how far we are from the crate - local distance = self:_GetDistance(gpos,tgtpos) - self:T(string.format("%s Distance remaining: %d", self.lid, distance)) - if distance <= 30 and distance ~= -1 then - -- arrived - self:Arrive() - end - return self - end - - --- (Internal) Arrived at crates in reach. Stop group. - -- @param #CTLD_ENGINEERING self - -- @return #CTLD_ENGINEERING self - function CTLD_ENGINEERING:Arrive() - self:T(self.lid.."Arrive") - self:SetStatus("Arrived") - self.currwpt = nil - local Grp = self.Group -- Wrapper.Group#GROUP - Grp:RouteStop() - return self - end - - --- (Internal) Return distance in meters between two coordinates. - -- @param #CTLD_ENGINEERING self - -- @param Core.Point#COORDINATE _point1 Coordinate one - -- @param Core.Point#COORDINATE _point2 Coordinate two - -- @return #number Distance in meters or -1 - function CTLD_ENGINEERING:_GetDistance(_point1, _point2) - self:T(self.lid .. " _GetDistance") - if _point1 and _point2 then - local distance1 = _point1:Get2DDistance(_point2) - local distance2 = _point1:DistanceFromPointVec2(_point2) - if distance1 and type(distance1) == "number" then - return distance1 - elseif distance2 and type(distance2) == "number" then - return distance2 - else - self:E("*****Cannot calculate distance!") - self:E({_point1,_point2}) - return -1 - end - else - self:E("******Cannot calculate distance!") - self:E({_point1,_point2}) - return -1 - end - end - -end +-- Last Update Feb 2026 do ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -912,6 +199,7 @@ do -- my_ctld.validateAndRepositionUnits = false -- Uses Disposition and other logic to find better ground positions for ground units avoiding trees, water, roads, runways, map scenery, statics and other units in the area. (Default is false) -- my_ctld.loadSavedCrates = true -- Load back crates (STATIC) from the save file. Useful for mission restart cleanup. (Default is true) -- my_ctld.UseC130LoadAndUnload = false -- When set to true, forces the C-130 player to use the C-130J built system to load the cargo onboard and to unload. (Default is false) +-- my_ctld.local = "en" -- Language locale to use, available are "en" (default), "de" and "fr" -- -- ## 2.1 CH-47 Chinook support -- @@ -1414,6 +702,7 @@ CTLD = { keeploadtable = true, allowCATransport = false, VehicleMoveFormation = AI.Task.VehicleFormation.VEE, + locale = "en", } ------------------------------ @@ -1539,7 +828,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.43" +CTLD.version="1.4.45" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1674,6 +963,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor self.prefixes = Prefixes or {"Cargoheli"} self.useprefix = true + self.locale = "en" self.maximumHoverHeight = 15 self.minimumHoverHeight = 4 @@ -2140,6 +1430,24 @@ end -- Helper and User Functions ------------------------------------------------------------------- +--- [Internal] Init localization +-- @param #CTLD self +-- @return #CTLD self +function CTLD:_InitLocalization() + self:T(self.lid.."_InitLocalization") + self.gettext = TEXTANDSOUND:New("CTLD","en") -- Core.TextAndSound#TEXTANDSOUND + for locale,table in pairs(self.Messages) do + local Locale = string.lower(tostring(locale)) + self:T("**** Adding locale: "..Locale) + for ID,Text in pairs(table) do + self:T(string.format('Adding ID %s',tostring(ID))) + self.gettext:AddEntry(Locale,tostring(ID),Text) + end + end + return self +end + + --- (Internal) Function to get capabilities of a chopper -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The unit @@ -2354,7 +1662,10 @@ function CTLD:_EventHandler(EventData) self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded local Group = client:GetGroup() - self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName), 10, false, Group) + local msg = self.gettext:GetEntry("CRATE_LOADED_GROUNDCREW",self.locale) + msg = string.format(msg,event.IniDynamicCargoName) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName), 10, false, Group) self:__CratesPickedUp(1, Group, client, dcargo) self:_RefreshCrateQuantityMenus(Group, client, nil) end @@ -2400,7 +1711,10 @@ function CTLD:_EventHandler(EventData) self.Loaded_Cargo[unitname] = loaded end local Group = client:GetGroup() - self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName), 10, false, Group) + local msg = self.gettext:GetEntry("CRATE_UNLOADED_GROUNDCREW",self.locale) + msg = string.format(msg,event.IniDynamicCargoName) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName), 10, false, Group) self:__CratesDropped(1,Group,client,{dcargo}) self:_RefreshCrateQuantityMenus(Group, client, nil) end @@ -2554,7 +1868,9 @@ function CTLD:_PreloadCrates(Group, Unit, Cargo, NumberOfCrates) local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if not cancrates then - self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + local msg = self.gettext:GetEntry("CHOPPER_CANNOT_CARRY",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) return self else -- have we loaded stuff already? @@ -2579,7 +1895,10 @@ function CTLD:_PreloadCrates(Group, Unit, Cargo, NumberOfCrates) crate:SetWasDropped(false) table.insert(loaded.Cargo, crate) crate.Positionable = nil - self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + local msg = self.gettext:GetEntry("CRATE_LOADED_ID",self.locale) + msg = string.format(msg,crate:GetID(),crate:GetName()) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) --self:__CratesPickedUp(1, Group, Unit, crate) self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) @@ -2644,7 +1963,10 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) local maxloadable = self:_GetMaxLoadableMass(Unit) if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 and not Inject then -- nothing left over - self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) + local msg = self.gettext:GetEntry("ALL_GONE",self.locale) + msg = string.format(msg,cgoname) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) return self end -- landed or hovering over load zone? @@ -2657,13 +1979,19 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) end if not Inject then if not inzone then - self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + local msg = self.gettext:GetEntry("NOT_CLOSE_ENOUGH_LOGISTICS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) if not self.debug then return self end elseif not grounded and not hoverload then - self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + local msg = self.gettext:GetEntry("NEED_TO_LAND_OR_HOVER_LOAD",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) if not self.debug then return self end elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_LOAD_TROOPS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group) if not self.debug then return self end end end @@ -2692,10 +2020,14 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) loaded.Cargo = {} end if troopsize + numberonboard > trooplimit then - self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + local msg = self.gettext:GetEntry("CRAMMED",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) return elseif maxloadable < cgonetmass then - self:_SendMessage("Sorry, that\'s too heavy to load!", 10, false, Group) + local msg = self.gettext:GetEntry("TOO_HEAVY",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Sorry, that\'s too heavy to load!", 10, false, Group) return else if not self:CanGetTroops(Group, Unit, Cargotype, 1, Inject) then @@ -2707,7 +2039,10 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:_SendMessage(string.format("%s boarded!", cgoname), 10, false, Group) + local msg = self.gettext:GetEntry("BOARDED",self.locale) + msg = string.format(msg,cgoname) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("%s boarded!", cgoname), 10, false, Group) self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group, Unit, Cargotype) self:_UpdateUnitCargoMass(Unit) @@ -2741,7 +2076,9 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) -- found one and matching distance? if nearestGroup == nil or nearestDistance > self.EngineerSearch then - self:_SendMessage("No unit close enough to repair!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_UNIT_TO_REPAIR",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No unit close enough to repair!", 10, false, Group) return nil, nil end @@ -2803,7 +2140,10 @@ function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering if NearestGroup ~= nil then if self.repairtime < 2 then self.repairtime = 30 end -- noob catch if not Engineering then - self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) + local msg = self.gettext:GetEntry("REPAIR_STARTED",self.locale) + msg = string.format(msg,build.Name, self.repairtime) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) end -- now we can build .... local name = CargoType:GetName() @@ -2825,7 +2165,10 @@ function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering self:__CratesRepairStarted(1,Group,Unit) else if not Engineering then - self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) + local msg = self.gettext:GetEntry("CANT_REPAIR_WITH",self.locale) + msg = string.format(msg,build.Name) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) else self:T("Can't repair this unit with " .. build.Name) end @@ -2845,11 +2188,15 @@ end local hassecondaries = false if not grounded and not hoverload then - self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + local msg = self.gettext:GetEntry("NEED_TO_LAND_OR_HOVER_LOAD",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) if not self.debug then return self end end if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to extract troops!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_EXTRACT_TROOPS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to extract troops!", 10, false, Group) if not self.debug then return self end end -- load troops into heli @@ -2890,7 +2237,9 @@ end end if nearestGroup == nil or nearestDistance > extractdistance then - self:_SendMessage("No units close enough to extract!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_UNITS_TO_EXTRACT",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No units close enough to extract!", 10, false, Group) return self end @@ -2913,7 +2262,10 @@ end end end if Cargotype == nil then - self:_SendMessage("Can't onboard " .. groupType, 10, false, Group) + local msg = self.gettext:GetEntry("CANT_ONBOARD",self.locale) + msg = string.format(msg,groupType) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Can't onboard " .. groupType, 10, false, Group) else local troopsize = Cargotype:GetCratesNeeded() -- #number @@ -2930,7 +2282,9 @@ end loaded.Cargo = {} end if troopsize + numberonboard > trooplimit then - self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + local msg = self.gettext:GetEntry("CRAMMED",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) nearestGroup.ExtractTime = 0 --return self else @@ -2942,8 +2296,12 @@ end loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:ScheduleOnce(running, self._SendMessage, self, string.format("%s boarded!", Cargotype.Name), 10, false, Group) - self:_SendMessage(string.format("%s boarding!", Cargotype.Name), 10, false, Group) + local boardedtext = self.gettext:GetEntry("BOARDED",self.locale) + self:ScheduleOnce(running, self._SendMessage, self, string.format(boardedtext, Cargotype.Name), 10, false, Group) + local msg = self.gettext:GetEntry("BOARDING",self.locale) + msg = string.format(msg,Cargotype.Name) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("%s boarding!", Cargotype.Name), 10, false, Group) self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) local groupname = nearestGroup:GetName() @@ -3010,13 +2368,19 @@ function CTLD:_LoadTroopsQuantity(Group, Unit, Cargo, quantity) end if not inzone then - self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + local msg = self.gettext:GetEntry("NOT_CLOSE_ENOUGH_LOGISTICS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) if not self.debug then return self end elseif not grounded and not hoverload then - self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + local msg = self.gettext:GetEntry("NEED_TO_LAND_OR_HOVER_LOAD",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) if not self.debug then return self end elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_LOAD_TROOPS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group) if not self.debug then return self end end @@ -3030,7 +2394,10 @@ function CTLD:_LoadTroopsQuantity(Group, Unit, Cargo, quantity) timer.scheduleFunction(function() self.suppressmessages = prevSuppress local dname = Cargo:GetName() - self:_SendMessage(string.format("Loaded %d %s.", n, dname), 10, false, Group) + local msg = self.gettext:GetEntry("LOADED_FULL",self.locale) + msg = string.format(msg,n, dname) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Loaded %d %s.", n, dname), 10, false, Group) end, {}, timer.getTime() + 0.2 * n + 0.05) return self end @@ -3057,8 +2424,10 @@ function CTLD:_AddTroopQuantityMenus(Group, Unit, parentMenu, cargoObj) if trooplimit > 0 then local space = trooplimit - onboard if space < troopsize then - local msg = "Troop limit reached" - if type(stock) == "number" and stock == 0 then msg = "Out of stock" end + local msg = self.gettext:GetEntry("MENU_TROOP_LIMIT",self.locale) + if type(stock) == "number" and stock == 0 then msg = self.gettext:GetEntry("MENU_OUT_OF_STOCK",self.locale) end + --local msg = "Troop limit reached" + --if type(stock) == "number" and stock == 0 then msg = "Out of stock" end MENU_GROUP_COMMAND:New(Group, msg, parentMenu, function() end) return self end @@ -3114,7 +2483,7 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum if type(stock) == "number" and stock >= 0 then availableSets = math.floor(stock) if availableSets <= 0 then - MENU_GROUP_COMMAND:New(Group, "Out of stock", parentMenu, function() end) + MENU_GROUP_COMMAND:New(Group, self.gettext:GetEntry("MENU_OUT_OF_STOCK",self.locale), parentMenu, function() end) return self end if availableSets < maxQuantity then @@ -3199,9 +2568,11 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum else local msg if maxMassSets and (not capacitySets or capacitySets >= 1) and maxMassSets < 1 then - msg = "Weight limit reached" + msg = self.gettext:GetEntry("WEIGHT_LIMIT",self.locale) + --msg = "Weight limit reached" else - msg = "Crate limit reached" + msg = self.gettext:GetEntry("CRATE_LIMIT",self.locale) + --msg = "Crate limit reached" end MENU_GROUP_COMMAND:New(Group, msg, parentMenu, self._SendMessage, self, msg, 10, false, Group) end @@ -3209,21 +2580,23 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum end if canLoad and not isHerc and not suppressGetAndLoad then - MENU_GROUP_COMMAND:New(Group, "Get", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) - MENU_GROUP_COMMAND:New(Group, "Get and Load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1) + MENU_GROUP_COMMAND:New(Group, self.gettext:GetEntry("MENU_GET",self.locale), parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) + MENU_GROUP_COMMAND:New(Group, self.gettext:GetEntry("MENU_GET_AND_LOAD",self.locale), parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1) else local msg if not isHerc and not suppressGetAndLoad then if maxMassSets and (not capacitySets or capacitySets >= 1) and maxMassSets < 1 then - msg = "Weight limit reached" + msg = self.gettext:GetEntry("WEIGHT_LIMIT",self.locale) + --msg = "Weight limit reached" else - msg = "Crate limit reached" + msg = self.gettext:GetEntry("CRATE_LIMIT",self.locale) + --msg = "Crate limit reached" end MENU_GROUP_COMMAND:New(Group, msg, parentMenu, self._SendMessage, self, msg, 10, false, Group) if canPartiallyLoad and (cgotype ~= CTLD_CARGO.Enum.STATIC) and (not suppressGetAndLoad) then - MENU_GROUP_COMMAND:New(Group, "Get anyway", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) + MENU_GROUP_COMMAND:New(Group, self.gettext:GetEntry("MENU_GET_ANYWAY",self.locale), parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) - MENU_GROUP_COMMAND:New(Group, "Partially load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1, true) + MENU_GROUP_COMMAND:New(Group, self.gettext:GetEntry("MENU_PARTIALLY_LOAD",self.locale), parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1, true) end end end @@ -3242,8 +2615,8 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum if canLoad and not isHerc and not suppressGetAndLoad then local qMenu = MENU_GROUP:New(Group, label, parentMenu) - MENU_GROUP_COMMAND:New(Group, "Get", qMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity) - MENU_GROUP_COMMAND:New(Group, "Get and Load", qMenu, self._GetAndLoad, self, Group, Unit, cargoObj, quantity) + MENU_GROUP_COMMAND:New(Group, self.gettext:GetEntry("MENU_GET",self.locale), qMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity) + MENU_GROUP_COMMAND:New(Group, self.gettext:GetEntry("MENU_GET_AND_LOAD",self.locale), qMenu, self._GetAndLoad, self, Group, Unit, cargoObj, quantity) else MENU_GROUP_COMMAND:New(Group, label, parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity) end @@ -3279,17 +2652,25 @@ function CTLD:_C130GetUnits(Group, Unit, Name) end end if not cfg then - self:_SendMessage("No unit configuration found for "..tostring(Name),10,false,Group) + local msg = self.gettext:GetEntry("NO_UNIT_CONFIG",self.locale) + msg = string.format(msg,Name) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No unit configuration found for "..tostring(Name),10,false,Group) return self end local stock = cfg.Stock if type(stock) == "number" and stock ~= -1 and stock <= 0 then - self:_SendMessage(string.format("Sorry, all %s are gone!",cfg.Name or "units"),10,false,Group) + local msg = self.gettext:GetEntry("ALL_GONE",self.locale) + msg = string.format(msg,cfg.Name or "units") + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Sorry, all %s are gone!",cfg.Name or "units"),10,false,Group) return self end local inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) if not inzone then - self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) + local msg = self.gettext:GetEntry("NOT_CLOSE_ENOUGH_LOGISTICS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You are not close enough to a logistics zone!",10,false,Group) return self end if not self:CanGetUnits(Group, Unit, cfg, 1, false) then @@ -3329,7 +2710,10 @@ function CTLD:_C130GetUnits(Group, Unit, Name) if nearbyCount >= maxUnitsNearby then break end end if nearbyCount >= maxUnitsNearby then - self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) + local msg = self.gettext:GetEntry("TOO_MANY_UNITS_NEARBY",self.locale) + msg = string.format(msg,maxUnitsNearby) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("You already have %d units nearby!",maxUnitsNearby),10,false,Group) return self end @@ -3379,7 +2763,10 @@ function CTLD:_C130GetUnits(Group, Unit, Name) if type(stock) == "number" and stock ~= -1 then cfg.Stock = stock - 1 end - self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or "selection"),10,false,Group) + local msg = self.gettext:GetEntry("DEPLOYED_NEAR_YOU",self.locale) + msg = string.format(msg,cfg.Name or "selection") + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("%s have been deployed near you!",cfg.Name or "selection"),10,false,Group) return self end @@ -3427,7 +2814,10 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress local cgoname = Cargo:GetName() local instock = Cargo:GetStock() if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then - self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group) + local msg = self.gettext:GetEntry("RAN_OUT_OF",self.locale) + msg = string.format(msg,cgoname) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group) return false end end @@ -3456,7 +2846,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress end if not inzone then - self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + local msg = self.gettext:GetEntry("NOT_CLOSE_ENOUGH_LOGISTICS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) if not self.debug then return self end end @@ -3467,7 +2859,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress if unitcoord then if not location:IsCoordinateInZone(unitcoord) then -- no we're not at the right spot - self:_SendMessage("The requested cargo is not available in this zone!", 10, false, Group) + local msg = self.gettext:GetEntry("CARGO_NOT_AVAILABLE_ZONE",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("The requested cargo is not available in this zone!", 10, false, Group) if not self.debug then return false end end end @@ -3479,7 +2873,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress local loaddist = self.CrateDistance or 35 local nearcrates, numbernearby = self:_FindCratesNearby(Group, Unit, loaddist, true, true, true) if numbernearby >= canloadcratesno and not drop then - self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) + local msg = self.gettext:GetEntry("ENOUGH_CRATES_NEARBY",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) return false end @@ -3765,9 +3161,11 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress Cargo:RemoveStock(requestedSets) self:_RefreshCrateQuantityMenus(Group, Unit, Cargo) end - local text = string.format("%d crates for %s have been positioned near you!", number, cratedisplayname) + local text = string.format(self.gettext:GetEntry("CRATES_POSITIONED",self.locale), number, cratedisplayname) + --local text = string.format("%d crates for %s have been positioned near you!", number, cratedisplayname) if drop then - text = string.format("%d crates for %s have been dropped!", number, cratedisplayname) + text = string.format(self.gettext:GetEntry("CRATES_DROPPED",self.locale), number, cratedisplayname) + --text = string.format("%d crates for %s have been dropped!", number, cratedisplayname) self:__CratesDropped(1, Group, Unit, droppedcargo) else if not quiet then @@ -3896,7 +3294,10 @@ function CTLD:_ListCratesNearby( _group, _unit) end self:_SendMessage(text:Text(), 30, true, _group) else - self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + local msg = self.gettext:GetEntry("NO_CRATES_WITHIN",self.locale) + msg = string.format(msg,finddist) + self:_SendMessage(msg, 10, false, _group) + --self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) end return self end @@ -3934,7 +3335,10 @@ function CTLD:_C130RemoveUnitsNearby(_group,_unit) local cname = cfg.Name or "Unit" table.insert(removedTable, { groupName = gr:GetName(), name = cname, template = tName, coordinate = gr:GetCoordinate() }) gr:Destroy(false) - self:_SendMessage(cname.." have been removed",10,false,_group) + local msg = self.gettext:GetEntry("UNITS_REMOVED",self.locale) + msg = string.format(msg,cname) + self:_SendMessage(msg, 10, false, _group) + --self:_SendMessage(cname.." have been removed",10,false,_group) removedAny = true didRemoveThis = true break @@ -3946,7 +3350,9 @@ function CTLD:_C130RemoveUnitsNearby(_group,_unit) end end if not removedAny then - self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) + local msg = self.gettext:GetEntry("NOTHING_TO_REMOVE",self.locale) + self:_SendMessage(msg, 10, false, _group) + --self:_SendMessage("Nothing to remove at this distance pilot!",10,false,_group) else -- Trigger FSM event for removed units (C-130 managed groups). self:__RemoveCratesNearby(1, _group, _unit, removedTable) @@ -3998,7 +3404,10 @@ function CTLD:_RemoveCratesNearby(_group, _unit) -- Trigger FSM event for removed crates. self:__RemoveCratesNearby(1, _group, _unit, crates) else - self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) + local msg = self.gettext:GetEntry("NO_CRATES_WITHIN",self.locale) + msg = string.format(msg,finddist) + self:_SendMessage(msg, 10, false, _group) + --self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end return self end @@ -4088,11 +3497,11 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype end end self:T(self.lid..string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) - if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true and not hercInnerBlocked then - index = index + 1 - found[#found+1] = cargo - maxloadable = maxloadable - weight - end + if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true and not hercInnerBlocked then + index = index + 1 + found[#found+1] = cargo + maxloadable = maxloadable - weight + end end end @@ -4120,7 +3529,9 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- Door check if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_LOAD_CARGO",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) if not self.debug then return self end end --- cases ------------------------------- @@ -4130,11 +3541,17 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- --> hover or land if not forcedhover ----------------------------------------- if not cancrates then - self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + local msg = self.gettext:GetEntry("CHOPPER_CANNOT_CARRY",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) elseif self.forcehoverload and not canhoverload then - self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) + local msg = self.gettext:GetEntry("HOVER_OVER_CRATES",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) elseif not grounded and not canhoverload then - self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) + local msg = self.gettext:GetEntry("LAND_OR_HOVER_OVER_CRATES",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) else -- have we loaded stuff already? local numberonboard = 0 @@ -4157,10 +3574,14 @@ function CTLD:_LoadCratesNearby(Group, Unit) if number == 0 and self.hoverautoloading then return self elseif number == 0 then - self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_LOADABLE_CRATES",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!", 10, false, Group) return self elseif numberonboard == cratelimit then - self:_SendMessage("Sorry, we are fully loaded!", 10, false, Group) + local msg = self.gettext:GetEntry("FULLY_LOADED",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Sorry, we are fully loaded!", 10, false, Group) return self else local capacity = cratelimit - numberonboard @@ -4207,14 +3628,26 @@ function CTLD:_LoadCratesNearby(Group, Unit) if needed > 1 then if fullSets > 0 and leftover == 0 then - self:_SendMessage(string.format("Loaded %d %s.", fullSets, cName), 10, false, Group) + local msg = self.gettext:GetEntry("LOADED_FULL",self.locale) + msg = string.format(msg,fullSets, cName) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Loaded %d %s.", fullSets, cName), 10, false, Group) elseif fullSets > 0 and leftover > 0 then - self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cName, leftover), 10, false, Group) + local msg = self.gettext:GetEntry("LOADED_SETS_LEFTOVER",self.locale) + msg = string.format(msg,fullSets, cName, leftover) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cName, leftover), 10, false, Group) else - self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cName), 15, false, Group) + local msg = self.gettext:GetEntry("LOADED_PARTIAL",self.locale) + msg = string.format(msg,loadedHere, needed, cName) + self:_SendMessage(msg, 15, false, Group) + --self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cName), 15, false, Group) end else - self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cName), 10, false, Group) + local msg = self.gettext:GetEntry("LOADED_SETS",self.locale) + msg = string.format(msg,loadedHere, cName) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cName), 10, false, Group) end end end @@ -4418,7 +3851,10 @@ function CTLD:_ListCargo(Group, Unit) local text = report:Text() self:_SendMessage(text, 30, true, Group) else - self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs", trooplimit, cratelimit, maxloadable), 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED",self.locale) + msg = string.format(msg,trooplimit, cratelimit, maxloadable) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs", trooplimit, cratelimit, maxloadable), 10, false, Group) end return self end @@ -4511,7 +3947,9 @@ function CTLD:_ListInventory(Group, Unit) local text = report:Text() self:_SendMessage(text, 30, true, Group) else - self:_SendMessage(string.format("Nothing in stock!"), 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_IN_STOCK",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Nothing in stock!"), 10, false, Group) end return self end @@ -4584,7 +4022,9 @@ function CTLD:_UnloadTroops(Group, Unit) local droppingatbase = false local canunload = true if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_UNLOAD_TROOPS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) if not self.debug then return self end end local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) @@ -4683,10 +4123,15 @@ function CTLD:_UnloadTroops(Group, Unit) parts[#parts + 1] = tostring(nCount).."x Engineers "..nName end if #parts > 0 then - self:_SendMessage("Dropped "..table.concat(parts, ", ").." into action!", 10, false, Group) + local msg = self.gettext:GetEntry("DROPPED_INTO_ACTION",self.locale) + msg = string.format(msg,table.concat(parts, ", ")) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Dropped "..table.concat(parts, ", ").." into action!", 10, false, Group) end else -- droppingatbase - self:_SendMessage("Troops have returned to base!", 10, false, Group) + local msg = self.gettext:GetEntry("TROOPS_RETURNED",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Troops have returned to base!", 10, false, Group) self:__TroopsRTB(1, Group, Unit, zonename, zone) end -- cleanup load list @@ -4729,9 +4174,13 @@ function CTLD:_UnloadTroops(Group, Unit) self:_RefreshTroopQuantityMenus(Group, Unit, nil) else if IsHerc then - self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED_AIRDROP",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else - self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED_HOVER",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end end return self @@ -4747,14 +4196,18 @@ function CTLD:_UnloadCrates(Group, Unit) if not self.dropcratesanywhere then -- #1570 local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) if not inzone then - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + local msg = self.gettext:GetEntry("NOT_CLOSE_ENOUGH_DROP",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) if not self.debug then return self end end end if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_DROP_CARGO",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) if not self.debug then return self end end local hoverunload = self:IsCorrectHover(Unit) @@ -4790,14 +4243,26 @@ function CTLD:_UnloadCrates(Group, Unit) local full = math.floor(count/needed) local left = count % needed if full > 0 and left == 0 then - self:_SendMessage(string.format("Dropped %d %s.",full,cname),10,false,Group) + local msg = self.gettext:GetEntry("DROPPED_FULL",self.locale) + msg = string.format(msg,full,cname) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Dropped %d %s.",full,cname),10,false,Group) elseif full > 0 and left > 0 then - self:_SendMessage(string.format("Dropped %d %s(s), with %d leftover crate(s).",full,cname,left),10,false,Group) + local msg = self.gettext:GetEntry("DROPPED_SETS_LEFTOVER",self.locale) + msg = string.format(msg,full,cname,left) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Dropped %d %s(s), with %d leftover crate(s).",full,cname,left),10,false,Group) else - self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group) + local msg = self.gettext:GetEntry("DROPPED_PARTIAL",self.locale) + msg = string.format(msg,count,needed,cname) + self:_SendMessage(msg, 15, false, Group) + --self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group) end else - self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group) + local msg = self.gettext:GetEntry("DROPPED_SETS",self.locale) + msg = string.format(msg,count,cname) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group) end end local loaded = {} @@ -4825,9 +4290,13 @@ function CTLD:_UnloadCrates(Group, Unit) self:_RefreshCrateQuantityMenus(Group, Unit, nil) else if IsHerc then - self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED_AIRDROP",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else - self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED_HOVER",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end end return self @@ -4860,7 +4329,9 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) if self:IsFixedWing(Unit) and self.enableFixedWing and not Engineering then local speed = Unit:GetVelocityKMH() if speed > 1 then - self:_SendMessage("You need to land / stop to build something, Pilot!", 10, false, Group) + local msg = self.gettext:GetEntry("NEED_TO_LAND_BUILD",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to land / stop to build something, Pilot!", 10, false, Group) return self end end @@ -4868,7 +4339,9 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) -- are we in a load zone? local inloadzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) if inloadzone then - self:_SendMessage("You cannot build in a loading area, Pilot!", 10, false, Group) + local msg = self.gettext:GetEntry("CANNOT_BUILD_LOADING_AREA",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You cannot build in a loading area, Pilot!", 10, false, Group) return self end end @@ -4987,7 +4460,10 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) if not notified then - self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) + local msg = self.gettext:GetEntry("BUILD_STARTED",self.locale) + msg = string.format(msg,self.buildtime) + self:_SendMessage(msg, 15, false, Group) + --self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) notified=true end self:__CratesBuildStarted(1,Group,Unit,build.Name) @@ -5007,7 +4483,10 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) if not notified then - self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) + local msg = self.gettext:GetEntry("BUILD_STARTED",self.locale) + msg = string.format(msg,self.buildtime) + self:_SendMessage(msg, 15, false, Group) + --self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) notified=true end self:__CratesBuildStarted(1,Group,Unit,build.Name) @@ -5021,7 +4500,12 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) end else - if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end + if not Engineering then + local msg = self.gettext:GetEntry("NO_CRATES_WITHIN_PLAIN",self.locale) + msg = string.format(msg,finddist) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + end end -- number > 0 return self end @@ -5067,7 +4551,9 @@ function CTLD:_PackCratesNearby(Group, Unit) end if not packedAny then - self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) + local msg = self.gettext:GetEntry("NOTHING_TO_PACK",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) return false end @@ -5152,7 +4638,12 @@ function CTLD:_RepairCrates(Group, Unit, Engineering) end end else - if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end + if not Engineering then + local msg = self.gettext:GetEntry("NO_CRATES_WITHIN_PLAIN",self.locale) + msg = string.format(msg,finddist) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + end end -- number > 0 return self end @@ -5307,7 +4798,9 @@ end function CTLD:_DropAndBuild(Group,Unit) if self.nobuildinloadzones then if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) then - self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) + local msg = self.gettext:GetEntry("CANNOT_BUILD_LOADING_AREA",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end @@ -5322,7 +4815,9 @@ function CTLD:_DropAndBuild(Group,Unit) function CTLD:_DropSingleAndBuild(Group,Unit,setIndex) if self.nobuildinloadzones then if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) then - self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) + local msg = self.gettext:GetEntry("CANNOT_BUILD_LOADING_AREA",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) return self end end @@ -5335,7 +4830,9 @@ function CTLD:_DropAndBuild(Group,Unit) -- @param Wrapper.Unit#UNIT Unit The calling unit function CTLD:_PackAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_LOAD_CARGO",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end if not self:_PackCratesNearby(Group,Unit) then @@ -5363,7 +4860,9 @@ end -- @param #number quantity function CTLD:_GetAndLoad(Group, Unit, cargoObj, quantity, LoadAnyWay) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_LOAD_CARGO",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) return self end local needed = cargoObj and cargoObj:GetCratesNeeded() or 1 @@ -5379,7 +4878,9 @@ function CTLD:_GetAndLoad(Group, Unit, cargoObj, quantity, LoadAnyWay) local perSet = needed > 0 and needed or 1 capacitySets = math.floor(space / perSet) if capacitySets < 1 and not LoadAnyWay then - self:_SendMessage("No capacity to load more now!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_CAPACITY_NOW",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No capacity to load more now!", 10, false, Group) return self end if capacitySets < 1 and LoadAnyWay then @@ -5397,7 +4898,9 @@ function CTLD:_GetAndLoad(Group, Unit, cargoObj, quantity, LoadAnyWay) inzone, ship, zone, distance, width = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if not inzone then - self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + local msg = self.gettext:GetEntry("NOT_CLOSE_ENOUGH_LOGISTICS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) return self end local total = needed * count @@ -5418,7 +4921,9 @@ end -- @param Wrapper.Unit#UNIT Unit The unit performing the pack-and-load function CTLD:_GetAllAndLoad(Group,Unit) if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_LOAD_CARGO",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) return self end @@ -5779,12 +5284,12 @@ function CTLD:_RefreshF10Menus() end local toptroops = nil local topcrates = nil - local topmenu = MENU_GROUP:New(_group, "CTLD", nil) + local topmenu = MENU_GROUP:New(_group, self.gettext:GetEntry("MENU_CTLD",self.locale), nil) _group.CTLDTopmenu = topmenu if cantroops then - local toptroops = MENU_GROUP:New(_group, "Manage Troops", topmenu) - local troopsmenu = MENU_GROUP:New(_group, "Load troops", toptroops) + local toptroops = MENU_GROUP:New(_group, self.gettext:GetEntry("MENU_MANAGE_TROOPS",self.locale), topmenu) + local troopsmenu = MENU_GROUP:New(_group, self.gettext:GetEntry("MENU_LOAD_TROOPS",self.locale), toptroops) _group.MyTopTroopsMenu = toptroops _group.CTLD_TroopMenus = {} @@ -5824,9 +5329,9 @@ function CTLD:_RefreshF10Menus() end end end - local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh() - MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() - MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() + local dropTroopsMenu=MENU_GROUP:New(_group,self.gettext:GetEntry("MENU_DROP_TROOPS",self.locale),toptroops):Refresh() + MENU_GROUP_COMMAND:New(_group,self.gettext:GetEntry("MENU_DROP_ALL_TROOPS",self.locale),dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() + MENU_GROUP_COMMAND:New(_group,self.gettext:GetEntry("MENU_EXTRACT_TROOPS",self.locale),toptroops,self._ExtractTroops,self,_group,_unit):Refresh() local uName=_unit:GetName() local loadedData=self.Loaded_Cargo[uName] if loadedData and loadedData.Cargo then @@ -5842,11 +5347,11 @@ function CTLD:_RefreshF10Menus() end end if cancrates then - local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) + local topcrates = MENU_GROUP:New(_group, self.gettext:GetEntry("MENU_MANAGE_CRATES",self.locale), topmenu) _group.MyTopCratesMenu = topcrates -- Build the “Get Crates” sub-menu items - local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + local cratesmenu = MENU_GROUP:New(_group,self.gettext:GetEntry("MENU_GET_CRATES",self.locale),topcrates) if self.onestepmenu then _group.CTLD_CrateMenus = {} @@ -5887,7 +5392,9 @@ function CTLD:_RefreshF10Menus() local txt local cargoLabel = self:_GetCargoDisplayName(cargoObj) if needed > 1 then - txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0) + local plural = "s" + if self.locale == "de" then plural = "n" end + txt = string.format(self.gettext:GetEntry("MENU_CRATES_NEEDED",self.locale),needed,plural,cargoLabel,cargoObj.PerCrateMass or 0) else txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0) end @@ -5936,7 +5443,9 @@ function CTLD:_RefreshF10Menus() local txt local cargoLabel = self:_GetCargoDisplayName(cargoObj) if needed > 1 then - txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0) + local plural = "s" + if self.locale == "de" then plural = "n" end + txt = string.format(self.gettext:GetEntry("MENU_CRATES_NEEDED",self.locale),needed,plural,cargoLabel,cargoObj.PerCrateMass or 0) else txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0) end @@ -5952,7 +5461,9 @@ function CTLD:_RefreshF10Menus() local txt local cargoLabel = self:_GetCargoDisplayName(cargoObj) if needed > 1 then - txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0) + local plural = "s" + if self.locale == "de" then plural = "n" end + txt = string.format(self.gettext:GetEntry("MENU_CRATES_NEEDED",self.locale),needed,plural,cargoLabel,cargoObj.PerCrateMass or 0) else txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0) end @@ -5969,7 +5480,9 @@ function CTLD:_RefreshF10Menus() local txt local cargoLabel = self:_GetCargoDisplayName(cargoObj) if needed > 1 then - txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0) + local plural = "s" + if self.locale == "de" then plural = "n" end + txt = string.format(self.gettext:GetEntry("MENU_CRATES_NEEDED",self.locale),needed,plural,cargoLabel,cargoObj.PerCrateMass or 0) else txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0) end @@ -5985,7 +5498,9 @@ function CTLD:_RefreshF10Menus() local txt local cargoLabel = self:_GetCargoDisplayName(cargoObj) if needed > 1 then - txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0) + local plural = "s" + if self.locale == "de" then plural = "n" end + txt = string.format(self.gettext:GetEntry("MENU_CRATES_NEEDED",self.locale),needed,plural,cargoLabel,cargoObj.PerCrateMass or 0) else txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0) end @@ -5998,31 +5513,31 @@ function CTLD:_RefreshF10Menus() end end - local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) + local loadCratesMenu=MENU_GROUP:New(_group,self.gettext:GetEntry("MENU_LOAD_CRATES",self.locale),topcrates) _group.MyLoadCratesMenu=loadCratesMenu - MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) - MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,self.gettext:GetEntry("MENU_LOAD_ALL",self.locale),loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,self.gettext:GetEntry("MENU_SHOW_LOADABLE_CRATES",self.locale),loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) - local dropCratesMenu = MENU_GROUP:New(_group,"Drop Crates",topcrates) + local dropCratesMenu = MENU_GROUP:New(_group,self.gettext:GetEntry("MENU_DROP_CRATES",self.locale),topcrates) topcrates.DropCratesMenu = dropCratesMenu if not self.nobuildmenu then - MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_BUILD_CRATES",self.locale), topcrates, self._BuildCrates, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_REPAIR",self.locale), topcrates, self._RepairCrates, self, _group, _unit):Refresh() end - local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) - MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) + local removecratesmenu = MENU_GROUP:New(_group, self.gettext:GetEntry("MENU_REMOVE_CRATES",self.locale), topcrates) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_REMOVE_CRATES_NEARBY",self.locale), removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) if self.onestepmenu then - local mPack=MENU_GROUP:New(_group,"Pack crates",topcrates) - MENU_GROUP_COMMAND:New(_group,"Pack",mPack,self._PackCratesNearby,self,_group,_unit) - MENU_GROUP_COMMAND:New(_group,"Pack and Load",mPack,self._PackAndLoad,self,_group,_unit) - MENU_GROUP_COMMAND:New(_group,"Pack and Remove",mPack,self._PackAndRemove,self,_group,_unit) - MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) + local mPack=MENU_GROUP:New(_group,self.gettext:GetEntry("MENU_PACK_CRATES",self.locale),topcrates) + MENU_GROUP_COMMAND:New(_group,self.gettext:GetEntry("MENU_PACK",self.locale),mPack,self._PackCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,self.gettext:GetEntry("MENU_PACK_AND_LOAD",self.locale),mPack,self._PackAndLoad,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,self.gettext:GetEntry("MENU_PACK_AND_REMOVE",self.locale),mPack,self._PackAndRemove,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_LIST_CRATES_NEARBY",self.locale), topcrates, self._ListCratesNearby, self, _group, _unit) else - MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_PACK_CRATES",self.locale), topcrates, self._PackCratesNearby, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_LIST_CRATES_NEARBY",self.locale), topcrates, self._ListCratesNearby, self, _group, _unit) end local uName = _unit:GetName() @@ -6045,9 +5560,9 @@ function CTLD:_RefreshF10Menus() end end if self:IsC130J(_unit) then - local topunits = MENU_GROUP:New(_group,"Manage Units",topmenu) - local getunits = MENU_GROUP:New(_group,"Get Units",topunits) - MENU_GROUP_COMMAND:New(_group,"Remove units nearby",topunits,self._C130RemoveUnitsNearby,self,_group,_unit) + local topunits = MENU_GROUP:New(_group,self.gettext:GetEntry("MENU_MANAGE_UNITS",self.locale),topmenu) + local getunits = MENU_GROUP:New(_group,self.gettext:GetEntry("MENU_GET_UNITS",self.locale),topunits) + MENU_GROUP_COMMAND:New(_group,self.gettext:GetEntry("MENU_REMOVE_UNITS_NEARBY",self.locale),topunits,self._C130RemoveUnitsNearby,self,_group,_unit) local unitentries = self.C130GetUnits or {} local unittype = _unit:GetTypeName() or "none" @@ -6087,27 +5602,27 @@ function CTLD:_RefreshF10Menus() ----------------------------------------------------- -- Misc sub‐menus ----------------------------------------------------- - MENU_GROUP_COMMAND:New(_group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "Inventory", topmenu, self._ListInventory, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_LIST_BOARDED_CARGO",self.locale), topmenu, self._ListCargo, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_INVENTORY",self.locale), topmenu, self._ListInventory, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_LIST_ZONE_BEACONS",self.locale), topmenu, self._ListRadioBeacons, self, _group, _unit) - local smoketopmenu = MENU_GROUP:New(_group, "Smokes, Flares, Beacons", topmenu) - MENU_GROUP_COMMAND:New(_group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokeself = MENU_GROUP:New(_group, "Drop smoke now", smoketopmenu) - MENU_GROUP_COMMAND:New(_group, "Red smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Red) - MENU_GROUP_COMMAND:New(_group, "Blue smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Blue) - MENU_GROUP_COMMAND:New(_group, "Green smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Green) - MENU_GROUP_COMMAND:New(_group, "Orange smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Orange) - MENU_GROUP_COMMAND:New(_group, "White smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.White) + local smoketopmenu = MENU_GROUP:New(_group, self.gettext:GetEntry("MENU_SMOKES_FLARES_BEACONS",self.locale), topmenu) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_SMOKE_ZONES_NEARBY",self.locale), smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokeself = MENU_GROUP:New(_group, self.gettext:GetEntry("MENU_DROP_SMOKE_NOW",self.locale), smoketopmenu) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_RED_SMOKE",self.locale), smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Red) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_BLUE_SMOKE",self.locale), smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Blue) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_GREEN_SMOKE",self.locale), smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Green) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_ORANGE_SMOKE",self.locale), smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Orange) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_WHITE_SMOKE",self.locale), smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.White) - MENU_GROUP_COMMAND:New(_group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) - MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) - MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_FLARE_ZONES_NEARBY",self.locale), smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_FIRE_FLARE_NOW",self.locale), smoketopmenu, self.SmokePositionNow, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_DROP_BEACON_NOW",self.locale), smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() if self:IsFixedWing(_unit) then - MENU_GROUP_COMMAND:New(_group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_SHOW_FLIGHT_PARAMS",self.locale), topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() else - MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + MENU_GROUP_COMMAND:New(_group, self.gettext:GetEntry("MENU_SHOW_HOVER_PARAMS",self.locale), topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() end -- Mark we built the menu @@ -6134,16 +5649,16 @@ function CTLD:_RefreshLoadCratesMenu(Group,Unit) if not Group.MyLoadCratesMenu then return end Group.MyLoadCratesMenu:RemoveSubMenus() if self:IsC130J(Unit) then - MENU_GROUP_COMMAND:New(Group,"Use C-130 Load system",Group.MyLoadCratesMenu,function() end) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_USE_C130_LOAD",self.locale),Group.MyLoadCratesMenu,function() end) return end local d=self.CrateDistance or 35 local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) if n==0 then - MENU_GROUP_COMMAND:New(Group,"No crates found! Rescan?",Group.MyLoadCratesMenu,function() self:_RefreshLoadCratesMenu(Group,Unit) end) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_NO_CRATES_FOUND_RESCAN",self.locale),Group.MyLoadCratesMenu,function() self:_RefreshLoadCratesMenu(Group,Unit) end) return end - MENU_GROUP_COMMAND:New(Group,"Load ALL",Group.MyLoadCratesMenu,self._LoadCratesNearby,self,Group,Unit) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_LOAD_ALL",self.locale),Group.MyLoadCratesMenu,self._LoadCratesNearby,self,Group,Unit) local cargoByName={} for _,crate in pairs(nearby) do @@ -6188,13 +5703,17 @@ function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName, details) local grounded = not self:IsUnitInAir(Unit) local hover = self:CanHoverLoad(Unit) if not grounded and not hover then - self:_SendMessage("You must land or hover to load crates!", 10, false, Group) + local msg = self.gettext:GetEntry("MUST_LAND_OR_HOVER_CRATES",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You must land or hover to load crates!", 10, false, Group) return self end -- 2) Check door if required if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_LOAD_CARGO",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) return self end @@ -6202,7 +5721,9 @@ function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName, details) local finddist = self.CrateDistance or 35 local cratesNearby, number = self:_FindCratesNearby(Group, Unit, finddist, false, false) if number == 0 then - self:_SendMessage("No crates found in range!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_CRATES_IN_RANGE",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No crates found in range!", 10, false, Group) return self end @@ -6215,7 +5736,10 @@ function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName, details) end end if not needed then - self:_SendMessage(string.format("No \"%s\" crates found in range!", cargoName), 10, false, Group) + local msg = self.gettext:GetEntry("NO_NAMED_CRATES_IN_RANGE",self.locale) + msg = string.format(msg,cargoName) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("No \"%s\" crates found in range!", cargoName), 10, false, Group) return self end @@ -6230,7 +5754,9 @@ function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName, details) local capabilities = self:_GetUnitCapabilities(Unit) local capacity = capabilities.cratelimit or 0 if loadedData.Cratesloaded >= capacity then - self:_SendMessage("No more capacity to load crates!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_MORE_CAPACITY",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No more capacity to load crates!", 10, false, Group) self.suppressmessages = prevSuppress return self end @@ -6239,7 +5765,9 @@ function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName, details) local spaceLeft = capacity - loadedData.Cratesloaded local toLoad = math.min(found, needed, spaceLeft) if toLoad < 1 then - self:_SendMessage("Cannot load crates: either none found or no capacity left.", 10, false, Group) + local msg = self.gettext:GetEntry("CANNOT_LOAD_NONE_OR_FULL",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Cannot load crates: either none found or no capacity left.", 10, false, Group) self.suppressmessages = prevSuppress return self end @@ -6281,20 +5809,35 @@ function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName, details) local loadedHere = toLoad if details or (not batch) then if loadedHere < needed and loadedData.Cratesloaded >= capacity then - self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!", loadedHere, needed, cargoName), 10, false, Group) + local msg = self.gettext:GetEntry("LOADED_PARTIAL_LIMIT",self.locale) + msg = string.format(msg,loadedHere, needed, cargoName) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!", loadedHere, needed, cargoName), 10, false, Group) else local fullSets = math.floor(loadedHere / needed) local leftover = loadedHere % needed if needed > 1 then if fullSets > 0 and leftover == 0 then - self:_SendMessage(string.format("Loaded %d %s.", fullSets, cargoName), 10, false, Group) + local msg = self.gettext:GetEntry("LOADED_FULL",self.locale) + msg = string.format(msg,fullSets, cargoName) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Loaded %d %s.", fullSets, cargoName), 10, false, Group) elseif fullSets > 0 and leftover > 0 then - self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cargoName, leftover), 10, false, Group) + local msg = self.gettext:GetEntry("LOADED_SETS_LEFTOVER",self.locale) + msg = string.format(msg,fullSets, cargoName, leftover) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cargoName, leftover), 10, false, Group) else - self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cargoName), 15, false, Group) + local msg = self.gettext:GetEntry("LOADED_PARTIAL",self.locale) + msg = string.format(msg,loadedHere, needed, cargoName) + self:_SendMessage(msg, 15, false, Group) + --self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cargoName), 15, false, Group) end else - self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cargoName), 10, false, Group) + local msg = self.gettext:GetEntry("LOADED_SETS",self.locale) + msg = string.format(msg,loadedHere, cargoName) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cargoName), 10, false, Group) end end end @@ -6311,9 +5854,11 @@ function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName, details) if batch.remaining <= 0 then self.suppressmessages = prevSuppress if not details then - local txt = string.format("Loaded %d %s.", batch.loaded, cargoName) + local txt = string.format(self.gettext:GetEntry("LOADED_BATCH",self.locale), batch.loaded, cargoName) + --local txt = string.format("Loaded %d %s.", batch.loaded, cargoName) if batch.partials and batch.partials > 0 then - txt = txt .. " Some sets could not be fully loaded." + txt = txt .. " " .. self.gettext:GetEntry("LOADED_BATCH_PARTIAL",self.locale) + --txt = txt .. " Some sets could not be fully loaded." end self:_SendMessage(txt, 10, false, batch.group) end @@ -6339,7 +5884,9 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) if not self.dropcratesanywhere then local inzone, zoneName, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.DROP) if not inzone then - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + local msg = self.gettext:GetEntry("NOT_CLOSE_ENOUGH_DROP",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) if not self.debug then return self end @@ -6348,14 +5895,18 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) -- Check if doors must be open if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_DROP_CARGO",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) if not self.debug then return self end end -- Check if the crate grouping data is available local unitName = Unit:GetName() if not self.CrateGroupList or not self.CrateGroupList[unitName] then - self:_SendMessage("No crate groups found for this unit!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_CRATE_GROUPS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No crate groups found for this unit!", 10, false, Group) if not self.debug then return self end return self end @@ -6363,14 +5914,18 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) -- Find the selected chunk/set by index local chunk = self.CrateGroupList[unitName][setIndex] if not chunk then - self:_SendMessage("No crate set found or index invalid!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_CRATE_SET",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No crate set found or index invalid!", 10, false, Group) if not self.debug then return self end return self end -- Check if the chunk is empty if #chunk == 0 then - self:_SendMessage("No crate found in that set!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_CRATE_IN_SET",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No crate found in that set!", 10, false, Group) if not self.debug then return self end return self end @@ -6385,9 +5940,13 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) end if not grounded and not hoverunload then if isHerc then - self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED_AIRDROP",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else - self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED_HOVER",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end if not self.debug then return self end return self @@ -6396,7 +5955,9 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) -- Get the first crate from this set local crateObj = chunk[1] if not crateObj then - self:_SendMessage("No crate found in that set!", 10, false, Group) + local msg = self.gettext:GetEntry("NO_CRATE_IN_SET",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("No crate found in that set!", 10, false, Group) if not self.debug then return self end return self end @@ -6414,12 +5975,21 @@ local cname = crateObj:GetName() or "Unknown" local count = #chunk if needed > 1 then if count == needed then - self:_SendMessage(string.format("Dropped %d %s.", 1, cname), 10, false, Group) + local msg = self.gettext:GetEntry("DROPPED_FULL",self.locale) + msg = string.format(msg,1, cname) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Dropped %d %s.", 1, cname), 10, false, Group) else - self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.", count, needed, cname), 15, false, Group) + local msg = self.gettext:GetEntry("DROPPED_PARTIAL",self.locale) + msg = string.format(msg,count, needed, cname) + self:_SendMessage(msg, 15, false, Group) + --self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.", count, needed, cname), 15, false, Group) end else -self:_SendMessage(string.format("Dropped %d %s(s).", count, cname), 10, false, Group) +local msg = self.gettext:GetEntry("DROPPED_SETS",self.locale) +msg = string.format(msg,count, cname) +self:_SendMessage(msg, 10, false, Group) +--self:_SendMessage(string.format("Dropped %d %s(s).", count, cname), 10, false, Group) end -- Rebuild the cargo list to remove the dropped crates local loadedData = self.Loaded_Cargo[unitName] @@ -6461,13 +6031,13 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) if topCrates.DropCratesMenu then topCrates.DropCratesMenu:RemoveSubMenus() else - topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) + topCrates.DropCratesMenu = MENU_GROUP:New(Group, self.gettext:GetEntry("MENU_DROP_CRATES",self.locale), topCrates) end local dropCratesMenu = topCrates.DropCratesMenu local loadedData = self.Loaded_Cargo[Unit:GetName()] if not loadedData or not loadedData.Cargo then - MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_NO_CRATES_TO_DROP",self.locale),dropCratesMenu,function() end) return end @@ -6486,7 +6056,7 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) end if dropableCrates==0 then - MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_NO_CRATES_TO_DROP",self.locale),dropCratesMenu,function() end) return end @@ -6497,7 +6067,7 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) -------------------------------------------------------------------- -- classic menu -------------------------------------------------------------------- - MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP_ALL_CRATES",self.locale),dropCratesMenu,self._UnloadCrates,self,Group,Unit) self.CrateGroupList=self.CrateGroupList or{} self.CrateGroupList[Unit:GetName()]={} @@ -6518,7 +6088,7 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) i=i+needed end if sets==1 then - MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP",self.locale),parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) local uName=UnitArg:GetName() for k=1,qty do local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] @@ -6535,8 +6105,9 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) end,self,Group,Unit,cName,needed,1) else for q=1,sets do - local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and "s" or ""),parentMenu) - MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) + local qm=MENU_GROUP:New(Group,string.format(self.gettext:GetEntry("MENU_DROP_N_SETS",self.locale),q,q>1 and "s" or ""),parentMenu) + --local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and "s" or ""),parentMenu) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP",self.locale),qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) local uName=UnitArg:GetName() for k=1,qty do local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] @@ -6571,10 +6142,10 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) -------------------------------------------------------------------- -- one-step (enhanced) menu -------------------------------------------------------------------- - local mAll=MENU_GROUP:New(Group,"Drop ALL crates",dropCratesMenu) - MENU_GROUP_COMMAND:New(Group,"Drop",mAll,self._UnloadCrates,self,Group,Unit) + local mAll=MENU_GROUP:New(Group,self.gettext:GetEntry("MENU_DROP_ALL_CRATES",self.locale),dropCratesMenu) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP",self.locale),mAll,self._UnloadCrates,self,Group,Unit) if not ( self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) ) then - MENU_GROUP_COMMAND:New(Group,"Drop and build",mAll,self._DropAndBuild,self,Group,Unit) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP_AND_BUILD",self.locale),mAll,self._DropAndBuild,self,Group,Unit) end self.CrateGroupList=self.CrateGroupList or{} @@ -6596,7 +6167,7 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) i=i+needed end if sets==1 then - MENU_GROUP_COMMAND:New(Group,"Drop",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP",self.locale),parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) local uName=UnitArg:GetName() for k=1,qty do local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] @@ -6612,7 +6183,7 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) end end,self,Group,Unit,cName,needed,1) if not ( self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) ) then - MENU_GROUP_COMMAND:New(Group,"Drop and build",parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP_AND_BUILD",self.locale),parentMenu,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) local uName=UnitArg:GetName() for k=1,qty do local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] @@ -6631,8 +6202,9 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) end else for q=1,sets do - local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and "s" or ""),parentMenu) - MENU_GROUP_COMMAND:New(Group,"Drop",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) + local qm=MENU_GROUP:New(Group,string.format(self.gettext:GetEntry("MENU_DROP_N_SETS",self.locale),q,q>1 and "s" or ""),parentMenu) + --local qm=MENU_GROUP:New(Group,string.format("Drop %d Set%s",q,q>1 and "s" or ""),parentMenu) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP",self.locale),qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) local uName=UnitArg:GetName() for k=1,qty do local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] @@ -6648,7 +6220,7 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) end end,self,Group,Unit,cName,needed,q) if not ( self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) ) then - MENU_GROUP_COMMAND:New(Group,"Drop and build",qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) + MENU_GROUP_COMMAND:New(Group,self.gettext:GetEntry("MENU_DROP_AND_BUILD",self.locale),qm,function(selfArg,GroupArg,UnitArg,cNameArg,neededArg,qty) local uName=UnitArg:GetName() for k=1,qty do local lst=selfArg.CrateGroupList and selfArg.CrateGroupList[uName] @@ -6705,7 +6277,9 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID, qty) end if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) + local msg = self.gettext:GetEntry("OPEN_DOORS_UNLOAD_TROOPS",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) if not self.debug then return self end end @@ -6721,14 +6295,20 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID, qty) if self.Loaded_Cargo[unitName] and (grounded or hoverunload) then if not droppingatbase or self.debug then if not self.TroopsIDToChunk or not self.TroopsIDToChunk[chunkID] then - self:_SendMessage(string.format("No troop cargo chunk found for ID %d!", chunkID), 10, false, Group) + local msg = self.gettext:GetEntry("NO_TROOP_CHUNK",self.locale) + msg = string.format(msg,chunkID) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("No troop cargo chunk found for ID %d!", chunkID), 10, false, Group) if not self.debug then return self end return self end local chunk = self.TroopsIDToChunk[chunkID] if not chunk or #chunk == 0 then - self:_SendMessage(string.format("Troop chunk is empty for ID %d!", chunkID), 10, false, Group) + local msg = self.gettext:GetEntry("TROOP_CHUNK_EMPTY",self.locale) + msg = string.format(msg,chunkID) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Troop chunk is empty for ID %d!", chunkID), 10, false, Group) if not self.debug then return self end return self end @@ -6817,11 +6397,16 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID, qty) parts[#parts + 1] = tostring(nCount).."x Engineers "..nName end if #parts > 0 then - self:_SendMessage("Dropped "..table.concat(parts, ", ").." into action!", 10, false, Group) + local msg = self.gettext:GetEntry("DROPPED_INTO_ACTION",self.locale) + msg = string.format(msg,table.concat(parts, ", ")) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Dropped "..table.concat(parts, ", ").." into action!", 10, false, Group) end else -- Return to base logic, remove ONLY the first cargo - self:_SendMessage("Troops have returned to base!", 10, false, Group) + local msg = self.gettext:GetEntry("TROOPS_RETURNED",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Troops have returned to base!", 10, false, Group) self:__TroopsRTB(1, Group, Unit, zonename, zone) if self.TroopsIDToChunk and self.TroopsIDToChunk[chunkID] then @@ -6872,9 +6457,13 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID, qty) else local isHerc = self:IsFixedWing(Unit) if isHerc then - self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED_AIRDROP",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else - self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + local msg = self.gettext:GetEntry("NOTHING_LOADED_HOVER",self.locale) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end end return self @@ -6895,10 +6484,10 @@ function CTLD:_RefreshDropTroopsMenu(Group, Unit) if dropTroopsMenu then dropTroopsMenu:RemoveSubMenus() else - dropTroopsMenu = MENU_GROUP:New(theGroup, "Drop Troops", topTroops) + dropTroopsMenu = MENU_GROUP:New(theGroup, self.gettext:GetEntry("MENU_DROP_TROOPS",self.locale), topTroops) topTroops.DropTroopsMenu = dropTroopsMenu end - MENU_GROUP_COMMAND:New(theGroup, "Drop ALL troops", dropTroopsMenu, self._UnloadTroops, self, theGroup, theUnit) + MENU_GROUP_COMMAND:New(theGroup, self.gettext:GetEntry("MENU_DROP_ALL_TROOPS",self.locale), dropTroopsMenu, self._UnloadTroops, self, theGroup, theUnit) local loadedData = self.Loaded_Cargo[theUnit:GetName()] if not loadedData or not loadedData.Cargo then return end @@ -6925,13 +6514,14 @@ function CTLD:_RefreshDropTroopsMenu(Group, Unit) local chunkID = objList[1]:GetID() self.TroopsIDToChunk[chunkID] = objList - local label = string.format("Drop %s (%d)", tName, count) + local label = string.format(self.gettext:GetEntry("MENU_DROP_N_TROOPS",self.locale), tName, count) if count == 1 then MENU_GROUP_COMMAND:New(theGroup, label, dropTroopsMenu, self._UnloadSingleTroopByID, self, theGroup, theUnit, chunkID, 1) else local parentMenu = MENU_GROUP:New(theGroup, label, dropTroopsMenu) for q = 1, count do - MENU_GROUP_COMMAND:New(theGroup, string.format("Drop (%d) %s", q, tName), parentMenu, self._UnloadSingleTroopByID, self, theGroup, theUnit, chunkID, q) + MENU_GROUP_COMMAND:New(theGroup, string.format(self.gettext:GetEntry("MENU_DROP_N_TROOPS",self.locale), q, tName), parentMenu, self._UnloadSingleTroopByID, self, theGroup, theUnit, chunkID, q) + --MENU_GROUP_COMMAND:New(theGroup, string.format("Drop (%d) %s", q, tName), parentMenu, self._UnloadSingleTroopByID, self, theGroup, theUnit, chunkID, q) end end end @@ -7631,7 +7221,8 @@ function CTLD:DropBeaconNow(Unit) local FM = FMbeacon.frequency -- MHz local VHF = VHFbeacon.frequency * 1000 -- KHz local UHF = UHFbeacon.frequency -- MHz - local text = string.format("Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF) + local text = string.format(self.gettext:GetEntry("DROPPED_BEACON",self.locale), Name, FM, VHF, UHF) + --local text = string.format("Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF) self:_SendMessage(text,15,false,Unit:GetGroup()) @@ -7954,7 +7545,10 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) end local txt = "smoking" if Flare then txt = "flaring" end - self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) + local msg = self.gettext:GetEntry("ROGER_ZONE",self.locale) + msg = string.format(msg,txt, zonename) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) smoked = true end end @@ -7962,7 +7556,10 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) end if not smoked then local distance = UTILS.MetersToNM(self.smokedistance) - self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) + local msg = self.gettext:GetEntry("NOT_CLOSE_ENOUGH_ZONE_NM",self.locale) + msg = string.format(msg,distance) + self:_SendMessage(msg, 10, false, Group) + --self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) end return self end @@ -8106,11 +7703,13 @@ end if not inhover then htxt = "false" end local text = "" if _SETTINGS:IsMetric() then - text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + text = string.format(self.gettext:GetEntry("HOVER_PARAMS_METRIC",self.locale), self.minimumHoverHeight, self.maximumHoverHeight, htxt) + --text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) else local minheight = UTILS.MetersToFeet(self.minimumHoverHeight) local maxheight = UTILS.MetersToFeet(self.maximumHoverHeight) - text = string.format("Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s", minheight, maxheight, htxt) + text = string.format(self.gettext:GetEntry("HOVER_PARAMS_IMPERIAL",self.locale), minheight, maxheight, htxt) + --text = string.format("Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) return self @@ -8128,11 +7727,13 @@ end if _SETTINGS:IsImperial() then local minheight = UTILS.MetersToFeet(self.FixedMinAngels) local maxheight = UTILS.MetersToFeet(self.FixedMaxAngels) - text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + text = string.format(self.gettext:GetEntry("FLIGHT_PARAMS_IMPERIAL",self.locale), minheight, maxheight, htxt) + --text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) else local minheight = self.FixedMinAngels local maxheight = self.FixedMaxAngels - text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) + text = string.format(self.gettext:GetEntry("FLIGHT_PARAMS_METRIC",self.locale), minheight, maxheight, htxt) + --text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) return self @@ -9146,6 +8747,7 @@ end function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) self:I(self.lid .. "Started ("..self.version..")") + self:_InitLocalization() if self.enableHercules then self.enableFixedWing = true end if self.UserSetGroup then self.PilotGroups = self.UserSetGroup @@ -9917,673 +9519,3 @@ end return self end end -- end do - -do ---- **Hercules Cargo AIR Drop Events** by Anubis Yinepu --- Moose CTLD OO refactoring by Applevangelist ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO CTLD_HERCULES ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- This script will only work for the Herculus mod by Anubis, and only for **Air Dropping** cargo from the Hercules. --- It *DOES NOT* work with the purchaseable Hercules module from ED. --- Use the standard Moose CTLD if you want to unload on the ground. --- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file --- Except for Ammo pallets, this script will spawn whatever payload gets launched from pylons 11, 12 and 13 --- Pylons 11, 12 and 13 are moveable within the Hercules cargobay area --- Ammo pallets can only be jettisoned from these pylons with no benefit to DCS world --- To benefit DCS world, Ammo pallets need to be off/on loaded using DCS arming and refueling window --- Cargo_Container_Enclosed = true: Cargo enclosed in container with parachute, need to be dropped from 100m (300ft) or more, except when parked on ground --- Cargo_Container_Enclosed = false: Open cargo with no parachute, need to be dropped from 10m (30ft) or less - ------------------------------------------------------- ---- **CTLD_HERCULES** class, extends Core.Base#BASE --- @type CTLD_HERCULES --- @field #string ClassName --- @field #string lid --- @field #string Name --- @field #string Version --- @extends Core.Base#BASE -CTLD_HERCULES = { - ClassName = "CTLD_HERCULES", - lid = "", - Name = "", - Version = "0.0.3", -} - ---- Define cargo types. --- @type CTLD_HERCULES.Types --- @field #table Type Name of cargo type, container (boolean) in container or not. -CTLD_HERCULES.Types = { - ["ATGM M1045 HMMWV TOW Air [7183lb]"] = {['name'] = "M1045 HMMWV TOW", ['container'] = true}, - ["ATGM M1045 HMMWV TOW Skid [7073lb]"] = {['name'] = "M1045 HMMWV TOW", ['container'] = false}, - ["APC M1043 HMMWV Armament Air [7023lb]"] = {['name'] = "M1043 HMMWV Armament", ['container'] = true}, - ["APC M1043 HMMWV Armament Skid [6912lb]"] = {['name'] = "M1043 HMMWV Armament", ['container'] = false}, - ["SAM Avenger M1097 Air [7200lb]"] = {['name'] = "M1097 Avenger", ['container'] = true}, - ["SAM Avenger M1097 Skid [7090lb]"] = {['name'] = "M1097 Avenger", ['container'] = false}, - ["APC Cobra Air [10912lb]"] = {['name'] = "Cobra", ['container'] = true}, - ["APC Cobra Skid [10802lb]"] = {['name'] = "Cobra", ['container'] = false}, - ["APC M113 Air [21624lb]"] = {['name'] = "M-113", ['container'] = true}, - ["APC M113 Skid [21494lb]"] = {['name'] = "M-113", ['container'] = false}, - ["Tanker M978 HEMTT [34000lb]"] = {['name'] = "M978 HEMTT Tanker", ['container'] = false}, - ["HEMTT TFFT [34400lb]"] = {['name'] = "HEMTT TFFT", ['container'] = false}, - ["SPG M1128 Stryker MGS [33036lb]"] = {['name'] = "M1128 Stryker MGS", ['container'] = false}, - ["AAA Vulcan M163 Air [21666lb]"] = {['name'] = "Vulcan", ['container'] = true}, - ["AAA Vulcan M163 Skid [21577lb]"] = {['name'] = "Vulcan", ['container'] = false}, - ["APC M1126 Stryker ICV [29542lb]"] = {['name'] = "M1126 Stryker ICV", ['container'] = false}, - ["ATGM M1134 Stryker [30337lb]"] = {['name'] = "M1134 Stryker ATGM", ['container'] = false}, - ["APC LAV-25 Air [22520lb]"] = {['name'] = "LAV-25", ['container'] = true}, - ["APC LAV-25 Skid [22514lb]"] = {['name'] = "LAV-25", ['container'] = false}, - ["M1025 HMMWV Air [6160lb]"] = {['name'] = "Hummer", ['container'] = true}, - ["M1025 HMMWV Skid [6050lb]"] = {['name'] = "Hummer", ['container'] = false}, - ["IFV M2A2 Bradley [34720lb]"] = {['name'] = "M-2 Bradley", ['container'] = false}, - ["IFV MCV-80 [34720lb]"] = {['name'] = "MCV-80", ['container'] = false}, - ["IFV BMP-1 [23232lb]"] = {['name'] = "BMP-1", ['container'] = false}, - ["IFV BMP-2 [25168lb]"] = {['name'] = "BMP-2", ['container'] = false}, - ["IFV BMP-3 [32912lb]"] = {['name'] = "BMP-3", ['container'] = false}, - ["ARV BRDM-2 Air [12320lb]"] = {['name'] = "BRDM-2", ['container'] = true}, - ["ARV BRDM-2 Skid [12210lb]"] = {['name'] = "BRDM-2", ['container'] = false}, - ["APC BTR-80 Air [23936lb]"] = {['name'] = "BTR-80", ['container'] = true}, - ["APC BTR-80 Skid [23826lb]"] = {['name'] = "BTR-80", ['container'] = false}, - ["APC BTR-82A Air [24998lb]"] = {['name'] = "BTR-82A", ['container'] = true}, - ["APC BTR-82A Skid [24888lb]"] = {['name'] = "BTR-82A", ['container'] = false}, - ["SAM ROLAND ADS [34720lb]"] = {['name'] = "Roland Radar", ['container'] = false}, - ["SAM ROLAND LN [34720b]"] = {['name'] = "Roland ADS", ['container'] = false}, - ["SAM SA-13 STRELA [21624lb]"] = {['name'] = "Strela-10M3", ['container'] = false}, - ["AAA ZSU-23-4 Shilka [32912lb]"] = {['name'] = "ZSU-23-4 Shilka", ['container'] = false}, - ["SAM SA-19 Tunguska 2S6 [34720lb]"] = {['name'] = "2S6 Tunguska", ['container'] = false}, - ["Transport UAZ-469 Air [3747lb]"] = {['name'] = "UAZ-469", ['container'] = true}, - ["Transport UAZ-469 Skid [3630lb]"] = {['name'] = "UAZ-469", ['container'] = false}, - ["AAA GEPARD [34720lb]"] = {['name'] = "Gepard", ['container'] = false}, - ["SAM CHAPARRAL Air [21624lb]"] = {['name'] = "M48 Chaparral", ['container'] = true}, - ["SAM CHAPARRAL Skid [21516lb]"] = {['name'] = "M48 Chaparral", ['container'] = false}, - ["SAM LINEBACKER [34720lb]"] = {['name'] = "M6 Linebacker", ['container'] = false}, - ["Transport URAL-375 [14815lb]"] = {['name'] = "Ural-375", ['container'] = false}, - ["Transport M818 [16000lb]"] = {['name'] = "M 818", ['container'] = false}, - ["IFV MARDER [34720lb]"] = {['name'] = "Marder", ['container'] = false}, - ["Transport Tigr Air [15900lb]"] = {['name'] = "Tigr_233036", ['container'] = true}, - ["Transport Tigr Skid [15730lb]"] = {['name'] = "Tigr_233036", ['container'] = false}, - ["IFV TPZ FUCH [33440lb]"] = {['name'] = "TPZ", ['container'] = false}, - ["IFV BMD-1 Air [18040lb]"] = {['name'] = "BMD-1", ['container'] = true}, - ["IFV BMD-1 Skid [17930lb]"] = {['name'] = "BMD-1", ['container'] = false}, - ["IFV BTR-D Air [18040lb]"] = {['name'] = "BTR_D", ['container'] = true}, - ["IFV BTR-D Skid [17930lb]"] = {['name'] = "BTR_D", ['container'] = false}, - ["EWR SBORKA Air [21624lb]"] = {['name'] = "Dog Ear radar", ['container'] = true}, - ["EWR SBORKA Skid [21624lb]"] = {['name'] = "Dog Ear radar", ['container'] = false}, - ["ART 2S9 NONA Air [19140lb]"] = {['name'] = "SAU 2-C9", ['container'] = true}, - ["ART 2S9 NONA Skid [19030lb]"] = {['name'] = "SAU 2-C9", ['container'] = false}, - ["ART GVOZDIKA [34720lb]"] = {['name'] = "SAU Gvozdika", ['container'] = false}, - ["APC MTLB Air [26400lb]"] = {['name'] = "MTLB", ['container'] = true}, - ["APC MTLB Skid [26290lb]"] = {['name'] = "MTLB", ['container'] = false}, -} - ---- Cargo Object --- @type CTLD_HERCULES.CargoObject --- @field #number Cargo_Drop_Direction --- @field #table Cargo_Contents --- @field #string Cargo_Type_name --- @field #boolean Container_Enclosed --- @field #boolean ParatrooperGroupSpawn --- @field #number Cargo_Country --- @field #boolean offload_cargo --- @field #boolean all_cargo_survive_to_the_ground --- @field #boolean all_cargo_gets_destroyed --- @field #boolean destroy_cargo_dropped_without_parachute --- @field Core.Timer#TIMER scheduleFunctionID - ---- [User] Instantiate a new object --- @param #CTLD_HERCULES self --- @param #string Coalition Coalition side, "red", "blue" or "neutral" --- @param #string Alias Name of this instance --- @param Ops.CTLD#CTLD CtldObject CTLD instance to link into --- @return #CTLD_HERCULES self --- @usage --- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: --- --- my_ctld.enableFixedWing = false -- avoid dual loading via CTLD F10 and F8 ground crew --- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) --- --- You also need: --- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate). --- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable. --- There's a **quick check output in the `dcs.log`** which tells you what's there and what not. --- E.g.: --- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING) --- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING) --- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING) --- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK) --- --- Expected template names are the ones in the rounded brackets. --- --- ### HINTS --- --- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does --- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD. --- **Do not use** the **splash damage** script together with this, your cargo will just explode when reaching the ground! --- --- ### Airdrops --- --- There are two ways of airdropping: --- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu) --- 2) Higher up and slow (>100m AGL) - here you can drop paratroopers and cargo which has "Air" at the end of the cargo name (loaded via F8 Ground Crew menu) --- --- ### General --- --- Use either this method to integrate the Hercules **or** the one from the "normal" CTLD. Never both! -function CTLD_HERCULES:New(Coalition, Alias, CtldObject) - -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #CTLD_HERCULES - - --set Coalition - if Coalition and type(Coalition)=="string" then - if Coalition=="blue" then - self.coalition=coalition.side.BLUE - self.coalitiontxt = Coalition - elseif Coalition=="red" then - self.coalition=coalition.side.RED - self.coalitiontxt = Coalition - elseif Coalition=="neutral" then - self.coalition=coalition.side.NEUTRAL - self.coalitiontxt = Coalition - else - self:E("ERROR: Unknown coalition in CTLD!") - end - else - self.coalition = Coalition - self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) - end - - -- Set alias. - if Alias then - self.alias=tostring(Alias) - else - self.alias="UNHCR" - if self.coalition then - if self.coalition==coalition.side.RED then - self.alias="Red CTLD Hercules" - elseif self.coalition==coalition.side.BLUE then - self.alias="Blue CTLD Hercules" - end - end - end - - -- Set some string id for output to DCS.log file. - self.lid=string.format("%s (%s) | ", self.alias, self.coalitiontxt) - - self.infantrytemplate = "Infantry" -- template for a group of 10 paratroopers - self.CTLD = CtldObject -- Ops.CTLD#CTLD - - self.verbose = true - - self.j = 0 - self.carrierGroups = {} - self.Cargo = {} - self.ParatrooperCount = {} - - self.ObjectTracker = {} - - -- Set some string id for output to DCS.log file. - self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") - - self:HandleEvent(EVENTS.Shot, self._HandleShot) - - self:I(self.lid .. "Started") - - self:CheckTemplates() - - return self -end - ---- [Internal] Function to check availability of templates --- @param #CTLD_HERCULES self --- @return #CTLD_HERCULES self -function CTLD_HERCULES:CheckTemplates() - self:T(self.lid .. 'CheckTemplates') - -- inject Paratroopers - self.Types["Paratroopers 10"] = { - name = self.infantrytemplate, - container = false, - available = false, - } - local missing = {} - local nomissing = 0 - local found = {} - local nofound = 0 - - -- list of groundcrew loadables - for _index,_tab in pairs (self.Types) do - local outcometxt = "MISSING" - if _DATABASE.Templates.Groups[_tab.name] then - outcometxt = "OK" - self.Types[_index].available= true - found[_tab.name] = true - else - self.Types[_index].available = false - missing[_tab.name] = true - end - if self.verbose then - self:I(string.format(self.lid .. "Checking template for %s (%s) ... %s", _index,_tab.name,outcometxt)) - end - end - for _,_name in pairs(found) do - nofound = nofound + 1 - end - for _,_name in pairs(missing) do - nomissing = nomissing + 1 - end - self:I(string.format(self.lid .. "Template Check Summary: Found %d, Missing %d, Total %d",nofound,nomissing,nofound+nomissing)) - return self -end - ---- [Internal] Function to spawn a soldier group of 10 units --- @param #CTLD_HERCULES self --- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#COORDINATE Cargo_Drop_Position --- @param #string Cargo_Type_name --- @param #number CargoHeading --- @param #number Cargo_Country --- @param #number GroupSpacing --- @return #CTLD_HERCULES self -function CTLD_HERCULES:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, Cargo_Country, GroupSpacing) - --- TODO: Rework into Moose Spawns - self:T(self.lid .. 'Soldier_SpawnGroup') - self:T(Cargo_Drop_Position) - -- create a matching #CTLD_CARGO type - local InjectTroopsType = CTLD_CARGO:New(nil,self.infantrytemplate,{self.infantrytemplate},CTLD_CARGO.Enum.TROOPS,true,true,10,nil,false,80) - -- get a #ZONE object - local position = Cargo_Drop_Position:GetVec2() - local dropzone = ZONE_RADIUS:New("Infantry " .. math.random(1,10000),position,100) - -- and go: - self.CTLD:InjectTroops(dropzone,InjectTroopsType) - return self -end - ---- [Internal] Function to spawn a group --- @param #CTLD_HERCULES self --- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#COORDINATE Cargo_Drop_Position --- @param #string Cargo_Type_name --- @param #number CargoHeading --- @param #number Cargo_Country --- @return #CTLD_HERCULES self -function CTLD_HERCULES:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, Cargo_Country) - --- TODO: Rework into Moose Spawns - self:T(self.lid .. "Cargo_SpawnGroup") - self:T(Cargo_Type_name) - if Cargo_Type_name ~= 'Container red 1' then - -- create a matching #CTLD_CARGO type - local InjectVehicleType = CTLD_CARGO:New(nil,Cargo_Type_name,{Cargo_Type_name},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000) - -- get a #ZONE object - local position = Cargo_Drop_Position:GetVec2() - local dropzone = ZONE_RADIUS:New("Vehicle " .. math.random(1,10000),position,100) - -- and go: - self.CTLD:InjectVehicles(dropzone,InjectVehicleType) - end - return self -end - ---- [Internal] Function to spawn static cargo --- @param #CTLD_HERCULES self --- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#COORDINATE Cargo_Drop_Position --- @param #string Cargo_Type_name --- @param #number CargoHeading --- @param #boolean dead --- @param #number Cargo_Country --- @return #CTLD_HERCULES self -function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, dead, Cargo_Country) - --- TODO: Rework into Moose Static Spawns - self:T(self.lid .. "Cargo_SpawnStatic") - self:T("Static " .. Cargo_Type_name .. " Dead " .. tostring(dead)) - local position = Cargo_Drop_Position:GetVec2() - local Zone = ZONE_RADIUS:New("Cargo Static " .. math.random(1,10000),position,100) - if not dead then - local injectstatic = CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) - self.CTLD:InjectStatics(Zone,injectstatic,true,true) - end - return self -end - ---- [Internal] Function to spawn cargo by type at position --- @param #CTLD_HERCULES self --- @param #string Cargo_Type_name --- @param Core.Point#COORDINATE Cargo_Drop_Position --- @return #CTLD_HERCULES self -function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name, _pos) - local theCargo = self.CTLD:_FindCratesCargoObject(_name) -- #CTLD_CARGO - if theCargo then - self.CTLD.CrateCounter = self.CTLD.CrateCounter + 1 - local CCat, CType, CShape = theCargo:GetStaticTypeAndShape() - local basetype = CType or self.CTLD.basetype or "container_cargo" - CCat = CCat or "Cargos" - local theStatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) - :InitCargoMass(theCargo.PerCrateMass) - :InitCargo(self.CTLD.enableslingload) - :InitCoordinate(_pos) - if CShape then - theStatic:InitShape(CShape) - end - theStatic:Spawn(270,_name .. "-Container-".. math.random(1,100000)) - - self.CTLD.Spawned_Crates[self.CTLD.CrateCounter] = theStatic - local newCargo = CTLD_CARGO:New(self.CTLD.CargoCounter, theCargo.Name, theCargo.Templates, theCargo.CargoType, true, false, theCargo.CratesNeeded, self.CTLD.Spawned_Crates[self.CTLD.CrateCounter], true, theCargo.PerCrateMass, nil, theCargo.Subcategory) - local map=theCargo:GetStaticResourceMap() - newCargo:SetStaticResourceMap(map) - table.insert(self.CTLD.Spawned_Cargo, newCargo) - - newCargo:SetWasDropped(true) - newCargo:SetHasMoved(true) - end - return self -end - ---- [Internal] Spawn cargo objects --- @param #CTLD_HERCULES self --- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param #number Cargo_Drop_Direction --- @param Core.Point#COORDINATE Cargo_Content_position --- @param #string Cargo_Type_name --- @param #boolean Cargo_over_water --- @param #boolean Container_Enclosed --- @param #boolean ParatrooperGroupSpawn --- @param #boolean offload_cargo --- @param #boolean all_cargo_survive_to_the_ground --- @param #boolean all_cargo_gets_destroyed --- @param #boolean destroy_cargo_dropped_without_parachute --- @param #number Cargo_Country --- @return #CTLD_HERCULES self -function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direction, Cargo_Content_position, Cargo_Type_name, Cargo_over_water, Container_Enclosed, ParatrooperGroupSpawn, offload_cargo, all_cargo_survive_to_the_ground, all_cargo_gets_destroyed, destroy_cargo_dropped_without_parachute, Cargo_Country) - self:T(self.lid .. 'Cargo_SpawnObjects') - - local CargoHeading = self.CargoHeading - - if offload_cargo == true or ParatrooperGroupSpawn == true then - if ParatrooperGroupSpawn == true then - self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) - else - self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) - end - else - if all_cargo_gets_destroyed == true or Cargo_over_water == true then - - else - if all_cargo_survive_to_the_ground == true then - if ParatrooperGroupSpawn == true then - self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) - else - self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) - end - if Container_Enclosed == true then - if ParatrooperGroupSpawn == false then - self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) - end - end - end - if destroy_cargo_dropped_without_parachute == true then - if Container_Enclosed == true then - if ParatrooperGroupSpawn == true then - self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) - else - if self.CTLD.dropAsCargoCrate then - self:Cargo_SpawnDroppedAsCargo(Cargo_Type_name, Cargo_Content_position) - else - self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) - self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) - end - end - else - self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) - end - end - end - end - return self -end - ---- [Internal] Function to calculate object height --- @param #CTLD_HERCULES self --- @param Wrapper.Group#GROUP group The group for which to calculate the height --- @return #number height over ground -function CTLD_HERCULES:Calculate_Object_Height_AGL(group) - self:T(self.lid .. "Calculate_Object_Height_AGL") - if group.ClassName and group.ClassName == "GROUP" then - local gcoord = group:GetCoordinate() - local height = group:GetHeight() - local lheight = gcoord:GetLandHeight() - self:T(self.lid .. "Height " .. height - lheight) - return height - lheight - else - -- DCS object - if group:isExist() then - local dcsposition = group:getPosition().p - local dcsvec2 = {x = dcsposition.x, y = dcsposition.z} -- Vec2 - local height = math.floor(group:getPosition().p.y - land.getHeight(dcsvec2)) - self.ObjectTracker[group.id_] = dcsposition -- Vec3 - self:T(self.lid .. "Height " .. height) - return height - else - return 0 - end - end -end - ---- [Internal] Function to check surface type --- @param #CTLD_HERCULES self --- @param Wrapper.Group#GROUP group The group for which to calculate the height --- @return #number height over ground -function CTLD_HERCULES:Check_SurfaceType(object) - self:T(self.lid .. "Check_SurfaceType") - -- LAND,--1 SHALLOW_WATER,--2 WATER,--3 ROAD,--4 RUNWAY--5 - if object:isExist() then - return land.getSurfaceType({x = object:getPosition().p.x, y = object:getPosition().p.z}) - else - return 1 - end -end - ---- [Internal] Function to track cargo objects --- @param #CTLD_HERCULES self --- @param #CTLD_HERCULES.CargoObject cargo --- @param Wrapper.Group#GROUP initiator --- @return #number height over ground -function CTLD_HERCULES:Cargo_Track(cargo, initiator) - self:T(self.lid .. "Cargo_Track") - local Cargo_Drop_initiator = initiator - if cargo.Cargo_Contents ~= nil then - if self:Calculate_Object_Height_AGL(cargo.Cargo_Contents) < 10 then --pallet less than 5m above ground before spawning - if self:Check_SurfaceType(cargo.Cargo_Contents) == 2 or self:Check_SurfaceType(cargo.Cargo_Contents) == 3 then - cargo.Cargo_over_water = true--pallets gets destroyed in water - end - local dcsvec3 = self.ObjectTracker[cargo.Cargo_Contents.id_] or initiator:GetVec3() -- last known position - self:T("SPAWNPOSITION: ") - self:T({dcsvec3}) - local Vec2 = { - x=dcsvec3.x, - y=dcsvec3.z, - } - local vec3 = COORDINATE:NewFromVec2(Vec2) - self.ObjectTracker[cargo.Cargo_Contents.id_] = nil - self:Cargo_SpawnObjects(Cargo_Drop_initiator,cargo.Cargo_Drop_Direction, vec3, cargo.Cargo_Type_name, cargo.Cargo_over_water, cargo.Container_Enclosed, cargo.ParatrooperGroupSpawn, cargo.offload_cargo, cargo.all_cargo_survive_to_the_ground, cargo.all_cargo_gets_destroyed, cargo.destroy_cargo_dropped_without_parachute, cargo.Cargo_Country) - if cargo.Cargo_Contents:isExist() then - cargo.Cargo_Contents:destroy()--remove pallet+parachute before hitting ground and replace with Cargo_SpawnContents - end - --timer.removeFunction(cargo.scheduleFunctionID) - cargo.scheduleFunctionID:Stop() - cargo = {} - end - end - return self -end - ---- [Internal] Function to calc north correction --- @param #CTLD_HERCULES self --- @param Core.Point#POINT_Vec3 point Position Vec3 --- @return #number north correction -function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_NorthCorrection(point) - self:T(self.lid .. "Calculate_Cargo_Drop_initiator_NorthCorrection") - if not point.z then --Vec2; convert to Vec3 - point.z = point.y - point.y = 0 - end - local lat, lon = coord.LOtoLL(point) - local north_posit = coord.LLtoLO(lat + 1, lon) - return math.atan2(north_posit.z - point.z, north_posit.x - point.x) -end - ---- [Internal] Function to calc initiator heading --- @param #CTLD_HERCULES self --- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @return #number north corrected heading -function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_Heading(Cargo_Drop_initiator) - self:T(self.lid .. "Calculate_Cargo_Drop_initiator_Heading") - local Heading = Cargo_Drop_initiator:GetHeading() - Heading = Heading + self:Calculate_Cargo_Drop_initiator_NorthCorrection(Cargo_Drop_initiator:GetVec3()) - if Heading < 0 then - Heading = Heading + (2 * math.pi)-- put heading in range of 0 to 2*pi - end - return Heading + 0.06 -- rad -end - ---- [Internal] Function to initialize dropped cargo --- @param #CTLD_HERCULES self --- @param Wrapper.Group#GROUP Initiator --- @param #table Cargo_Contents Table 'weapon' from event data --- @param #string Cargo_Type_name Name of this cargo --- @param #boolean Container_Enclosed Is container? --- @param #boolean SoldierGroup Is soldier group? --- @param #boolean ParatrooperGroupSpawnInit Is paratroopers? --- @return #CTLD_HERCULES self -function CTLD_HERCULES:Cargo_Initialize(Initiator, Cargo_Contents, Cargo_Type_name, Container_Enclosed, SoldierGroup, ParatrooperGroupSpawnInit) - self:T(self.lid .. "Cargo_Initialize") - local Cargo_Drop_initiator = Initiator:GetName() - if Cargo_Drop_initiator ~= nil then - if ParatrooperGroupSpawnInit == true then - self:T("Paratrooper Drop") - -- Paratroopers - if not self.ParatrooperCount[Cargo_Drop_initiator] then - self.ParatrooperCount[Cargo_Drop_initiator] = 1 - else - self.ParatrooperCount[Cargo_Drop_initiator] = self.ParatrooperCount[Cargo_Drop_initiator] + 1 - end - - local Paratroopers = self.ParatrooperCount[Cargo_Drop_initiator] - - self:T("Paratrooper Drop Number " .. self.ParatrooperCount[Cargo_Drop_initiator]) - - local SpawnParas = false - - if math.fmod(Paratroopers,10) == 0 then - SpawnParas = true - end - - self.j = self.j + 1 - self.Cargo[self.j] = {} - self.Cargo[self.j].Cargo_Drop_Direction = self:Calculate_Cargo_Drop_initiator_Heading(Initiator) - self.Cargo[self.j].Cargo_Contents = Cargo_Contents - self.Cargo[self.j].Cargo_Type_name = Cargo_Type_name - self.Cargo[self.j].Container_Enclosed = Container_Enclosed - self.Cargo[self.j].ParatrooperGroupSpawn = SpawnParas - self.Cargo[self.j].Cargo_Country = Initiator:GetCountry() - - if self:Calculate_Object_Height_AGL(Initiator) < 5.0 then --aircraft on ground - self.Cargo[self.j].offload_cargo = true - elseif self:Calculate_Object_Height_AGL(Initiator) < 10.0 then --aircraft less than 10m above ground - self.Cargo[self.j].all_cargo_survive_to_the_ground = true - elseif self:Calculate_Object_Height_AGL(Initiator) < 100.0 then --aircraft more than 10m but less than 100m above ground - self.Cargo[self.j].all_cargo_gets_destroyed = true - else - self.Cargo[self.j].all_cargo_gets_destroyed = false - end - - local timer = TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) - self.Cargo[self.j].scheduleFunctionID = timer - timer:Start(1,1,600) - - else - -- no paras - self.j = self.j + 1 - self.Cargo[self.j] = {} - self.Cargo[self.j].Cargo_Drop_Direction = self:Calculate_Cargo_Drop_initiator_Heading(Initiator) - self.Cargo[self.j].Cargo_Contents = Cargo_Contents - self.Cargo[self.j].Cargo_Type_name = Cargo_Type_name - self.Cargo[self.j].Container_Enclosed = Container_Enclosed - self.Cargo[self.j].ParatrooperGroupSpawn = false - self.Cargo[self.j].Cargo_Country = Initiator:GetCountry() - - if self:Calculate_Object_Height_AGL(Initiator) < 5.0 then--aircraft on ground - self.Cargo[self.j].offload_cargo = true - elseif self:Calculate_Object_Height_AGL(Initiator) < 10.0 then--aircraft less than 10m above ground - self.Cargo[self.j].all_cargo_survive_to_the_ground = true - elseif self:Calculate_Object_Height_AGL(Initiator) < 100.0 then--aircraft more than 10m but less than 100m above ground - self.Cargo[self.j].all_cargo_gets_destroyed = true - else - self.Cargo[self.j].destroy_cargo_dropped_without_parachute = true --aircraft more than 100m above ground - end - - local timer = TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) - self.Cargo[self.j].scheduleFunctionID = timer - timer:Start(1,1,600) - end - end - return self -end - ---- [Internal] Function to change cargotype per group (Wrench) --- @param #CTLD_HERCULES self --- @param #number key Carrier key id --- @param #string cargoType Type of cargo --- @param #number cargoNum Number of cargo objects --- @return #CTLD_HERCULES self -function CTLD_HERCULES:SetType(key,cargoType,cargoNum) - self:T(self.lid .. "SetType") - self.carrierGroups[key]['cargoType'] = cargoType - self.carrierGroups[key]['cargoNum'] = cargoNum - return self -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- EventHandlers ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- [Internal] Function to capture SHOT event --- @param #CTLD_HERCULES self --- @param Core.Event#EVENTDATA Cargo_Drop_Event The event data --- @return #CTLD_HERCULES self -function CTLD_HERCULES:_HandleShot(Cargo_Drop_Event) - self:T(self.lid .. "Shot Event ID:" .. Cargo_Drop_Event.id) - if Cargo_Drop_Event.id == EVENTS.Shot then - - local GT_Name = "" - local SoldierGroup = false - local ParatrooperGroupSpawnInit = false - - local GT_DisplayName = Weapon.getDesc(Cargo_Drop_Event.weapon).typeName:sub(15, -1)--Remove "weapons.bombs." from string - self:T(string.format("%sCargo_Drop_Event: %s", self.lid, Weapon.getDesc(Cargo_Drop_Event.weapon).typeName)) - - if (GT_DisplayName == "Squad 30 x Soldier [7950lb]") then - self:Cargo_Initialize(Cargo_Drop_Event.IniGroup, Cargo_Drop_Event.weapon, "Soldier M4 GRG", false, true, true) - end - - if self.Types[GT_DisplayName] then - local GT_Name = self.Types[GT_DisplayName]['name'] - local Cargo_Container_Enclosed = self.Types[GT_DisplayName]['container'] - self:Cargo_Initialize(Cargo_Drop_Event.IniGroup, Cargo_Drop_Event.weapon, GT_Name, Cargo_Container_Enclosed) - end - end - return self -end - ---- [Internal] Function to capture BIRTH event --- @param #CTLD_HERCULES self --- @param Core.Event#EVENTDATA event The event data --- @return #CTLD_HERCULES self -function CTLD_HERCULES:_HandleBirth(event) - -- not sure what this is needed for? I think this for setting generic crates "content" setting. - self:T(self.lid .. "Birth Event ID:" .. event.id) - return self -end - -end - -------------------------------------------------------------------- --- End Ops.CTLD.lua -------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/CTLD_CARGO.lua b/Moose Development/Moose/Ops/CTLD_CARGO.lua new file mode 100644 index 000000000..3c39233d9 --- /dev/null +++ b/Moose Development/Moose/Ops/CTLD_CARGO.lua @@ -0,0 +1,720 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- TODO CTLD_CARGO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +do + +------------------------------------------------------ +--- **CTLD_CARGO** class, extends Core.Base#BASE +-- @type CTLD_CARGO +-- @field #string ClassName Class name. +-- @field #number ID ID of this cargo. +-- @field #string Name Name for menu. +-- @field #string DisplayName Display name for menu/messages. +-- @field #table Templates Table of #POSITIONABLE objects. +-- @field #string CargoType Enumerator of Type. +-- @field #boolean HasBeenMoved Flag for moving. +-- @field #boolean LoadDirectly Flag for direct loading. +-- @field #number CratesNeeded Crates needed to build. +-- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. +-- @field #boolean HasBeenDropped True if dropped from heli. +-- @field #number PerCrateMass Mass in kg. +-- @field #number Stock Number of builds available, -1 for unlimited. +-- @field #string Subcategory Sub-category name. +-- @field #boolean DontShowInMenu Show this item in menu or not. +-- @field Core.Zone#ZONE Location Location (if set) where to get this cargo item. +-- @field #table ResourceMap Resource Map information table if it has been set for static cargo items. +-- @field #string StaticShape Individual shape if set. +-- @field #string StaticType Individual type if set. +-- @field #string StaticCategory Individual static category if set. +-- @field #list<#string> TypeNames Table of unit types able to pick this cargo up. +-- @field #number Stock0 Initial stock, if any given. +-- @extends Core.Base#BASE + +--- +-- @field #CTLD_CARGO CTLD_CARGO +CTLD_CARGO = { + ClassName = "CTLD_CARGO", + ID = 0, + Name = "none", + DisplayName = "none", + Templates = {}, + CargoType = "none", + HasBeenMoved = false, + LoadDirectly = false, + CratesNeeded = 0, + Positionable = nil, + HasBeenDropped = false, + PerCrateMass = 0, + Stock = nil, + Stock0 = nil, + Mark = nil, + DontShowInMenu = false, + Location = nil, + } + + --- Define cargo types. + -- @type CTLD_CARGO.Enum + -- @field #string VEHICLE + -- @field #string TROOPS + -- @field #string FOB + -- @field #string CRATE + -- @field #string REPAIR + -- @field #string ENGINEERS + -- @field #string STATIC + -- @field #string GCLOADABLE + CTLD_CARGO.Enum = { + VEHICLE = "Vehicle", -- #string vehicles + TROOPS = "Troops", -- #string troops + FOB = "FOB", -- #string FOB + CRATE = "Crate", -- #string crate + REPAIR = "Repair", -- #string repair + ENGINEERS = "Engineers", -- #string engineers + STATIC = "Static", -- #string statics + GCLOADABLE = "GC_Loadable", -- #string dynamiccargo + } + + --- Function to create new CTLD_CARGO object. + -- @param #CTLD_CARGO self + -- @param #number ID ID of this #CTLD_CARGO + -- @param #string Name Name for menu. + -- @param #table Templates Table of #POSITIONABLE objects. + -- @param #CTLD_CARGO.Enum Sorte Enumerator of Type. + -- @param #boolean HasBeenMoved Flag for moving. + -- @param #boolean LoadDirectly Flag for direct loading. + -- @param #number CratesNeeded Crates needed to build. + -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. + -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. + -- @param #number PerCrateMass Mass in kg + -- @param #number Stock Number of builds available, nil for unlimited + -- @param #string Subcategory Name of subcategory, handy if using > 10 types to load. + -- @param #boolean DontShowInMenu Show this item in menu or not (default: false == show it). + -- @param Core.Zone#ZONE Location (optional) Where the cargo is available (one location only). + -- @return #CTLD_CARGO self + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory, DontShowInMenu, Location) + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO + self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) + self.ID = ID or math.random(100000,1000000) + self.Name = Name or "none" -- #string + self.DisplayName = Name or "none" -- #string + self.Templates = Templates or {} -- #table + self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum + self.HasBeenMoved = HasBeenMoved or false -- #boolean + self.LoadDirectly = LoadDirectly or false -- #boolean + self.CratesNeeded = CratesNeeded or 0 -- #number + self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE + self.HasBeenDropped = Dropped or false --#boolean + self.PerCrateMass = PerCrateMass or 0 -- #number + self.Stock = Stock or nil --#number + self.Stock0 = Stock or nil --#number + self.Mark = nil + self.Subcategory = Subcategory or "Other" + self.DontShowInMenu = DontShowInMenu or false + self.ResourceMap = nil + self.StaticType = "container_cargo" -- "container_cargo" + if self:IsStatic() then + self.StaticType = self.Templates + end + self.StaticShape = nil + self.TypeNames = nil + self.StaticCategory = "Cargos" + if type(Location) == "string" then + Location = ZONE:New(Location) + end + self.Location = Location + self.NoMoveToZone = false + return self + end + + --- Add specific static type and shape to this CARGO. + -- @param #CTLD_CARGO self + -- @param #string TypeName + -- @param #string ShapeName + -- @return #CTLD_CARGO self + function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) + self.StaticCategory = Category or "Cargos" + self.StaticType = TypeName or "container_cargo" + self.StaticShape = ShapeName + return self + end + + --- Get the specific static type and shape from this CARGO if set. + -- @param #CTLD_CARGO self + -- @return #string Category + -- @return #string TypeName + -- @return #string ShapeName + function CTLD_CARGO:GetStaticTypeAndShape() + return self.StaticCategory, self.StaticType, self.StaticShape + end + + --- Add specific unit types to this CARGO (restrict what types can pick this up). + -- @param #CTLD_CARGO self + -- @param #string UnitTypes Unit type name, can also be a #list<#string> table of unit type names. + -- @return #CTLD_CARGO self + function CTLD_CARGO:AddUnitTypeName(UnitTypes) + if not self.TypeNames then self.TypeNames = {} end + if type(UnitTypes) ~= "table" then UnitTypes = {UnitTypes} end + for _,_singletype in pairs(UnitTypes or {}) do + self.TypeNames[_singletype]=_singletype + end + return self + end + + --- Check if a specific unit can carry this CARGO (restrict what types can pick this up). + -- @param #CTLD_CARGO self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD_CARGO:UnitCanCarry(Unit) + if not Unit then return false end + if self.TypeNames == nil then return true end + local typename = Unit:GetTypeName() or "none" + if self.TypeNames[typename] then + return true + else + return false + end + end + + --- Add Resource Map information table + -- @param #CTLD_CARGO self + -- @param #table ResourceMap + -- @return #CTLD_CARGO self + function CTLD_CARGO:SetStaticResourceMap(ResourceMap) + self.ResourceMap = ResourceMap + return self + end + + --- Get Resource Map information table + -- @param #CTLD_CARGO self + -- @return #table ResourceMap + function CTLD_CARGO:GetStaticResourceMap() + return self.ResourceMap + end + + --- Query Location. + -- @param #CTLD_CARGO self + -- @return Core.Zone#ZONE location or `nil` if not set + function CTLD_CARGO:GetLocation() + return self.Location + end + + --- Query ID. + -- @param #CTLD_CARGO self + -- @return #number ID + function CTLD_CARGO:GetID() + return self.ID + end + + --- Query Subcategory + -- @param #CTLD_CARGO self + -- @return #string SubCategory + function CTLD_CARGO:GetSubCat() + return self.Subcategory + end + + --- Query Mass. + -- @param #CTLD_CARGO self + -- @return #number Mass in kg + function CTLD_CARGO:GetMass() + return self.PerCrateMass + end + + --- Query Name. + -- @param #CTLD_CARGO self + -- @return #string Name + function CTLD_CARGO:GetName() + return self.Name + end + + --- Set display name. + -- @param #CTLD_CARGO self + -- @param #string DisplayName Display label used in menus/messages (optional). + -- @return #CTLD_CARGO self + function CTLD_CARGO:SetDisplayName(DisplayName) + if type(DisplayName) == "string" and DisplayName ~= "" then + self.DisplayName = DisplayName + else + self.DisplayName = self.Name + end + return self + end + + --- Query display name. + -- @param #CTLD_CARGO self + -- @return #string Display name, or Name if not set + function CTLD_CARGO:GetDisplayName() + return self.DisplayName or self.Name + end + + --- Query Templates. + -- @param #CTLD_CARGO self + -- @return #table Templates + function CTLD_CARGO:GetTemplates() + return self.Templates + end + + --- Query has moved. + -- @param #CTLD_CARGO self + -- @return #boolean Has moved + function CTLD_CARGO:HasMoved() + return self.HasBeenMoved + end + + --- Query was dropped. + -- @param #CTLD_CARGO self + -- @param #boolean hercOnly If true, only treat Herc drops as 'dropped'. + -- @return #boolean Has been dropped. + function CTLD_CARGO:WasDropped(hercOnly) + if hercOnly then + return self.HasBeenDropped and self.IsHercDrop==true + end + return self.HasBeenDropped + end + + --- Query directly loadable. + -- @param #CTLD_CARGO self + -- @return #boolean loadable + function CTLD_CARGO:CanLoadDirectly() + return self.LoadDirectly + end + + --- Query number of crates or troopsize. + -- @param #CTLD_CARGO self + -- @return #number Crates or size of troops. + function CTLD_CARGO:GetCratesNeeded() + return self.CratesNeeded + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO.Enum Type + function CTLD_CARGO:GetType() + return self.CargoType + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return Wrapper.Positionable#POSITIONABLE Positionable + function CTLD_CARGO:GetPositionable() + return self.Positionable + end + + --- Set HasMoved. + -- @param #CTLD_CARGO self + -- @param #boolean moved + function CTLD_CARGO:SetHasMoved(moved) + self.HasBeenMoved = moved or false + end + + --- Query if cargo has been loaded. + -- @param #CTLD_CARGO self + -- @param #boolean loaded + function CTLD_CARGO:Isloaded() + if self.HasBeenMoved and not self:WasDropped() then + return true + else + return false + end + end + + --- Set WasDropped. + -- @param #CTLD_CARGO self + -- @param #boolean dropped + -- @param #boolean isHercDrop set when _GetCrates is used by the herc + function CTLD_CARGO:SetWasDropped(dropped, isHercDrop) + self.HasBeenDropped = dropped or false + self.IsHercDrop = isHercDrop or false + end + + --- Get Stock. + -- @param #CTLD_CARGO self + -- @return #number Stock or -1 if unlimited. + function CTLD_CARGO:GetStock() + if self.Stock then + return self.Stock + else + return -1 + end + end + + --- Get Stock0. + -- @param #CTLD_CARGO self + -- @return #number Stock0 or -1 if unlimited. + function CTLD_CARGO:GetStock0() + if self.Stock0 then + return self.Stock0 + else + return -1 + end + end + + --- Get relative Stock. + -- @param #CTLD_CARGO self + -- @return #number Stock Percentage like 75, or -1 if unlimited. + function CTLD_CARGO:GetRelativeStock() + if self.Stock and self.Stock0 then + return math.floor((self.Stock/self.Stock0)*100) + else + return -1 + end + end + + --- Add Stock. + -- @param #CTLD_CARGO self + -- @param #number Number to add, none if nil. + -- @return #CTLD_CARGO self + function CTLD_CARGO:AddStock(Number) + if self.Stock then -- Stock nil? + local number = Number or 1 + self.Stock = self.Stock + number + end + return self + end + + --- Remove Stock. + -- @param #CTLD_CARGO self + -- @param #number Number to reduce, none if nil. + -- @return #CTLD_CARGO self + function CTLD_CARGO:RemoveStock(Number) + if self.Stock then -- Stock nil? + local number = Number or 1 + self.Stock = self.Stock - number + if self.Stock < 0 then self.Stock = 0 end + end + return self + end + + --- Set Stock. + -- @param #CTLD_CARGO self + -- @param #number Number to set, nil means unlimited. + -- @return #CTLD_CARGO self + function CTLD_CARGO:SetStock(Number) + self.Stock = Number + return self + end + + --- Query crate type for REPAIR + -- @param #CTLD_CARGO self + -- @param #boolean + function CTLD_CARGO:IsRepair() + if self.CargoType == "Repair" then + return true + else + return false + end + end + + --- Query crate type for STATIC + -- @param #CTLD_CARGO self + -- @return #boolean + function CTLD_CARGO:IsStatic() + if self.CargoType == "Static" then + return true + else + return false + end + end + + --- Add mark + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO self + function CTLD_CARGO:AddMark(Mark) + self.Mark = Mark + return self + end + + --- Get mark + -- @param #CTLD_CARGO self + -- @return #string Mark + function CTLD_CARGO:GetMark(Mark) + return self.Mark + end + + --- Wipe mark + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO self + function CTLD_CARGO:WipeMark() + self.Mark = nil + return self + end + + --- Get overall mass of a cargo object, i.e. crates needed x mass per crate + -- @param #CTLD_CARGO self + -- @return #number mass + function CTLD_CARGO:GetNetMass() + return self.CratesNeeded * self.PerCrateMass + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- END CTLD_CARGO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +do + +------------------------------------------------------ +--- **CTLD_ENGINEERING** class, extends Core.Base#BASE +-- @type CTLD_ENGINEERING +-- @field #string ClassName +-- @field #string lid +-- @field #string Name +-- @field Wrapper.Group#GROUP Group +-- @field Wrapper.Unit#UNIT Unit +-- @field Wrapper.Group#GROUP HeliGroup +-- @field Wrapper.Unit#UNIT HeliUnit +-- @field #string State +-- @extends Core.Base#BASE + +--- +-- @field #CTLD_ENGINEERING CTLD_ENGINEERING +CTLD_ENGINEERING = { + ClassName = "CTLD_ENGINEERING", + lid = "", + Name = "none", + Group = nil, + Unit = nil, + --C_Ops = nil, + HeliGroup = nil, + HeliUnit = nil, + State = "", + } + + --- CTLD_ENGINEERING class version. + -- @field #string version + CTLD_ENGINEERING.Version = "0.0.3" + + --- Create a new instance. + -- @param #CTLD_ENGINEERING self + -- @param #string Name + -- @param #string GroupName Name of Engineering #GROUP object + -- @param Wrapper.Group#GROUP HeliGroup HeliGroup + -- @param Wrapper.Unit#UNIT HeliUnit HeliUnit + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:New(Name, GroupName, HeliGroup, HeliUnit) + + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #CTLD_ENGINEERING + + --BASE:I({Name, GroupName}) + + self.Name = Name or "Engineer Squad" -- #string + self.Group = GROUP:FindByName(GroupName) -- Wrapper.Group#GROUP + self.Unit = self.Group:GetUnit(1) -- Wrapper.Unit#UNIT + self.HeliGroup = HeliGroup -- Wrapper.Group#GROUP + self.HeliUnit = HeliUnit -- Wrapper.Unit#UNIT + self.currwpt = nil -- Core.Point#COORDINATE + self.lid = string.format("%s (%s) | ",self.Name, self.Version) + -- Start State. + self.State = "Stopped" + self.marktimer = 300 -- wait this many secs before trying a crate again + self:Start() + local parent = self:GetParent(self) + return self + end + + --- (Internal) Set the status + -- @param #CTLD_ENGINEERING self + -- @param #string State + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:SetStatus(State) + self.State = State + return self + end + + --- (Internal) Get the status + -- @param #CTLD_ENGINEERING self + -- @return #string State + function CTLD_ENGINEERING:GetStatus() + return self.State + end + + --- (Internal) Check the status + -- @param #CTLD_ENGINEERING self + -- @param #string State + -- @return #boolean Outcome + function CTLD_ENGINEERING:IsStatus(State) + return self.State == State + end + + --- (Internal) Check the negative status + -- @param #CTLD_ENGINEERING self + -- @param #string State + -- @return #boolean Outcome + function CTLD_ENGINEERING:IsNotStatus(State) + return self.State ~= State + end + + --- (Internal) Set start status. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Start() + self:T(self.lid.."Start") + self:SetStatus("Running") + return self + end + + --- (Internal) Set stop status. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Stop() + self:T(self.lid.."Stop") + self:SetStatus("Stopped") + return self + end + + --- (Internal) Set build status. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Build() + self:T(self.lid.."Build") + self:SetStatus("Building") + return self + end + + --- (Internal) Set done status. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Done() + self:T(self.lid.."Done") + local grp = self.Group -- Wrapper.Group#GROUP + grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond") + self:SetStatus("Running") + return self + end + + --- (Internal) Search for crates in reach. + -- @param #CTLD_ENGINEERING self + -- @param #table crates Table of found crate Ops.CTLD#CTLD_CARGO objects. + -- @param #number number Number of crates found. + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Search(crates,number) + self:T(self.lid.."Search") + self:SetStatus("Searching") + -- find crates close by + --local COps = self.C_Ops -- Ops.CTLD#CTLD + local dist = self.distance -- #number + local group = self.Group -- Wrapper.Group#GROUP + --local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table + local ctable = {} + local ind = 0 + if number > 0 then + -- get set of dropped only + for _,_cargo in pairs (crates) do + local cgotype = _cargo:GetType() + if _cargo:WasDropped() and cgotype ~= CTLD_CARGO.Enum.STATIC then + local ok = false + local chalk = _cargo:GetMark() + if chalk == nil then + ok = true + else + -- have we tried this cargo recently? + local tag = chalk.tag or "none" + local timestamp = chalk.timestamp or 0 + -- enough time gone? + local gone = timer.getAbsTime() - timestamp + if gone >= self.marktimer then + ok = true + _cargo:WipeMark() + end -- end time check + end -- end chalk + if ok then + local chalk = {} + chalk.tag = "Engineers" + chalk.timestamp = timer.getAbsTime() + _cargo:AddMark(chalk) + ind = ind + 1 + table.insert(ctable,ind,_cargo) + end + end -- end dropped + end -- end for + end -- end number + + if ind > 0 then + local crate = ctable[1] -- Ops.CTLD#CTLD_CARGO + local static = crate:GetPositionable() -- Wrapper.Static#STATIC + local crate_pos = static:GetCoordinate() -- Core.Point#COORDINATE + local gpos = group:GetCoord() -- Core.Point#COORDINATE + -- see how far we are from the crate + local distance = self:_GetDistance(gpos,crate_pos) + self:T(string.format("%s Distance to crate: %d", self.lid, distance)) + -- move there + if distance > 30 and distance ~= -1 and self:IsStatus("Searching") then + group:RouteGroundTo(crate_pos,15,"Line abreast",1) + self.currwpt = crate_pos -- Core.Point#COORDINATE + self:Move() + elseif distance <= 30 and distance ~= -1 then + -- arrived + self:Arrive() + end + else + self:T(self.lid.."No crates in reach!") + end + return self + end + + --- (Internal) Move towards crates in reach. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Move() + self:T(self.lid.."Move") + self:SetStatus("Moving") + -- check if we arrived on target + --local COps = self.C_Ops -- Ops.CTLD#CTLD + local group = self.Group -- Wrapper.Group#GROUP + local tgtpos = self.currwpt -- Core.Point#COORDINATE + local gpos = group:GetCoord() -- Core.Point#COORDINATE + -- see how far we are from the crate + local distance = self:_GetDistance(gpos,tgtpos) + self:T(string.format("%s Distance remaining: %d", self.lid, distance)) + if distance <= 30 and distance ~= -1 then + -- arrived + self:Arrive() + end + return self + end + + --- (Internal) Arrived at crates in reach. Stop group. + -- @param #CTLD_ENGINEERING self + -- @return #CTLD_ENGINEERING self + function CTLD_ENGINEERING:Arrive() + self:T(self.lid.."Arrive") + self:SetStatus("Arrived") + self.currwpt = nil + local Grp = self.Group -- Wrapper.Group#GROUP + Grp:RouteStop() + return self + end + + --- (Internal) Return distance in meters between two coordinates. + -- @param #CTLD_ENGINEERING self + -- @param Core.Point#COORDINATE _point1 Coordinate one + -- @param Core.Point#COORDINATE _point2 Coordinate two + -- @return #number Distance in meters or -1 + function CTLD_ENGINEERING:_GetDistance(_point1, _point2) + self:T(self.lid .. " _GetDistance") + if _point1 and _point2 then + local distance1 = _point1:Get2DDistance(_point2) + local distance2 = _point1:DistanceFromPointVec2(_point2) + if distance1 and type(distance1) == "number" then + return distance1 + elseif distance2 and type(distance2) == "number" then + return distance2 + else + self:E("*****Cannot calculate distance!") + self:E({_point1,_point2}) + return -1 + end + else + self:E("******Cannot calculate distance!") + self:E({_point1,_point2}) + return -1 + end + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- END CTLD_ENGINEERING +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/Moose Development/Moose/Ops/CTLD_Hercules.lua b/Moose Development/Moose/Ops/CTLD_Hercules.lua new file mode 100644 index 000000000..c5000058d --- /dev/null +++ b/Moose Development/Moose/Ops/CTLD_Hercules.lua @@ -0,0 +1,669 @@ +do +--- **Hercules Cargo AIR Drop Events** by Anubis Yinepu +-- Moose CTLD OO refactoring by Applevangelist +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- TODO CTLD_HERCULES +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- This script will only work for the Herculus mod by Anubis, and only for **Air Dropping** cargo from the Hercules. +-- It *DOES NOT* work with the purchaseable Hercules module from ED. +-- Use the standard Moose CTLD if you want to unload on the ground. +-- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file +-- Except for Ammo pallets, this script will spawn whatever payload gets launched from pylons 11, 12 and 13 +-- Pylons 11, 12 and 13 are moveable within the Hercules cargobay area +-- Ammo pallets can only be jettisoned from these pylons with no benefit to DCS world +-- To benefit DCS world, Ammo pallets need to be off/on loaded using DCS arming and refueling window +-- Cargo_Container_Enclosed = true: Cargo enclosed in container with parachute, need to be dropped from 100m (300ft) or more, except when parked on ground +-- Cargo_Container_Enclosed = false: Open cargo with no parachute, need to be dropped from 10m (30ft) or less + +------------------------------------------------------ +--- **CTLD_HERCULES** class, extends Core.Base#BASE +-- @type CTLD_HERCULES +-- @field #string ClassName +-- @field #string lid +-- @field #string Name +-- @field #string Version +-- @extends Core.Base#BASE +CTLD_HERCULES = { + ClassName = "CTLD_HERCULES", + lid = "", + Name = "", + Version = "0.0.3", +} + +--- Define cargo types. +-- @type CTLD_HERCULES.Types +-- @field #table Type Name of cargo type, container (boolean) in container or not. +CTLD_HERCULES.Types = { + ["ATGM M1045 HMMWV TOW Air [7183lb]"] = {['name'] = "M1045 HMMWV TOW", ['container'] = true}, + ["ATGM M1045 HMMWV TOW Skid [7073lb]"] = {['name'] = "M1045 HMMWV TOW", ['container'] = false}, + ["APC M1043 HMMWV Armament Air [7023lb]"] = {['name'] = "M1043 HMMWV Armament", ['container'] = true}, + ["APC M1043 HMMWV Armament Skid [6912lb]"] = {['name'] = "M1043 HMMWV Armament", ['container'] = false}, + ["SAM Avenger M1097 Air [7200lb]"] = {['name'] = "M1097 Avenger", ['container'] = true}, + ["SAM Avenger M1097 Skid [7090lb]"] = {['name'] = "M1097 Avenger", ['container'] = false}, + ["APC Cobra Air [10912lb]"] = {['name'] = "Cobra", ['container'] = true}, + ["APC Cobra Skid [10802lb]"] = {['name'] = "Cobra", ['container'] = false}, + ["APC M113 Air [21624lb]"] = {['name'] = "M-113", ['container'] = true}, + ["APC M113 Skid [21494lb]"] = {['name'] = "M-113", ['container'] = false}, + ["Tanker M978 HEMTT [34000lb]"] = {['name'] = "M978 HEMTT Tanker", ['container'] = false}, + ["HEMTT TFFT [34400lb]"] = {['name'] = "HEMTT TFFT", ['container'] = false}, + ["SPG M1128 Stryker MGS [33036lb]"] = {['name'] = "M1128 Stryker MGS", ['container'] = false}, + ["AAA Vulcan M163 Air [21666lb]"] = {['name'] = "Vulcan", ['container'] = true}, + ["AAA Vulcan M163 Skid [21577lb]"] = {['name'] = "Vulcan", ['container'] = false}, + ["APC M1126 Stryker ICV [29542lb]"] = {['name'] = "M1126 Stryker ICV", ['container'] = false}, + ["ATGM M1134 Stryker [30337lb]"] = {['name'] = "M1134 Stryker ATGM", ['container'] = false}, + ["APC LAV-25 Air [22520lb]"] = {['name'] = "LAV-25", ['container'] = true}, + ["APC LAV-25 Skid [22514lb]"] = {['name'] = "LAV-25", ['container'] = false}, + ["M1025 HMMWV Air [6160lb]"] = {['name'] = "Hummer", ['container'] = true}, + ["M1025 HMMWV Skid [6050lb]"] = {['name'] = "Hummer", ['container'] = false}, + ["IFV M2A2 Bradley [34720lb]"] = {['name'] = "M-2 Bradley", ['container'] = false}, + ["IFV MCV-80 [34720lb]"] = {['name'] = "MCV-80", ['container'] = false}, + ["IFV BMP-1 [23232lb]"] = {['name'] = "BMP-1", ['container'] = false}, + ["IFV BMP-2 [25168lb]"] = {['name'] = "BMP-2", ['container'] = false}, + ["IFV BMP-3 [32912lb]"] = {['name'] = "BMP-3", ['container'] = false}, + ["ARV BRDM-2 Air [12320lb]"] = {['name'] = "BRDM-2", ['container'] = true}, + ["ARV BRDM-2 Skid [12210lb]"] = {['name'] = "BRDM-2", ['container'] = false}, + ["APC BTR-80 Air [23936lb]"] = {['name'] = "BTR-80", ['container'] = true}, + ["APC BTR-80 Skid [23826lb]"] = {['name'] = "BTR-80", ['container'] = false}, + ["APC BTR-82A Air [24998lb]"] = {['name'] = "BTR-82A", ['container'] = true}, + ["APC BTR-82A Skid [24888lb]"] = {['name'] = "BTR-82A", ['container'] = false}, + ["SAM ROLAND ADS [34720lb]"] = {['name'] = "Roland Radar", ['container'] = false}, + ["SAM ROLAND LN [34720b]"] = {['name'] = "Roland ADS", ['container'] = false}, + ["SAM SA-13 STRELA [21624lb]"] = {['name'] = "Strela-10M3", ['container'] = false}, + ["AAA ZSU-23-4 Shilka [32912lb]"] = {['name'] = "ZSU-23-4 Shilka", ['container'] = false}, + ["SAM SA-19 Tunguska 2S6 [34720lb]"] = {['name'] = "2S6 Tunguska", ['container'] = false}, + ["Transport UAZ-469 Air [3747lb]"] = {['name'] = "UAZ-469", ['container'] = true}, + ["Transport UAZ-469 Skid [3630lb]"] = {['name'] = "UAZ-469", ['container'] = false}, + ["AAA GEPARD [34720lb]"] = {['name'] = "Gepard", ['container'] = false}, + ["SAM CHAPARRAL Air [21624lb]"] = {['name'] = "M48 Chaparral", ['container'] = true}, + ["SAM CHAPARRAL Skid [21516lb]"] = {['name'] = "M48 Chaparral", ['container'] = false}, + ["SAM LINEBACKER [34720lb]"] = {['name'] = "M6 Linebacker", ['container'] = false}, + ["Transport URAL-375 [14815lb]"] = {['name'] = "Ural-375", ['container'] = false}, + ["Transport M818 [16000lb]"] = {['name'] = "M 818", ['container'] = false}, + ["IFV MARDER [34720lb]"] = {['name'] = "Marder", ['container'] = false}, + ["Transport Tigr Air [15900lb]"] = {['name'] = "Tigr_233036", ['container'] = true}, + ["Transport Tigr Skid [15730lb]"] = {['name'] = "Tigr_233036", ['container'] = false}, + ["IFV TPZ FUCH [33440lb]"] = {['name'] = "TPZ", ['container'] = false}, + ["IFV BMD-1 Air [18040lb]"] = {['name'] = "BMD-1", ['container'] = true}, + ["IFV BMD-1 Skid [17930lb]"] = {['name'] = "BMD-1", ['container'] = false}, + ["IFV BTR-D Air [18040lb]"] = {['name'] = "BTR_D", ['container'] = true}, + ["IFV BTR-D Skid [17930lb]"] = {['name'] = "BTR_D", ['container'] = false}, + ["EWR SBORKA Air [21624lb]"] = {['name'] = "Dog Ear radar", ['container'] = true}, + ["EWR SBORKA Skid [21624lb]"] = {['name'] = "Dog Ear radar", ['container'] = false}, + ["ART 2S9 NONA Air [19140lb]"] = {['name'] = "SAU 2-C9", ['container'] = true}, + ["ART 2S9 NONA Skid [19030lb]"] = {['name'] = "SAU 2-C9", ['container'] = false}, + ["ART GVOZDIKA [34720lb]"] = {['name'] = "SAU Gvozdika", ['container'] = false}, + ["APC MTLB Air [26400lb]"] = {['name'] = "MTLB", ['container'] = true}, + ["APC MTLB Skid [26290lb]"] = {['name'] = "MTLB", ['container'] = false}, +} + +--- Cargo Object +-- @type CTLD_HERCULES.CargoObject +-- @field #number Cargo_Drop_Direction +-- @field #table Cargo_Contents +-- @field #string Cargo_Type_name +-- @field #boolean Container_Enclosed +-- @field #boolean ParatrooperGroupSpawn +-- @field #number Cargo_Country +-- @field #boolean offload_cargo +-- @field #boolean all_cargo_survive_to_the_ground +-- @field #boolean all_cargo_gets_destroyed +-- @field #boolean destroy_cargo_dropped_without_parachute +-- @field Core.Timer#TIMER scheduleFunctionID + +--- [User] Instantiate a new object +-- @param #CTLD_HERCULES self +-- @param #string Coalition Coalition side, "red", "blue" or "neutral" +-- @param #string Alias Name of this instance +-- @param Ops.CTLD#CTLD CtldObject CTLD instance to link into +-- @return #CTLD_HERCULES self +-- @usage +-- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: +-- +-- my_ctld.enableFixedWing = false -- avoid dual loading via CTLD F10 and F8 ground crew +-- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) +-- +-- You also need: +-- * A template called "Infantry" for 10 Paratroopers (as set via herccargo.infantrytemplate). +-- * Depending on what you are loading with the help of the ground crew, there are 42 more templates for the various vehicles that are loadable. +-- There's a **quick check output in the `dcs.log`** which tells you what's there and what not. +-- E.g.: +-- ...Checking template for APC BTR-82A Air [24998lb] (BTR-82A) ... MISSING) +-- ...Checking template for ART 2S9 NONA Skid [19030lb] (SAU 2-C9) ... MISSING) +-- ...Checking template for EWR SBORKA Air [21624lb] (Dog Ear radar) ... MISSING) +-- ...Checking template for Transport Tigr Air [15900lb] (Tigr_233036) ... OK) +-- +-- Expected template names are the ones in the rounded brackets. +-- +-- ### HINTS +-- +-- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does +-- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD. +-- **Do not use** the **splash damage** script together with this, your cargo will just explode when reaching the ground! +-- +-- ### Airdrops +-- +-- There are two ways of airdropping: +-- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu) +-- 2) Higher up and slow (>100m AGL) - here you can drop paratroopers and cargo which has "Air" at the end of the cargo name (loaded via F8 Ground Crew menu) +-- +-- ### General +-- +-- Use either this method to integrate the Hercules **or** the one from the "normal" CTLD. Never both! +function CTLD_HERCULES:New(Coalition, Alias, CtldObject) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CTLD_HERCULES + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E("ERROR: Unknown coalition in CTLD!") + end + else + self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) + end + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="UNHCR" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="Red CTLD Hercules" + elseif self.coalition==coalition.side.BLUE then + self.alias="Blue CTLD Hercules" + end + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalitiontxt) + + self.infantrytemplate = "Infantry" -- template for a group of 10 paratroopers + self.CTLD = CtldObject -- Ops.CTLD#CTLD + + self.verbose = true + + self.j = 0 + self.carrierGroups = {} + self.Cargo = {} + self.ParatrooperCount = {} + + self.ObjectTracker = {} + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + self:HandleEvent(EVENTS.Shot, self._HandleShot) + + self:I(self.lid .. "Started") + + self:CheckTemplates() + + return self +end + +--- [Internal] Function to check availability of templates +-- @param #CTLD_HERCULES self +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:CheckTemplates() + self:T(self.lid .. 'CheckTemplates') + -- inject Paratroopers + self.Types["Paratroopers 10"] = { + name = self.infantrytemplate, + container = false, + available = false, + } + local missing = {} + local nomissing = 0 + local found = {} + local nofound = 0 + + -- list of groundcrew loadables + for _index,_tab in pairs (self.Types) do + local outcometxt = "MISSING" + if _DATABASE.Templates.Groups[_tab.name] then + outcometxt = "OK" + self.Types[_index].available= true + found[_tab.name] = true + else + self.Types[_index].available = false + missing[_tab.name] = true + end + if self.verbose then + self:I(string.format(self.lid .. "Checking template for %s (%s) ... %s", _index,_tab.name,outcometxt)) + end + end + for _,_name in pairs(found) do + nofound = nofound + 1 + end + for _,_name in pairs(missing) do + nomissing = nomissing + 1 + end + self:I(string.format(self.lid .. "Template Check Summary: Found %d, Missing %d, Total %d",nofound,nomissing,nofound+nomissing)) + return self +end + +--- [Internal] Function to spawn a soldier group of 10 units +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#COORDINATE Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #number Cargo_Country +-- @param #number GroupSpacing +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, Cargo_Country, GroupSpacing) + --- TODO: Rework into Moose Spawns + self:T(self.lid .. 'Soldier_SpawnGroup') + self:T(Cargo_Drop_Position) + -- create a matching #CTLD_CARGO type + local InjectTroopsType = CTLD_CARGO:New(nil,self.infantrytemplate,{self.infantrytemplate},CTLD_CARGO.Enum.TROOPS,true,true,10,nil,false,80) + -- get a #ZONE object + local position = Cargo_Drop_Position:GetVec2() + local dropzone = ZONE_RADIUS:New("Infantry " .. math.random(1,10000),position,100) + -- and go: + self.CTLD:InjectTroops(dropzone,InjectTroopsType) + return self +end + +--- [Internal] Function to spawn a group +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#COORDINATE Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, Cargo_Country) + --- TODO: Rework into Moose Spawns + self:T(self.lid .. "Cargo_SpawnGroup") + self:T(Cargo_Type_name) + if Cargo_Type_name ~= 'Container red 1' then + -- create a matching #CTLD_CARGO type + local InjectVehicleType = CTLD_CARGO:New(nil,Cargo_Type_name,{Cargo_Type_name},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000) + -- get a #ZONE object + local position = Cargo_Drop_Position:GetVec2() + local dropzone = ZONE_RADIUS:New("Vehicle " .. math.random(1,10000),position,100) + -- and go: + self.CTLD:InjectVehicles(dropzone,InjectVehicleType) + end + return self +end + +--- [Internal] Function to spawn static cargo +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param Core.Point#COORDINATE Cargo_Drop_Position +-- @param #string Cargo_Type_name +-- @param #number CargoHeading +-- @param #boolean dead +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Position, Cargo_Type_name, CargoHeading, dead, Cargo_Country) + --- TODO: Rework into Moose Static Spawns + self:T(self.lid .. "Cargo_SpawnStatic") + self:T("Static " .. Cargo_Type_name .. " Dead " .. tostring(dead)) + local position = Cargo_Drop_Position:GetVec2() + local Zone = ZONE_RADIUS:New("Cargo Static " .. math.random(1,10000),position,100) + if not dead then + local injectstatic = CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) + self.CTLD:InjectStatics(Zone,injectstatic,true,true) + end + return self +end + +--- [Internal] Function to spawn cargo by type at position +-- @param #CTLD_HERCULES self +-- @param #string Cargo_Type_name +-- @param Core.Point#COORDINATE Cargo_Drop_Position +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name, _pos) + local theCargo = self.CTLD:_FindCratesCargoObject(_name) -- #CTLD_CARGO + if theCargo then + self.CTLD.CrateCounter = self.CTLD.CrateCounter + 1 + local CCat, CType, CShape = theCargo:GetStaticTypeAndShape() + local basetype = CType or self.CTLD.basetype or "container_cargo" + CCat = CCat or "Cargos" + local theStatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) + :InitCargoMass(theCargo.PerCrateMass) + :InitCargo(self.CTLD.enableslingload) + :InitCoordinate(_pos) + if CShape then + theStatic:InitShape(CShape) + end + theStatic:Spawn(270,_name .. "-Container-".. math.random(1,100000)) + + self.CTLD.Spawned_Crates[self.CTLD.CrateCounter] = theStatic + local newCargo = CTLD_CARGO:New(self.CTLD.CargoCounter, theCargo.Name, theCargo.Templates, theCargo.CargoType, true, false, theCargo.CratesNeeded, self.CTLD.Spawned_Crates[self.CTLD.CrateCounter], true, theCargo.PerCrateMass, nil, theCargo.Subcategory) + local map=theCargo:GetStaticResourceMap() + newCargo:SetStaticResourceMap(map) + table.insert(self.CTLD.Spawned_Cargo, newCargo) + + newCargo:SetWasDropped(true) + newCargo:SetHasMoved(true) + end + return self +end + +--- [Internal] Spawn cargo objects +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @param #number Cargo_Drop_Direction +-- @param Core.Point#COORDINATE Cargo_Content_position +-- @param #string Cargo_Type_name +-- @param #boolean Cargo_over_water +-- @param #boolean Container_Enclosed +-- @param #boolean ParatrooperGroupSpawn +-- @param #boolean offload_cargo +-- @param #boolean all_cargo_survive_to_the_ground +-- @param #boolean all_cargo_gets_destroyed +-- @param #boolean destroy_cargo_dropped_without_parachute +-- @param #number Cargo_Country +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direction, Cargo_Content_position, Cargo_Type_name, Cargo_over_water, Container_Enclosed, ParatrooperGroupSpawn, offload_cargo, all_cargo_survive_to_the_ground, all_cargo_gets_destroyed, destroy_cargo_dropped_without_parachute, Cargo_Country) + self:T(self.lid .. 'Cargo_SpawnObjects') + + local CargoHeading = self.CargoHeading + + if offload_cargo == true or ParatrooperGroupSpawn == true then + if ParatrooperGroupSpawn == true then + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) + end + else + if all_cargo_gets_destroyed == true or Cargo_over_water == true then + + else + if all_cargo_survive_to_the_ground == true then + if ParatrooperGroupSpawn == true then + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) + end + if Container_Enclosed == true then + if ParatrooperGroupSpawn == false then + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) + end + end + end + if destroy_cargo_dropped_without_parachute == true then + if Container_Enclosed == true then + if ParatrooperGroupSpawn == true then + self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + else + if self.CTLD.dropAsCargoCrate then + self:Cargo_SpawnDroppedAsCargo(Cargo_Type_name, Cargo_Content_position) + else + self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, "Hercules_Container_Parachute_Static", CargoHeading, false, Cargo_Country) + end + end + else + self:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, true, Cargo_Country) + end + end + end + end + return self +end + +--- [Internal] Function to calculate object height +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP group The group for which to calculate the height +-- @return #number height over ground +function CTLD_HERCULES:Calculate_Object_Height_AGL(group) + self:T(self.lid .. "Calculate_Object_Height_AGL") + if group.ClassName and group.ClassName == "GROUP" then + local gcoord = group:GetCoordinate() + local height = group:GetHeight() + local lheight = gcoord:GetLandHeight() + self:T(self.lid .. "Height " .. height - lheight) + return height - lheight + else + -- DCS object + if group:isExist() then + local dcsposition = group:getPosition().p + local dcsvec2 = {x = dcsposition.x, y = dcsposition.z} -- Vec2 + local height = math.floor(group:getPosition().p.y - land.getHeight(dcsvec2)) + self.ObjectTracker[group.id_] = dcsposition -- Vec3 + self:T(self.lid .. "Height " .. height) + return height + else + return 0 + end + end +end + +--- [Internal] Function to check surface type +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP group The group for which to calculate the height +-- @return #number height over ground +function CTLD_HERCULES:Check_SurfaceType(object) + self:T(self.lid .. "Check_SurfaceType") + -- LAND,--1 SHALLOW_WATER,--2 WATER,--3 ROAD,--4 RUNWAY--5 + if object:isExist() then + return land.getSurfaceType({x = object:getPosition().p.x, y = object:getPosition().p.z}) + else + return 1 + end +end + +--- [Internal] Function to track cargo objects +-- @param #CTLD_HERCULES self +-- @param #CTLD_HERCULES.CargoObject cargo +-- @param Wrapper.Group#GROUP initiator +-- @return #number height over ground +function CTLD_HERCULES:Cargo_Track(cargo, initiator) + self:T(self.lid .. "Cargo_Track") + local Cargo_Drop_initiator = initiator + if cargo.Cargo_Contents ~= nil then + if self:Calculate_Object_Height_AGL(cargo.Cargo_Contents) < 10 then --pallet less than 5m above ground before spawning + if self:Check_SurfaceType(cargo.Cargo_Contents) == 2 or self:Check_SurfaceType(cargo.Cargo_Contents) == 3 then + cargo.Cargo_over_water = true--pallets gets destroyed in water + end + local dcsvec3 = self.ObjectTracker[cargo.Cargo_Contents.id_] or initiator:GetVec3() -- last known position + self:T("SPAWNPOSITION: ") + self:T({dcsvec3}) + local Vec2 = { + x=dcsvec3.x, + y=dcsvec3.z, + } + local vec3 = COORDINATE:NewFromVec2(Vec2) + self.ObjectTracker[cargo.Cargo_Contents.id_] = nil + self:Cargo_SpawnObjects(Cargo_Drop_initiator,cargo.Cargo_Drop_Direction, vec3, cargo.Cargo_Type_name, cargo.Cargo_over_water, cargo.Container_Enclosed, cargo.ParatrooperGroupSpawn, cargo.offload_cargo, cargo.all_cargo_survive_to_the_ground, cargo.all_cargo_gets_destroyed, cargo.destroy_cargo_dropped_without_parachute, cargo.Cargo_Country) + if cargo.Cargo_Contents:isExist() then + cargo.Cargo_Contents:destroy()--remove pallet+parachute before hitting ground and replace with Cargo_SpawnContents + end + --timer.removeFunction(cargo.scheduleFunctionID) + cargo.scheduleFunctionID:Stop() + cargo = {} + end + end + return self +end + +--- [Internal] Function to calc north correction +-- @param #CTLD_HERCULES self +-- @param Core.Point#POINT_Vec3 point Position Vec3 +-- @return #number north correction +function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_NorthCorrection(point) + self:T(self.lid .. "Calculate_Cargo_Drop_initiator_NorthCorrection") + if not point.z then --Vec2; convert to Vec3 + point.z = point.y + point.y = 0 + end + local lat, lon = coord.LOtoLL(point) + local north_posit = coord.LLtoLO(lat + 1, lon) + return math.atan2(north_posit.z - point.z, north_posit.x - point.x) +end + +--- [Internal] Function to calc initiator heading +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Cargo_Drop_initiator +-- @return #number north corrected heading +function CTLD_HERCULES:Calculate_Cargo_Drop_initiator_Heading(Cargo_Drop_initiator) + self:T(self.lid .. "Calculate_Cargo_Drop_initiator_Heading") + local Heading = Cargo_Drop_initiator:GetHeading() + Heading = Heading + self:Calculate_Cargo_Drop_initiator_NorthCorrection(Cargo_Drop_initiator:GetVec3()) + if Heading < 0 then + Heading = Heading + (2 * math.pi)-- put heading in range of 0 to 2*pi + end + return Heading + 0.06 -- rad +end + +--- [Internal] Function to initialize dropped cargo +-- @param #CTLD_HERCULES self +-- @param Wrapper.Group#GROUP Initiator +-- @param #table Cargo_Contents Table 'weapon' from event data +-- @param #string Cargo_Type_name Name of this cargo +-- @param #boolean Container_Enclosed Is container? +-- @param #boolean SoldierGroup Is soldier group? +-- @param #boolean ParatrooperGroupSpawnInit Is paratroopers? +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:Cargo_Initialize(Initiator, Cargo_Contents, Cargo_Type_name, Container_Enclosed, SoldierGroup, ParatrooperGroupSpawnInit) + self:T(self.lid .. "Cargo_Initialize") + local Cargo_Drop_initiator = Initiator:GetName() + if Cargo_Drop_initiator ~= nil then + if ParatrooperGroupSpawnInit == true then + self:T("Paratrooper Drop") + -- Paratroopers + if not self.ParatrooperCount[Cargo_Drop_initiator] then + self.ParatrooperCount[Cargo_Drop_initiator] = 1 + else + self.ParatrooperCount[Cargo_Drop_initiator] = self.ParatrooperCount[Cargo_Drop_initiator] + 1 + end + + local Paratroopers = self.ParatrooperCount[Cargo_Drop_initiator] + + self:T("Paratrooper Drop Number " .. self.ParatrooperCount[Cargo_Drop_initiator]) + + local SpawnParas = false + + if math.fmod(Paratroopers,10) == 0 then + SpawnParas = true + end + + self.j = self.j + 1 + self.Cargo[self.j] = {} + self.Cargo[self.j].Cargo_Drop_Direction = self:Calculate_Cargo_Drop_initiator_Heading(Initiator) + self.Cargo[self.j].Cargo_Contents = Cargo_Contents + self.Cargo[self.j].Cargo_Type_name = Cargo_Type_name + self.Cargo[self.j].Container_Enclosed = Container_Enclosed + self.Cargo[self.j].ParatrooperGroupSpawn = SpawnParas + self.Cargo[self.j].Cargo_Country = Initiator:GetCountry() + + if self:Calculate_Object_Height_AGL(Initiator) < 5.0 then --aircraft on ground + self.Cargo[self.j].offload_cargo = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 10.0 then --aircraft less than 10m above ground + self.Cargo[self.j].all_cargo_survive_to_the_ground = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 100.0 then --aircraft more than 10m but less than 100m above ground + self.Cargo[self.j].all_cargo_gets_destroyed = true + else + self.Cargo[self.j].all_cargo_gets_destroyed = false + end + + local timer = TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) + self.Cargo[self.j].scheduleFunctionID = timer + timer:Start(1,1,600) + + else + -- no paras + self.j = self.j + 1 + self.Cargo[self.j] = {} + self.Cargo[self.j].Cargo_Drop_Direction = self:Calculate_Cargo_Drop_initiator_Heading(Initiator) + self.Cargo[self.j].Cargo_Contents = Cargo_Contents + self.Cargo[self.j].Cargo_Type_name = Cargo_Type_name + self.Cargo[self.j].Container_Enclosed = Container_Enclosed + self.Cargo[self.j].ParatrooperGroupSpawn = false + self.Cargo[self.j].Cargo_Country = Initiator:GetCountry() + + if self:Calculate_Object_Height_AGL(Initiator) < 5.0 then--aircraft on ground + self.Cargo[self.j].offload_cargo = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 10.0 then--aircraft less than 10m above ground + self.Cargo[self.j].all_cargo_survive_to_the_ground = true + elseif self:Calculate_Object_Height_AGL(Initiator) < 100.0 then--aircraft more than 10m but less than 100m above ground + self.Cargo[self.j].all_cargo_gets_destroyed = true + else + self.Cargo[self.j].destroy_cargo_dropped_without_parachute = true --aircraft more than 100m above ground + end + + local timer = TIMER:New(self.Cargo_Track,self,self.Cargo[self.j],Initiator) + self.Cargo[self.j].scheduleFunctionID = timer + timer:Start(1,1,600) + end + end + return self +end + +--- [Internal] Function to change cargotype per group (Wrench) +-- @param #CTLD_HERCULES self +-- @param #number key Carrier key id +-- @param #string cargoType Type of cargo +-- @param #number cargoNum Number of cargo objects +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:SetType(key,cargoType,cargoNum) + self:T(self.lid .. "SetType") + self.carrierGroups[key]['cargoType'] = cargoType + self.carrierGroups[key]['cargoNum'] = cargoNum + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- EventHandlers +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +--- [Internal] Function to capture SHOT event +-- @param #CTLD_HERCULES self +-- @param Core.Event#EVENTDATA Cargo_Drop_Event The event data +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:_HandleShot(Cargo_Drop_Event) + self:T(self.lid .. "Shot Event ID:" .. Cargo_Drop_Event.id) + if Cargo_Drop_Event.id == EVENTS.Shot then + + local GT_Name = "" + local SoldierGroup = false + local ParatrooperGroupSpawnInit = false + + local GT_DisplayName = Weapon.getDesc(Cargo_Drop_Event.weapon).typeName:sub(15, -1)--Remove "weapons.bombs." from string + self:T(string.format("%sCargo_Drop_Event: %s", self.lid, Weapon.getDesc(Cargo_Drop_Event.weapon).typeName)) + + if (GT_DisplayName == "Squad 30 x Soldier [7950lb]") then + self:Cargo_Initialize(Cargo_Drop_Event.IniGroup, Cargo_Drop_Event.weapon, "Soldier M4 GRG", false, true, true) + end + + if self.Types[GT_DisplayName] then + local GT_Name = self.Types[GT_DisplayName]['name'] + local Cargo_Container_Enclosed = self.Types[GT_DisplayName]['container'] + self:Cargo_Initialize(Cargo_Drop_Event.IniGroup, Cargo_Drop_Event.weapon, GT_Name, Cargo_Container_Enclosed) + end + end + return self +end + +--- [Internal] Function to capture BIRTH event +-- @param #CTLD_HERCULES self +-- @param Core.Event#EVENTDATA event The event data +-- @return #CTLD_HERCULES self +function CTLD_HERCULES:_HandleBirth(event) + -- not sure what this is needed for? I think this for setting generic crates "content" setting. + self:T(self.lid .. "Birth Event ID:" .. event.id) + return self +end + +end + +------------------------------------------------------------------- +-- End Ops.CTLD.lua +------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/CTLD_Localization.lua b/Moose Development/Moose/Ops/CTLD_Localization.lua new file mode 100644 index 000000000..e604ffc60 --- /dev/null +++ b/Moose Development/Moose/Ops/CTLD_Localization.lua @@ -0,0 +1,752 @@ +--- +-- @field Messages +CTLD.Messages = { + EN = { + -- ============================================================ + -- Crate / Cargo Loading + -- ============================================================ + CRATE_LOADED_GROUNDCREW = "Crate %s loaded by ground crew!", + CRATE_UNLOADED_GROUNDCREW = "Crate %s unloaded by ground crew!", + CRATE_LOADED_ID = "Crate ID %d for %s loaded!", + LOADED_FULL = "Loaded %d %s.", + LOADED_SETS_LEFTOVER = "Loaded %d %s(s), with %d leftover crate(s).", + LOADED_SETS = "Loaded %d %s(s).", + LOADED_PARTIAL = "Loaded only %d/%d crate(s) of %s.", + LOADED_PARTIAL_LIMIT = "Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!", + LOADED_BATCH = "Loaded %d %s.", + LOADED_BATCH_PARTIAL = "Some sets could not be fully loaded.", + -- ============================================================ + -- Dropping / Unloading + -- ============================================================ + DROPPED_FULL = "Dropped %d %s.", + DROPPED_SETS_LEFTOVER = "Dropped %d %s(s), with %d leftover crate(s).", + DROPPED_SETS = "Dropped %d %s(s).", + DROPPED_PARTIAL = "Dropped %d/%d crate(s) of %s.", + DROPPED_INTO_ACTION = "Dropped %s into action!", + DROPPED_BEACON = "Dropped %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", + CRATES_POSITIONED = "%d crates for %s have been positioned near you!", + CRATES_DROPPED = "%d crates for %s have been dropped!", + -- ============================================================ + -- Troops + -- ============================================================ + BOARDED = "%s boarded!", + BOARDING = "%s boarding!", + TROOPS_RETURNED = "Troops have returned to base!", + -- ============================================================ + -- Deployment + -- ============================================================ + DEPLOYED_NEAR_YOU = "%s have been deployed near you!", + UNITS_REMOVED = "%s have been removed", + -- ============================================================ + -- Build / Repair + -- ============================================================ + BUILD_STARTED = "Build started, ready in %d seconds!", + REPAIR_STARTED = "Repair started using %s taking %d secs", + NO_UNIT_TO_REPAIR = "No unit close enough to repair!", + CANT_REPAIR_WITH = "Can't repair this unit with %s", + CRATES_MOVE_BEFORE_BUILD = "*** Crates need to be moved before building!", + -- ============================================================ + -- Errors - Chopper / Weight / Capacity + -- ============================================================ + CHOPPER_CANNOT_CARRY = "Sorry this chopper cannot carry crates!", + TOO_HEAVY = "Sorry, that's too heavy to load!", + FULLY_LOADED = "Sorry, we are fully loaded!", + CRAMMED = "Sorry, we're crammed already!", + NO_CAPACITY_NOW = "No capacity to load more now!", + NO_MORE_CAPACITY = "No more capacity to load crates!", + CANNOT_LOAD_NONE_OR_FULL = "Cannot load crates: either none found or no capacity left.", + -- ============================================================ + -- Errors - Position + -- ============================================================ + NEED_TO_LAND_OR_HOVER_LOAD = "You need to land or hover in position to load!", + HOVER_OVER_CRATES = "Hover over the crates to pick them up!", + LAND_OR_HOVER_OVER_CRATES = "Land or hover over the crates to pick them up!", + MUST_LAND_OR_HOVER_CRATES = "You must land or hover to load crates!", + NEED_TO_LAND_BUILD = "You need to land / stop to build something, Pilot!", + NOT_CLOSE_ENOUGH_LOGISTICS = "You are not close enough to a logistics zone!", + NOT_CLOSE_ENOUGH_DROP = "You are not close enough to a drop zone!", + NOT_CLOSE_ENOUGH_ZONE_NM = "Negative, need to be closer than %dnm to a zone!", + CANNOT_BUILD_LOADING_AREA = "You cannot build in a loading area, Pilot!", + -- ============================================================ + -- Errors - Doors + -- ============================================================ + OPEN_DOORS_LOAD_CARGO = "You need to open the door(s) to load cargo!", + OPEN_DOORS_LOAD_TROOPS = "You need to open the door(s) to load troops!", + OPEN_DOORS_EXTRACT_TROOPS = "You need to open the door(s) to extract troops!", + OPEN_DOORS_UNLOAD_TROOPS = "You need to open the door(s) to unload troops!", + OPEN_DOORS_DROP_CARGO = "You need to open the door(s) to drop cargo!", + -- ============================================================ + -- Errors - Stock / Availability + -- ============================================================ + ALL_GONE = "Sorry, all %s are gone!", + RAN_OUT_OF = "Sorry, we ran out of %s", + CARGO_NOT_AVAILABLE_ZONE = "The requested cargo is not available in this zone!", + ENOUGH_CRATES_NEARBY = "There are enough crates nearby already! Take care of those first!", + NO_CRATES_WITHIN = "No (loadable) crates within %d meters!", + NO_CRATES_WITHIN_PLAIN = "No crates within %d meters!", + NO_CRATES_IN_RANGE = "No crates found in range!", + NO_NAMED_CRATES_IN_RANGE = "No \"%s\" crates found in range!", + NO_LOADABLE_CRATES = "Sorry, no loadable crates nearby or max cargo weight reached!", + NO_UNITS_TO_EXTRACT = "No units close enough to extract!", + NO_UNIT_CONFIG = "No unit configuration found for %s", + CANT_ONBOARD = "Can't onboard %s", + TOO_MANY_UNITS_NEARBY = "You already have %d units nearby!", + NO_CRATE_GROUPS = "No crate groups found for this unit!", + NO_CRATE_SET = "No crate set found or index invalid!", + NO_CRATE_IN_SET = "No crate found in that set!", + NO_TROOP_CHUNK = "No troop cargo chunk found for ID %d!", + TROOP_CHUNK_EMPTY = "Troop chunk is empty for ID %d!", + -- ============================================================ + -- Nothing loaded / in stock + -- ============================================================ + NOTHING_LOADED = "Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs", + NOTHING_LOADED_AIRDROP = "Nothing loaded or not within airdrop parameters!", + NOTHING_LOADED_HOVER = "Nothing loaded or not hovering within parameters!", + NOTHING_IN_STOCK = "Nothing in stock!", + NOTHING_TO_PACK = "Nothing to pack at this distance pilot!", + NOTHING_TO_REMOVE = "Nothing to remove at this distance pilot!", + -- ============================================================ + -- Zone / Info + -- ============================================================ + ROGER_ZONE = "Roger, %s zone %s!", + -- ============================================================ + -- Report: Hover / Flight Parameters + -- ============================================================ + HOVER_PARAMS_METRIC = "Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", + HOVER_PARAMS_IMPERIAL = "Hover parameters (autoload/drop):\n - Min height %dft \n - Max height %dft \n - Max speed 6ftps \n - In parameter: %s", + FLIGHT_PARAMS_IMPERIAL = "Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", + FLIGHT_PARAMS_METRIC = "Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", + -- ============================================================ + -- Report Titles (REPORT:New()) + -- ============================================================ + REPORT_CRATES_FOUND = "Crates Found Nearby:", + REPORT_REMOVING_CRATES = "Removing Crates Found Nearby:", + REPORT_TRANSPORT_CHECKOUT = "Transport Checkout Sheet", + REPORT_INVENTORY = "Inventory Sheet", + REPORT_BUILD_CHECKLIST = "Checklist Buildable Crates", + REPORT_REPAIR_CHECKLIST = "Checklist Repairs", + REPORT_BEACONS = "Active Zone Beacons", + -- ============================================================ + -- Report Section Headers (report:Add()) + -- ============================================================ + REPORT_SECTION_TROOPS = " -- TROOPS --", + REPORT_SECTION_CRATES = " -- CRATES --", + REPORT_SECTION_CRATES_GC = " -- CRATES loaded via Ground Crew --", + REPORT_SECTION_NONE = " N O N E", + REPORT_SECTION_NONE_ALT = " --- None found! ---", + REPORT_SECTION_NONE_REPAIR = " --- None Found ---", + REPORT_GC_LOADABLE_HINT = "Probably ground crew loadable (F8)", + REPORT_TOTAL_MASS = "Total Mass: %s kg. Loadable: %s kg.", + REPORT_TROOPS_CRATES_COUNT = "Troops: %d(%d), Crates: %d(%d)", + REPORT_TROOPS_CRATETYPES_COUNT = "Troops: %d, Cratetypes: %d", + -- ============================================================ + -- Report Row Templates (per-item lines in reports) + -- ============================================================ + REPORT_ROW_TROOP = "Troop: %s size %d", + REPORT_ROW_CRATE = "Crate: %s %d/%d", + REPORT_ROW_CRATE_SIZE1 = "Crate: %s size 1", + REPORT_ROW_GC_CRATE = "GC loaded Crate: %s size 1", + REPORT_ROW_DROPPED_CRATE = "Dropped crate for %s, %dkg", + REPORT_ROW_CRATE_KG = "Crate for %s, %dkg", + REPORT_ROW_CRATE_REMOVED = "Crate for %s, %dkg removed", + REPORT_ROW_UNIT_STOCK = "Unit: %s | Soldiers: %d | Stock: %s", + REPORT_ROW_TYPE_CRATE_STOCK = "Type: %s | Crates per Set: %d | Stock: %s", + REPORT_ROW_TYPE_STOCK = "Type: %s | Stock: %s", + REPORT_ROW_BUILD_CHECK = "Type: %s | Required %d | Found %d | Can Build %s", + REPORT_ROW_REPAIR_CHECK = "Type: %s | Required %d | Found %d | Can Repair %s", + REPORT_ROW_BEACON = " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", + -- ============================================================ + -- Weight / Crate limit tokens + -- ============================================================ + WEIGHT_LIMIT = "Weight limit reached", + CRATE_LIMIT = "Crate limit reached", + -- ============================================================ + -- Menu labels - Top level + -- ============================================================ + MENU_CTLD = "CTLD", + MENU_MANAGE_TROOPS = "Manage Troops", + MENU_MANAGE_CRATES = "Manage Crates", + MENU_MANAGE_UNITS = "Manage Units", + -- ============================================================ + -- Menu labels - Troops + -- ============================================================ + MENU_LOAD_TROOPS = "Load troops", + MENU_DROP_TROOPS = "Drop Troops", + MENU_DROP_ALL_TROOPS = "Drop ALL troops", + MENU_EXTRACT_TROOPS = "Extract troops", + MENU_DROP_N_TROOPS = "Drop (%d) %s", + -- ============================================================ + -- Menu labels - Crates: Get + -- ============================================================ + MENU_GET_CRATES = "Get Crates", + MENU_GET = "Get", + MENU_GET_AND_LOAD = "Get and Load", + MENU_GET_ANYWAY = "Get anyway", + MENU_PARTIALLY_LOAD = "Partially load", + MENU_OUT_OF_STOCK = "Out of stock", + MENU_TROOP_LIMIT = "Troop limit reached", + -- ============================================================ + -- Menu labels - Crates: Load + -- ============================================================ + MENU_LOAD_CRATES = "Load Crates", + MENU_LOAD_ALL = "Load ALL", + MENU_SHOW_LOADABLE_CRATES = "Show loadable crates", + MENU_NO_CRATES_FOUND_RESCAN = "No crates found! Rescan?", + MENU_USE_C130_LOAD = "Use C-130 Load system", + -- ============================================================ + -- Menu labels - Crates: Drop + -- ============================================================ + MENU_DROP_CRATES = "Drop Crates", + MENU_DROP_ALL_CRATES = "Drop ALL crates", + MENU_DROP = "Drop", + MENU_DROP_AND_BUILD = "Drop and build", + MENU_DROP_N_SETS = "Drop %d Set%s", + MENU_NO_CRATES_TO_DROP = "No crates to drop!", + -- ============================================================ + -- Menu labels - Crates: Build / Repair / Pack / Remove + -- ============================================================ + MENU_BUILD_CRATES = "Build crates", + MENU_REPAIR = "Repair", + MENU_PACK_CRATES = "Pack crates", + MENU_PACK = "Pack", + MENU_PACK_AND_LOAD = "Pack and Load", + MENU_PACK_AND_REMOVE = "Pack and Remove", + MENU_REMOVE_CRATES = "Remove crates", + MENU_REMOVE_CRATES_NEARBY = "Remove crates nearby", + MENU_LIST_CRATES_NEARBY = "List crates nearby", + MENU_CRATES_NEEDED = "%d crate%s %s (%dkg)", + -- ============================================================ + -- Menu labels - Units (C-130) + -- ============================================================ + MENU_GET_UNITS = "Get Units", + MENU_REMOVE_UNITS_NEARBY = "Remove units nearby", + -- ============================================================ + -- Menu labels - Info / Cargo + -- ============================================================ + MENU_LIST_BOARDED_CARGO = "List boarded cargo", + MENU_INVENTORY = "Inventory", + MENU_LIST_ZONE_BEACONS = "List active zone beacons", + -- ============================================================ + -- Menu labels - Smokes / Flares / Beacons + -- ============================================================ + MENU_SMOKES_FLARES_BEACONS = "Smokes, Flares, Beacons", + MENU_SMOKE_ZONES_NEARBY = "Smoke zones nearby", + MENU_DROP_SMOKE_NOW = "Drop smoke now", + MENU_RED_SMOKE = "Red smoke", + MENU_BLUE_SMOKE = "Blue smoke", + MENU_GREEN_SMOKE = "Green smoke", + MENU_ORANGE_SMOKE = "Orange smoke", + MENU_WHITE_SMOKE = "White smoke", + MENU_FLARE_ZONES_NEARBY = "Flare zones nearby", + MENU_FIRE_FLARE_NOW = "Fire flare now", + MENU_DROP_BEACON_NOW = "Drop beacon now", + -- ============================================================ + -- Menu labels - Parameters + -- ============================================================ + MENU_SHOW_FLIGHT_PARAMS = "Show flight parameters", + MENU_SHOW_HOVER_PARAMS = "Show hover parameters", + STOCK_NONE = "none", + STOCK_UNLIMITED = "unlimited", + BUILD_YES = "YES", + BUILD_NO = "NO", + }, + DE = { + -- ============================================================ + -- Kiste / Fracht laden + -- ============================================================ + CRATE_LOADED_GROUNDCREW = "Kiste %s vom Bodenpersonal geladen!", + CRATE_UNLOADED_GROUNDCREW = "Kiste %s vom Bodenpersonal entladen!", + CRATE_LOADED_ID = "Kiste ID %d für %s geladen!", + LOADED_FULL = "%d %s geladen.", + LOADED_SETS_LEFTOVER = "%d %s geladen, %d Kiste(n) übrig.", + LOADED_SETS = "%d %s geladen.", + LOADED_PARTIAL = "Nur %d/%d Kiste(n) von %s geladen.", + LOADED_PARTIAL_LIMIT = "Nur %d/%d Kiste(n) von %s geladen. Frachtlimit erreicht!", + LOADED_BATCH = "%d %s geladen.", + LOADED_BATCH_PARTIAL = "Einige Sets konnten nicht vollständig geladen werden.", + -- ============================================================ + -- Abwerfen / Entladen + -- ============================================================ + DROPPED_FULL = "%d %s abgeworfen.", + DROPPED_SETS_LEFTOVER = "%d %s abgeworfen, %d Kiste(n) übrig.", + DROPPED_SETS = "%d %s abgeworfen.", + DROPPED_PARTIAL = "%d/%d Kiste(n) von %s abgeworfen.", + DROPPED_INTO_ACTION = "%s im Einsatz abgesetzt!", + DROPPED_BEACON = "%s abgesetzt | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", + CRATES_POSITIONED = "%d Kisten für %s in Ihrer Nähe positioniert!", + CRATES_DROPPED = "%d Kisten für %s abgeworfen!", + -- ============================================================ + -- Truppen + -- ============================================================ + BOARDED = "%s eingestiegen!", + BOARDING = "%s steigt ein!", + TROOPS_RETURNED = "Truppen zur Basis zurückgekehrt!", + -- ============================================================ + -- Einsatz + -- ============================================================ + DEPLOYED_NEAR_YOU = "%s in Ihrer Nähe eingesetzt!", + UNITS_REMOVED = "%s entfernt", + -- ============================================================ + -- Bauen / Reparieren + -- ============================================================ + BUILD_STARTED = "Bau gestartet, fertig in %d Sekunden!", + REPAIR_STARTED = "Reparatur mit %s gestartet, dauert %d Sek.", + NO_UNIT_TO_REPAIR = "Keine Einheit in Reichweite zum Reparieren!", + CANT_REPAIR_WITH = "Diese Einheit kann nicht mit %s repariert werden", + CRATES_MOVE_BEFORE_BUILD = "*** Kisten müssen vor dem Bau verschoben werden!", + -- ============================================================ + -- Fehler - Hubschrauber / Gewicht / Kapazität + -- ============================================================ + CHOPPER_CANNOT_CARRY = "Dieser Hubschrauber kann keine Kisten transportieren!", + TOO_HEAVY = "Entschuldigung, das ist zu schwer zum Laden!", + FULLY_LOADED = "Entschuldigung, wir sind voll beladen!", + CRAMMED = "Entschuldigung, wir sind bereits voll besetzt!", + NO_CAPACITY_NOW = "Aktuell keine Ladekapazität mehr vorhanden!", + NO_MORE_CAPACITY = "Keine Kapazität mehr für weitere Kisten!", + CANNOT_LOAD_NONE_OR_FULL = "Laden nicht möglich: keine Kisten gefunden oder Kapazität erschöpft.", + -- ============================================================ + -- Fehler - Position + -- ============================================================ + NEED_TO_LAND_OR_HOVER_LOAD = "Bitte landen oder schweben Sie zum Laden!", + HOVER_OVER_CRATES = "Schweben Sie über die Kisten, um sie aufzunehmen!", + LAND_OR_HOVER_OVER_CRATES = "Landen oder schweben Sie über die Kisten, um sie aufzunehmen!", + MUST_LAND_OR_HOVER_CRATES = "Sie müssen landen oder schweben, um Kisten zu laden!", + NEED_TO_LAND_BUILD = "Sie müssen landen / anhalten, um etwas zu bauen, Pilot!", + NOT_CLOSE_ENOUGH_LOGISTICS = "Sie sind nicht nah genug an einer Logistikzone!", + NOT_CLOSE_ENOUGH_DROP = "Sie sind nicht nah genug an einer Abwurfzone!", + NOT_CLOSE_ENOUGH_ZONE_NM = "Negativ, Sie müssen näher als %d Seemeilen an einer Zone sein!", + CANNOT_BUILD_LOADING_AREA = "In einem Ladebereich kann nicht gebaut werden, Pilot!", + -- ============================================================ + -- Fehler - Türen + -- ============================================================ + OPEN_DOORS_LOAD_CARGO = "Bitte öffnen Sie die Tür(en) zum Laden von Fracht!", + OPEN_DOORS_LOAD_TROOPS = "Bitte öffnen Sie die Tür(en) zum Einladen von Truppen!", + OPEN_DOORS_EXTRACT_TROOPS = "Bitte öffnen Sie die Tür(en) zum Aussteigen der Truppen!", + OPEN_DOORS_UNLOAD_TROOPS = "Bitte öffnen Sie die Tür(en) zum Entladen der Truppen!", + OPEN_DOORS_DROP_CARGO = "Bitte öffnen Sie die Tür(en) zum Abwerfen der Fracht!", + -- ============================================================ + -- Fehler - Bestand / Verfügbarkeit + -- ============================================================ + ALL_GONE = "Entschuldigung, alle %s sind vergriffen!", + RAN_OUT_OF = "Entschuldigung, %s ist nicht mehr vorrätig", + CARGO_NOT_AVAILABLE_ZONE = "Die angeforderte Fracht ist in dieser Zone nicht verfügbar!", + ENOUGH_CRATES_NEARBY = "Es sind bereits genügend Kisten in der Nähe! Bitte zuerst um diese kümmern!", + NO_CRATES_WITHIN = "Keine (ladbaren) Kisten in %d Metern Umkreis!", + NO_CRATES_WITHIN_PLAIN = "Keine Kisten in %d Metern Umkreis!", + NO_CRATES_IN_RANGE = "Keine Kisten in Reichweite gefunden!", + NO_NAMED_CRATES_IN_RANGE = "Keine \"%s\"-Kisten in Reichweite gefunden!", + NO_LOADABLE_CRATES = "Entschuldigung, keine ladbaren Kisten in der Nähe oder maximales Frachtgewicht erreicht!", + NO_UNITS_TO_EXTRACT = "Keine Einheiten nah genug zum Aussteigen!", + NO_UNIT_CONFIG = "Keine Einheitenkonfiguration für %s gefunden", + CANT_ONBOARD = "%s kann nicht eingeladen werden", + TOO_MANY_UNITS_NEARBY = "Sie haben bereits %d Einheiten in der Nähe!", + NO_CRATE_GROUPS = "Keine Kistengruppen für diese Einheit gefunden!", + NO_CRATE_SET = "Kein Kistenset gefunden oder Index ungültig!", + NO_CRATE_IN_SET = "Keine Kiste in diesem Set gefunden!", + NO_TROOP_CHUNK = "Kein Truppenfracht-Block für ID %d gefunden!", + TROOP_CHUNK_EMPTY = "Truppenfracht-Block für ID %d ist leer!", + -- ============================================================ + -- Nichts geladen / kein Bestand + -- ============================================================ + NOTHING_LOADED = "Nichts geladen!\nTruppenlimit: %d | Kistenlimit: %d | Gewichtslimit: %d kg", + NOTHING_LOADED_AIRDROP = "Nichts geladen oder nicht innerhalb der Abwurfparameter!", + NOTHING_LOADED_HOVER = "Nichts geladen oder Schwebeparameter nicht erfüllt!", + NOTHING_IN_STOCK = "Nichts vorrätig!", + NOTHING_TO_PACK = "Nichts in dieser Entfernung zum Verpacken, Pilot!", + NOTHING_TO_REMOVE = "Nichts in dieser Entfernung zum Entfernen, Pilot!", + -- ============================================================ + -- Zone / Info + -- ============================================================ + ROGER_ZONE = "Verstanden, %s Zone %s!", + -- ============================================================ + -- Report: Schwebe- / Flugparameter + -- ============================================================ + HOVER_PARAMS_METRIC = "Schwebeparameter (Autoladen/Abwurf):\n - Min. Höhe %dm \n - Max. Höhe %dm \n - Max. Geschwindigkeit 2m/s \n - Im Parameter: %s", + HOVER_PARAMS_IMPERIAL = "Schwebeparameter (Autoladen/Abwurf):\n - Min. Höhe %dft \n - Max. Höhe %dft \n - Max. Geschwindigkeit 6ft/s \n - Im Parameter: %s", + FLIGHT_PARAMS_IMPERIAL = "Flugparameter (Luftabwurf):\n - Min. Höhe %dft \n - Max. Höhe %dft \n - Im Parameter: %s", + FLIGHT_PARAMS_METRIC = "Flugparameter (Luftabwurf):\n - Min. Höhe %dm \n - Max. Höhe %dm \n - Im Parameter: %s", + -- ============================================================ + -- Report-Titel + -- ============================================================ + REPORT_CRATES_FOUND = "Kisten in der Nähe:", + REPORT_REMOVING_CRATES = "Entferne Kisten in der Nähe:", + REPORT_TRANSPORT_CHECKOUT = "Transport-Checkliste", + REPORT_INVENTORY = "Inventarliste", + REPORT_BUILD_CHECKLIST = "Checkliste baubare Kisten", + REPORT_REPAIR_CHECKLIST = "Checkliste Reparaturen", + REPORT_BEACONS = "Aktive Zonenfeuer", + -- ============================================================ + -- Report-Sektionskopfzeilen + -- ============================================================ + REPORT_SECTION_TROOPS = " -- TRUPPEN --", + REPORT_SECTION_CRATES = " -- KISTEN --", + REPORT_SECTION_CRATES_GC = " -- KISTEN via Bodenpersonal geladen --", + REPORT_SECTION_NONE = " K E I N E", + REPORT_SECTION_NONE_ALT = " --- Keine gefunden! ---", + REPORT_SECTION_NONE_REPAIR = " --- Keine gefunden ---", + REPORT_GC_LOADABLE_HINT = "Wahrscheinlich durch Bodenpersonal ladbar (F8)", + REPORT_TOTAL_MASS = "Gesamtgewicht: %s kg. Ladbar: %s kg.", + REPORT_TROOPS_CRATES_COUNT = "Truppen: %d(%d), Kisten: %d(%d)", + REPORT_TROOPS_CRATETYPES_COUNT = "Truppen: %d, Kistentypen: %d", + -- ============================================================ + -- Report-Zeilenvorlagen + -- ============================================================ + REPORT_ROW_TROOP = "Truppe: %s Größe %d", + REPORT_ROW_CRATE = "Kiste: %s %d/%d", + REPORT_ROW_CRATE_SIZE1 = "Kiste: %s Größe 1", + REPORT_ROW_GC_CRATE = "Bodenpersonal-Kiste: %s Größe 1", + REPORT_ROW_DROPPED_CRATE = "Abgeworfene Kiste für %s, %dkg", + REPORT_ROW_CRATE_KG = "Kiste für %s, %dkg", + REPORT_ROW_CRATE_REMOVED = "Kiste für %s, %dkg entfernt", + REPORT_ROW_UNIT_STOCK = "Einheit: %s | Soldaten: %d | Bestand: %s", + REPORT_ROW_TYPE_CRATE_STOCK = "Typ: %s | Kisten pro Set: %d | Bestand: %s", + REPORT_ROW_TYPE_STOCK = "Typ: %s | Bestand: %s", + REPORT_ROW_BUILD_CHECK = "Typ: %s | Benötigt: %d | Gefunden: %d | Baubar: %s", + REPORT_ROW_REPAIR_CHECK = "Typ: %s | Benötigt: %d | Gefunden: %d | Reparierbar: %s", + REPORT_ROW_BEACON = " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", + -- ============================================================ + -- Gewichts- / Kistenlimit-Token + -- ============================================================ + WEIGHT_LIMIT = "Gewichtslimit erreicht", + CRATE_LIMIT = "Kistenlimit erreicht", + -- ============================================================ + -- Menübezeichnungen - Obere Ebene + -- ============================================================ + MENU_CTLD = "CTLD", + MENU_MANAGE_TROOPS = "Truppen verwalten", + MENU_MANAGE_CRATES = "Kisten verwalten", + MENU_MANAGE_UNITS = "Einheiten verwalten", + -- ============================================================ + -- Menübezeichnungen - Truppen + -- ============================================================ + MENU_LOAD_TROOPS = "Truppen einladen", + MENU_DROP_TROOPS = "Truppen absetzen", + MENU_DROP_ALL_TROOPS = "ALLE Truppen absetzen", + MENU_EXTRACT_TROOPS = "Truppen aufnehmen", + MENU_DROP_N_TROOPS = "(%d) %s absetzen", + -- ============================================================ + -- Menübezeichnungen - Kisten: Holen + -- ============================================================ + MENU_GET_CRATES = "Kisten holen", + MENU_GET = "Holen", + MENU_GET_AND_LOAD = "Holen und laden", + MENU_GET_ANYWAY = "Trotzdem holen", + MENU_PARTIALLY_LOAD = "Teilweise laden", + MENU_OUT_OF_STOCK = "Nicht vorrätig", + MENU_TROOP_LIMIT = "Truppenlimit erreicht", + -- ============================================================ + -- Menübezeichnungen - Kisten: Laden + -- ============================================================ + MENU_LOAD_CRATES = "Kisten laden", + MENU_LOAD_ALL = "ALLE laden", + MENU_SHOW_LOADABLE_CRATES = "Ladbare Kisten anzeigen", + MENU_NO_CRATES_FOUND_RESCAN = "Keine Kisten gefunden! Neu scannen?", + MENU_USE_C130_LOAD = "C-130-Ladesystem verwenden", + -- ============================================================ + -- Menübezeichnungen - Kisten: Abwerfen + -- ============================================================ + MENU_DROP_CRATES = "Kisten abwerfen", + MENU_DROP_ALL_CRATES = "ALLE Kisten abwerfen", + MENU_DROP = "Abwerfen", + MENU_DROP_AND_BUILD = "Abwerfen und bauen", + MENU_DROP_N_SETS = "%d Set%s abwerfen", + MENU_NO_CRATES_TO_DROP = "Keine Kisten zum Abwerfen!", + -- ============================================================ + -- Menübezeichnungen - Kisten: Bauen / Reparieren / Packen / Entfernen + -- ============================================================ + MENU_BUILD_CRATES = "Kisten bauen", + MENU_REPAIR = "Reparieren", + MENU_PACK_CRATES = "Kisten packen", + MENU_PACK = "Packen", + MENU_PACK_AND_LOAD = "Packen und laden", + MENU_PACK_AND_REMOVE = "Packen und entfernen", + MENU_REMOVE_CRATES = "Kisten entfernen", + MENU_REMOVE_CRATES_NEARBY = "Nahe Kisten entfernen", + MENU_LIST_CRATES_NEARBY = "Nahe Kisten auflisten", + MENU_CRATES_NEEDED = "%d Kiste%s %s (%dkg)", + -- ============================================================ + -- Menübezeichnungen - Einheiten (C-130) + -- ============================================================ + MENU_GET_UNITS = "Einheiten holen", + MENU_REMOVE_UNITS_NEARBY = "Nahe Einheiten entfernen", + -- ============================================================ + -- Menübezeichnungen - Info / Fracht + -- ============================================================ + MENU_LIST_BOARDED_CARGO = "Geladene Fracht anzeigen", + MENU_INVENTORY = "Inventar", + MENU_LIST_ZONE_BEACONS = "Aktive Zonenfeuer anzeigen", + -- ============================================================ + -- Menübezeichnungen - Rauch / Leuchtfeuer / Baken + -- ============================================================ + MENU_SMOKES_FLARES_BEACONS = "Rauch, Leuchtfeuer, Baken", + MENU_SMOKE_ZONES_NEARBY = "Nahe Zonen einrauchen", + MENU_DROP_SMOKE_NOW = "Rauch jetzt setzen", + MENU_RED_SMOKE = "Roter Rauch", + MENU_BLUE_SMOKE = "Blauer Rauch", + MENU_GREEN_SMOKE = "Grüner Rauch", + MENU_ORANGE_SMOKE = "Oranger Rauch", + MENU_WHITE_SMOKE = "Weißer Rauch", + MENU_FLARE_ZONES_NEARBY = "Nahe Zonen befeuern", + MENU_FIRE_FLARE_NOW = "Leuchtfeuer jetzt abfeuern", + MENU_DROP_BEACON_NOW = "Bake jetzt setzen", + -- ============================================================ + -- Menübezeichnungen - Parameter + -- ============================================================ + MENU_SHOW_FLIGHT_PARAMS = "Flugparameter anzeigen", + MENU_SHOW_HOVER_PARAMS = "Schwebeparameter anzeigen", + STOCK_NONE = "keiner", + STOCK_UNLIMITED = "unbegrenzt", + BUILD_YES = "JA", + BUILD_NO = "NEIN", +}, +FR = { + --- ============================================================ + -- Chargement caisse / fret + -- ============================================================ + CRATE_LOADED_GROUNDCREW = "Caisse(s) %s chargée(s) par l'équipe au sol !", + CRATE_UNLOADED_GROUNDCREW = "Caisse(s) %s déchargée(s) par l'équipe au sol !", + CRATE_LOADED_ID = "Caisse(s) ID %d pour %s chargée(s) !", + LOADED_FULL = "%d %s chargé(s).", + LOADED_SETS_LEFTOVER = "%d %s chargé(s), %d caisse(s) restante(s).", + LOADED_SETS = "%d %s chargé(s).", + LOADED_PARTIAL = "Seulement %d/%d caisse(s) de %s chargée(s).", + LOADED_PARTIAL_LIMIT = "Seulement %d/%d caisse(s) de %s chargée(s). Limite de fret atteinte !", + LOADED_BATCH = "%d %s chargé(s).", + LOADED_BATCH_PARTIAL = "Certains ensembles n'ont pas pu être complètement chargés.", + -- ============================================================ + -- Largage / Déchargement + -- ============================================================ + DROPPED_FULL = "%d %s largué(s).", + DROPPED_SETS_LEFTOVER = "%d %s largué(s), %d caisse(s) restante(s).", + DROPPED_SETS = "%d %s largué(s).", + DROPPED_PARTIAL = "%d/%d caisse(s) de %s larguée(s).", + DROPPED_INTO_ACTION = "%s engagé(s) en action !", + DROPPED_BEACON = "%s largué | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", + CRATES_POSITIONED = "%d caisses pour %s positionnées près de vous !", + CRATES_DROPPED = "%d caisses pour %s larguées !", + -- ============================================================ + -- Troupes + -- ============================================================ + BOARDED = "%s embarqué(s) !", + BOARDING = "%s en cours d'embarquement !", + TROOPS_RETURNED = "Les troupes sont retournées à la base !", + -- ============================================================ + -- Déploiement + -- ============================================================ + DEPLOYED_NEAR_YOU = "%s déployé(s) près de vous !", + UNITS_REMOVED = "%s supprimé(s)", + -- ============================================================ + -- Construction / Réparation + -- ============================================================ + BUILD_STARTED = "Construction démarrée, prête dans %d secondes !", + REPAIR_STARTED = "Réparation démarrée avec %s, durée %d sec.", + NO_UNIT_TO_REPAIR = "Aucune unité(s) assez proche pour être réparée(s) !", + CANT_REPAIR_WITH = "Impossible de réparer cette unité avec %s", + CRATES_MOVE_BEFORE_BUILD = "*** Les caisses doivent être déplacées avant la construction !", + -- ============================================================ + -- Erreurs - Hélicoptère / Poids / Capacité + -- ============================================================ + CHOPPER_CANNOT_CARRY = "Cet hélicoptère ne peut pas transporter de caisses !", + TOO_HEAVY = "Désolé, c'est trop lourd à charger !", + FULLY_LOADED = "Désolé, capacité maximale atteinte !", + CRAMMED = "Désolé, nous sommes déjà au complet !", + NO_CAPACITY_NOW = "Aucune capacité de chargement disponible pour le moment !", + NO_MORE_CAPACITY = "Plus de capacité pour charger des caisses !", + CANNOT_LOAD_NONE_OR_FULL = "Chargement impossible : aucune caisse trouvée ou capacité épuisée.", + -- ============================================================ + -- Erreurs - Position + -- ============================================================ + NEED_TO_LAND_OR_HOVER_LOAD = "Vous devez atterrir ou rester en vol stationnaire pour charger !", + HOVER_OVER_CRATES = "Survolez les caisses en stationnaire pour les récupérer !", + LAND_OR_HOVER_OVER_CRATES = "Atterrissez ou survolez les caisses en stationnaire pour les récupérer !", + MUST_LAND_OR_HOVER_CRATES = "Vous devez atterrir ou rester en stationnaire pour charger les caisses !", + NEED_TO_LAND_BUILD = "Vous devez atterrir / vous arrêter pour construire quelque chose, Pilote !", + NOT_CLOSE_ENOUGH_LOGISTICS = "Vous n'êtes pas assez proche d'une zone logistique !", + NOT_CLOSE_ENOUGH_DROP = "Vous n'êtes pas assez proche d'une zone de largage !", + NOT_CLOSE_ENOUGH_ZONE_NM = "Négatif, vous devez être à moins de %d nm d'une zone !", + CANNOT_BUILD_LOADING_AREA = "Vous ne pouvez pas construire dans une zone de chargement, Pilote !", + -- ============================================================ + -- Erreurs - Portes + -- ============================================================ + OPEN_DOORS_LOAD_CARGO = "Vous devez ouvrir la/les porte(s) pour charger du fret !", + OPEN_DOORS_LOAD_TROOPS = "Vous devez ouvrir la/les porte(s) pour embarquer des troupes !", + OPEN_DOORS_EXTRACT_TROOPS = "Vous devez ouvrir la/les porte(s) pour extraire des troupes !", + OPEN_DOORS_UNLOAD_TROOPS = "Vous devez ouvrir la/les porte(s) pour débarquer des troupes !", + OPEN_DOORS_DROP_CARGO = "Vous devez ouvrir la/les porte(s) pour larguer du fret !", + -- ============================================================ + -- Erreurs - Stock / Disponibilité + -- ============================================================ + ALL_GONE = "Désolé, tous les %s sont épuisés !", + RAN_OUT_OF = "Désolé, nous n'avons plus de %s !", + CARGO_NOT_AVAILABLE_ZONE = "Le fret demandé n'est pas disponible dans cette zone !", + ENOUGH_CRATES_NEARBY = "Il y a déjà suffisamment de caisses à proximité ! Occupez-vous d'abord de celles-ci !", + NO_CRATES_WITHIN = "Aucune caisse (chargeable) dans un rayon de %d mètres !", + NO_CRATES_WITHIN_PLAIN = "Aucune caisse dans un rayon de %d mètres !", + NO_CRATES_IN_RANGE = "Aucune caisse trouvée à portée !", + NO_NAMED_CRATES_IN_RANGE = "Aucune caisse \"%s\" trouvée à portée !", + NO_LOADABLE_CRATES = "Désolé, aucune caisse chargeable à proximité ou poids maximum atteint !", + NO_UNITS_TO_EXTRACT = "Aucune unité assez proche pour être extraite !", + NO_UNIT_CONFIG = "Aucune configuration d'unité trouvée pour %s", + CANT_ONBOARD = "Impossible d'embarquer %s", + TOO_MANY_UNITS_NEARBY = "Vous avez déjà %d unités à proximité !", + NO_CRATE_GROUPS = "Aucun groupe de caisses trouvé pour cette unité !", + NO_CRATE_SET = "Aucun ensemble de caisses trouvé ou index invalide !", + NO_CRATE_IN_SET = "Aucune caisse trouvée dans cet ensemble !", + NO_TROOP_CHUNK = "Aucun bloc de fret de troupes trouvé pour l'ID %d !", + TROOP_CHUNK_EMPTY = "Le bloc de fret de troupes pour l'ID %d est vide !", + -- ============================================================ + -- Rien de chargé / en stock + -- ============================================================ + NOTHING_LOADED = "Rien de chargé !\nLimite de troupes : %d | Limite de caisses : %d | Limite en poids : %d kg", + NOTHING_LOADED_AIRDROP = "Rien de chargé ou paramètres de largage non respectés !", + NOTHING_LOADED_HOVER = "Rien de chargé ou paramètres de vol stationnaire non respectés !", + NOTHING_IN_STOCK = "Rien en stock !", + NOTHING_TO_PACK = "Rien à charger à cette distance, Pilote !", + NOTHING_TO_REMOVE = "Rien à retirer à cette distance, Pilote !", + -- ============================================================ + -- Zone / Info + -- ============================================================ + ROGER_ZONE = "Compris, zone %s %s !", + -- ============================================================ + -- Rapport : Paramètres stationnaire / vol + -- ============================================================ + HOVER_PARAMS_METRIC = "Paramètres stationnaires (autochargement/largage) :\n - Hauteur min. %dm \n - Hauteur max. %dm \n - Vitesse max. 2m/s \n - Dans les paramètres : %s", + HOVER_PARAMS_IMPERIAL = "Paramètres stationnaires (autochargement/largage) :\n - Hauteur min. %dft \n - Hauteur max. %dft \n - Vitesse max. 6ft/s \n - Dans les paramètres : %s", + FLIGHT_PARAMS_IMPERIAL = "Paramètres de vol (largage aérien) :\n - Hauteur min. %dft \n - Hauteur max. %dft \n - Dans les paramètres : %s", + FLIGHT_PARAMS_METRIC = "Paramètres de vol (largage aérien) :\n - Hauteur min. %dm \n - Hauteur max. %dm \n - Dans les paramètres : %s", + -- ============================================================ + -- Titres de rapport + -- ============================================================ + REPORT_CRATES_FOUND = "Caisses trouvées à proximité :", + REPORT_REMOVING_CRATES = "Suppression des caisses à proximité :", + REPORT_TRANSPORT_CHECKOUT = "Fiche de contrôle transport", + REPORT_INVENTORY = "Fiche d'inventaire", + REPORT_BUILD_CHECKLIST = "Checklist caisses constructibles", + REPORT_REPAIR_CHECKLIST = "Checklist réparations", + REPORT_BEACONS = "Balises de zone actives", + -- ============================================================ + -- En-têtes de sections de rapport + -- ============================================================ + REPORT_SECTION_TROOPS = " -- TROUPES --", + REPORT_SECTION_CRATES = " -- CAISSES --", + REPORT_SECTION_CRATES_GC = " -- CAISSES chargées via équipe au sol --", + REPORT_SECTION_NONE = " A U C U N", + REPORT_SECTION_NONE_ALT = " --- Aucun trouvé ! ---", + REPORT_SECTION_NONE_REPAIR = " --- Aucun trouvé ---", + REPORT_GC_LOADABLE_HINT = "Probablement chargeable via l’équipe au sol (F8)", + REPORT_TOTAL_MASS = "Masse totale : %s kg. Chargeable : %s kg.", + REPORT_TROOPS_CRATES_COUNT = "Troupes : %d(%d), Caisses : %d(%d)", + REPORT_TROOPS_CRATETYPES_COUNT = "Troupes : %d, Types de caisses : %d", + -- ============================================================ + -- Modèles de lignes de rapport + -- ============================================================ + REPORT_ROW_TROOP = "Troupe : %s taille %d", + REPORT_ROW_CRATE = "Caisse : %s %d/%d", + REPORT_ROW_CRATE_SIZE1 = "Caisse : %s taille 1", + REPORT_ROW_GC_CRATE = "Caisses chargées par l'équipe au sol : %s taille 1", + REPORT_ROW_DROPPED_CRATE = "Caisses larguées pour %s, %dkg", + REPORT_ROW_CRATE_KG = "Caisses pour %s, %dkg", + REPORT_ROW_CRATE_REMOVED = "Caisses pour %s, %dkg retirées", + REPORT_ROW_UNIT_STOCK = "Unités : %s | Soldats : %d | Stock : %s", + REPORT_ROW_TYPE_CRATE_STOCK = "Type : %s | Caisses par ensemble : %d | Stock : %s", + REPORT_ROW_TYPE_STOCK = "Type : %s | Stock : %s", + REPORT_ROW_BUILD_CHECK = "Type : %s | Requis : %d | Trouvé : %d | Constructible : %s", + REPORT_ROW_REPAIR_CHECK = "Type : %s | Requis : %d | Trouvé : %d | Réparable : %s", + REPORT_ROW_BEACON = " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", + -- ============================================================ + -- Tokens limite poids / caisses + -- ============================================================ + WEIGHT_LIMIT = "Limite de poids atteinte", + CRATE_LIMIT = "Limite de caisses atteinte", + -- ============================================================ + -- Libellés de menu - Niveau supérieur + -- ============================================================ + MENU_CTLD = "CTLD", + MENU_MANAGE_TROOPS = "Gérer les troupes", + MENU_MANAGE_CRATES = "Gérer les caisses", + MENU_MANAGE_UNITS = "Gérer les unités", + -- ============================================================ + -- Libellés de menu - Troupes + -- ============================================================ + MENU_LOAD_TROOPS = "Embarquer troupes", + MENU_DROP_TROOPS = "Déposer troupes", + MENU_DROP_ALL_TROOPS = "Déposer TOUTES les troupes", + MENU_EXTRACT_TROOPS = "Extraire troupes", + MENU_DROP_N_TROOPS = "Déposer (%d) %s", + -- ============================================================ + -- Libellés de menu - Caisses : Récupérer + -- ============================================================ + MENU_GET_CRATES = "Récupérer caisses", + MENU_GET = "Récupérer", + MENU_GET_AND_LOAD = "Récupérer et charger", + MENU_GET_ANYWAY = "Récupérer quand même", + MENU_PARTIALLY_LOAD = "Chargement partiel", + MENU_OUT_OF_STOCK = "Rupture de stock", + MENU_TROOP_LIMIT = "Limite de troupes atteinte", + -- ============================================================ + -- Libellés de menu - Caisses : Charger + -- ============================================================ + MENU_LOAD_CRATES = "Charger caisses", + MENU_LOAD_ALL = "Tout charger", + MENU_SHOW_LOADABLE_CRATES = "Afficher caisses chargeables", + MENU_NO_CRATES_FOUND_RESCAN = "Aucune caisse trouvée ! Rescanner ?", + MENU_USE_C130_LOAD = "Utiliser le système de chargement C-130", + -- ============================================================ + -- Libellés de menu - Caisses : Larguer + -- ============================================================ + MENU_DROP_CRATES = "Larguer caisses", + MENU_DROP_ALL_CRATES = "Larguer TOUTES les caisses", + MENU_DROP = "Larguer", + MENU_DROP_AND_BUILD = "Larguer et construire", + MENU_DROP_N_SETS = "Larguer %d ensemble%s", + MENU_NO_CRATES_TO_DROP = "Aucune caisse à larguer !", + -- ============================================================ + -- Libellés de menu - Caisses : Construire / Réparer / Emballer / Retirer + -- ============================================================ + MENU_BUILD_CRATES = "Construire caisses", + MENU_REPAIR = "Réparer", + MENU_PACK_CRATES = "Emballer caisses", + MENU_PACK = "Emballer", + MENU_PACK_AND_LOAD = "Emballer et charger", + MENU_PACK_AND_REMOVE = "Emballer et retirer", + MENU_REMOVE_CRATES = "Retirer caisses", + MENU_REMOVE_CRATES_NEARBY = "Retirer caisses proches", + MENU_LIST_CRATES_NEARBY = "Lister caisses proches", + MENU_CRATES_NEEDED = "%d caisse%s %s (%dkg)", + -- ============================================================ + -- Libellés de menu - Unités (C-130) + -- ============================================================ + MENU_GET_UNITS = "Récupérer unités", + MENU_REMOVE_UNITS_NEARBY = "Retirer les unités proches", + -- ============================================================ + -- Libellés de menu - Info / Fret + -- ============================================================ + MENU_LIST_BOARDED_CARGO = "Lister le fret embarqué", + MENU_INVENTORY = "Inventaire", + MENU_LIST_ZONE_BEACONS = "Lister les balises de zones actives", + -- ============================================================ + -- Libellés de menu - Fumigènes / Fusées / Balises + -- ============================================================ + MENU_SMOKES_FLARES_BEACONS = "Fumigènes, Fusées, Balises", + MENU_SMOKE_ZONES_NEARBY = "Fumigène sur les zones proches", + MENU_DROP_SMOKE_NOW = "Poser fumigène maintenant", + MENU_RED_SMOKE = "Fumigène rouge", + MENU_BLUE_SMOKE = "Fumigène bleu", + MENU_GREEN_SMOKE = "Fumigène vert", + MENU_ORANGE_SMOKE = "Fumigène orange", + MENU_WHITE_SMOKE = "Fumigène blanc", + MENU_FLARE_ZONES_NEARBY = "Baliser zones proches", + MENU_FIRE_FLARE_NOW = "Tirer une fusée maintenant", + MENU_DROP_BEACON_NOW = "Poser une balise maintenant", + -- ============================================================ + -- Libellés de menu - Paramètres + -- ============================================================ + MENU_SHOW_FLIGHT_PARAMS = "Afficher paramètres de vol", + MENU_SHOW_HOVER_PARAMS = "Afficher les paramètres stationnaire", + STOCK_NONE = "aucun", + STOCK_UNLIMITED = "illimité", + BUILD_YES = "OUI", + BUILD_NO = "NON", + }, + } + \ No newline at end of file From f1386dd2295dc4b5efe83f2f6f8a6b3b9ab68b10 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 28 Feb 2026 18:31:29 +0100 Subject: [PATCH 327/349] xx --- Moose Development/Moose/Ops/CTLD.lua | 101 ++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 2943a6b02..488ea513b 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -199,7 +199,7 @@ do -- my_ctld.validateAndRepositionUnits = false -- Uses Disposition and other logic to find better ground positions for ground units avoiding trees, water, roads, runways, map scenery, statics and other units in the area. (Default is false) -- my_ctld.loadSavedCrates = true -- Load back crates (STATIC) from the save file. Useful for mission restart cleanup. (Default is true) -- my_ctld.UseC130LoadAndUnload = false -- When set to true, forces the C-130 player to use the C-130J built system to load the cargo onboard and to unload. (Default is false) --- my_ctld.local = "en" -- Language locale to use, available are "en" (default), "de" and "fr" +-- my_ctld.locale = "en" -- Language locale to use, available are "en" (default), "de" and "fr" -- -- ## 2.1 CH-47 Chinook support -- @@ -703,6 +703,7 @@ CTLD = { allowCATransport = false, VehicleMoveFormation = AI.Task.VehicleFormation.VEE, locale = "en", + usesrs = false } ------------------------------ @@ -1447,6 +1448,69 @@ function CTLD:_InitLocalization() return self end +--- [User] Set SRS TTS details - see @{Sound.SRS} for details.`SetSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible. +-- @param #CTLD self +-- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 30 or {30,124.5}. Defaults to {30,124.5}. There needs to be exactly the same number of modulations! +-- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies! +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" +-- @param #string Gender (Optional) Defaults to "male" +-- @param #string Culture (Optional) Defaults to "en-US" +-- @param #number Port (Optional) Defaults to 5002 +-- @param #string Voice (Optional) Use a specifc voice with the @{Sound.SRS#SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. +-- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS. Or Piper voice types with HOUND backend. +-- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest) +-- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS; if you use a config file for MSRS, hand in nil here. +-- @param #string AccessKey (Optional) Your Google API access key. This is necessary if DCS-gRPC is used as backend; if you use a config file for MSRS, hand in nil here. +-- @param #string Backend (Optional) MSRS Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC; if you use a config file for MSRS, hand in nil here. +-- @param #string Provider (Optional) MSRS Provider to be used, can be MSRS.Provider.Google or MSRS.Provider.WINDOWS etc; if you use a config file for MSRS, hand in nil here. +-- @return #CTLD self +function CTLD:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend,Provider) + self:T(self.lid.."SetSRS") + self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- + self.Gender = Gender or MSRS.gender or "male" -- + self.Culture = Culture or MSRS.culture or "en-US" -- + self.Port = Port or MSRS.port or 5002 -- + self.Voice = Voice or MSRS.voice + self.PathToGoogleKey = PathToGoogleKey -- + self.AccessKey = AccessKey + self.Volume = Volume or 1.0 -- + self.usesrs = true + self.Frequency = Frequency or {30,124.5} -- + self.BCFrequency = self.Frequency + self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} -- + self.BCModulation = self.Modulation + -- set up SRS + self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,Backend) + self.SRS:SetCoalition(self.Coalition) + self.Label = self.MenuName or self.Name + self.SRS:SetLabel(self.Label) + self.SRS:SetGender(self.Gender) + self.SRS:SetCulture(self.Culture) + self.SRS:SetPort(self.Port) + self.SRS:SetVolume(self.Volume) + self.SRS.Label = "CTLD" + if Provider then + self.SRS:SetProvider(Provider) + end + if self.PathToGoogleKey then + self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.AccessKey) + self.SRS:SetProvider(Provider or MSRS.Provider.GOOGLE) + end + -- Pre-configured Google? + if (not PathToGoogleKey) and self.SRS:GetProvider() == MSRS.Provider.GOOGLE then + self.PathToGoogleKey = MSRS.poptions.gcloud.credentials + self.Voice = Voice or MSRS.poptions.gcloud.voice + self.AccessKey = AccessKey or MSRS.poptions.gcloud.key + end + if Backend then + self.SRS:SetBackend(Backend) + end + self.SRS:SetVoice(self.Voice) + self.SRSQueue = MSRSQUEUE:New(self.Label) + self.SRSQueue:SetTransmitOnlyWithPlayers(true) + self.SRSQueue.Label = "CTLD" + return self +end --- (Internal) Function to get capabilities of a chopper -- @param #CTLD self @@ -1750,10 +1814,15 @@ end -- @param #number Time Number of seconds to display the message. -- @param #boolean Clearscreen Clear screen or not. -- @param Wrapper.Group#GROUP Group The group receiving the message. -function CTLD:_SendMessage(Text, Time, Clearscreen, Group) +-- @param #boolean Silent If true, do not speak out messages via SRS/TTS (if SRS is set up) +function CTLD:_SendMessage(Text, Time, Clearscreen, Group, Silent) self:T(self.lid .. " _SendMessage") if not self.suppressmessages then local m = MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) + if self.usesrs == true and Silent ~= true then + self.SRSQueue:NewTransmission(Text,duration,self.SRS,tstart,1,subgroups,subtitle,subduration,self.Frequency,self.Modulation,self.Gender, + self.Culture,self.Voice,self.Volume,self.Label,coordinate,self.Speed) + end end return self end @@ -2872,7 +2941,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 35 local nearcrates, numbernearby = self:_FindCratesNearby(Group, Unit, loaddist, true, true, true) - if numbernearby >= canloadcratesno and not drop then + if numbernearby >= canloadcratesno and (not drop) and (not pack) then local msg = self.gettext:GetEntry("ENOUGH_CRATES_NEARBY",self.locale) self:_SendMessage(msg, 10, false, Group) --self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) @@ -3292,11 +3361,11 @@ function CTLD:_ListCratesNearby( _group, _unit) end end end - self:_SendMessage(text:Text(), 30, true, _group) + self:_SendMessage(text:Text(), 30, true, _group,true) else local msg = self.gettext:GetEntry("NO_CRATES_WITHIN",self.locale) msg = string.format(msg,finddist) - self:_SendMessage(msg, 10, false, _group) + self:_SendMessage(msg, 10, false, _group,true) --self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) end return self @@ -3389,7 +3458,7 @@ function CTLD:_RemoveCratesNearby(_group, _unit) text:Add(" N O N E") end text:Add("------------------------------------------------------------") - self:_SendMessage(text:Text(),30,true,_group) + self:_SendMessage(text:Text(),30,true,_group,true) local done = {} for _, e in pairs(crates) do local n = e:GetName() or "none" @@ -3406,7 +3475,7 @@ function CTLD:_RemoveCratesNearby(_group, _unit) else local msg = self.gettext:GetEntry("NO_CRATES_WITHIN",self.locale) msg = string.format(msg,finddist) - self:_SendMessage(msg, 10, false, _group) + self:_SendMessage(msg, 10, false, _group,true) --self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end return self @@ -3849,11 +3918,11 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add("Total Mass: ".. loadedmass .. " kg. Loadable: "..maxloadable.." kg.") local text = report:Text() - self:_SendMessage(text, 30, true, Group) + self:_SendMessage(text, 30, true, Group,true) else local msg = self.gettext:GetEntry("NOTHING_LOADED",self.locale) msg = string.format(msg,trooplimit, cratelimit, maxloadable) - self:_SendMessage(msg, 10, false, Group) + self:_SendMessage(msg, 10, false, Group,true) --self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs", trooplimit, cratelimit, maxloadable), 10, false, Group) end return self @@ -3945,10 +4014,10 @@ function CTLD:_ListInventory(Group, Unit) report:Add(" N O N E") end local text = report:Text() - self:_SendMessage(text, 30, true, Group) + self:_SendMessage(text, 30, true, Group,true) else local msg = self.gettext:GetEntry("NOTHING_IN_STOCK",self.locale) - self:_SendMessage(msg, 10, false, Group) + self:_SendMessage(msg, 10, false, Group,true) --self:_SendMessage(string.format("Nothing in stock!"), 10, false, Group) end return self @@ -4431,7 +4500,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) report:Add("------------------------------------------------------------") local text = report:Text() if not Engineering then - self:_SendMessage(text, 30, true, Group) + self:_SendMessage(text, 30, true, Group,true) else self:T(text) end @@ -4623,7 +4692,7 @@ function CTLD:_RepairCrates(Group, Unit, Engineering) report:Add("------------------------------------------------------------") local text = report:Text() if not Engineering then - self:_SendMessage(text, 30, true, Group) + self:_SendMessage(text, 30, true, Group,true) else self:T(text) end @@ -4641,7 +4710,7 @@ function CTLD:_RepairCrates(Group, Unit, Engineering) if not Engineering then local msg = self.gettext:GetEntry("NO_CRATES_WITHIN_PLAIN",self.locale) msg = string.format(msg,finddist) - self:_SendMessage(msg, 10, false, Group) + self:_SendMessage(msg, 10, false, Group,true) --self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end end -- number > 0 @@ -6514,7 +6583,7 @@ function CTLD:_RefreshDropTroopsMenu(Group, Unit) local chunkID = objList[1]:GetID() self.TroopsIDToChunk[chunkID] = objList - local label = string.format(self.gettext:GetEntry("MENU_DROP_N_TROOPS",self.locale), tName, count) + local label = string.format(self.gettext:GetEntry("MENU_DROP_N_TROOPS",self.locale), count, tName) if count == 1 then MENU_GROUP_COMMAND:New(theGroup, label, dropTroopsMenu, self._UnloadSingleTroopByID, self, theGroup, theUnit, chunkID, 1) else @@ -7287,7 +7356,7 @@ function CTLD:_ListRadioBeacons(Group, Unit) report:Add(" N O N E") end report:Add("------------------------------------------------------------") - self:_SendMessage(report:Text(), 30, true, Group) + self:_SendMessage(report:Text(), 30, true, Group,true) return self end From 085330c93085d96c4995da8b091a6293ce203f61 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 1 Mar 2026 14:00:27 +0100 Subject: [PATCH 328/349] xx --- Moose Development/Moose/Core/Point.lua | 2 +- Moose Development/Moose/Ops/CSAR.lua | 15 +++++++++++++-- Moose Development/Moose/Ops/CTLD.lua | 7 ++++--- Moose Development/Moose/Ops/CTLD_Localization.lua | 3 +++ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 8d4a3189c..f67ffb59d 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -3292,7 +3292,7 @@ do -- COORDINATE local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( FromCoordinate ) - return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision ) + return "BR " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision ) end --- Return a Bearing string from a COORDINATE to the (self) COORDINATE. diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index e66d40ae1..c30547478 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -279,6 +279,8 @@ -- REQUESTSAR = "%s requests SAR at %s, beacon at %.2f KHz!", -- REQUESTSARBEACON = "%s requests SAR at %s, beacon at %.2f KHz!", -- KHZ = "kilo hertz", +-- FILLAT = "at", +-- FILLFOR = "for", -- }, -- -- e.g. for Spanish: @@ -377,6 +379,8 @@ CSAR.Messages = { REQUESTSAR = "%s requests SAR at %s, beacon at %.2f KHz!", REQUESTSARBEACON = "%s requests SAR at %s, beacon at %.2f KHz!", KHZ = "kilo hertz", + FILLAT = "at", + FILLFOR = "for", }, DE = { HEARYOULONG = "%s: %s. Ich höre Sie! Endlich, das ist Musik in meinen Ohren!\nIch zünde eine Rauchgranate, wenn Sie %s entfernt sind.\nLanden Sie oder hovern Sie beim Rauch.", @@ -414,6 +418,8 @@ CSAR.Messages = { REQUESTSAR = "%s fordert SAR bei %s an, ADF %.2f KHz!", REQUESTSARBEACON = "%s fordert SAR bei %s an, ADF %.2f KHz!", KHZ = "Kilohertz", + FILLAT = "bei", + FILLFOR = "für", }, FR = { HEARYOULONG = "%s: %s. Je vous entends! Enfin, c'est de la musique dans mes oreilles!\nJe lancerai une fumée quand vous serez à %s.\nAtterrissez ou survolez la fumée.", @@ -451,6 +457,8 @@ CSAR.Messages = { REQUESTSAR = "%s demande un SAR à %s, balise à %.2f KHz!", REQUESTSARBEACON = "%s demande un SAR à %s, balise à %.2f KHz!", KHZ = "kilohertz", + FILLAT = "au", + FILLFOR = "pour", }, } @@ -2130,6 +2138,8 @@ function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit) -- attention this is the distance from the ASKING unit to target, not from RECCE to target! local startcoordinate = _Unit:GetCoordinate() _coordinatesText = _coordinate:ToStringBR(startcoordinate,settings) + local fillfor = self.gettext:GetEntry("FILLFOR",self.locale) + _coordinatesText = string.gsub(_coordinatesText,"for","") end end end @@ -2174,10 +2184,11 @@ function CSAR:_DisplayActiveSAR(_unitName) else distancetext = string.format("%.1fkm", _distance/1000.0) end + local fillat = self.gettext:GetEntry("FILLAT",self.locale) if _value.frequency == 0 or self.CreateRadioBeacons == false then--shagrat insert CASEVAC without Frequency - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) + table.insert(_csarList, { dist = _distance, msg = string.format("%s %s %s - %s ", _value.desc, fillat, _coordinatesText, distancetext) }) else - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + table.insert(_csarList, { dist = _distance, msg = string.format("%s %s %s - %.2f KHz ADF - %s ", _value.desc, fillat, _coordinatesText, _value.frequency / 1000, distancetext) }) end end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 488ea513b..c68be9667 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -5744,11 +5744,12 @@ function CTLD:_RefreshLoadCratesMenu(Group,Unit) while i<=#list do local left=#list-i+1 local label - if left>=needed then - label=string.format("%d. Load %s",lineIndex,cName) + local loadkey = self.gettext:GetEntry("MENU_LOAD_SINGLE",self.locale) + if left>=needed then + label=string.format("%d. %s %s",lineIndex,loadkey, cName) i=i+needed else - label=string.format("%d. Load %s (%d/%d)",lineIndex,cName,left,needed) + label=string.format("%d. %s %s (%d/%d)",lineIndex,loadkey, cName,left,needed) i=#list+1 end MENU_GROUP_COMMAND:New(Group,label,Group.MyLoadCratesMenu,self._LoadSingleCrateSet,self,Group,Unit,cName) diff --git a/Moose Development/Moose/Ops/CTLD_Localization.lua b/Moose Development/Moose/Ops/CTLD_Localization.lua index e604ffc60..b32671f62 100644 --- a/Moose Development/Moose/Ops/CTLD_Localization.lua +++ b/Moose Development/Moose/Ops/CTLD_Localization.lua @@ -193,6 +193,7 @@ CTLD.Messages = { MENU_SHOW_LOADABLE_CRATES = "Show loadable crates", MENU_NO_CRATES_FOUND_RESCAN = "No crates found! Rescan?", MENU_USE_C130_LOAD = "Use C-130 Load system", + MENU_LOAD_SINGLE = "Load", -- ============================================================ -- Menu labels - Crates: Drop -- ============================================================ @@ -442,6 +443,7 @@ CTLD.Messages = { MENU_SHOW_LOADABLE_CRATES = "Ladbare Kisten anzeigen", MENU_NO_CRATES_FOUND_RESCAN = "Keine Kisten gefunden! Neu scannen?", MENU_USE_C130_LOAD = "C-130-Ladesystem verwenden", + MENU_LOAD_SINGLE = "Lade", -- ============================================================ -- Menübezeichnungen - Kisten: Abwerfen -- ============================================================ @@ -691,6 +693,7 @@ FR = { MENU_SHOW_LOADABLE_CRATES = "Afficher caisses chargeables", MENU_NO_CRATES_FOUND_RESCAN = "Aucune caisse trouvée ! Rescanner ?", MENU_USE_C130_LOAD = "Utiliser le système de chargement C-130", + MENU_LOAD_SINGLE = "Charger", -- ============================================================ -- Libellés de menu - Caisses : Larguer -- ============================================================ From 25d3fb389cd4bc8df516cfc019725fdb341332e4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 Mar 2026 10:15:50 +0100 Subject: [PATCH 329/349] xx --- Moose Development/Moose/Core/Spawn.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 45af7269a..0a64a7d86 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -802,7 +802,7 @@ end --- [Airplane - F15/16/18/AWACS/B1B/Tanker only] Set the STN Link16 starting number of the Group; each unit of the spawned group will have a consecutive STN set. -- @param #SPAWN self --- @param #number Octal The octal number (digits 1..7, max 5 digits, i.e. 1..77777) to set the STN to. Every STN needs to be unique! +-- @param #number Octal The octal number (digits 0..7, max 5 digits, i.e. 1..77777, cannot be zero) to set the STN to. Every STN needs to be unique! -- @return #SPAWN self function SPAWN:InitSTN(Octal) --self:F( { Octal = Octal } ) @@ -820,7 +820,7 @@ end --- [Airplane - A10-C II only] Set the SADL TN starting number of the Group; each unit of the spawned group will have a consecutive SADL set. -- @param #SPAWN self --- @param #number Octal The octal number (digits 1..7, max 4 digits, i.e. 1..7777) to set the SADL to. Every SADL needs to be unique! +-- @param #number Octal The octal number (digits 0..7, max 4 digits, i.e. 1..7777, cannot be zero) to set the SADL to. Every SADL needs to be unique! -- @return #SPAWN self function SPAWN:InitSADL(Octal) --self:F( { Octal = Octal } ) From f4345e100554f2416e6cb9bc5700bd1bdb222e06 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 Mar 2026 10:17:02 +0100 Subject: [PATCH 330/349] xx --- .../Moose/Functional/ZoneCaptureCoalition.lua | 62 +++++-------------- 1 file changed, 16 insertions(+), 46 deletions(-) diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index 256e36d00..c5d3817d3 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -48,7 +48,7 @@ do -- ZONE_CAPTURE_COALITION - -- @type ZONE_CAPTURE_COALITION + --- @type ZONE_CAPTURE_COALITION -- @field #string ClassName Name of the class. -- @field #number MarkBlue ID of blue F10 mark. -- @field #number MarkRed ID of red F10 mark. @@ -220,36 +220,6 @@ do -- ZONE_CAPTURE_COALITION -- A capture zone has been setup that guards the presence of the troops. -- Troops are guarded by red forces. Blue is required to destroy the red forces and capture the zones. -- - -- At first, we setup the Command Centers - -- - -- do - -- - -- RU_CC = COMMANDCENTER:New( GROUP:FindByName( "REDHQ" ), "Russia HQ" ) - -- US_CC = COMMANDCENTER:New( GROUP:FindByName( "BLUEHQ" ), "USA HQ" ) - -- - -- end - -- - -- Next, we define the mission, and add some scoring to it. - -- - -- do -- Missions - -- - -- US_Mission_EchoBay = MISSION:New( US_CC, "Echo Bay", "Primary", - -- "Welcome trainee. The airport Groom Lake in Echo Bay needs to be captured.\n" .. - -- "There are five random capture zones located at the airbase.\n" .. - -- "Move to one of the capture zones, destroy the fuel tanks in the capture zone, " .. - -- "and occupy each capture zone with a platoon.\n " .. - -- "Your orders are to hold position until all capture zones are taken.\n" .. - -- "Use the map (F10) for a clear indication of the location of each capture zone.\n" .. - -- "Note that heavy resistance can be expected at the airbase!\n" .. - -- "Mission 'Echo Bay' is complete when all five capture zones are taken, and held for at least 5 minutes!" - -- , coalition.side.RED ) - -- - -- US_Mission_EchoBay:Start() - -- - -- end - -- - -- - -- Now the real work starts. -- We define a **CaptureZone** object, which is a ZONE object. -- Within the mission, a trigger zone is created with the name __CaptureZone__, with the defined radius within the mission editor. -- @@ -280,12 +250,12 @@ do -- ZONE_CAPTURE_COALITION -- self:E( { Coalition = Coalition } ) -- if Coalition == coalition.side.BLUE then -- ZoneCaptureCoalition:Smoke( SMOKECOLOR.Blue ) - -- US_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) - -- RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of the USA", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) + -- MESSAGE:New(string.format( "%s is under protection of the USA", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.BLUE) + -- MESSAGE:New(string.format( "%s is under protection of the USA", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.RED) -- else -- ZoneCaptureCoalition:Smoke( SMOKECOLOR.Red ) - -- RU_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) - -- US_CC:MessageTypeToCoalition( string.format( "%s is under protection of Russia", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) + -- MESSAGE:New(string.format( "%s is under protection of Russia", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.BLUE) + -- MESSAGE:New(string.format( "%s is under protection of Russia", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.RED) -- end -- end -- end @@ -297,8 +267,8 @@ do -- ZONE_CAPTURE_COALITION -- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self -- function ZoneCaptureCoalition:OnEnterEmpty() -- self:Smoke( SMOKECOLOR.Green ) - -- US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) - -- RU_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) + -- MESSAGE:New(string.format( "%s is unprotected, and can be captured!", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.BLUE) + -- MESSAGE:New(string.format( "%s is unprotected, and can be captured!", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.RED) -- end -- -- The next Event Handlers speak for itself. @@ -310,11 +280,11 @@ do -- ZONE_CAPTURE_COALITION -- local Coalition = self:GetCoalition() -- self:E({Coalition = Coalition}) -- if Coalition == coalition.side.BLUE then - -- US_CC:MessageTypeToCoalition( string.format( "%s is under attack by Russia", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) - -- RU_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) + -- MESSAGE:New(string.format( "%s is under attack by Russia", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.BLUE) + -- MESSAGE:New(string.format( "We are attacking %s", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.RED) -- else - -- RU_CC:MessageTypeToCoalition( string.format( "%s is under attack by the USA", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) - -- US_CC:MessageTypeToCoalition( string.format( "We are attacking %s", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) + -- MESSAGE:New(string.format( "%s is under attack by the USA", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.RED) + -- MESSAGE:New(string.format( "We are attacking %s", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.BLUE) -- end -- end -- @@ -326,12 +296,12 @@ do -- ZONE_CAPTURE_COALITION -- local Coalition = self:GetCoalition() -- self:E({Coalition = Coalition}) -- if Coalition == coalition.side.BLUE then - -- RU_CC:MessageTypeToCoalition( string.format( "%s is captured by the USA, we lost it!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) - -- US_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) + -- MESSAGE:New(string.format( "%s is captured by the USA, we lost it!", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.RED) + -- MESSAGE:New(string.format( "We captured %s, Excellent job!", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.BLUE) -- else - -- US_CC:MessageTypeToCoalition( string.format( "%s is captured by Russia, we lost it!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) - -- RU_CC:MessageTypeToCoalition( string.format( "We captured %s, Excellent job!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) - -- end + -- MESSAGE:New(string.format( "%s is captured by Russia, we lost it!", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.BLUE) + -- MESSAGE:New(string.format( "We captured %s, Excellent job!", ZoneCaptureCoalition:GetZoneName() ),15,MESSAGE.Type.Information):ToCoalition(coalition.side.RED) + -- end -- -- self:__Guard( 30 ) -- end From 360567ccd7472e50eb1c1657cb85b82ed057ac83 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 Mar 2026 16:46:11 +0100 Subject: [PATCH 331/349] xx --- Moose Development/Moose/Ops/EasyGCICAP.lua | 5 +- Moose Development/Moose/Sound/SRS.lua | 54 ++++++++++++++----- Moose Development/Moose/Sound/SoundOutput.lua | 14 ++++- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 50bc14284..e47ff103b 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -1239,10 +1239,11 @@ function EASYGCICAP:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetMissionRange(self.missionrange) - local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING + local wing = self.wings[AirbaseName][1] -- Ops.AirWing#AIRWING wing:AddSquadron(Squadron_One) - wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5},75) + --local countsquads = UTILS.TableLength(wing.cohorts) + wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5},100) return self end diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index cd20b5033..4a93f6ce1 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -731,12 +731,14 @@ MSRS.Backend = { -- @field #string AZURE Microsoft Azure (`azure`). Only possible with DCS-gRPC backend. -- @field #string AMAZON Amazon Web Service (`aws`). Only possible with DCS-gRPC backend. -- @field #string PIPER Piper local voice service. Only possible with Hound-TTS backend. +-- @field #string KITTEN Kitten voice server. Only possible with Hound-TTS backend. MSRS.Provider = { WINDOWS = "win", GOOGLE = "gcloud", AZURE = "azure", AMAZON = "aws", PIPER = "piper", + KITTEN = "kitten", } --- Function for UUID. @@ -1197,6 +1199,17 @@ function MSRS:SetVoicePiper(Voice) return self end +--- Set to use a specific voice if Kitten is used as provider (only Hound-TTS backend). Note that this will override any gender and culture settings. +-- @param #MSRS self +-- @param #string Voice (Optional) [Kitten Voices](https://github.com/uriba107/HoundTTS#installation). Default `"Bella"`. +-- @return #MSRS self +function MSRS:SetVoiceKitten(Voice) + self:F( {Voice=Voice} ) + self:SetVoiceProvider(Voice or "Bella", MSRS.Provider.KITTEN) + + return self +end + --- Set to use a specific voice if Microsoft Azure is use as provider (only DCS-gRPC backend). Note that this will override any gender and culture settings. -- @param #MSRS self -- @param #string Voice (Optional) [Azure Voice](https://learn.microsoft.com/azure/cognitive-services/speech-service/language-support). Default `"en-US-AriaNeural"`. @@ -1449,6 +1462,14 @@ function MSRS:SetTTSProviderPiper() return self end +--- Use Kitten to provide text-to-speech. Only supported if used in combination with Hound-TTS as backend. +-- @param #MSRS self +-- @return #MSRS self +function MSRS:SetTTSProviderKitten() + self:F() + self:SetProvider(MSRS.Provider.KITTEN) + return self +end --- Print SRS help to DCS log file. -- @param #MSRS self @@ -1536,7 +1557,7 @@ function MSRS:PlaySoundText(SoundText, Delay) if self.backend==MSRS.Backend.GRPC then self:_DCSgRPCtts(SoundText.text, nil, SoundText.gender, SoundText.culture, SoundText.voice, SoundText.volume, SoundText.label, SoundText.coordinate) elseif self.backend == MSRS.Backend.HOUND then - self:_HoundTextToSpeech(SoundText.text,nil,nil,SoundText.volume,SoundText.label,self.coalition,SoundText.coordinate,SoundText.Speed,SoundText.gender,SoundText.culture,SoundText.voice) + self:_HoundTextToSpeech(SoundText.text,nil,nil,SoundText.volume,SoundText.label,self.coalition,SoundText.coordinate,SoundText.Speed,SoundText.gender,SoundText.culture,SoundText.voice,nil,SoundText.speaker) else -- Get command. @@ -1561,21 +1582,22 @@ end -- @param #number Delay Delay in seconds, before the message is played. -- @param Core.Point#COORDINATE Coordinate Coordinate. -- @param #number Speed +-- @param #string Speaker Speaker (Sub-Voice) for PIPER only -- @return #MSRS self -function MSRS:PlayText(Text, Delay, Coordinate, Speed) +function MSRS:PlayText(Text, Delay, Coordinate, Speed, Speaker) self:F( {Text, Delay, Coordinate} ) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, nil, Coordinate) + self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, nil, Coordinate, Speed, Speaker) else if self.backend==MSRS.Backend.GRPC then self:T(self.lid.."Transmitting") self:_DCSgRPCtts(Text, nil, nil , nil, nil, nil, nil, Coordinate) elseif self.backend==MSRS.Backend.HOUND then - self:_HoundTextToSpeech(Text,nil,nil,nil,nil,nil,Coordinate,Speed) + self:_HoundTextToSpeech(Text,nil,nil,nil,nil,nil,Coordinate,Speed,nil,Speaker) else - self:PlayTextExt(Text, Delay, nil, nil, nil, nil, nil, nil, nil, Coordinate, Speed) + self:PlayTextExt(Text, Delay, nil, nil, nil, nil, nil, nil, nil, Coordinate, Speed, Speaker) end end @@ -1596,8 +1618,9 @@ end -- @param #string Label Label. -- @param Core.Point#COORDINATE Coordinate Coordinate. -- @param #number Speed Speed. +-- @param #string Speaker Speaker (Sub-Voice) for PIPER only -- @return #MSRS self -function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate,Speed) +function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate,Speed,Speaker) self:T({Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate, Speed} ) if Delay and Delay>0 then @@ -1628,7 +1651,7 @@ function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture local UseGoogle = (self.provider == MSRS.Provider.GOOGLE) and true or nil - self:_HoundTextToSpeech(Text,Frequencies,Modulations,Volume,Label,self.coalition,Coordinate,Speed,Gender,Culture,Voice,UseGoogle) + self:_HoundTextToSpeech(Text,Frequencies,Modulations,Volume,Label,self.coalition,Coordinate,Speed,Gender,Culture,Voice,UseGoogle,Speaker) end @@ -2029,8 +2052,9 @@ end -- @param #string Culture (Optional) Culture to use. -- @param #string Voice (Optional) Voice to use. -- @param #boolean UseGoogle (Optional) If to use Google TTS. +-- @param #string Speaker Speaker (Sub-Voice) for PIPER only -- @return SpeechTime Speech time in seconds. -function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Coalition,Point,Speed,Gender,Culture,Voice,UseGoogle) +function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Coalition,Point,Speed,Gender,Culture,Voice,UseGoogle,Speaker) self:I(self.lid.."_HoundTextToSpeech") Frequencies = UTILS.EnsureTable(Frequencies) @@ -2087,6 +2111,7 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co speed = speed, culture = culture, gender = gender, + speaker = Speaker, } local speechtime = HoundTTS.Transmit(Message, TransmissionP, ProviderP) @@ -2407,6 +2432,8 @@ MSRSQUEUE = { -- @field #number volume Volume -- @field #string label Label to be used -- @field Core.Point#COORDINATE coordinate Coordinate for this transmission +-- @field #number speed Speed of speech 1=100% +-- @field #string speaker PIPER subvoice "speaker" -- @field #number speed Speed to be used --- Create a new MSRSQUEUE object for a given radio frequency/modulation. @@ -2493,8 +2520,9 @@ end -- @param #string label (Optional) Label to be used -- @param Core.Point#COORDINATE coordinate (Optional) Coordinate to be used -- @param #number speed (Optional) Speed to be used +-- @param #string speaker (Optional) PIPER voice can have various speakers, set this here if you use PIPER/HOUND with a fitting voice. -- @return #MSRSQUEUE.Transmission Radio transmission table. -function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate,speed) +function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate,speed,speaker) self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label, S=speed}) if self.TransmitOnlyWithPlayers then if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then @@ -2536,7 +2564,9 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr transmission.label = label or msrs.Label transmission.coordinate = coordinate or msrs.coordinate transmission.speed = speed or 1.0 - + if speaker then + transmission.speaker = speaker + end -- Add transmission to queue. self:AddTransmission(transmission) @@ -2550,9 +2580,9 @@ function MSRSQUEUE:Broadcast(transmission) self:T(self.lid.."Broadcast") if transmission.frequency then - transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label, transmission.coordinate, transmission.speed) + transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label, transmission.coordinate, transmission.speed, transmission.speaker) else - transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate,transmission.speed) + transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate,transmission.speed,transmission.speaker) end local function texttogroup(gid) diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index aa54b895c..05546943f 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -301,6 +301,7 @@ do -- Text-To-Speech -- @field #string culture Culture, e.g. "en-GB". -- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings. -- @field #number speed Specific speed to be used. + -- @field #string speaker (PIPER/HOUND only) sub-voice speaker to be used. -- @extends Core.Base#BASE @@ -427,7 +428,7 @@ do -- Text-To-Speech return self end - --- Set to use a specific speed. + --- Set to use a specific speed. -- @param #SOUNDTEXT self -- @param #number Speed -- @return #SOUNDTEXT self @@ -438,4 +439,15 @@ do -- Text-To-Speech return self end + --- Set to use a specific speaker (PIPER sub-voice). + -- @param #SOUNDTEXT self + -- @param #string Speaker + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetSpeaker(Speaker) + + self.speaker = Speaker + + return self + end + end \ No newline at end of file From 33bd6cc5a4029401a35efe6c754b53e8b7897a8b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 7 Mar 2026 11:34:46 +0100 Subject: [PATCH 332/349] xx --- Moose Development/Moose/Core/Message.lua | 9 +++++++-- Moose Development/Moose/Ops/CSAR.lua | 8 +++++--- Moose Development/Moose/Sound/SRS.lua | 22 +++++++++++++--------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 7492bb997..47cbf62c1 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -495,7 +495,8 @@ _MESSAGESRS = {} -- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest). -- @param #string Label (optional) Label, defaults to "MESSAGE" or the Message Category set. -- @param Core.Point#COORDINATE Coordinate (optional) Coordinate this messages originates from. --- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC +-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC or MSRS.Backend.HOUND etc +-- @param #string Provider (optional) Privider to be used, can be MSRS.Provider.WINDOWS or MSRS.Backend.GOOGLE or MSRS.Backend.PIPER etc -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only @@ -503,7 +504,7 @@ _MESSAGESRS = {} -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- -function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend) +function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend,Provider) _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" @@ -536,6 +537,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G _MESSAGESRS.MSRS:SetProvider(MSRS.Provider.GOOGLE) end + if Provider then + _MESSAGESRS.MSRS:SetProvider(Provider) + end + _MESSAGESRS.label = Label or MSRS.Label or "MESSAGE" _MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index c30547478..44bf10e53 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -2833,17 +2833,19 @@ function CSAR:onafterStart(From, Event, To) self.msrs = MSRS:New(path,channel,modulation) -- Sound.SRS#MSRS self.msrs:SetPort(self.SRSport) self.msrs:SetLabel("CSAR") - self.msrs:SetBackend(self.SRSBackend) - self.msrs:SetProvider(self.SRSProvider) + self.msrs:SetBackend(self.SRSBackend) self.msrs.speed = self.SRSSpeed self.msrs:SetCulture(self.SRSCulture) self.msrs:SetCoalition(self.coalition) self.msrs:SetVoice(self.SRSVoice) self.msrs:SetGender(self.SRSGender) - if self.SRSGPathToCredentials then + if self.SRSGPathToCredentials and (not self.SRSProvider) then self.msrs:SetProviderOptionsGoogle(self.SRSGPathToCredentials,self.SRSGPathToCredentials) self.msrs:SetProvider(MSRS.Provider.GOOGLE) end + if self.SRSProvider then + self.msrs:SetProvider(self.SRSProvider) + end self.msrs:SetVolume(self.SRSVolume) self.msrs:SetLabel("CSAR") self.SRSQueue = MSRSQUEUE:New("CSAR") -- Sound.SRS#MSRSQUEUE diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 4a93f6ce1..4b5be04e2 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1649,9 +1649,9 @@ function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture elseif self.backend==MSRS.Backend.HOUND then -- BASE:I("MSRS.Backend.HOUND") - local UseGoogle = (self.provider == MSRS.Provider.GOOGLE) and true or nil + --local UseGoogle = (self.provider == MSRS.Provider.GOOGLE) and true or nil - self:_HoundTextToSpeech(Text,Frequencies,Modulations,Volume,Label,self.coalition,Coordinate,Speed,Gender,Culture,Voice,UseGoogle,Speaker) + self:_HoundTextToSpeech(Text,Frequencies,Modulations,Volume,Label,self.coalition,Coordinate,Speed,Gender,Culture,Voice,nil,Speaker) end @@ -2086,15 +2086,15 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co modus=modus:gsub("0", "AM") modus=modus:gsub("1", "FM") - self:I({T=Message,F=freqs,M=modus,V=voice,Vx=volume,L=label,C=coal,GGL=tostring(UseGoogle)}) - - if (UseGoogle ~= true) and self.provider == MSRS.Provider.GOOGLE then - UseGoogle = true - end + --self:I({T=Message,F=freqs,M=modus,V=voice,Vx=volume,L=label,C=coal,GGL=tostring(UseGoogle)}) + self:I({T=Message}) + --if (UseGoogle ~= true) and self.provider == MSRS.Provider.GOOGLE then + --UseGoogle = true + --end local provider = self.provider - provider=provider:gsub("gcloud", "google") - provider=provider:gsub("win", "sapi") + --provider=provider:gsub("gcloud", "google") + --provider=provider:gsub("win", "sapi") local TransmissionP = { freqs = freqs, @@ -2114,6 +2114,9 @@ function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Co speaker = Speaker, } + UTILS.PrintTableToLog(TransmissionP,indent,noprint,maxDepth,seen) + UTILS.PrintTableToLog(ProviderP,indent,noprint,maxDepth,seen) + local speechtime = HoundTTS.Transmit(Message, TransmissionP, ProviderP) return speechtime @@ -2524,6 +2527,7 @@ end -- @return #MSRSQUEUE.Transmission Radio transmission table. function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate,speed,speaker) self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label, S=speed}) + self:I({provider=msrs.provider}) if self.TransmitOnlyWithPlayers then if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then return self From ccd4d3c9a82c7f29de8129a7a503e3da54201439 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 8 Mar 2026 15:37:20 +0100 Subject: [PATCH 333/349] xx --- Moose Development/Moose/Ops/ArmyGroup.lua | 209 ++++++++++++++++++++- Moose Development/Moose/Ops/EasyGCICAP.lua | 44 ++++- 2 files changed, 249 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 7efd439fe..0dffa2fd0 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -68,7 +68,7 @@ ARMYGROUP = { --- Army Group version. -- @field #string version -ARMYGROUP.version="1.0.3" +ARMYGROUP.version="1.0.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -2332,6 +2332,213 @@ function ARMYGROUP:onafterUnsuppressed(From, Event, To) end +---------------------------------------------------------------- +-- HuntingPatrol.lua +-- +-- Adds simple "hunting patrol" behavior on top of ARMYGROUP: +-- +-- * Group patrols normally using its existing waypoints +-- * While combat-ready, periodically scans a zone for enemies +-- * When an enemy is found, lock it and call EngageTarget() +-- * Stay on that target until it dies +-- * When target is dead, Disengage() and let ARMYGROUP resume patrol +---------------------------------------------------------------- + +--- Module table +ARMYGROUP.HuntingPatrol = {} + +---------------------------------------------------------------- +-- ARMYGROUP extension state +---------------------------------------------------------------- +-- We store a few extra fields on ARMYGROUP: +-- hp_zone : ZONE object to hunt inside +-- hp_speed : speed in knots when engaging +-- hp_formation : formation string when engaging +-- hp_target : currently locked UNIT (or nil) +-- hp_timer : TIMER object for periodic updates +-- hp_interval : scan interval in seconds +---------------------------------------------------------------- + +--- Enable hunting patrol behavior for this ARMYGROUP. +-- @param #ARMYGROUP self +-- @param Core.Zone#ZONE_BASE Zone The zone to hunt inside. +-- @param #number Speed Speed in knots when engaging. +-- @param #string Formation Formation used during engagement (optional). +-- @param #number Interval Scan interval in seconds (optional, default 5). +-- @return #ARMYGROUP self +function ARMYGROUP:EnableHuntingPatrol(Zone, Speed, Formation, Interval) + + self.hp_zone = Zone + self.hp_speed = tonumber(Speed) or 20 + self.hp_formation = Formation or nil + + -- Force numeric, safe interval + local intervalNum = tonumber(Interval) + if not intervalNum or intervalNum <= 0 then + intervalNum = 5 + end + self.hp_interval = intervalNum + + self.hp_target = nil + + if self.hp_timer then + self.hp_timer:Stop() + self.hp_timer = nil + end + + self.hp_timer = TIMER:New(self._HuntingPatrolUpdate, self):Start(1, self.hp_interval) + + self:T(self.lid .. "HuntingPatrol: enabled. Interval=" .. tostring(self.hp_interval)) + return self +end + +--- Disable hunting patrol behavior. +-- @param #ARMYGROUP self +-- @return #ARMYGROUP self +function ARMYGROUP:DisableHuntingPatrol() + if self.hp_timer then + self.hp_timer:Stop() + self.hp_timer = nil + end + if self.HuntingEnemySet then + self.HuntingEnemySet:FilterStop() + self.HuntingEnemySet = nil + end + self.hp_target = nil + self:T(self.lid .. "HuntingPatrol: disabled.") + return self +end + +---------------------------------------------------------------- +-- Internal: periodic update +---------------------------------------------------------------- +-- This is called by the TIMER created in EnableHuntingPatrol(). +-- It: +-- +-- - checks if group is alive & combat-ready +-- - if a target is locked, checks if it is still alive +-- - if no target, scans the zone for enemies and locks one +-- - uses ARMYGROUP:EngageTarget() / Disengage() +-- +---------------------------------------------------------------- +-- @param #ARMYGROUP self +-- @return #ARMYGROUP self +function ARMYGROUP:_HuntingPatrolUpdate() + + -- If group is dead, stop. + if not self:IsAlive() then + self:DisableHuntingPatrol() + return + end + + -- Only run during PATROLZONE missions + local mission = self:GetMissionCurrent() + if not mission or mission.type ~= AUFTRAG.Type.PATROLZONE then + return + end + + -- Only act when combat-ready + if not self:IsCombatReady() then + return + end + + -- If we have a locked target, keep or drop it + if self.hp_target then + if not self.hp_target:IsAlive() then + self.hp_target = nil + self:Disengage() + end + return + end + + -- No target → scan for new enemies + local enemy = self:_HuntingPatrolFindEnemyInZone(self.hp_zone) + if enemy then + self.hp_target = enemy + self:EngageTarget(enemy, self.hp_speed, self.hp_formation) + end +end + +---------------------------------------------------------------- +-- Internal: find one enemy ground unit inside a zone +---------------------------------------------------------------- +-- Very simple: build a SET_UNIT, filter by coalition and zone, +-- and pick the closest unit to this ARMYGROUP. +---------------------------------------------------------------- +-- @param #ARMYGROUP self +-- @param Core.Zone#ZONE Zone The zont to look at +-- @return Wrapper.Unit#UNIT Enemy The nearest enemy UNIT in zone +function ARMYGROUP:_HuntingPatrolFindEnemyInZone(Zone) + + if not Zone then return nil end + + -- Determine our coalition. + local myCoalition = self:GetCoalition() + + if not self.HuntingEnemySet then + + self.HuntingEnemySet = SET_UNIT:New() + :FilterActive(true) + :FilterCategories("ground") + :FilterStart() + + end + + local enemies = {} + + self.HuntingEnemySet:ForEachUnitCompletelyInZone(Zone, function(unit) + if unit:GetCoalition() ~= myCoalition and unit:IsAlive() then + table.insert(enemies, unit) + end + end) + + if #enemies == 0 then + return nil + end + + -- Pick the closest enemy. + local myCoord = self:GetCoordinate() + + table.sort(enemies, function(a, b) + return myCoord:Get2DDistance(a:GetCoordinate()) < + myCoord:Get2DDistance(b:GetCoordinate()) + end) + + return enemies[1] +end + +---------------------------------------------------------------- +-- Optional helper: convenience wrapper for your campaign code +---------------------------------------------------------------- +-- Example usage in your ArmyManagement: +-- +-- local group = GROUP:FindByName("MyDefenseGroup") +-- local army = ARMYGROUP:New(group) +-- army:SetPatrolAdInfinitum(true) +-- army:EnableHuntingPatrol(myZone, 20, "Off Road", 5) +-- +-- ARMYGROUP will: +-- +-- * patrol its waypoints forever +-- * periodically scan myZone +-- * when an enemy appears, EngageTarget() +-- * when target dies, Disengage() and resume patrol +-- +---------------------------------------------------------------- +-- @param Wrapper.Group#GROUP group The GROUP object to be made into an ARMYGROUP and send hunting. +-- @param Core.Zone#ZONE zone The zone object where to search for enemies. +-- @param #number speed Speed in knots when engaging. +-- @param #string formation Formation used during engagement (optional). +-- @param #number interval Scan interval in seconds (optional, default 5). +-- @return #ARMYGROUP ArmyGroup The new ArmyGroup. +function ARMYGROUP.EnableHuntingPatrolForGroup(group, zone, speed, formation, interval) + local army = ARMYGROUP:New(group) + army:SetPatrolAdInfinitum(true) + army:EnableHuntingPatrol(zone, speed, formation, interval) + return army +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index e47ff103b..4cbdb9ee1 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -21,7 +21,7 @@ -- ------------------------------------------------------------------------- -- Date: September 2023 --- Last Update: Jan 2026 +-- Last Update: Mar 2026 ------------------------------------------------------------------------- -- --- **Ops** - Easy GCI & CAP Manager @@ -286,7 +286,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.34" +EASYGCICAP.version="0.1.35" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- @@ -355,6 +355,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) -- From State --> Event --> To State self:SetStartState("Stopped") self:AddTransition("Stopped", "Start", "Running") + self:AddTransition("Stopped", "Restart", "Running") self:AddTransition("Running", "Stop", "Stopped") self:AddTransition("*", "Status", "*") @@ -392,6 +393,20 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) -- @param #string Event Event. -- @param #string To To state. + --- On Before "Restart" event. Use `myinstance:Restart()` in case you Stopped the instance before and want to restart it now. + -- @function [parent=#EASYGCICAP] OnBeforeRestart + -- @param #EASYGCICAP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- On After "Restart" event. Use `myinstance:Restart()` in case you Stopped the instance before and want to restart it now. + -- @function [parent=#EASYGCICAP] OnAfterRestart + -- @param #EASYGCICAP self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + return self end @@ -1798,8 +1813,31 @@ end function EASYGCICAP:onafterStop(From,Event,To) self:T({From,Event,To}) self.Intel:Stop() + -- self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename } for _,_wing in pairs(self.wings or {}) do - _wing:Stop() + for _,_aw in pairs(_wing) do + _wing[1]:Stop() + end + end + return self +end + +--- (Internal) FSM Function onafterRestart +-- @param #EASYGCICAP self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #EASYGCICAP self +function EASYGCICAP:onafterRestart(From,Event,To) + self:T({From,Event,To}) + if self:Is("Stopped") then + self.Intel:Start() + -- self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename } + for _,_wing in pairs(self.wings or {}) do + for _,_aw in pairs(_wing) do + _wing[1]:Start() + end + end end return self end From 1e0afb96afc300d3a869056da0b475735a21d71f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 12 Mar 2026 07:40:16 +0100 Subject: [PATCH 334/349] xx --- .../Moose/Ops/CTLD_Localization.lua | 342 +++++++++--------- 1 file changed, 171 insertions(+), 171 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD_Localization.lua b/Moose Development/Moose/Ops/CTLD_Localization.lua index 398c67c92..9de32b22a 100644 --- a/Moose Development/Moose/Ops/CTLD_Localization.lua +++ b/Moose Development/Moose/Ops/CTLD_Localization.lua @@ -752,176 +752,176 @@ FR = { BUILD_NO = "NON", }, ES={ -CRATE_LOADED_GROUNDCREW="Contenedor %s cargado por el quipo de tierra.", -CRATE_UNLOADED_GROUNDCREW="Contenedor %s descargado por el quipo de tierra.", -CRATE_LOADED_ID="Contenedor ID %d para %s cargado.", -LOADED_FULL="Cargado %d %s.", -LOADED_SETS_LEFTOVER="Cargado %d %s(s), de %d contenedor(es) restante(s).", -LOADED_SETS="Cargado %d %s(s).", -LOADED_PARTIAL="Cargado sólo %d/%d contenedor(es) de %s.", -LOADED_PARTIAL_LIMIT="Cargado sólo %d/%d contenedor(es) de %s. Límite de carga alcanzado.", -LOADED_BATCH="Cargado %d %s.", -LOADED_BATCH_PARTIAL="Some sets could not be fully loaded.", -DROPPED_FULL="Entregado %d %s.", -DROPPED_SETS_LEFTOVER="Entregado %d %s(s), de %d conenedor(es) restante(s).", -DROPPED_SETS="Entregado %d %s(s).", -DROPPED_PARTIAL="Entregado %d/%d contenedor(es) de %s.", -DROPPED_INTO_ACTION="¡Soltados %s en acción!", -DROPPED_BEACON="Entregado %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", -CRATES_POSITIONED="%d contenedores para %s servidos cerca de ti.", -CRATES_DROPPED="%d contenedores para %s han sido entregados.", -BOARDED="¡%s a bordo!", -BOARDING="¡%s entrando!", -TROOPS_RETURNED="¡Las tropas han vuelto a la base!", -DEPLOYED_NEAR_YOU="%s han sido servidas cerca de tí.", -UNITS_REMOVED="%s ha sido eliminado", -BUILD_STARTED="Construcción comenzada, listo en %d segundos.", -REPAIR_STARTED="Reparación comenzaza usando %s, tardará %d segundos.", -NO_UNIT_TO_REPAIR="No hay unidades cercanas que necesiten reparación.", -CANT_REPAIR_WITH="No se puede reparar esta unidad con %s", -CRATES_MOVE_BEFORE_BUILD="*** Los contenedores deben ser movidos antes de construirse.", -CHOPPER_CANNOT_CARRY="Lo siento, este helicóptero no puede transportar contenedores.", -TOO_HEAVY="Lo siento, esta carga es muy pesada.", -FULLY_LOADED="Lo siento, vamos hasta arriba.", -CRAMMED="Lo siento, vamos a tope.", -NO_CAPACITY_NOW="No queda capacidad para cargar más.", -NO_MORE_CAPACITY="No queda capacidad para cargar más contenedores.", -CANNOT_LOAD_NONE_OR_FULL="No se pueden cargar contenedores: n hay o no queda capacidad.", -NEED_TO_LAND_OR_HOVER_LOAD="Necesitas aterrizar o mantenerte en estacionario para cargar.", -HOVER_OVER_CRATES="Mantente en estacionario sobre el contenedor para recogerlo.", -LAND_OR_HOVER_OVER_CRATES="Aterriza o mantente en estacionario para recoger el contenedor.", -MUST_LAND_OR_HOVER_CRATES="Necesitas aterrizar o mantenerte en eestacionario para cargar contenedores.", -NEED_TO_LAND_BUILD="Necesitas aterrizar/parar para construir algo.", -NOT_CLOSE_ENOUGH_LOGISTICS="No estás cerca de una zona de logística.", -NOT_CLOSE_ENOUGH_DROP="No estás en una zona de entrega.", -NOT_CLOSE_ENOUGH_ZONE_NM="Negativo, tienes que estar a menos de %dnm de la zona.", -CANNOT_BUILD_LOADING_AREA="No puedes construir en la zona de carga.", -OPEN_DOORS_LOAD_CARGO="Necesitas abrir las puertas para cargar.", -OPEN_DOORS_LOAD_TROOPS="Necesitas abrir las puertas para que embarquen las tropas.", -OPEN_DOORS_EXTRACT_TROOPS="Necesitas abrir las puertas para poder sacar a las tropas de aquí.", -OPEN_DOORS_UNLOAD_TROOPS="Necesitas abrir las puertas para que desembarquen las tropas.", -OPEN_DOORS_DROP_CARGO="Necesitas abrir las puertas para descargar la carga.", -ALL_GONE="Lo siento, todos %s se han servido.", -RAN_OUT_OF="Lo siento, nos hemos quedad sin %s", -CARGO_NOT_AVAILABLE_ZONE="La carga solicitada no está disponible en esta zona.", -ENOUGH_CRATES_NEARBY="Hay contenedores cerca de ti listas. Encárgate primero de ellos.", -NO_CRATES_WITHIN="No ha contenedores (cargables) en %d metros.", -NO_CRATES_WITHIN_PLAIN="No hay contenedores en %d metros.", -NO_CRATES_IN_RANGE="No se han encontrado contenedores en rango.", -NO_NAMED_CRATES_IN_RANGE="No se han encontrado \"%s\" conenedores en rango.", -NO_LOADABLE_CRATES="Lo siento, no hay contenedores cercanos o se ha alcanzado el peso máximo.", -NO_UNITS_TO_EXTRACT="No hay unidades cercanas para extracción.", -NO_UNIT_CONFIG="No se ha encontrado configuración de unidad para %s", -CANT_ONBOARD="No puede subir %s", -TOO_MANY_UNITS_NEARBY="Ya tienes %d unidades próximas.", -NO_CRATE_GROUPS="No se han encontrado grupos de contendeores para esta unidad.", -NO_CRATE_SET="No se ha encontrado contenedor o su index es inválido.", -NO_CRATE_IN_SET="No se ha encontrado contenedor para este set.", -NO_TROOP_CHUNK="No se han encontrado tropas para el id %d!", -TROOP_CHUNK_EMPTY="Troop chunk is empty for ID %d!", -NOTHING_LOADED="Nada cargado.\nLímite tropas: %d | Límite contenedores: %d | Peso límite: %d kg.", -NOTHING_LOADED_AIRDROP="Nada cargado o no estás en parámetros de lanzamiento aéreo.", -NOTHING_LOADED_HOVER="Nada cargado o no estás en parámetros de estacionario.", -NOTHING_IN_STOCK="¡Nada en stock!", -NOTHING_TO_PACK="Nada para empaquetar a esta distancia.", -NOTHING_TO_REMOVE="Nada para eliminar a esta distancia.", -ROGER_ZONE="Recibido, %s cona %s!", -HOVER_PARAMS_METRIC="Parámetros en estacionario (autocarga/suelta):\n - Altura mínima %dm \n - Altura máxima %dm \n - Velocidad máxima 2mps \n - En parámetros: %s", -HOVER_PARAMS_IMPERIAL="Parámetros en estacionario (autocarga/suelta):\n - Altura mínima %dft \n - Altura máxima %dft \n - Velocidad máxima 6ftps \n - En parámetros: %s", -FLIGHT_PARAMS_IMPERIAL="Parámetros vuelo (lanzamiento aéreo):\n - Altura mínima %dft \n - Altura máxima %dft \n - En parámetros: %s", -FLIGHT_PARAMS_METRIC="Parámetros vuelo (lanzamiento aéreo):\n - Altura mínima %dm \n - Altura máxima %dm \n - En parámetros: %s", -REPORT_CRATES_FOUND="Contenedores encontrados cerca:", -REPORT_REMOVING_CRATES="Contenedores eliminados cerca:", -REPORT_TRANSPORT_CHECKOUT="Informe de transporte", -REPORT_INVENTORY="Inventario", -REPORT_BUILD_CHECKLIST="Contenedores construibles", -REPORT_REPAIR_CHECKLIST="Reparaciones", -REPORT_BEACONS="Active Zone Beacons", -REPORT_SECTION_TROOPS=" -- TROPAS --", -REPORT_SECTION_CRATES=" -- CONTENEDORES --", -REPORT_SECTION_CRATES_GC=" -- Contenedores cargados por equipo de tierra --", -REPORT_SECTION_NONE=" N A D A", -REPORT_SECTION_NONE_ALT=" --- Nada encontrado ---", -REPORT_SECTION_NONE_REPAIR=" --- Nada encontrado ---", -REPORT_GC_LOADABLE_HINT="Probablemente cargable por el equipo de tierra (F8)", -REPORT_TOTAL_MASS="Peso total: %s kg. Cargable: %s kg.", -REPORT_TROOPS_CRATES_COUNT="Tropas: %d(%d), Contenedores: %d(%d)", -REPORT_TROOPS_CRATETYPES_COUNT="Tropas: %d, Tipos contenedores: %d", -REPORT_ROW_TROOP="Tropas: %s tamaño %d", -REPORT_ROW_CRATE="Contenedores: %s %d/%d", -REPORT_ROW_CRATE_SIZE1="Contenedores: %s tamaño 1", -REPORT_ROW_GC_CRATE="Contenedores cargados: %s tamaño 1", -REPORT_ROW_DROPPED_CRATE="Entregado contenedor para %s, %dkg", -REPORT_ROW_CRATE_KG="Contenedor para %s, %dkg", -REPORT_ROW_CRATE_REMOVED="Contenedor para %s, %dkg eliminado", -REPORT_ROW_UNIT_STOCK="Unidad: %s | Soldados: %d | Stock: %s", -REPORT_ROW_TYPE_CRATE_STOCK="Tipo: %s | Contenedores por set: %d | Stock: %s", -REPORT_ROW_TYPE_STOCK="Tipo: %s | Stock: %s", -REPORT_ROW_BUILD_CHECK="Tipo: %s | Rquiere %d | Encontrados %d | Construible %s", -REPORT_ROW_REPAIR_CHECK="Tipo: %s | Requiere %d | Encontrados %d | Reparable %s", -REPORT_ROW_BEACON=" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", -WEIGHT_LIMIT="Alcanzado límite de pesoWeight limit reached", -CRATE_LIMIT="Alcanzado límite contenedores", -MENU_CTLD="CTLD", -MENU_MANAGE_TROOPS="Gestionar tropas", -MENU_MANAGE_CRATES="Gestionar contenedores", -MENU_MANAGE_UNITS="Gestionar unidades", -MENU_LOAD_TROOPS="Cargar tropas", -MENU_DROP_TROOPS="Entregar tropas", -MENU_DROP_ALL_TROOPS="Entregar TODAS tropas", -MENU_EXTRACT_TROOPS="Extraer tropas", -MENU_DROP_N_TROOPS="Soltar (%d) %s", -MENU_GET_CRATES="Solicitar contenedores", -MENU_GET="Solicitar", -MENU_GET_AND_LOAD="Solicitar y cargar", -MENU_GET_ANYWAY="Solicitar de todas formas", -MENU_PARTIALLY_LOAD="Carga parcial", -MENU_OUT_OF_STOCK="Sin stock", -MENU_TROOP_LIMIT="Limite de tropas alcanzado", -MENU_LOAD_CRATES="Cargar contenedores", -MENU_LOAD_ALL="Cargar TODO", -MENU_SHOW_LOADABLE_CRATES="Mostrar contenedores carbables", -MENU_NO_CRATES_FOUND_RESCAN="Contenedores no encontrados, ¿buscar?", -MENU_USE_C130_LOAD="Usar sistmea de carga del C-130", -MENU_LOAD_SINGLE="Cargar", -MENU_DROP_CRATES="Soltar cargas", -MENU_DROP_ALL_CRATES="Soltar TODAS cargas", -MENU_DROP="Soltar", -MENU_DROP_AND_BUILD="Soltar y constuir", -MENU_DROP_N_SETS="Soltar %d Set %s", -MENU_NO_CRATES_TO_DROP="No hay cargas para soltar", -MENU_BUILD_CRATES="Construir contenedores", -MENU_REPAIR="Reparar", -MENU_PACK_CRATES="Empaquetar cargas", -MENU_PACK="Empaquetar", -MENU_PACK_AND_LOAD="Empaquetar y cargar", -MENU_PACK_AND_REMOVE="Empaquetar y eliminar", -MENU_REMOVE_CRATES="Eliminar cargas", -MENU_REMOVE_CRATES_NEARBY="Eliminar cargas cercanas", -MENU_LIST_CRATES_NEARBY="Listar cargas cercanas", -MENU_CRATES_NEEDED="%d contenedor%s %s (%dkg)", -MENU_GET_UNITS="Obtener unidades", -MENU_REMOVE_UNITS_NEARBY="Eliminar unidades cercanas", -MENU_LIST_BOARDED_CARGO="Lista de cargas a bordo", -MENU_INVENTORY="Inventario", -MENU_LIST_ZONE_BEACONS="Lista de balizas activas", -MENU_SMOKES_FLARES_BEACONS="Humos, Bengalas, Balizas", -MENU_SMOKE_ZONES_NEARBY="Humo en zonas cercanas", -MENU_DROP_SMOKE_NOW="Lanzar humo ahora", -MENU_RED_SMOKE="Humo rojo", -MENU_BLUE_SMOKE="Humo azul", -MENU_GREEN_SMOKE="Humo verde", -MENU_ORANGE_SMOKE="Humo naranja", -MENU_WHITE_SMOKE="Humo blanco", -MENU_FLARE_ZONES_NEARBY="Bengalas en zonas cercanas", -MENU_FIRE_FLARE_NOW="Disparar bengala ahora", -MENU_DROP_BEACON_NOW="Soltar baliza ahora", -MENU_SHOW_FLIGHT_PARAMS="Mostrar parámetros de vuelo", -MENU_SHOW_HOVER_PARAMS="Mostrar parámetros estacionario", -STOCK_NONE="Nada", -STOCK_UNLIMITED="ilimitado", -BUILD_YES="SI", -BUILD_NO="NO", -}, + CRATE_LOADED_GROUNDCREW="Contenedor %s cargado por el quipo de tierra.", + CRATE_UNLOADED_GROUNDCREW="Contenedor %s descargado por el quipo de tierra.", + CRATE_LOADED_ID="Contenedor ID %d para %s cargado.", + LOADED_FULL="Cargado %d %s.", + LOADED_SETS_LEFTOVER="Cargado %d %s(s), de %d contenedor(es) restante(s).", + LOADED_SETS="Cargado %d %s(s).", + LOADED_PARTIAL="Cargado sólo %d/%d contenedor(es) de %s.", + LOADED_PARTIAL_LIMIT="Cargado sólo %d/%d contenedor(es) de %s. Límite de carga alcanzado.", + LOADED_BATCH="Cargado %d %s.", + LOADED_BATCH_PARTIAL="Some sets could not be fully loaded.", + DROPPED_FULL="Entregado %d %s.", + DROPPED_SETS_LEFTOVER="Entregado %d %s(s), de %d conenedor(es) restante(s).", + DROPPED_SETS="Entregado %d %s(s).", + DROPPED_PARTIAL="Entregado %d/%d contenedor(es) de %s.", + DROPPED_INTO_ACTION="¡Soltados %s en acción!", + DROPPED_BEACON="Entregado %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", + CRATES_POSITIONED="%d contenedores para %s servidos cerca de ti.", + CRATES_DROPPED="%d contenedores para %s han sido entregados.", + BOARDED="¡%s a bordo!", + BOARDING="¡%s entrando!", + TROOPS_RETURNED="¡Las tropas han vuelto a la base!", + DEPLOYED_NEAR_YOU="%s han sido servidas cerca de tí.", + UNITS_REMOVED="%s ha sido eliminado", + BUILD_STARTED="Construcción comenzada, listo en %d segundos.", + REPAIR_STARTED="Reparación comenzaza usando %s, tardará %d segundos.", + NO_UNIT_TO_REPAIR="No hay unidades cercanas que necesiten reparación.", + CANT_REPAIR_WITH="No se puede reparar esta unidad con %s", + CRATES_MOVE_BEFORE_BUILD="*** Los contenedores deben ser movidos antes de construirse.", + CHOPPER_CANNOT_CARRY="Lo siento, este helicóptero no puede transportar contenedores.", + TOO_HEAVY="Lo siento, esta carga es muy pesada.", + FULLY_LOADED="Lo siento, vamos hasta arriba.", + CRAMMED="Lo siento, vamos a tope.", + NO_CAPACITY_NOW="No queda capacidad para cargar más.", + NO_MORE_CAPACITY="No queda capacidad para cargar más contenedores.", + CANNOT_LOAD_NONE_OR_FULL="No se pueden cargar contenedores: n hay o no queda capacidad.", + NEED_TO_LAND_OR_HOVER_LOAD="Necesitas aterrizar o mantenerte en estacionario para cargar.", + HOVER_OVER_CRATES="Mantente en estacionario sobre el contenedor para recogerlo.", + LAND_OR_HOVER_OVER_CRATES="Aterriza o mantente en estacionario para recoger el contenedor.", + MUST_LAND_OR_HOVER_CRATES="Necesitas aterrizar o mantenerte en eestacionario para cargar contenedores.", + NEED_TO_LAND_BUILD="Necesitas aterrizar/parar para construir algo.", + NOT_CLOSE_ENOUGH_LOGISTICS="No estás cerca de una zona de logística.", + NOT_CLOSE_ENOUGH_DROP="No estás en una zona de entrega.", + NOT_CLOSE_ENOUGH_ZONE_NM="Negativo, tienes que estar a menos de %dnm de la zona.", + CANNOT_BUILD_LOADING_AREA="No puedes construir en la zona de carga.", + OPEN_DOORS_LOAD_CARGO="Necesitas abrir las puertas para cargar.", + OPEN_DOORS_LOAD_TROOPS="Necesitas abrir las puertas para que embarquen las tropas.", + OPEN_DOORS_EXTRACT_TROOPS="Necesitas abrir las puertas para poder sacar a las tropas de aquí.", + OPEN_DOORS_UNLOAD_TROOPS="Necesitas abrir las puertas para que desembarquen las tropas.", + OPEN_DOORS_DROP_CARGO="Necesitas abrir las puertas para descargar la carga.", + ALL_GONE="Lo siento, todos %s se han servido.", + RAN_OUT_OF="Lo siento, nos hemos quedad sin %s", + CARGO_NOT_AVAILABLE_ZONE="La carga solicitada no está disponible en esta zona.", + ENOUGH_CRATES_NEARBY="Hay contenedores cerca de ti listas. Encárgate primero de ellos.", + NO_CRATES_WITHIN="No ha contenedores (cargables) en %d metros.", + NO_CRATES_WITHIN_PLAIN="No hay contenedores en %d metros.", + NO_CRATES_IN_RANGE="No se han encontrado contenedores en rango.", + NO_NAMED_CRATES_IN_RANGE="No se han encontrado \"%s\" conenedores en rango.", + NO_LOADABLE_CRATES="Lo siento, no hay contenedores cercanos o se ha alcanzado el peso máximo.", + NO_UNITS_TO_EXTRACT="No hay unidades cercanas para extracción.", + NO_UNIT_CONFIG="No se ha encontrado configuración de unidad para %s", + CANT_ONBOARD="No puede subir %s", + TOO_MANY_UNITS_NEARBY="Ya tienes %d unidades próximas.", + NO_CRATE_GROUPS="No se han encontrado grupos de contendeores para esta unidad.", + NO_CRATE_SET="No se ha encontrado contenedor o su index es inválido.", + NO_CRATE_IN_SET="No se ha encontrado contenedor para este set.", + NO_TROOP_CHUNK="No se han encontrado tropas para el id %d!", + TROOP_CHUNK_EMPTY="Troop chunk is empty for ID %d!", + NOTHING_LOADED="Nada cargado.\nLímite tropas: %d | Límite contenedores: %d | Peso límite: %d kg.", + NOTHING_LOADED_AIRDROP="Nada cargado o no estás en parámetros de lanzamiento aéreo.", + NOTHING_LOADED_HOVER="Nada cargado o no estás en parámetros de estacionario.", + NOTHING_IN_STOCK="¡Nada en stock!", + NOTHING_TO_PACK="Nada para empaquetar a esta distancia.", + NOTHING_TO_REMOVE="Nada para eliminar a esta distancia.", + ROGER_ZONE="Recibido, %s cona %s!", + HOVER_PARAMS_METRIC="Parámetros en estacionario (autocarga/suelta):\n - Altura mínima %dm \n - Altura máxima %dm \n - Velocidad máxima 2mps \n - En parámetros: %s", + HOVER_PARAMS_IMPERIAL="Parámetros en estacionario (autocarga/suelta):\n - Altura mínima %dft \n - Altura máxima %dft \n - Velocidad máxima 6ftps \n - En parámetros: %s", + FLIGHT_PARAMS_IMPERIAL="Parámetros vuelo (lanzamiento aéreo):\n - Altura mínima %dft \n - Altura máxima %dft \n - En parámetros: %s", + FLIGHT_PARAMS_METRIC="Parámetros vuelo (lanzamiento aéreo):\n - Altura mínima %dm \n - Altura máxima %dm \n - En parámetros: %s", + REPORT_CRATES_FOUND="Contenedores encontrados cerca:", + REPORT_REMOVING_CRATES="Contenedores eliminados cerca:", + REPORT_TRANSPORT_CHECKOUT="Informe de transporte", + REPORT_INVENTORY="Inventario", + REPORT_BUILD_CHECKLIST="Contenedores construibles", + REPORT_REPAIR_CHECKLIST="Reparaciones", + REPORT_BEACONS="Active Zone Beacons", + REPORT_SECTION_TROOPS=" -- TROPAS --", + REPORT_SECTION_CRATES=" -- CONTENEDORES --", + REPORT_SECTION_CRATES_GC=" -- Contenedores cargados por equipo de tierra --", + REPORT_SECTION_NONE=" N A D A", + REPORT_SECTION_NONE_ALT=" --- Nada encontrado ---", + REPORT_SECTION_NONE_REPAIR=" --- Nada encontrado ---", + REPORT_GC_LOADABLE_HINT="Probablemente cargable por el equipo de tierra (F8)", + REPORT_TOTAL_MASS="Peso total: %s kg. Cargable: %s kg.", + REPORT_TROOPS_CRATES_COUNT="Tropas: %d(%d), Contenedores: %d(%d)", + REPORT_TROOPS_CRATETYPES_COUNT="Tropas: %d, Tipos contenedores: %d", + REPORT_ROW_TROOP="Tropas: %s tamaño %d", + REPORT_ROW_CRATE="Contenedores: %s %d/%d", + REPORT_ROW_CRATE_SIZE1="Contenedores: %s tamaño 1", + REPORT_ROW_GC_CRATE="Contenedores cargados: %s tamaño 1", + REPORT_ROW_DROPPED_CRATE="Entregado contenedor para %s, %dkg", + REPORT_ROW_CRATE_KG="Contenedor para %s, %dkg", + REPORT_ROW_CRATE_REMOVED="Contenedor para %s, %dkg eliminado", + REPORT_ROW_UNIT_STOCK="Unidad: %s | Soldados: %d | Stock: %s", + REPORT_ROW_TYPE_CRATE_STOCK="Tipo: %s | Contenedores por set: %d | Stock: %s", + REPORT_ROW_TYPE_STOCK="Tipo: %s | Stock: %s", + REPORT_ROW_BUILD_CHECK="Tipo: %s | Rquiere %d | Encontrados %d | Construible %s", + REPORT_ROW_REPAIR_CHECK="Tipo: %s | Requiere %d | Encontrados %d | Reparable %s", + REPORT_ROW_BEACON=" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", + WEIGHT_LIMIT="Alcanzado límite de pesoWeight limit reached", + CRATE_LIMIT="Alcanzado límite contenedores", + MENU_CTLD="CTLD", + MENU_MANAGE_TROOPS="Gestionar tropas", + MENU_MANAGE_CRATES="Gestionar contenedores", + MENU_MANAGE_UNITS="Gestionar unidades", + MENU_LOAD_TROOPS="Cargar tropas", + MENU_DROP_TROOPS="Entregar tropas", + MENU_DROP_ALL_TROOPS="Entregar TODAS tropas", + MENU_EXTRACT_TROOPS="Extraer tropas", + MENU_DROP_N_TROOPS="Soltar (%d) %s", + MENU_GET_CRATES="Solicitar contenedores", + MENU_GET="Solicitar", + MENU_GET_AND_LOAD="Solicitar y cargar", + MENU_GET_ANYWAY="Solicitar de todas formas", + MENU_PARTIALLY_LOAD="Carga parcial", + MENU_OUT_OF_STOCK="Sin stock", + MENU_TROOP_LIMIT="Limite de tropas alcanzado", + MENU_LOAD_CRATES="Cargar contenedores", + MENU_LOAD_ALL="Cargar TODO", + MENU_SHOW_LOADABLE_CRATES="Mostrar contenedores carbables", + MENU_NO_CRATES_FOUND_RESCAN="Contenedores no encontrados, ¿buscar?", + MENU_USE_C130_LOAD="Usar sistmea de carga del C-130", + MENU_LOAD_SINGLE="Cargar", + MENU_DROP_CRATES="Soltar cargas", + MENU_DROP_ALL_CRATES="Soltar TODAS cargas", + MENU_DROP="Soltar", + MENU_DROP_AND_BUILD="Soltar y constuir", + MENU_DROP_N_SETS="Soltar %d Set %s", + MENU_NO_CRATES_TO_DROP="No hay cargas para soltar", + MENU_BUILD_CRATES="Construir contenedores", + MENU_REPAIR="Reparar", + MENU_PACK_CRATES="Empaquetar cargas", + MENU_PACK="Empaquetar", + MENU_PACK_AND_LOAD="Empaquetar y cargar", + MENU_PACK_AND_REMOVE="Empaquetar y eliminar", + MENU_REMOVE_CRATES="Eliminar cargas", + MENU_REMOVE_CRATES_NEARBY="Eliminar cargas cercanas", + MENU_LIST_CRATES_NEARBY="Listar cargas cercanas", + MENU_CRATES_NEEDED="%d contenedor%s %s (%dkg)", + MENU_GET_UNITS="Obtener unidades", + MENU_REMOVE_UNITS_NEARBY="Eliminar unidades cercanas", + MENU_LIST_BOARDED_CARGO="Lista de cargas a bordo", + MENU_INVENTORY="Inventario", + MENU_LIST_ZONE_BEACONS="Lista de balizas activas", + MENU_SMOKES_FLARES_BEACONS="Humos, Bengalas, Balizas", + MENU_SMOKE_ZONES_NEARBY="Humo en zonas cercanas", + MENU_DROP_SMOKE_NOW="Lanzar humo ahora", + MENU_RED_SMOKE="Humo rojo", + MENU_BLUE_SMOKE="Humo azul", + MENU_GREEN_SMOKE="Humo verde", + MENU_ORANGE_SMOKE="Humo naranja", + MENU_WHITE_SMOKE="Humo blanco", + MENU_FLARE_ZONES_NEARBY="Bengalas en zonas cercanas", + MENU_FIRE_FLARE_NOW="Disparar bengala ahora", + MENU_DROP_BEACON_NOW="Soltar baliza ahora", + MENU_SHOW_FLIGHT_PARAMS="Mostrar parámetros de vuelo", + MENU_SHOW_HOVER_PARAMS="Mostrar parámetros estacionario", + STOCK_NONE="Nada", + STOCK_UNLIMITED="ilimitado", + BUILD_YES="SI", + BUILD_NO="NO", + }, } From a008e6cc26c276da287e40e99680500a59d996fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 14 Mar 2026 15:37:45 +0100 Subject: [PATCH 335/349] xx --- Moose Development/Moose/Sound/SRS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index a06772710..7240b9a5a 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -2598,7 +2598,7 @@ function MSRSQUEUE:Broadcast(transmission) trigger.action.outTextForGroup(gid, transmission.subtitle, transmission.subduration, true) end - if transmission.subgroups and #transmission.subgroups>0 then + if transmission.subgroups and #transmission.subgroups>0 and transmission.subtitle then for _,_group in pairs(transmission.subgroups) do local group=_group --Wrapper.Group#GROUP From e5fc4bc1dc625e9da90b48e459fb92db4b82fd4d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 21 Mar 2026 12:21:00 +0100 Subject: [PATCH 336/349] #CTLD Added option to define beacon frequencies when adding a zone --- Moose Development/Moose/Ops/CTLD.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 73324b79d..ddf836bb5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -8113,11 +8113,12 @@ end -- @param #string Type Type of this zone, #CTLD.CargoZoneType -- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red -- @param #string Active Is this zone currently active? --- @param #string HasBeacon Does this zone have a beacon if it is active? --- @param #number Shiplength Length of Ship for shipzones --- @param #number Shipwidth Width of Ship for shipzones +-- @param #string (Optional) HasBeacon Does this zone have a beacon if it is active? +-- @param #number (Optional) Shiplength Length of Ship for shipzones +-- @param #number (Optional) Shipwidth Width of Ship for shipzones +-- @param #table (Optional) BeaconFrequencies PreSet Frequencies in MHz (Million(!) Hertz), table of values , e.g. `{FM=0.124,UHF=215,VHF=110}` -- @return #CTLD self -function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth) +function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth, BeaconFrequencies) self:T(self.lid .. " AddCTLDZone") local zone = ZONE:FindByName(Name) @@ -8156,6 +8157,11 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship ctldzone.fmbeacon = self:_GetFMBeacon(Name) ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) ctldzone.vhfbeacon = self:_GetVHFBeacon(Name) + if BeaconFrequencies then + ctldzone.fmbeacon.frequency = BeaconFrequencies.FM or ctldzone.fmbeacon.frequency + ctldzone.vhfbeacon.frequency = BeaconFrequencies.VHF or ctldzone.vhfbeacon.frequency + ctldzone.uhfbeacon.frequency = BeaconFrequencies.UHF or ctldzone.uhfbeacon.frequency + end else ctldzone.fmbeacon = nil ctldzone.uhfbeacon = nil @@ -8369,7 +8375,7 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) else local ZoneCoord = Zone:GetCoordinate() local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0} - local Frequency = Mhz * 1000000 -- Freq in Hert + local Frequency = Mhz * 1000000 -- Freq in Hertz local Sound = self.RadioPath..Sound trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straightt self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation)) From 6efbc95043de4c2e9760e6f0253ed3eeabbd2aef Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 21 Mar 2026 12:21:21 +0100 Subject: [PATCH 337/349] xx --- Moose Development/Moose/Ops/Intelligence.lua | 32 +++++++++----------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index a78217ac3..09462393d 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1082,7 +1082,7 @@ function INTEL:UpdateIntel() local recce=_recce --Wrapper.Unit#UNIT -- Get detected units. - if self.DopplerRadar then + if self.DopplerRadar == true then self:GetDetectedUnitsDoppler(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK) else self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK) @@ -2629,6 +2629,7 @@ end -- Default true. -- @return #INTEL self function INTEL:SetDopplerRadar(MinAltAGL, NotchHalfDeg, MinSpeedMps, RadarRangeKm, RCS) + self:I(self.lid .. "SetDopplerRadar") self.DopplerRadar = true self.DopplerMinAltAGL = MinAltAGL or 500 self.DopplerNotchSin = math.sin(math.rad(NotchHalfDeg or 15)) @@ -2642,6 +2643,7 @@ end -- @param #INTEL self -- @return #INTEL self function INTEL:SetDopplerRadarOff() + self:I(self.lid .. "SetDopplerRadarOff") self.DopplerRadar = false return self end @@ -2653,6 +2655,7 @@ end -- @param #number RCS_m2 Side-on RCS in m² -- @return #INTEL self function INTEL:SetTypeRCS(TypeName, RCS_m2) + self:I(self.lid .. "SetTypeRCS") INTEL.RCS_Table[TypeName] = RCS_m2 return self end @@ -2675,6 +2678,7 @@ end -- @param DCS#Vec3 tvel Target velocity vector (pre-computed) -- @return #number Effective RCS in m² function INTEL:_GetAspectRCS(TargetUnit, rpos, spd, tvel) + self:I(self.lid .. "_GetAspectRCS") -- Look up base (side-on) RCS local typename = TargetUnit:GetTypeName() local base_rcs = INTEL.RCS_Table[typename] @@ -2712,12 +2716,12 @@ end -- @return #boolean true = detected -- @return #string rejection reason: "speed" | "clutter" | "notch" | "rcs" function INTEL:_CheckDopplerDetection(TargetUnit, RadarUnit) - + self:I(self.lid .. "_CheckDopplerDetection") -- Pre-compute common geometry (shared by notch + RCS checks) local spd = TargetUnit:GetVelocityMPS() local rpos = RadarUnit:GetVec3() local tpos = TargetUnit:GetVec3() - local tvel = TargetUnit:GetVelocity() + local tvel = TargetUnit:GetVelocityVec3() local dx = tpos.x - rpos.x local dz = tpos.z - rpos.z @@ -2794,18 +2798,14 @@ end -- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST. -- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. -- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. -function INTEL:GetDetectedUnitsDoppler(Unit, DetectedUnits, RecceDetecting, - DetectVisual, DetectOptical, DetectRadar, - DetectIRST, DetectRWR, DetectDLINK) - +function INTEL:GetDetectedUnitsDoppler(Unit, DetectedUnits, RecceDetecting,DetectVisual, DetectOptical, DetectRadar,DetectIRST, DetectRWR, DetectDLINK) + self:I(self.lid .. "GetDetectedUnitsDoppler") -- Run the original detection - self:GetDetectedUnits(Unit,DetectedUnits,RecceDetecting,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK)(self, Unit, DetectedUnits, RecceDetecting, - DetectVisual, DetectOptical, DetectRadar, - DetectIRST, DetectRWR, DetectDLINK) + self:GetDetectedUnits(Unit,DetectedUnits,RecceDetecting,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) -- Apply Doppler post-filter only when radar channel is active - if not self.DopplerRadar then return end - if DetectRadar == false then return end + if self.DopplerRadar == false then return end + if DetectRadar == false then return end local remove = {} for name, unit in pairs(DetectedUnits) do @@ -2814,11 +2814,9 @@ function INTEL:GetDetectedUnitsDoppler(Unit, DetectedUnits, RecceDetecting, local ok, reason = self:_CheckDopplerDetection(unit, Unit) if not ok then table.insert(remove, name) - if self.verbose and self.verbose >= 2 then - self:T(string.format( - "%sDoppler: suppressed %s [%s] by %s", - self.lid, name, reason, Unit:GetName())) - end + --if self.verbose and self.verbose >= 2 then + self:I(string.format("%sDoppler: suppressed %s [%s] by %s",self.lid, name, reason, Unit:GetName())) + --end end end end From 09849c7a44c9f5ffcecf9f11febe2692674919a4 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:45:34 +0100 Subject: [PATCH 338/349] Update Intelligence.lua --- Moose Development/Moose/Ops/Intelligence.lua | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 09462393d..781ca1bed 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -111,12 +111,12 @@ INTEL = { DetectAccoustic = false, DetectAccousticRadius = 1000, DetectAccousticUnitTypes = {Unit.Category.HELICOPTER}, - DopplerRadar = true, + DopplerRadar = false, DopplerMinAltAGL = 500, DopplerNotchSin = math.sin(math.rad(15)), DopplerMinSpeedMps = 50, DopplerRCS = true, - DopplerRadarRangeM = 200 * 1000, + RangeM = 200 * 1000, } --- Detected item info. @@ -2629,7 +2629,7 @@ end -- Default true. -- @return #INTEL self function INTEL:SetDopplerRadar(MinAltAGL, NotchHalfDeg, MinSpeedMps, RadarRangeKm, RCS) - self:I(self.lid .. "SetDopplerRadar") + self:T(self.lid .. "SetDopplerRadar") self.DopplerRadar = true self.DopplerMinAltAGL = MinAltAGL or 500 self.DopplerNotchSin = math.sin(math.rad(NotchHalfDeg or 15)) @@ -2643,7 +2643,7 @@ end -- @param #INTEL self -- @return #INTEL self function INTEL:SetDopplerRadarOff() - self:I(self.lid .. "SetDopplerRadarOff") + self:T(self.lid .. "SetDopplerRadarOff") self.DopplerRadar = false return self end @@ -2655,7 +2655,7 @@ end -- @param #number RCS_m2 Side-on RCS in m² -- @return #INTEL self function INTEL:SetTypeRCS(TypeName, RCS_m2) - self:I(self.lid .. "SetTypeRCS") + self:T(self.lid .. "SetTypeRCS") INTEL.RCS_Table[TypeName] = RCS_m2 return self end @@ -2678,7 +2678,7 @@ end -- @param DCS#Vec3 tvel Target velocity vector (pre-computed) -- @return #number Effective RCS in m² function INTEL:_GetAspectRCS(TargetUnit, rpos, spd, tvel) - self:I(self.lid .. "_GetAspectRCS") + self:T(self.lid .. "_GetAspectRCS") -- Look up base (side-on) RCS local typename = TargetUnit:GetTypeName() local base_rcs = INTEL.RCS_Table[typename] @@ -2716,7 +2716,7 @@ end -- @return #boolean true = detected -- @return #string rejection reason: "speed" | "clutter" | "notch" | "rcs" function INTEL:_CheckDopplerDetection(TargetUnit, RadarUnit) - self:I(self.lid .. "_CheckDopplerDetection") + self:T(self.lid .. "_CheckDopplerDetection") -- Pre-compute common geometry (shared by notch + RCS checks) local spd = TargetUnit:GetVelocityMPS() local rpos = RadarUnit:GetVec3() @@ -2799,7 +2799,7 @@ end -- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. -- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. function INTEL:GetDetectedUnitsDoppler(Unit, DetectedUnits, RecceDetecting,DetectVisual, DetectOptical, DetectRadar,DetectIRST, DetectRWR, DetectDLINK) - self:I(self.lid .. "GetDetectedUnitsDoppler") + self:T(self.lid .. "GetDetectedUnitsDoppler") -- Run the original detection self:GetDetectedUnits(Unit,DetectedUnits,RecceDetecting,DetectVisual,DetectOptical,DetectRadar,DetectIRST,DetectRWR,DetectDLINK) @@ -2815,7 +2815,7 @@ function INTEL:GetDetectedUnitsDoppler(Unit, DetectedUnits, RecceDetecting,Detec if not ok then table.insert(remove, name) --if self.verbose and self.verbose >= 2 then - self:I(string.format("%sDoppler: suppressed %s [%s] by %s",self.lid, name, reason, Unit:GetName())) + self:T(string.format("%sDoppler: suppressed %s [%s] by %s",self.lid, name, reason, Unit:GetName())) --end end end @@ -3027,7 +3027,7 @@ end -- @return #INTEL_DLINK self function INTEL_DLINK:SetDLinkCacheTime(seconds) self.cachetime = math.abs(seconds or 120) - self:I(self.lid.."Caching for "..self.cachetime.." seconds.") + self:T(self.lid.."Caching for "..self.cachetime.." seconds.") return self end @@ -3117,7 +3117,7 @@ end function INTEL_DLINK:onafterStop(From, Event, To) self:T({From, Event, To}) local text = string.format("Version %s stopped.", self.version) - self:I(self.lid .. text) + self:T(self.lid .. text) return self end From b48035fb37f54a79dad12e462e8a2f2a346ee0cc Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:36:18 +0100 Subject: [PATCH 339/349] Update build-includes.yml --- .github/workflows/build-includes.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-includes.yml b/.github/workflows/build-includes.yml index 1cf75c086..3e62bd5bb 100644 --- a/.github/workflows/build-includes.yml +++ b/.github/workflows/build-includes.yml @@ -40,7 +40,7 @@ jobs: # Prepare build environment ######################################################################### - name: Check out repository code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Prepare build output folders run: | @@ -115,7 +115,7 @@ jobs: # Push to MOOSE_INCLUDE ######################################################################### - name: Checkout MOOSE_INCLUDE - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ github.repository_owner }}/MOOSE_INCLUDE path: './build/MOOSE_INCLUDE' From c4cb9fb82c14fdb544652b3430c8a6f469d3671a Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:37:24 +0100 Subject: [PATCH 340/349] Update build-docs.yml --- .github/workflows/build-docs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index f3eba25c2..fecbf12e1 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -40,7 +40,7 @@ jobs: # Prepare build environment ######################################################################### - name: Check out repository code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Prepare build output folders run: | @@ -48,7 +48,7 @@ jobs: mkdir -p build/doc - name: Checkout FlightControls modified luadocumentor - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: Applevangelist/luadocumentor path: './build/tools/luadocumentor' @@ -129,7 +129,7 @@ jobs: fi - name: Checkout ${{ steps.set_doc_repo.outputs.docrepo }} to folder MOOSE_DOCS - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ github.repository_owner }}/${{ steps.set_doc_repo.outputs.docrepo }} path: './build/MOOSE_DOCS' From 054e362e8c2dcbb37d3b001988931db5a70f0192 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:39:18 +0100 Subject: [PATCH 341/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 207ef105d..7e2ff091a 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: @@ -43,7 +43,7 @@ jobs: working-directory: docs/ - name: Setup Pages id: pages - uses: actions/configure-pages@v4 + uses: actions/configure-pages@v5 - name: Build with Jekyll # Outputs to the './_site' directory by default run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" @@ -52,7 +52,7 @@ jobs: working-directory: docs/ - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: docs/_site/ @@ -66,13 +66,13 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 check: runs-on: ubuntu-latest needs: deploy steps: - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 - run: npm install linkinator - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --retry-errors --retry-errors-count 3 --retry-errors-jitter From 1811e28fbb3b96b9f3b2aa666fd57953c1529609 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:42:15 +0100 Subject: [PATCH 342/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 7e2ff091a..5fcb0af06 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -66,7 +66,7 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@v4 check: runs-on: ubuntu-latest From 44136b23b10a60e5f5b2d5a59878850c504a3919 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:52:29 +0100 Subject: [PATCH 343/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 5fcb0af06..012e581f2 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -75,4 +75,4 @@ jobs: - name: Setup Node uses: actions/setup-node@v5 - run: npm install linkinator - - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --retry-errors --retry-errors-count 3 --retry-errors-jitter + - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip-files "*AI_*" --skip-files "archive" --retry-errors --retry-errors-count 3 --retry-errors-jitter From 781a2043b748edb012a59a8ecf4443d0bd75aef9 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:55:11 +0100 Subject: [PATCH 344/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 012e581f2..90e3eae6a 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -75,4 +75,4 @@ jobs: - name: Setup Node uses: actions/setup-node@v5 - run: npm install linkinator - - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip-files "*AI_*" --skip-files "archive" --retry-errors --retry-errors-count 3 --retry-errors-jitter + - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip-files "*AI.AI*" --skip-files "*CARGO*--skip-files "archive" --retry-errors --retry-errors-count 3 --retry-errors-jitter From 9e329abcfa4c56101dcec20be81a683431230823 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:57:22 +0100 Subject: [PATCH 345/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 90e3eae6a..d229f7d4c 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -75,4 +75,4 @@ jobs: - name: Setup Node uses: actions/setup-node@v5 - run: npm install linkinator - - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip-files "*AI.AI*" --skip-files "*CARGO*--skip-files "archive" --retry-errors --retry-errors-count 3 --retry-errors-jitter + - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip-files "*AI.AI*" --skip-files "*CARGO* --skip-files "archive" --retry-errors --retry-errors-count 3 --retry-errors-jitter From d28477a65578d9d6a30d5ae2cfad43f9abcb7226 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 12:00:15 +0100 Subject: [PATCH 346/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index d229f7d4c..b8e751060 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -75,4 +75,4 @@ jobs: - name: Setup Node uses: actions/setup-node@v5 - run: npm install linkinator - - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip-files "*AI.AI*" --skip-files "*CARGO* --skip-files "archive" --retry-errors --retry-errors-count 3 --retry-errors-jitter + - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip-files "*AI.AI*" --skip-files "*CARGO*" --skip-files "archive" --retry-errors --retry-errors-count 3 --retry-errors-jitter From bd5816e451700cde1121b7352ddc1862e1742ff9 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 12:15:13 +0100 Subject: [PATCH 347/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index b8e751060..948944098 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -75,4 +75,4 @@ jobs: - name: Setup Node uses: actions/setup-node@v5 - run: npm install linkinator - - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip-files "*AI.AI*" --skip-files "*CARGO*" --skip-files "archive" --retry-errors --retry-errors-count 3 --retry-errors-jitter + - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip 'https:\/\/flightcontrol-master\.github\.io\/MOOSE_DOCS_DEVELOP\/Documentation\/(?:AI|Tasking|Cargo)\.[^\/\s]+\.html$' --retry-errors --retry-errors-count 3 --retry-errors-jitter From 80c55a451d2611590ce1c4aee3e7143a8357e7f4 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 12:17:40 +0100 Subject: [PATCH 348/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 948944098..60bc66749 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -75,4 +75,4 @@ jobs: - name: Setup Node uses: actions/setup-node@v5 - run: npm install linkinator - - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip 'https:\/\/flightcontrol-master\.github\.io\/MOOSE_DOCS_DEVELOP\/Documentation\/(?:AI|Tasking|Cargo)\.[^\/\s]+\.html$' --retry-errors --retry-errors-count 3 --retry-errors-jitter + - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip 'https:\/\/flightcontrol-master\.github\.io\/MOOSE_DOCS_DEVELOP\/Documentation\/(?:AI|Tasking|Cargo|MissileTrainer)\.[^\/\s]+\.html$' --retry-errors --retry-errors-count 3 --retry-errors-jitter From ff957b72c47fa631b65b3b82d843218d46cfe9d7 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Mar 2026 12:20:15 +0100 Subject: [PATCH 349/349] Update gh-pages.yml --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 60bc66749..05cbd405c 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -75,4 +75,4 @@ jobs: - name: Setup Node uses: actions/setup-node@v5 - run: npm install linkinator - - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip 'https:\/\/flightcontrol-master\.github\.io\/MOOSE_DOCS_DEVELOP\/Documentation\/(?:AI|Tasking|Cargo|MissileTrainer)\.[^\/\s]+\.html$' --retry-errors --retry-errors-count 3 --retry-errors-jitter + - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --skip 'https:\/\/flightcontrol-master\.github\.io\/MOOSE_DOCS_DEVELOP\/Documentation\/(?:AI|Tasking|Cargo|MissileTrainer)\.[^\/\s]+\.html$' --status-code "404:skip" --retry-errors --retry-errors-count 3 --retry-errors-jitter