From 8c92a578ed14c0456cfe52055da429bd9af67a65 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 21 Jul 2025 14:50:04 +0200 Subject: [PATCH 01/73] #UTILS - added UTILS.SpawnMASHStatics() --- 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 7ae4cdc8f1b27380f33ddd11a6a6d80c853fbf7c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 21 Jul 2025 15:02:45 +0200 Subject: [PATCH 02/73] #Documentation --- Moose Development/Moose/Utilities/Utils.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index b6ac21de1..00ed738b2 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4267,6 +4267,8 @@ end -- @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. +-- @return #table Table of Wrapper.Static#STATIC objects that were spawned. +-- @return #string ADFName Name of the ADF Beacon to remove it later. -- @usage -- -- MASH Template example, this one is the built in one used in the function: -- MASHTemplates = { From 2ee0597d48f81f3d71bd860c3c1723af2e799fa6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 22 Jul 2025 13:08:12 +0200 Subject: [PATCH 03/73] #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 ada38fa3ea593eebfd0028a9764ac3b18fb63d5a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 22 Jul 2025 13:08:46 +0200 Subject: [PATCH 04/73] #AIRBOSS - SRS 2.2.x path in documentation --- 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 fcf723279..7f2a4b6d8 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1756,7 +1756,7 @@ AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version = "1.4.0" +AIRBOSS.version = "1.4.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3081,7 +3081,7 @@ 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 folder, e.g. "C:\\Program Files\\DCS-SimpleRadio\\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". From ac8cc408c1ef619b870ce27118d1d877a8dc7d0c Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 23 Jul 2025 11:48:07 +0200 Subject: [PATCH 05/73] [FIXED] `Disposition.getSimpleZones` --- Moose Development/Moose/Core/Point.lua | 16 +++++++++ Moose Development/Moose/Core/Zone.lua | 36 +++++++++++++++++++++ Moose Development/Moose/Utilities/Utils.lua | 10 ++++++ 3 files changed, 62 insertions(+) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index a74356985..557180a32 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -3797,6 +3797,22 @@ do -- COORDINATE function COORDINATE:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) return COORDINATE:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) end + + +--- Search for clear zones in a given area. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param #number SearchRadius Radius of the search area. +-- @param #number PosRadius Required clear radius around each position. +-- @param #number NumPositions Number of positions to find. +-- @return #table A table of Core.Point#COORDINATE that are clear of map objects within the given PosRadius. + function COORDINATE:GetSimpleZones(SearchRadius, PosRadius, NumPositions) + local clearPositions = UTILS.GetSimpleZones(self:GetVec3(), SearchRadius, PosRadius, NumPositions) + local coords = {} + for _, pos in ipairs(clearPositions) do + local coord = COORDINATE:NewFromVec2(pos) + table.insert(coords, coord) + end + return coords + end end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 6664364ed..134cbf23b 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1509,6 +1509,42 @@ function ZONE_RADIUS:IsVec3InZone( Vec3 ) return InZone 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 #number PosRadius Required clear radius around each position. +-- @param #number NumPositions Number of positions to find. +-- @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 ZONE_RADIUS:GetClearZonePositions(PosRadius, NumPositions) + local clearPositions = UTILS.GetSimpleZones(self:GetVec3(), self:GetRadius(), PosRadius, NumPositions) + if clearPositions or #clearPositions > 0 then + local validZones = {} + for _, vec2 in pairs(clearPositions) do + if self.zone:IsVec2InZone(vec2) then + table.insert(validZones, vec2) + end + end + if #validZones > 0 then + return validZones + 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 #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 ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius, NumPositions) + local radius = PosRadius or math.min(self.Radius/10, 200) + local clearPositions = self:GetClearZonePositions(radius, NumPositions or 50) + if clearPositions or #clearPositions > 0 then + local randomPosition = clearPositions[math.random(1, #clearPositions)] + return COORDINATE:NewFromVec2(randomPosition), radius + end + return nil +end + --- Returns a random Vec2 location within the zone. -- @param #ZONE_RADIUS self -- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index b184e46aa..aeddb7fa9 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4630,3 +4630,13 @@ function UTILS.DestroyScenery(name, level) net.dostring_in("mission",string.format("a_scenery_destruction_zone(%d, %d)", z.zoneId, level)) end end + +--- Search for clear zones in a given area. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param DCS##Vec3 Center position vector for the search area. +-- @param #number SearchRadius Radius of the search area. +-- @param #number PosRadius Required clear radius around each position. +-- @param #number NumPositions Number of positions to find. +-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. +function UTILS.GetSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions) + return Disposition.getSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions) +end From c1e8ee12e0bd0ccb03944cef5ca682e91e933a16 Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 23 Jul 2025 11:48:33 +0200 Subject: [PATCH 06/73] [ADDED] `Disposition.getSimpleZones` --- Moose Development/Moose/Core/Zone.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 134cbf23b..d663bc93b 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1542,6 +1542,7 @@ function ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius, NumPositions) local randomPosition = clearPositions[math.random(1, #clearPositions)] return COORDINATE:NewFromVec2(randomPosition), radius end + return nil end From 03763e16d6c240f1f5781ad8ee46b1c1b6012852 Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 23 Jul 2025 12:19:00 +0200 Subject: [PATCH 07/73] [ADDED] `Disposition.getSimpleZones` --- 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 d663bc93b..a44698097 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1518,7 +1518,7 @@ function ZONE_RADIUS:GetClearZonePositions(PosRadius, NumPositions) if clearPositions or #clearPositions > 0 then local validZones = {} for _, vec2 in pairs(clearPositions) do - if self.zone:IsVec2InZone(vec2) then + if self:IsVec2InZone(vec2) then table.insert(validZones, vec2) end end From 11b0ce6275a931467e241e7e1badb9d3b994b5b9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Jul 2025 12:34:52 +0200 Subject: [PATCH 08/73] #AIRBASE - remove some differences between data produced by _InitRunways and GetRunwayData --- Moose Development/Moose/Wrapper/Airbase.lua | 55 ++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 520f8b923..80c4d20cb 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1467,7 +1467,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 @@ -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 367014ebf3ee7c195c842bee35551a6289c2c510 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Jul 2025 15:47:56 +0200 Subject: [PATCH 09/73] xxx --- Moose Development/Moose/Wrapper/Airbase.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 80c4d20cb..a24c00cbe 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -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 a462c5a4937a153e10f4e43dcdec752fca594048 Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 24 Jul 2025 01:51:10 +0200 Subject: [PATCH 10/73] [Fixed] `Disposition.getSimpleZones` --- Moose Development/Moose/Core/Point.lua | 17 ++++++++++------- Moose Development/Moose/Core/Zone.lua | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 557180a32..fc77cdeb6 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -3803,17 +3803,20 @@ do -- COORDINATE -- @param #number SearchRadius Radius of the search area. -- @param #number PosRadius Required clear radius around each position. -- @param #number NumPositions Number of positions to find. --- @return #table A table of Core.Point#COORDINATE that are clear of map objects within the given PosRadius. +-- @return #table A table of Core.Point#COORDINATE that are clear of map objects within the given PosRadius. nil if no positions are found. function COORDINATE:GetSimpleZones(SearchRadius, PosRadius, NumPositions) local clearPositions = UTILS.GetSimpleZones(self:GetVec3(), SearchRadius, PosRadius, NumPositions) - local coords = {} - for _, pos in ipairs(clearPositions) do - local coord = COORDINATE:NewFromVec2(pos) - table.insert(coords, coord) + if clearPositions and #clearPositions > 0 then + local coords = {} + for _, pos in pairs(clearPositions) do + local coord = COORDINATE:NewFromVec2(pos) + table.insert(coords, coord) + end + return coords end - return coords + return nil end - + end do diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index a44698097..401aab808 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1515,7 +1515,7 @@ end -- @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 ZONE_RADIUS:GetClearZonePositions(PosRadius, NumPositions) local clearPositions = UTILS.GetSimpleZones(self:GetVec3(), self:GetRadius(), PosRadius, NumPositions) - if clearPositions or #clearPositions > 0 then + if clearPositions and #clearPositions > 0 then local validZones = {} for _, vec2 in pairs(clearPositions) do if self:IsVec2InZone(vec2) then @@ -1538,7 +1538,7 @@ end function ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius, NumPositions) local radius = PosRadius or math.min(self.Radius/10, 200) local clearPositions = self:GetClearZonePositions(radius, NumPositions or 50) - if clearPositions or #clearPositions > 0 then + if clearPositions and #clearPositions > 0 then local randomPosition = clearPositions[math.random(1, #clearPositions)] return COORDINATE:NewFromVec2(randomPosition), radius end From 4e56078d2a0bc6b8eab33b4fcfc1d660dc30e578 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 24 Jul 2025 16:17:04 +0200 Subject: [PATCH 11/73] #CONTROLLABLE - added options for landing approaches * Prefer vertical for helos and for aircraft * Straight in * Overhead break * Force pair * Restrict pair --- .../Moose/Wrapper/Controllable.lua | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 689d841a5..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 = { @@ -1432,7 +1441,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', @@ -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 40253ea8bb02298e0fff0a4e3e0050616a887ef8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 24 Jul 2025 18:27:44 +0200 Subject: [PATCH 12/73] xx --- Moose Development/Moose/Core/Database.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 2d985a021..a8c814260 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1112,7 +1112,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) else self.STNS[stn] = UnitTemplate.name - self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) + self:T("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) end end if UnitTemplate.AddPropAircraft.SADL_TN then @@ -1121,7 +1121,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) else self.SADL[sadl] = UnitTemplate.name - self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) + self:T("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) end end end @@ -1382,7 +1382,7 @@ function DATABASE:GetCoalitionFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CoalitionID end - self:E("WARNING: Template does not exist for client "..tostring(ClientName)) + self:T("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end @@ -1394,7 +1394,7 @@ function DATABASE:GetCategoryFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CategoryID end - self:E("WARNING: Template does not exist for client "..tostring(ClientName)) + self:T("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end @@ -1406,7 +1406,7 @@ function DATABASE:GetCountryFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CountryID end - self:E("WARNING: Template does not exist for client "..tostring(ClientName)) + self:T("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end From 74712b6e27770abed1e6996c8e3112ba40fbed6b Mon Sep 17 00:00:00 2001 From: smiki Date: Fri, 25 Jul 2025 14:17:03 +0200 Subject: [PATCH 13/73] [ADDED] `Disposition.getSimpleZones` to ZONE_POLYGON_BASE to support all zone types --- Moose Development/Moose/Core/Zone.lua | 41 ++++++++++----------- Moose Development/Moose/Utilities/Utils.lua | 39 ++++++++++++++++++++ 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 401aab808..7095d9f62 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1514,19 +1514,7 @@ end -- @param #number NumPositions Number of positions to find. -- @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 ZONE_RADIUS:GetClearZonePositions(PosRadius, NumPositions) - local clearPositions = UTILS.GetSimpleZones(self:GetVec3(), self:GetRadius(), PosRadius, NumPositions) - if clearPositions and #clearPositions > 0 then - local validZones = {} - for _, vec2 in pairs(clearPositions) do - if self:IsVec2InZone(vec2) then - table.insert(validZones, vec2) - end - end - if #validZones > 0 then - return validZones - end - end - return nil + return UTILS.GetClearZonePositions(self, PosRadius, NumPositions) end @@ -1536,14 +1524,7 @@ end -- @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 ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius, NumPositions) - local radius = PosRadius or math.min(self.Radius/10, 200) - local clearPositions = self:GetClearZonePositions(radius, NumPositions or 50) - if clearPositions and #clearPositions > 0 then - local randomPosition = clearPositions[math.random(1, #clearPositions)] - return COORDINATE:NewFromVec2(randomPosition), radius - end - - return nil + return UTILS.GetRandomClearZoneCoordinate(self, PosRadius, NumPositions) end --- Returns a random Vec2 location within the zone. @@ -2524,6 +2505,24 @@ function ZONE_POLYGON_BASE:Flush() return self 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 #number PosRadius Required clear radius around each position. +-- @param #number NumPositions Number of positions to find. +-- @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 ZONE_POLYGON_BASE:GetClearZonePositions(PosRadius, NumPositions) + return UTILS.GetClearZonePositions(self, PosRadius, NumPositions) +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 #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 ZONE_POLYGON_BASE:GetRandomClearZoneCoordinate(PosRadius, NumPositions) + return UTILS.GetRandomClearZoneCoordinate(self, PosRadius, NumPositions) +end + --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self -- @param #boolean UnBound If true, the tyres will be destroyed. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index aeddb7fa9..f1a4d3e6c 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4640,3 +4640,42 @@ end 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 From 30203668e4d3765a08cc1dc05957fb066a7e9962 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jul 2025 14:52:07 +0200 Subject: [PATCH 14/73] Revert "#UTILS - Added FindNearestPointOnCircle()" This reverts commit 2cc1ddd4679b0e3fb7a5f72ea5e4822112e2f2d1. --- Moose Development/Moose/Utilities/Utils.lua | 39 --------------------- 1 file changed, 39 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f1a4d3e6c..aeddb7fa9 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4640,42 +4640,3 @@ end 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 From b2a084d669649eb522b115af38b3c6a8c7bf51ba Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jul 2025 14:54:53 +0200 Subject: [PATCH 15/73] xx --- Moose Development/Moose/Utilities/Utils.lua | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index aeddb7fa9..f1a4d3e6c 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4640,3 +4640,42 @@ end 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 From 7d3fc1740a16553105a0fd9505bdbf44d7a1d989 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jul 2025 14:57:58 +0200 Subject: [PATCH 16/73] xx --- Moose Development/Moose/Utilities/Utils.lua | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f1a4d3e6c..ac71ae3f6 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4679,3 +4679,42 @@ function UTILS.GetRandomClearZoneCoordinate(Zone, PosRadius, NumPositions) 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. +-- @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 23ff128ac894447f96a3e1c647fc2fb96c524eae Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jul 2025 19:05:01 +0200 Subject: [PATCH 17/73] #ZONE added ZONE_BASE:FindNearestCoordinateOnRadius() --- Moose Development/Moose/Core/ScheduleDispatcher.lua | 2 +- Moose Development/Moose/Core/Zone.lua | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index 023c20c95..8e062d82b 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. From a5726c0ed81f16c9f34eae473358881ff6814fe2 Mon Sep 17 00:00:00 2001 From: smiki Date: Fri, 25 Jul 2025 23:27:01 +0200 Subject: [PATCH 18/73] [ADDED] `UTILS.ShowPicture`. Overlay pictures for players. Refactoring --- Moose Development/Moose/Core/Zone.lua | 4 +++ Moose Development/Moose/Utilities/Utils.lua | 27 +++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 7095d9f62..3e15506fb 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1510,6 +1510,7 @@ function ZONE_RADIUS:IsVec3InZone( Vec3 ) 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 #ZONE_RADIUS self -- @param #number PosRadius Required clear radius around each position. -- @param #number NumPositions Number of positions to find. -- @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. @@ -1519,6 +1520,7 @@ 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 #ZONE_RADIUS self -- @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. @@ -2506,6 +2508,7 @@ function ZONE_POLYGON_BASE:Flush() 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 #ZONE_POLYGON_BASE self -- @param #number PosRadius Required clear radius around each position. -- @param #number NumPositions Number of positions to find. -- @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. @@ -2515,6 +2518,7 @@ 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 #ZONE_POLYGON_BASE self -- @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. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index f1a4d3e6c..b69300f1a 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4593,6 +4593,33 @@ function UTILS.GetEnvZone(name) end end +--- net.dostring_in +function UTILS.DoStringIn(State,DoString) + return net.dostring_in(State,DoString) +end + +--- Show a picture on the screen +-- @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 +-- @param #number HorizontalAlign Horizontal alignment of the picture, defaults to 1 (left), can be 0 (center) or 2 (right) +-- @param #number VerticalAlign Vertical alignment of the picture, defaults to 1 (top), can be 0 (center) or 2 (bottom) +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, defaults to 0 (percent), can be 1 (pixels) +function UTILS.ShowPicture(FileName, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + net.dostring_in("mission", string.format("a_out_picture(getValueResourceByKey(\"%s\"), %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", FileName, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +end + --- Show a helper gate at a DCS#Vec3 position -- @param DCS#Vec3 pos The position -- @param number heading Heading in degrees, can be 0..359 degrees From 5e724e7a3fc4daa89422bcbcfe0850d43fa5ab79 Mon Sep 17 00:00:00 2001 From: smiki Date: Fri, 25 Jul 2025 23:39:53 +0200 Subject: [PATCH 19/73] [ADDED] `COORDINATE:GetLandProfile` --- Moose Development/Moose/Core/Point.lua | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index fc77cdeb6..0d3c7f21d 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -837,6 +837,26 @@ do -- COORDINATE return land.getHeight( Vec2 ) end + --- Returns a table of `Vec3` points representing the terrain profile between two points. + -- @param #COORDINATE self + -- @param Destination DCS#Vec3 Ending point of the profile. + -- @return #table DCS#Vec3 table of the profile + function COORDINATE:GetLandProfileVec3(Destination) + return land.profile(self:GetVec3(), Destination) + end + + --- Returns a table of `Vec3` points representing the terrain profile between two points. + -- @param #COORDINATE self + -- @param Destination #COORDINATE Ending coordinate of the profile. + -- @return #table #COORDINATE table of the profile + function COORDINATE:GetLandProfileCoordinates(Destination) + local points = self:GetLandProfileVec3(Destination:GetVec3()) + local coords = {} + for _, point in ipairs(points) do + table.insert(coords, COORDINATE:NewFromVec3(point)) + end + return coords + end --- Set the heading of the coordinate, if applicable. -- @param #COORDINATE self From b6b668687354a2678df671d87e5f5d78e19d3a45 Mon Sep 17 00:00:00 2001 From: smiki Date: Fri, 25 Jul 2025 23:43:00 +0200 Subject: [PATCH 20/73] [ADDED] `COORDINATE:GetLandProfile` --- Moose Development/Moose/Core/Point.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 0d3c7f21d..09af3b47e 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -837,7 +837,7 @@ do -- COORDINATE return land.getHeight( Vec2 ) end - --- Returns a table of `Vec3` points representing the terrain profile between two points. + --- Returns a table of DCS#Vec3 points representing the terrain profile between two points. -- @param #COORDINATE self -- @param Destination DCS#Vec3 Ending point of the profile. -- @return #table DCS#Vec3 table of the profile @@ -845,7 +845,7 @@ do -- COORDINATE return land.profile(self:GetVec3(), Destination) end - --- Returns a table of `Vec3` points representing the terrain profile between two points. + --- Returns a table of #COORDINATE representing the terrain profile between two points. -- @param #COORDINATE self -- @param Destination #COORDINATE Ending coordinate of the profile. -- @return #table #COORDINATE table of the profile From cc60e8590157297b03ebcfe84f0b8bc33a6caf98 Mon Sep 17 00:00:00 2001 From: smiki Date: Sun, 27 Jul 2025 13:18:56 +0200 Subject: [PATCH 21/73] [ADDED] `UTILS.LoadMission` and `UTILS.SetMissionBriefing` --- Moose Development/Moose/Utilities/Utils.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 42a7f779b..11dcdcbb1 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4620,6 +4620,24 @@ function UTILS.ShowPicture(FileName, Duration, ClearView, StartDelay, Horizontal net.dostring_in("mission", string.format("a_out_picture(getValueResourceByKey(\"%s\"), %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", FileName, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) end +--- Load a mission file. This will replace the current mission with the one given carrying along the online clients. +-- @param #string FileName Mission filename +function UTILS.LoadMission(FileName) + net.dostring_in("mission", string.format("a_load_mission(\"%s\")", FileName)) +end + +--- Set the mission briefing for a coalition. +-- @param #number Coalition Briefing coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL +-- @param #string Text Briefing text, can contain newlines, will be converted formatted properly for DCS +-- @param #string Picture Picture filename, can be a file in the DEFAULT folder inside the .miz +function UTILS.SetMissionBriefing(Coalition, Text, Picture) + Text = Text or "" + Text = Text:gsub("\n", "\\n") + Picture = Picture or "" + local coalName = string.lower(UTILS.GetCoalitionName(Coalition)) + net.dostring_in("mission", string.format("a_set_briefing(\"%s\", getValueResourceByKey(\"%s\"), \"%s\")", coalName, Picture, Text)) +end + --- Show a helper gate at a DCS#Vec3 position -- @param DCS#Vec3 pos The position -- @param number heading Heading in degrees, can be 0..359 degrees From b9be3aa7f888cf1b3f92704bf13d4884d708e4bb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 27 Jul 2025 14:50:45 +0200 Subject: [PATCH 22/73] xx --- Moose Development/Moose/Ops/CSAR.lua | 1694 +++++++++++++------------- 1 file changed, 847 insertions(+), 847 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 8dede4270..ce21f7121 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1,25 +1,25 @@ --- **Ops** - Combat Search and Rescue. -- -- === --- +-- -- **CSAR** - MOOSE based Helicopter CSAR Operations. --- +-- -- === --- +-- -- ## Missions:--- **Ops** -- Combat Search and Rescue. -- -- === --- +-- -- **CSAR** - MOOSE based Helicopter CSAR Operations. --- +-- -- === --- +-- -- ## Missions: -- -- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/CSAR) --- +-- -- === --- +-- -- **Main Features:** -- -- * MOOSE-based Helicopter CSAR Operations for Players. @@ -49,24 +49,24 @@ -- === -- -- # CSAR Concept --- +-- -- * MOOSE-based Helicopter CSAR Operations for Players. -- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. --- * No need for extra MIST loading. +-- * No need for extra MIST loading. -- * Additional events to tailor your mission. -- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASVAC requests). --- +-- -- ## 0. Prerequisites --- +-- -- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. -- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". --- +-- -- Example sound files are here: [Moose Sound](https://github.com/FlightControl-Master/MOOSE_SOUND/tree/master/CTLD%20CSAR) --- +-- -- ## 1. Basic Setup --- +-- -- A basic setup example is the following: --- +-- -- -- Instantiate and start a CSAR for the blue side, with template "Downed Pilot" and alias "Luftrettung" -- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") -- -- options @@ -74,9 +74,9 @@ -- my_csar.invisiblecrew = false -- downed pilot spawn is visible -- -- start the FSM -- my_csar:__Start(5) --- +-- -- ## 2. Options --- +-- -- The following options are available (with their defaults). Only set the ones you want changed: -- -- mycsar.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. @@ -87,7 +87,7 @@ -- mycsar.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. -- mycsar.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. -- mycsar.enableForAI = false -- set to false to disable AI pilots from being rescued. --- mycsar.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to mycsar.extractDistance in meters. +-- mycsar.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to mycsar.extractDistance in meters. -- mycsar.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal. -- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible. @@ -95,14 +95,14 @@ -- 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. +-- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. -- mycsar.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. -- mycsar.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. --- mycsar.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- mycsar.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- mycsar.verbose = 0 -- set to > 1 for stats output for debugging. -- -- limit amount of downed pilots spawned by **ejection** events -- mycsar.limitmaxdownedpilots = true --- mycsar.maxdownedpilots = 10 +-- mycsar.maxdownedpilots = 10 -- -- allow to set far/near distance for approach and optionally pilot must open doors -- mycsar.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- mycsar.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters @@ -119,16 +119,16 @@ -- 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 --- +-- -- -- 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.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\\ExternalAudio\\" -- adjust your own path in your SRS installation -- server(!) -- mycsar.SRSchannel = 300 -- radio channel @@ -147,88 +147,88 @@ -- 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 --- +-- -- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: --- +-- -- mycsar.rescues -- number of successful landings *with* saved pilots -- mycsar.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) --- +-- -- ## 4. Events -- -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. -- These are: --- --- ### 4.1. PilotDown. --- +-- +-- ### 4.1. PilotDown. +-- -- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: --- +-- -- function my_csar:OnAfterPilotDown(from, event, to, spawnedgroup, frequency, groupname, coordinates_text) -- ... your code here ... -- end --- --- ### 4.2. Approach. --- +-- +-- ### 4.2. Approach. +-- -- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: --- +-- -- function my_csar:OnAfterApproach(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.3. Boarded. --- +-- +-- ### 4.3. Boarded. +-- -- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: --- +-- -- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname, description) -- ... your code here ... -- end --- --- ### 4.4. Returning. --- +-- +-- ### 4.4. Returning. +-- -- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: --- +-- -- function my_csar:OnAfterReturning(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.5. Rescued. --- +-- +-- ### 4.5. Rescued. +-- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: --- +-- -- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname, pilotssaved) -- ... your code here ... --- end +-- end -- -- ## 5. Spawn downed pilots at location to be picked up. --- +-- -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: --- +-- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- -- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat --- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) --- +-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) +-- -- ## 6. Save and load downed pilots - Persistance --- +-- -- You can save and later load back downed pilots to make your mission persistent. -- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts. -- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you. --- +-- -- Use the following options to manage your saves: --- +-- -- mycsar.enableLoadSave = true -- allow auto-saving and loading of files -- mycsar.saveinterval = 600 -- save every 10 minutes -- mycsar.filename = "missionsave.csv" -- example filename -- mycsar.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path --- +-- -- Then use an initial load at the beginning of your mission: --- +-- -- mycsar:__Load(10) --- +-- -- **Caveat:** --- Dropped troop noMessage and forcedesc parameters aren't saved. +-- Dropped troop noMessage and forcedesc parameters aren't saved. -- -- @field #CSAR CSAR = { @@ -254,7 +254,7 @@ CSAR = { hoverStatus = {}, -- tracks status of a helis hover above a downed pilot pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for pilotLives = {}, -- tracks how many lives a pilot has - useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below csarPrefix = {}, template = nil, mash = {}, @@ -300,10 +300,10 @@ CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 CSAR.AircraftType["Mi-8MTV2"] = 12 -CSAR.AircraftType["Mi-8MT"] = 12 -CSAR.AircraftType["Mi-24P"] = 8 +CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 -CSAR.AircraftType["Bell-47"] = 2 +CSAR.AircraftType["Bell-47"] = 2 CSAR.AircraftType["UH-60L"] = 10 CSAR.AircraftType["AH-64D_BLK_II"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2 @@ -334,12 +334,12 @@ CSAR.version="1.0.33" -- @param #string Alias An *optional* alias how this object is called in the logs etc. -- @return #CSAR self function CSAR:New(Coalition, Template, Alias) - + -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #CSAR - + BASE:T({Coalition, Template, Alias}) - + --set Coalition if Coalition and type(Coalition)=="string" then if Coalition=="blue" then @@ -358,12 +358,12 @@ function CSAR:New(Coalition, Template, Alias) self.coalition = Coalition self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) end - + -- Set alias. if Alias then self.alias=tostring(Alias) else - self.alias="Red Cross" + self.alias="Red Cross" if self.coalition then if self.coalition==coalition.side.RED then self.alias="IFRC" @@ -372,10 +372,10 @@ function CSAR:New(Coalition, Template, Alias) end end end - + -- 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") - + -- Start State. self:SetStartState("Stopped") @@ -385,12 +385,12 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "Status", "*") -- CSAR status update. self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. - self:AddTransition("*", "Landed", "*") -- CSAR heli landed + self:AddTransition("*", "Landed", "*") -- CSAR heli landed self:AddTransition("*", "Boarded", "*") -- Pilot boarded. self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. self:AddTransition("*", "KIA", "*") -- Pilot killed in action. - self:AddTransition("*", "Load", "*") -- CSAR load event. + self:AddTransition("*", "Load", "*") -- CSAR load event. self:AddTransition("*", "Save", "*") -- CSAR save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -411,7 +411,7 @@ function CSAR:New(Coalition, Template, Alias) self.woundedGroups = {} -- contains the new group of units self.downedPilots = {} -- Replacement woundedGroups self.downedpilotcounter = 1 - + -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH self.rescuedpilots = 0 -- counter for saved pilots @@ -421,9 +421,9 @@ function CSAR:New(Coalition, Template, Alias) self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal - self.invisiblecrew = false -- Set to true to make wounded crew insvisible - self.messageTime = 15 -- Time to show longer messages for in seconds - self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS + self.invisiblecrew = false -- Set to true to make wounded crew insvisible + self.messageTime = 15 -- Time to show longer messages for in seconds + self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter self.loadtimemax = 135 -- seconds @@ -432,11 +432,11 @@ function CSAR:New(Coalition, Template, Alias) 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.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.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.autosmoke = false -- automatically smoke location when heli is near self.autosmokedistance = 2000 -- distance for autosmoke -- added 0.1.4 @@ -449,34 +449,34 @@ function CSAR:New(Coalition, Template, Alias) self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters self.pilotmustopendoors = false -- switch to true to enable check on open doors self.suppressmessages = false - + -- added 0.1.11r1 self.rescuehoverheight = 20 self.rescuehoverdistance = 10 - + -- added 0.1.12 self.countryblue= country.id.USA self.countryred = country.id.RUSSIA self.countryneutral = country.id.UN_PEACEKEEPERS - + -- added 0.1.3 self.csarUsePara = false -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection - + -- added 0.1.4 self.wetfeettemplate = nil self.usewetfeet = false - + -- added 1.0.15 self.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane - + self.ADFRadioPwr = 500 - + -- 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 -- needs SRS => 1.9.6 to work (works on the *server* side) @@ -493,20 +493,20 @@ function CSAR:New(Coalition, Template, Alias) self.CSARVoice = MSRS.Voices.Google.Standard.en_US_Standard_A self.CSARVoiceMS = MSRS.Voices.Microsoft.Hedda self.coordinate = nil -- Core.Point#COORDINATE - + local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CSAR_%s_Persist.csv",AliaS) - + -- load and save downed pilots self.enableLoadSave = false self.filepath = nil self.saveinterval = 600 - + ------------------------ --- Pseudo Functions --- ------------------------ - - --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. + + --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. -- @function [parent=#CSAR] Start -- @param #CSAR self @@ -531,7 +531,7 @@ function CSAR:New(Coalition, Template, Alias) -- @function [parent=#CSAR] __Status -- @param #CSAR self -- @param #number delay Delay in seconds. - -- + -- -- --- Triggers the FSM event "Load". -- @function [parent=#CSAR] Load -- @param #CSAR self @@ -540,7 +540,7 @@ function CSAR:New(Coalition, Template, Alias) -- @function [parent=#CSAR] __Load -- @param #CSAR self -- @param #number delay Delay in seconds. - + --- Triggers the FSM event "Save". -- @function [parent=#CSAR] Load -- @param #CSAR self @@ -549,7 +549,7 @@ function CSAR:New(Coalition, Template, Alias) -- @function [parent=#CSAR] __Save -- @param #CSAR self -- @param #number delay Delay in seconds. - + --- On After "PilotDown" event. Downed Pilot detected. -- @function [parent=#CSAR] OnAfterPilotDown -- @param #CSAR self @@ -561,7 +561,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Leadername Name of the #UNIT of the downed pilot. -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. -- @param #string Playername Player name if any given. Might be nil! - + --- On After "Aproach" event. Heli close to downed Pilot. -- @function [parent=#CSAR] OnAfterApproach -- @param #CSAR self @@ -570,8 +570,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. -- @param #string Woundedgroupname Name of the downed pilot\'s group. - - --- On After "Landed" event. Heli landed at an airbase. + + --- On After "Landed" event. Heli landed at an airbase. -- @function [parent=#CSAR] OnAfterLanded -- @param #CSAR self -- @param #string From From state. @@ -579,8 +579,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string HeliName Name of the #UNIT which has landed. -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where the heli landed. - - --- On After "Boarded" event. Downed pilot boarded heli. + + --- On After "Boarded" event. Downed pilot boarded heli. -- @function [parent=#CSAR] OnAfterBoarded -- @param #CSAR self -- @param #string From From state. @@ -590,7 +590,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Woundedgroupname Name of the downed pilot\'s group. -- @param #string Description Descriptive name of the group. - --- On After "Returning" event. Heli can return home with downed pilot(s). + --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning -- @param #CSAR self -- @param #string From From state. @@ -598,8 +598,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. -- @param #string Woundedgroupname Name of the downed pilot\'s group. - - --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. + + --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued -- @param #CSAR self -- @param #string From From state. @@ -608,7 +608,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. - + --- On After "KIA" event. Pilot is dead. -- @function [parent=#CSAR] OnAfterKIA -- @param #CSAR self @@ -616,7 +616,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Pilotname Name of the pilot KIA. - + --- FSM Function OnAfterLoad. -- @function [parent=#CSAR] OnAfterLoad -- @param #CSAR self @@ -625,7 +625,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @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 "CSAR__Persist.csv". - + --- FSM Function OnAfterSave. -- @function [parent=#CSAR] OnAfterSave -- @param #CSAR self @@ -634,7 +634,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string path (Optional) Path where the file is saved. 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 saving. Default is "CSAR__Persist.csv". - + return self end @@ -656,7 +656,7 @@ end -- @return #CSAR self. 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 local DownedPilot = {} -- #CSAR.DownedPilot DownedPilot.desc = Description or "" @@ -672,7 +672,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 @@ -688,14 +688,14 @@ end --- (Internal) Count pilots on board. -- @param #CSAR self -- @param #string _heliName --- @return #number count +-- @return #number count function CSAR:_PilotsOnboard(_heliName) self:T(self.lid .. " _PilotsOnboard") - local count = 0 + local count = 0 if self.inTransitGroups[_heliName] then - for _, _group in pairs(self.inTransitGroups[_heliName]) do - count = count + 1 - end + for _, _group in pairs(self.inTransitGroups[_heliName]) do + count = count + 1 + end end return count end @@ -705,15 +705,15 @@ end -- @param #string _unitname Name of unit. -- @return #boolean Outcome function CSAR:_DoubleEjection(_unitname) - if self.lastCrash[_unitname] then - local _time = self.lastCrash[_unitname] - if timer.getTime() - _time < 10 then - self:E(self.lid.."Caught double ejection!") - return true - end + if self.lastCrash[_unitname] then + local _time = self.lastCrash[_unitname] + if timer.getTime() - _time < 10 then + self:E(self.lid.."Caught double ejection!") + return true end - self.lastCrash[_unitname] = timer.getTime() - return false + end + self.lastCrash[_unitname] = timer.getTime() + return false end --- (User) Add a PLAYERTASK - FSM events will check success @@ -744,8 +744,8 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet) for i=1,10 do math.random(i,10000) end - if point:IsSurfaceTypeWater() or wetfeet then - point.y = 0 + if point:IsSurfaceTypeWater() or wetfeet then + point.y = 0 end local template = self.template if self.usewetfeet and wetfeet then @@ -770,29 +770,29 @@ end function CSAR:_AddSpecialOptions(group) self:T(self.lid.." _AddSpecialOptions") self:T({group}) - + local immortalcrew = self.immortalcrew local invisiblecrew = self.invisiblecrew if immortalcrew then local _setImmortal = { - id = 'SetImmortal', - params = { - value = true - } + id = 'SetImmortal', + params = { + value = true + } } group:SetCommand(_setImmortal) end if invisiblecrew then local _setInvisible = { - id = 'SetInvisible', - params = { - value = true - } + id = 'SetInvisible', + params = { + value = true + } } - group:SetCommand(_setInvisible) + group:SetCommand(_setInvisible) end - + group:OptionAlarmStateGreen() group:OptionROEHoldFire() return self @@ -807,7 +807,7 @@ end -- @param #string _unitName Unitname -- @param #string _playerName Playername -- @param #number _freq Frequency --- @param #boolean noMessage +-- @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 @@ -818,31 +818,31 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local template = self.template local wetfeet = false - + local surface = _point:GetSurfaceType() if surface == land.SurfaceType.WATER then wetfeet = true end - + if not _freq then _freq = self:_GenerateADFFrequency() if not _freq then _freq = 333000 end --noob catch - end - + end + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq,wetfeet) - + local _typeName = _typeName or "Pilot" - + if not noMessage then if _freq ~= 0 then --shagrat different CASEVAC msg - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) - else - self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) + else + self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) + end end - end - + local BeaconName - + if _playerName then BeaconName = _playerName..math.random(1,10000) elseif _unitName then @@ -850,37 +850,37 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla 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 + + if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName) end - + self:_AddSpecialOptions(_spawnedGroup) local _text = _description if not forcedesc then if _playerName ~= nil then - if _freq ~= 0 then --shagrat - _text = "Pilot " .. _playerName - else - _text = "TIC - " .. _playerName - end + if _freq ~= 0 then --shagrat + _text = "Pilot " .. _playerName + else + _text = "TIC - " .. _playerName + end elseif _unitName ~= nil then - if _freq ~= 0 then --shagrat - _text = "AI Pilot of " .. _unitName - else - _text = "TIC - " .. _unitName + if _freq ~= 0 then --shagrat + _text = "AI Pilot of " .. _unitName + else + _text = "TIC - " .. _unitName + end end end - end self:T({_spawnedGroup, _alias}) - + local _GroupName = _spawnedGroup:GetName() or _alias 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. - + return _spawnedGroup, _alias end @@ -897,7 +897,7 @@ end function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() - + local _triggerZone = nil if type(_zone) == "string" then _triggerZone = ZONE:New(_zone) -- trigger to use as reference position @@ -906,16 +906,16 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _triggerZone = _zone -- is already a zone end end - + if _triggerZone == nil then self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) return end - + local _description = _description or "PoW" local unitname = unitname or "Old Rusty" local typename = typename or "Phantom II" - + local pos = {} if _randomPoint then local _pos = _triggerZone:GetRandomPointVec3() @@ -923,7 +923,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ else pos = _triggerZone:GetCoordinate() end - + local _country = 0 if _coalition == coalition.side.BLUE then _country = self.countryblue @@ -932,9 +932,9 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ else _country = self.countryneutral end - + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc) - + return self end @@ -949,7 +949,7 @@ end -- @param #string Typename (optional) Type of plane. -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: --- +-- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) @@ -968,14 +968,14 @@ end -- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitname, typename, forcedesc) --shagrat added internal Function _SpawnCASEVAC self:T(self.lid .. " _SpawnCASEVAC") - + local _description = _description or "CASEVAC" local unitname = unitname or "CASEVAC" local typename = typename or "Ground Commander" - + local pos = {} pos = _Point - + local _country = 0 if _coalition == coalition.side.BLUE then _country = self.countryblue @@ -986,7 +986,7 @@ function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitn end --shagrat set frequency to 0 as "flag" for no beacon self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, 0, _nomessage, _description, forcedesc) - + return self end @@ -1000,10 +1000,10 @@ end -- @param #string Typename (optional) Type of plane. -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: --- +-- -- -- Create casualty "CASEVAC" at coordinate Core.Point#COORDINATE for the blue coalition. -- my_csar:SpawnCASEVAC( coordinate, coalition.side.BLUE ) -function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) +function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) return self end --shagrat end added CASEVAC @@ -1013,57 +1013,57 @@ end --shagrat end added CASEVAC function CSAR:_EventHandler(EventData) self:T(self.lid .. " _EventHandler") self:T({Event = EventData.id}) - + local _event = EventData -- Core.Event#EVENTDATA - - -- no Player + + -- no Player if self.enableForAI == false and _event.IniPlayerName == nil then - return self - end - - -- no event + return self + end + + -- no event if _event == nil or _event.initiator == nil then return self - - -- take off + + -- take off elseif _event.id == EVENTS.Takeoff then -- taken off self:T(self.lid .. " Event unit - Takeoff") - + local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return self --ignore! + return self --ignore! end - + if _event.IniGroupName then - self.takenOff[_event.IniUnitName] = true + self.takenOff[_event.IniUnitName] = true end - + return self - - -- player enter unit + + -- player enter unit elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit self:T(self.lid .. " Event unit - Player Enter") - + local _coalition = _event.IniCoalition self:T("Coalition = "..UTILS.GetCoalitionName(_coalition)) if _coalition ~= self.coalition then - return self --ignore! + return self --ignore! end - + if _event.IniPlayerName then - self.takenOff[_event.IniPlayerName] = nil + self.takenOff[_event.IniPlayerName] = nil end - + -- jumped into flying plane? self:T("Taken Off: "..tostring(_event.IniUnit:InAir(true))) - + if _event.IniUnit:InAir(true) then self.takenOff[_event.IniPlayerName] = true end - + local _unit = _event.IniUnit local _group = _event.IniGroup - + local function IsBronco(Group) local grp = Group -- Wrapper.Group#GROUP local typename = grp:GetTypeName() @@ -1071,81 +1071,81 @@ function CSAR:_EventHandler(EventData) if typename == "Bronco-OV-10A" then return true end return false end - + if _unit:IsHelicopter() or _group:IsHelicopter() or IsBronco(_group) then self:_AddMedevacMenuItem() - end - - return self - - elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then - -- Pilot dead - - self:T(self.lid .. " Event unit - Pilot Dead") - - local _unit = _event.IniUnit - local _unitname = _event.IniUnitName - local _group = _event.IniGroup - - if _unit == nil then - return self -- error! - end - - local _coalition = _event.IniCoalition - if _coalition ~= self.coalition then - return self --ignore! - end - - -- Catch multiple events here? - if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then - if self:_DoubleEjection(_unitname) then - return self - end + end - else - self:T(self.lid .. " Pilot has not taken off, ignore") - end - - return self - - elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then - if _event.id == EVENTS.PilotDead and self.csarOncrash == false then - return self - end - self:T(self.lid .. " Event unit - Pilot Ejected") - - local _unit = _event.IniUnit - local _unitname = _event.IniUnitName - local _group = _event.IniGroup - - self:T({_unit.UnitName, _unitname, _group.GroupName}) - - if _unit == nil then - self:T("Unit NIL!") - return self -- error! - end - - --local _coalition = _unit:GetCoalition() -- nil now for some reason - local _coalition = _group:GetCoalition() - if _coalition ~= self.coalition then - self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition)) - return self --ignore! - end - - - self:T("Airborne: "..tostring(_group:IsAirborne())) - self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName])) - - if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then - self:T(self.lid .. " Pilot has not taken off, ignore") - -- return self -- give up, pilot hasnt taken off - end - + return self + + elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then + -- Pilot dead + + self:T(self.lid .. " Event unit - Pilot Dead") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return self -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return self --ignore! + end + + -- Catch multiple events here? + if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then - self:T("Double Ejection!") return self end + else + self:T(self.lid .. " Pilot has not taken off, ignore") + end + + return self + + elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then + if _event.id == EVENTS.PilotDead and self.csarOncrash == false then + return self + end + self:T(self.lid .. " Event unit - Pilot Ejected") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + self:T({_unit.UnitName, _unitname, _group.GroupName}) + + if _unit == nil then + self:T("Unit NIL!") + return self -- error! + end + + --local _coalition = _unit:GetCoalition() -- nil now for some reason + local _coalition = _group:GetCoalition() + if _coalition ~= self.coalition then + self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition)) + return self --ignore! + end + + + self:T("Airborne: "..tostring(_group:IsAirborne())) + self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName])) + + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then + self:T(self.lid .. " Pilot has not taken off, ignore") + -- return self -- give up, pilot hasnt taken off + end + + if self:_DoubleEjection(_unitname) then + self:T("Double Ejection!") + return self + end + local initdcscoord = nil local initcoord = nil @@ -1159,105 +1159,105 @@ function CSAR:_EventHandler(EventData) self:T({initdcscoord}) end - -- Remove downed pilot if already exists to replace with new one. - if _event.IniPlayerName then - local PilotTable = self.downedPilots --#CSAR.DownedPilot - local _foundPilot = nil - for _,_pilot in pairs(PilotTable) do - if _pilot.player == _event.IniPlayerName and _pilot.alive == true then - _foundPilot = _pilot - break - end - end - if _foundPilot then - self:T("Downed pilot already exists!") - _foundPilot.group:Destroy(false) - self:_RemoveNameFromDownedPilots(_foundPilot.name) - self:_CheckDownedPilotTable() - end + -- Remove downed pilot if already exists to replace with new one. + if _event.IniPlayerName then + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local _foundPilot = nil + for _,_pilot in pairs(PilotTable) do + if _pilot.player == _event.IniPlayerName and _pilot.alive == true then + _foundPilot = _pilot + break + end end + if _foundPilot then + self:T("Downed pilot already exists!") + _foundPilot.group:Destroy(false) + self:_RemoveNameFromDownedPilots(_foundPilot.name) + self:_CheckDownedPilotTable() + end + end + + -- limit no of pilots in the field. + if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then + self:T("Maxed Downed Pilot!") + return self + end + - -- limit no of pilots in the field. - if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then - self:T("Maxed Downed Pilot!") - return self - end - - -- TODO: Over water check --- EVENTS.LandingAfterEjection NOT triggered by DCS, so handle csarUsePara = true case -- might create dual pilots in edge cases - + local wetfeet = false - + --local surface = _unit:GetCoordinate():GetSurfaceType() local surface = initcoord:GetSurfaceType() - + if surface == land.SurfaceType.WATER then self:T("Wet feet!") wetfeet = true - end - -- all checks passed, get going. + end + -- all checks passed, get going. if self.csarUsePara == false or (self.csarUsePara and wetfeet ) then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") + self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") return self end - + elseif _event.id == EVENTS.Land then - self:T(self.lid .. " Landing") - - if _event.IniUnitName then - self.takenOff[_event.IniUnitName] = nil + self:T(self.lid .. " Landing") + + if _event.IniUnitName then + self.takenOff[_event.IniUnitName] = nil + end + + if self.allowFARPRescue then + + local _unit = _event.IniUnit -- Wrapper.Unit#UNIT + + if _unit == nil then + self:T(self.lid .. " Unit nil on landing") + return self -- error! end - - if self.allowFARPRescue then - - local _unit = _event.IniUnit -- Wrapper.Unit#UNIT - - if _unit == nil then - self:T(self.lid .. " Unit nil on landing") - return self -- error! - end - - --local _coalition = _event.IniCoalition - local _coalition = _event.IniGroup:GetCoalition() - if _coalition ~= self.coalition then - self:T(self.lid .. " Wrong coalition") - return self --ignore! - end - - self.takenOff[_event.IniUnitName] = nil - - local _place = _event.Place -- Wrapper.Airbase#AIRBASE - - if _place == nil then - self:T(self.lid .. " Landing Place Nil") - return self -- error! - end - - -- anyone on board? - if self.inTransitGroups[_event.IniUnitName] == nil then - -- ignore - return self - end - - 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) - else - self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) - end - end - - return self + + --local _coalition = _event.IniCoalition + local _coalition = _event.IniGroup:GetCoalition() + if _coalition ~= self.coalition then + self:T(self.lid .. " Wrong coalition") + return self --ignore! end - + + self.takenOff[_event.IniUnitName] = nil + + local _place = _event.Place -- Wrapper.Airbase#AIRBASE + + if _place == nil then + self:T(self.lid .. " Landing Place Nil") + return self -- error! + end + + -- anyone on board? + if self.inTransitGroups[_event.IniUnitName] == nil then + -- ignore + return self + end + + 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) + else + self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) + end + end + + return self + end + ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location if (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then self:T("LANDING_AFTER_EJECTION") local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' - local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" + local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" local _country = _event.initiator:getCountry() local _coalition = coalition.getCountryCoalition( _country ) self:T("Country = ".._country.." Coalition = ".._coalition) @@ -1265,11 +1265,11 @@ function CSAR:_EventHandler(EventData) local _freq = self:_GenerateADFFrequency() self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. - + Unit.destroy(_event.initiator) -- shagrat remove static Pilot model - end + end end - + return self end @@ -1287,38 +1287,38 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage, _pla local _freqk = _freq / 1000 local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) local _leadername = _leader:GetName() - + 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' - 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) + 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' + 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) + end + else --shagrat CASEVAC msg + local _text = string.format("Pickup Zone at %s.", _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 ) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) + end end - else --shagrat CASEVAC msg - local _text = string.format("Pickup Zone at %s.", _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 ) - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) - end - end end - + for _,_heliName in pairs(self.csarUnits) do self:_CheckWoundedGroupStatus(_heliName, _groupName) end - -- trigger FSM event + -- trigger FSM event self:__PilotDown(2,_downedGroup, _freqk, _groupName, _coordinatesText, _playername) - + return self end @@ -1336,7 +1336,7 @@ function CSAR:_CheckNameInDownedPilots(name) found = true table = _pilot break - end + end end return found, table end @@ -1366,13 +1366,13 @@ end -- @return #CSAR self function CSAR:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) if not ShortCallsign or ShortCallsign == false then - self.ShortCallsign = false + self.ShortCallsign = false else - self.ShortCallsign = true + self.ShortCallsign = true end self.Keepnumber = Keepnumber or false self.CallsignTranslations = CallsignTranslations - return self + return self end --- (Internal) Check if a name is in downed pilot table and remove it. @@ -1405,13 +1405,13 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...not found in list!") return end - + local _woundedGroup = _downedpilot.group - if _woundedGroup ~= nil and _woundedGroup:IsAlive() then + if _woundedGroup ~= nil and _woundedGroup:IsAlive() then local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT - + local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking - + if _heliUnit == nil then self.heliVisibleMessage[_lookupKeyHeli] = nil self.heliCloseMessage[_lookupKeyHeli] = nil @@ -1419,15 +1419,15 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...heliunit nil!") return end - + local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) -- autosmoke if (self.autosmoke == true) and (_distance < self.autosmokedistance) and (_distance ~= -1) then - self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) + self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) end - + if _distance < self.approachdist_near and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then -- we\'re close, reschedule @@ -1437,21 +1437,21 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then -- message once if self.heliVisibleMessage[_lookupKeyHeli] == nil then - local _pilotName = _downedpilot.desc - if self.autosmoke == true then - local dist = self.autosmokedistance / 1000 - local disttext = string.format("%.0fkm",dist) - if _SETTINGS:IsImperial() then - 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) - 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 _pilotName = _downedpilot.desc + if self.autosmoke == true then + local dist = self.autosmokedistance / 1000 + local disttext = string.format("%.0fkm",dist) + if _SETTINGS:IsImperial() then + local dist = UTILS.MetersToNM(self.autosmokedistance) + disttext = string.format("%.0fnm",dist) end - --mark as shown for THIS heli and THIS group - self.heliVisibleMessage[_lookupKeyHeli] = true - 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) + 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) + end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end self.heliCloseMessage[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away @@ -1459,11 +1459,11 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:__Approach(-10,heliname,woundedgroupname) end else - self:T("...Downed Pilot KIA?!") - if not _downedpilot.alive then - --self:__KIA(1,_downedpilot.name) - self:_RemoveNameFromDownedPilots(_downedpilot.name, true) - end + self:T("...Downed Pilot KIA?!") + if not _downedpilot.alive then + --self:__KIA(1,_downedpilot.name) + self:_RemoveNameFromDownedPilots(_downedpilot.name, true) + end end return self end @@ -1477,11 +1477,11 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) -- have we popped smoke already in the last 5 mins local _lastSmoke = self.smokeMarkers[_woundedGroupName] if _lastSmoke == nil or timer.getTime() > _lastSmoke then - - local _smokecolor = self.smokecolor - local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360) ) --shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot - _smokecoord:Smoke(_smokecolor) - self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time + + local _smokecolor = self.smokecolor + local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360) ) --shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot + _smokecoord:Smoke(_smokecolor) + self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time end return self end @@ -1498,43 +1498,43 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam local _heliName = _heliUnit:GetName() local _groups = self.inTransitGroups[_heliName] local _unitsInHelicopter = self:_PilotsOnboard(_heliName) - + -- init table if there is none for this helicopter if not _groups then - self.inTransitGroups[_heliName] = {} - _groups = self.inTransitGroups[_heliName] + self.inTransitGroups[_heliName] = {} + _groups = self.inTransitGroups[_heliName] end - + -- if the heli can\'t pick them up, show a message and return local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] if _maxUnits == nil then _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) - return self + 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) + return self end - + local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) local grouptable = downedgrouptable --#CSAR.DownedPilot self.inTransitGroups[_heliName][_woundedGroupName] = - { + { originalUnit = grouptable.originalUnit, woundedGroup = _woundedGroupName, side = self.coalition, desc = grouptable.desc, player = grouptable.player, - } + } _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) - + self:_UpdateUnitCargoMass(_heliName) - + self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc) - + return self end @@ -1588,136 +1588,136 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG local _woundedLeader = _woundedGroup local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking - + local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot local _pilotName = _pilotable.desc - + local _reset = true - + if (_distance < 500) then - 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) - 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) - end - self.heliCloseMessage[_lookupKeyHeli] = true + 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) + 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) end - self:T(self.lid .. "[Pickup Debug] Checking landed vs Hover for ".._lookupKeyHeli) - -- have we landed close enough? - if not _heliUnit:InAir() then - self:T(self.lid .. "[Pickup Debug] Helo landed: ".._lookupKeyHeli) - if self.pilotRuntoExtractPoint == true then - if (_distance < self.extractDistance) then - local _time = self.landedStatus[_lookupKeyHeli] - self:T(self.lid .. "[Pickup Debug] Check pilot running or arrived ".._lookupKeyHeli) - if _time == nil then - self:T(self.lid .. "[Pickup Debug] Pilot running not arrived yet ".._lookupKeyHeli) - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) - _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) - else - _time = self.landedStatus[_lookupKeyHeli] - 10 - self.landedStatus[_lookupKeyHeli] = _time - end - --if _time <= 0 or _distance < self.loadDistance then - self:T(self.lid .. "[Pickup Debug] Pilot close enough? ".._lookupKeyHeli) - 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) - self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) - return false - else - self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) - self.landedStatus[_lookupKeyHeli] = nil - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return true - end - end + self.heliCloseMessage[_lookupKeyHeli] = true + end + self:T(self.lid .. "[Pickup Debug] Checking landed vs Hover for ".._lookupKeyHeli) + -- have we landed close enough? + if not _heliUnit:InAir() then + self:T(self.lid .. "[Pickup Debug] Helo landed: ".._lookupKeyHeli) + if self.pilotRuntoExtractPoint == true then + if (_distance < self.extractDistance) then + local _time = self.landedStatus[_lookupKeyHeli] + self:T(self.lid .. "[Pickup Debug] Check pilot running or arrived ".._lookupKeyHeli) + if _time == nil then + self:T(self.lid .. "[Pickup Debug] Pilot running not arrived yet ".._lookupKeyHeli) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) + _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) + else + _time = self.landedStatus[_lookupKeyHeli] - 10 + self.landedStatus[_lookupKeyHeli] = _time + end + --if _time <= 0 or _distance < self.loadDistance then + self:T(self.lid .. "[Pickup Debug] Pilot close enough? ".._lookupKeyHeli) + 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) + self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) + return false + else + self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return true end - else - self:T(self.lid .. "[Pickup Debug] Helo landed, pilot NOT set to run to helo ".._lookupKeyHeli) - if (_distance < self.loadDistance) then - 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) - return false - else - self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return true - end end end else - self:T(self.lid .. "[Pickup Debug] Helo hovering".._lookupKeyHeli) - local _unitsInHelicopter = self:_PilotsOnboard(_heliName) - local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] - if _maxUnits == nil then - _maxUnits = self.max_units - end - self:T(self.lid .. "[Pickup Debug] Check capacity and close enough for winching ".._lookupKeyHeli) - if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then - -- DONE - make variable - if _distance < self.rescuehoverdistance then - self:T(self.lid .. "[Pickup Debug] Helo hovering close enough ".._lookupKeyHeli) - --check height! - local leaderheight = _woundedLeader:GetHeight() - if leaderheight < 0 then leaderheight = 0 end - local _height = _heliUnit:GetHeight() - leaderheight - - -- DONE - make variable - if _height <= self.rescuehoverheight then - self:T(self.lid .. "[Pickup Debug] Helo hovering low enough ".._lookupKeyHeli) - local _time = self.hoverStatus[_lookupKeyHeli] - - if _time == nil then - self.hoverStatus[_lookupKeyHeli] = 10 - _time = 10 - else - _time = self.hoverStatus[_lookupKeyHeli] - 10 - self.hoverStatus[_lookupKeyHeli] = _time - end - 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) - 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) - self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) - return false - else - self.hoverStatus[_lookupKeyHeli] = nil - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - self:T(self.lid .. "[Pickup Debug] Pilot picked up ".._lookupKeyHeli) - return true - end - end - _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) - self:T(self.lid .. "[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli) - return false - end - end - + self:T(self.lid .. "[Pickup Debug] Helo landed, pilot NOT set to run to helo ".._lookupKeyHeli) + if (_distance < self.loadDistance) then + 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) + return false + else + self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return true end + end end + else + self:T(self.lid .. "[Pickup Debug] Helo hovering".._lookupKeyHeli) + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + self:T(self.lid .. "[Pickup Debug] Check capacity and close enough for winching ".._lookupKeyHeli) + if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then + -- DONE - make variable + if _distance < self.rescuehoverdistance then + self:T(self.lid .. "[Pickup Debug] Helo hovering close enough ".._lookupKeyHeli) + --check height! + local leaderheight = _woundedLeader:GetHeight() + if leaderheight < 0 then leaderheight = 0 end + local _height = _heliUnit:GetHeight() - leaderheight + + -- DONE - make variable + if _height <= self.rescuehoverheight then + self:T(self.lid .. "[Pickup Debug] Helo hovering low enough ".._lookupKeyHeli) + local _time = self.hoverStatus[_lookupKeyHeli] + + if _time == nil then + self.hoverStatus[_lookupKeyHeli] = 10 + _time = 10 + else + _time = self.hoverStatus[_lookupKeyHeli] - 10 + self.hoverStatus[_lookupKeyHeli] = _time + end + 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) + 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) + self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) + return false + else + self.hoverStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. "[Pickup Debug] Pilot picked up ".._lookupKeyHeli) + return true + end + end + _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) + self:T(self.lid .. "[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli) + return false + end + end + + end + end end - + if _reset then - self.hoverStatus[_lookupKeyHeli] = nil + self.hoverStatus[_lookupKeyHeli] = nil end - + if _distance < 500 then return true else @@ -1738,14 +1738,14 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule) local _woundedGroupName = groupname if (_heliUnit == nil) then - --helicopter crashed? - self.inTransitGroups[heliname] = nil - return + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return end if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then - -- Groups already rescued - return + -- Groups already rescued + return end local _dist = self:_GetClosestMASH(_heliUnit) @@ -1754,9 +1754,9 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule) self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance can not be determined!") return end - + 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 self:T(self.lid.."[Drop off debug] Distance ok, door check") if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then @@ -1771,8 +1771,8 @@ 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:__Returning(5,heliname,_woundedGroupName, isairport) + self:ScheduleOnce(5,self._ScheduledSARFlight,self,heliname,groupname, isairport, noreschedule) end return self end @@ -1784,22 +1784,22 @@ function CSAR:_RescuePilots(_heliUnit) self:T(self.lid .. " _RescuePilots") local _heliName = _heliUnit:GetName() local _rescuedGroups = self.inTransitGroups[_heliName] - + if _rescuedGroups == nil then - -- Groups already rescued - return + -- Groups already rescued + return end 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) - + self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) - + self:_UpdateUnitCargoMass(_heliName) - + -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) return self @@ -1861,9 +1861,9 @@ function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit) if self.coordtype == 0 then -- Lat/Long DMTM _coordinatesText = _coordinate:ToStringLLDDM() elseif self.coordtype == 1 then -- Lat/Long DMS - _coordinatesText = _coordinate:ToStringLLDMS() + _coordinatesText = _coordinate:ToStringLLDMS() elseif self.coordtype == 2 then -- MGRS - _coordinatesText = _coordinate:ToStringMGRS() + _coordinatesText = _coordinate:ToStringMGRS() else -- Bullseye Metric --(medevac.coordtype == 4 or 3) _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end @@ -1896,15 +1896,15 @@ end -- @param #string _unitName Unit to display to function CSAR:_DisplayActiveSAR(_unitName) self:T(self.lid .. " _DisplayActiveSAR") - local _msg = "Active MEDEVAC/SAR:" + local _msg = "Active MEDEVAC/SAR:" local _heli = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT if _heli == nil then - return + return end - + local _heliSide = self.coalition local _csarList = {} - + local _DownedPilotTable = self.downedPilots self:T({Table=_DownedPilotTable}) for _, _value in pairs(_DownedPilotTable) do @@ -1912,40 +1912,40 @@ function CSAR:_DisplayActiveSAR(_unitName) self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) self:T({Table=_value}) local _woundedGroup = _value.group - if _woundedGroup and _value.alive then - 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 = "" - 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) - 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 + if _woundedGroup and _value.alive then + 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 = "" + 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) + 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 - + local function sortDistance(a, b) - return a.dist < b.dist + return a.dist < b.dist end - + table.sort(_csarList, sortDistance) - + for _, _line in pairs(_csarList) do - _msg = _msg .. "\n" .. _line.msg + _msg = _msg .. "\n" .. _line.msg end - + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2, false, false, true) return self end @@ -1962,30 +1962,30 @@ function CSAR:_GetClosestDownedPilot(_heli) local _distance = 0 local _closestGroupInfo = nil local _heliCoord = _heli:GetCoordinate() or _heli:GetCoordinate() - - if _heliCoord == nil then - self:E("****Error obtaining coordinate!") - return nil - end - - local DownedPilotsTable = self.downedPilots - - for _, _groupInfo in UTILS.spairs(DownedPilotsTable) do - --for _, _groupInfo in pairs(DownedPilotsTable) do - local _woundedName = _groupInfo.name - local _tempWounded = _groupInfo.group - - -- check group exists and not moving to someone else - if _tempWounded then - local _tempCoord = _tempWounded:GetCoordinate() - _distance = self:_GetDistance(_heliCoord, _tempCoord) - if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then - _shortestDistance = _distance - _closestGroup = _tempWounded - _closestGroupInfo = _groupInfo - end + if _heliCoord == nil then + self:E("****Error obtaining coordinate!") + return nil + end + + local DownedPilotsTable = self.downedPilots + + for _, _groupInfo in UTILS.spairs(DownedPilotsTable) do + --for _, _groupInfo in pairs(DownedPilotsTable) do + local _woundedName = _groupInfo.name + local _tempWounded = _groupInfo.group + + -- check group exists and not moving to someone else + if _tempWounded then + local _tempCoord = _tempWounded:GetCoordinate() + _distance = self:_GetDistance(_heliCoord, _tempCoord) + + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + _closestGroup = _tempWounded + _closestGroupInfo = _groupInfo end + end end return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } @@ -1998,35 +1998,35 @@ function CSAR:_SignalFlare(_unitName) self:T(self.lid .. " _SignalFlare") local _heli = self:_GetSARHeli(_unitName) if _heli == nil then - return + return end - + local _closest = self:_GetClosestDownedPilot(_heli) local smokedist = 8000 if self.approachdist_far > smokedist then smokedist = self.approachdist_far end 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 = "" - 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 - 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() - _coord:FlareRed(_clockDir) + + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = "" + 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 - 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() + _coord:FlareRed(_clockDir) else - local _distance = smokedist - local dtext = "" - if _SETTINGS:IsImperial() then - dtext = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) - else - dtext = string.format("%.1fkm",smokedist/1000) - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",dtext), self.messageTime, false, false, true) + local _distance = smokedist + local dtext = "" + if _SETTINGS:IsImperial() then + dtext = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + dtext = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",dtext), self.messageTime, false, false, true) end return self end @@ -2034,7 +2034,7 @@ end --- (Internal) Display info to all SAR groups. -- @param #CSAR self -- @param #string _message Message to display. --- @param #number _side Coalition of message. +-- @param #number _side Coalition of message. -- @param #number _messagetime How long to show. -- @param #boolean ToSRS If true or nil, send to SRS TTS -- @param #boolean ToScreen If true or nil, send to Screen @@ -2054,7 +2054,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen) for _, _unitName in pairs(self.csarUnits) do local _unit = self:_GetSARHeli(_unitName) if _unit and not self.suppressmessages then - self:_DisplayMessageToSAR(_unit, _message, _messagetime) + self:_DisplayMessageToSAR(_unit, _message, _messagetime) end end end @@ -2068,30 +2068,30 @@ function CSAR:_ReqIRStrobe( _unitName ) self:T(self.lid .. " _ReqIRStrobe") local _heli = self:_GetSARHeli(_unitName) if _heli == nil then - return + 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) + 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) + 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 @@ -2103,32 +2103,32 @@ function CSAR:_Reqsmoke( _unitName ) self:T(self.lid .. " _Reqsmoke") local _heli = self:_GetSARHeli(_unitName) if _heli == nil then - return + 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 - Popping smoke 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() - local color = self.smokecolor - _coord:Smoke(color) + 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 - Popping smoke 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() + local color = self.smokecolor + _coord: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 Pilots within %s",_distance), self.messageTime, false, false, true) + 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 @@ -2150,36 +2150,36 @@ function CSAR:_GetClosestMASH(_heli) local _distance = 0 local _helicoord = _heli:GetCoordinate() local MashName = nil - + 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" 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 - MashName = _mashUnit:GetName() or "Unknown" - 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 + MashName = _mashUnit:GetName() or "Unknown" + end end end - + if _shortestDistance ~= -1 then - return _shortestDistance, MashName + return _shortestDistance, MashName else - return -1 + return -1 end - + end --- (Internal) Display onboarded rescued pilots. @@ -2187,47 +2187,47 @@ end -- @param #string _unitName Name of the chopper function CSAR:_CheckOnboard(_unitName) self:T(self.lid .. " _CheckOnboard") - local _unit = self:_GetSARHeli(_unitName) - if _unit == nil then - return + local _unit = self:_GetSARHeli(_unitName) + if _unit == nil then + return + end + --list onboard pilots + local _inTransit = self.inTransitGroups[_unitName] + if _inTransit == nil then + self:_DisplayMessageToSAR(_unit, "No Rescued Pilots onboard", self.messageTime, false, false, true) + else + local _text = "Onboard - RTB to FARP/Airfield or MASH: " + for _, _onboard in pairs(self.inTransitGroups[_unitName]) do + _text = _text .. "\n" .. _onboard.desc end - --list onboard pilots - local _inTransit = self.inTransitGroups[_unitName] - if _inTransit == nil then - self:_DisplayMessageToSAR(_unit, "No Rescued Pilots onboard", self.messageTime, false, false, true) - else - local _text = "Onboard - RTB to FARP/Airfield or MASH: " - for _, _onboard in pairs(self.inTransitGroups[_unitName]) do - _text = _text .. "\n" .. _onboard.desc - end - self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2, false, false, true) - end - return self + self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2, false, false, true) + end + return self end --- (Internal) Populate F10 menu for CSAR players. -- @param #CSAR self function CSAR:_AddMedevacMenuItem() self:T(self.lid .. " _AddMedevacMenuItem") - + local coalition = self.coalition local allheligroupset = self.allheligroupset -- Core.Set#SET_GROUP local _allHeliGroups = allheligroupset:GetSetObjects() -- rebuild units table local _UnitList = {} - for _key, _group in pairs (_allHeliGroups) do + for _key, _group in pairs (_allHeliGroups) do local _unit = _group:GetFirstUnitAlive() -- Asume that there is only one unit in the flight for players if _unit then - --self:T("Unitname ".._unit:GetName().." IsAlive "..tostring(_unit:IsAlive()).." IsPlayer "..tostring(_unit:IsPlayer())) - if _unit:IsAlive() and _unit:IsPlayer() then + --self:T("Unitname ".._unit:GetName().." IsAlive "..tostring(_unit:IsAlive()).." IsPlayer "..tostring(_unit:IsPlayer())) + if _unit:IsAlive() and _unit:IsPlayer() then local unitName = _unit:GetName() - _UnitList[unitName] = unitName + _UnitList[unitName] = unitName end -- end isAlive end -- end if _unit end -- end for self.csarUnits = _UnitList - - -- build unit menus + + -- build unit menus for _, _unitName in pairs(self.csarUnits) do local _unit = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT if _unit then @@ -2250,7 +2250,7 @@ function CSAR:_AddMedevacMenuItem() end end end - end + end return self end @@ -2284,7 +2284,7 @@ end -- @param #CSAR self function CSAR:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") - + local FreeVHFFrequencies = {} FreeVHFFrequencies = UTILS.GenerateVHFrequencies() self.FreeVHFFrequencies = FreeVHFFrequencies @@ -2298,8 +2298,8 @@ function CSAR:_GenerateADFFrequency() self:T(self.lid .. " _GenerateADFFrequency") -- get a free freq for a beacon if #self.FreeVHFFrequencies <= 3 then - self.FreeVHFFrequencies = self.UsedVHFFrequencies - self.UsedVHFFrequencies = {} + self.FreeVHFFrequencies = self.UsedVHFFrequencies + self.UsedVHFFrequencies = {} end local _vhf = table.remove(self.FreeVHFFrequencies, math.random(#self.FreeVHFFrequencies)) return _vhf @@ -2312,7 +2312,7 @@ end -- @return #number direction function CSAR:_GetClockDirection(_heli, _group) self:T(self.lid .. " _GetClockDirection") - + local _playerPosition = _heli:GetCoordinate() -- get position of helicopter local _targetpostions = _group:GetCoordinate() -- get position of downed pilot local _heading = _heli:GetHeading() -- heading @@ -2324,12 +2324,12 @@ function CSAR:_GetClockDirection(_heli, _group) if _heading and Angle then clock = 12 --if angle == 0 then angle = 360 end - clock = _heading-Angle + clock = _heading-Angle hours = (clock/30)*-1 clock = 12+hours clock = UTILS.Round(clock,0) if clock > 12 then clock = clock-12 end - end + end return clock end @@ -2340,62 +2340,62 @@ end -- @param #string BeaconName Beacon Name to use -- @return #CSAR self function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName) - 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 - if _current == _freq then - table.insert(self.FreeVHFFrequencies, _freq) - table.remove(self.UsedVHFFrequencies, _i) - end - end - return - end - - if _group:IsAlive() then - local _radioUnit = _group:GetUnit(1) - if _radioUnit then - local name = _radioUnit:GetName() - local Frequency = _freq -- Freq in Hertz - --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)) - trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight + 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 + if _current == _freq then + table.insert(self.FreeVHFFrequencies, _freq) + table.remove(self.UsedVHFFrequencies, _i) end end - - return self + return + end + + if _group:IsAlive() then + local _radioUnit = _group:GetUnit(1) + if _radioUnit then + local name = _radioUnit:GetName() + local Frequency = _freq -- Freq in Hertz + --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)) + trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight + end + end + + return self end --- (Internal) Helper function to (re-)add beacon to downed pilot. -- @param #CSAR self -- @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 - self:T({_pilot.name}) - 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,bname) - else - if frequency > 0 then - trigger.action.stopRadioTransmission(bname) - end + 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 + self:T({_pilot.name}) + 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,bname) + else + if frequency > 0 then + trigger.action.stopRadioTransmission(bname) end end end - return self + end + return self end --- (Internal) Helper function to count active downed pilots. @@ -2417,45 +2417,45 @@ end -- @param #CSAR self -- @return #boolean True or false. function CSAR:_ReachedPilotLimit() - self:T(self.lid .. " _ReachedPilotLimit") - local limit = self.maxdownedpilots - local islimited = self.limitmaxdownedpilots - local count = self:_CountActiveDownedPilots() - if islimited and (count >= limit) then - if self.useFIFOLimitReplacement then - local oldIndex = -1 - local oldDownedPilot = nil - for _index, _downedpilot in pairs(self.downedPilots) do - oldIndex = _index - oldDownedPilot = _downedpilot - break - end - if oldDownedPilot then - oldDownedPilot.group:Destroy(false) - oldDownedPilot.alive = false - self:_CheckDownedPilotTable() - return false - end - end - return true - else + self:T(self.lid .. " _ReachedPilotLimit") + local limit = self.maxdownedpilots + local islimited = self.limitmaxdownedpilots + local count = self:_CountActiveDownedPilots() + if islimited and (count >= limit) then + if self.useFIFOLimitReplacement then + local oldIndex = -1 + local oldDownedPilot = nil + for _index, _downedpilot in pairs(self.downedPilots) do + oldIndex = _index + oldDownedPilot = _downedpilot + break + end + if oldDownedPilot then + oldDownedPilot.group:Destroy(false) + oldDownedPilot.alive = false + self:_CheckDownedPilotTable() return false + end end + return true + else + return false + 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 +--- 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 --- - ------------------------------ +------------------------------ +--- FSM internal Functions --- +------------------------------ --- (Internal) Function called after Start() event. -- @param #CSAR self. @@ -2473,7 +2473,7 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) - + if self.UserSetGroup then self.allheligroupset = self.UserSetGroup elseif self.allowbronco then @@ -2485,12 +2485,12 @@ 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() - + 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 @@ -2506,10 +2506,10 @@ function CSAR:onafterStart(From, Event, To) self:T("Objects in SET: "..self.mash:Count()) end --]] - + if not self.coordinate then local csarhq = self.mash:GetRandom() - if csarhq then + if csarhq then self.coordinate = csarhq:GetCoordinate() end end @@ -2535,16 +2535,16 @@ function CSAR:onafterStart(From, Event, To) self.msrs:SetLabel("CSAR") self.SRSQueue = MSRSQUEUE:New("CSAR") -- Sound.SRS#MSRSQUEUE end - + self:__Status(-10) - + if self.enableLoadSave then local interval = self.saveinterval local filename = self.filename local filepath = self.filepath self:__Save(interval,filepath,filename) end - + return self end @@ -2553,11 +2553,11 @@ end function CSAR:_CheckDownedPilotTable() local pilots = self.downedPilots local npilots = {} - + for _ind,_entry in pairs(pilots) do local _group = _entry.group if _group:IsAlive() then - npilots[_ind] = _entry + npilots[_ind] = _entry else if _entry.alive then self:__KIA(1,_entry.desc) @@ -2577,12 +2577,12 @@ function CSAR:onbeforeStatus(From, Event, To) self:T({From, Event, To}) -- housekeeping self:_AddMedevacMenuItem() - + if not self.BeaconTimer or (self.BeaconTimer and not self.BeaconTimer:IsRunning()) then self.BeaconTimer = TIMER:New(self._RefreshRadioBeacons,self) self.BeaconTimer:Start(2,self.beaconRefresher) end - + self:_CheckDownedPilotTable() for _,_sar in pairs (self.csarUnits) do local PilotTable = self.downedPilots @@ -2593,7 +2593,7 @@ function CSAR:onbeforeStatus(From, Event, To) local timestamp = entry.timestamp or 0 local now = timer.getAbsTime() if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. - self:_CheckWoundedGroupStatus(_sar,name) + self:_CheckWoundedGroupStatus(_sar,name) end end end @@ -2615,14 +2615,14 @@ function CSAR:onafterStatus(From, Event, To) end local PilotsInFieldN = self:_CountActiveDownedPilots() - + local PilotsBoarded = 0 for _, _unitName in pairs(self.inTransitGroups) do for _,_units in pairs(_unitName) do PilotsBoarded = PilotsBoarded + 1 end end - + if self.verbose > 0 then local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) @@ -2692,7 +2692,7 @@ function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) -- 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)) + if (targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2)) or (string.find(task.CSARPilotName,Woundedgroupname)) then if task.Clients:HasUniqueID(playername) then -- success @@ -2706,7 +2706,7 @@ function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) return self end ---- (Internal) Function called before Returning() event. +--- (Internal) Function called before Returning() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -2741,10 +2741,10 @@ function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) local subtype = task:GetSubType() -- right subtype? if Event == subtype and not task:IsDone() then - if task.Clients:HasUniqueID(playername) then - -- success - task:__Success(-1) - end + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end end end ) @@ -2791,7 +2791,7 @@ function CSAR:onbeforeSave(From, Event, To, path, filename) if not self.enableLoadSave then return self end - -- Thanks to @FunkyFranky + -- Thanks to @FunkyFranky -- Check io module is available. if not io then self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") @@ -2815,7 +2815,7 @@ end -- @param #string filename (Optional) File name for saving. Default is Default is "CSAR__Persist.csv". function CSAR:onafterSave(From, Event, To, path, filename) self:T({From, Event, To, path, filename}) - -- Thanks to @FunkyFranky + -- Thanks to @FunkyFranky if not self.enableLoadSave then return self end @@ -2830,7 +2830,7 @@ function CSAR:onafterSave(From, Event, To, path, filename) if lfs then path=self.filepath or lfs.writedir() end - + -- Set file name. filename=filename or self.filename @@ -2838,9 +2838,9 @@ function CSAR:onafterSave(From, Event, To, path, filename) if path~=nil then filename=path.."\\"..filename end - + local pilots = self.downedPilots - + --local data = "LoadedData = {\n" local data = "playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n" local n = 0 @@ -2858,15 +2858,15 @@ function CSAR:onafterSave(From, Event, To, path, filename) local location = group:GetVec3() 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) - + data = data .. txt end end - + _savefile(filename, data) - + -- AutoSave if self.enableLoadSave then local interval = self.saveinterval @@ -2891,19 +2891,19 @@ function CSAR:onbeforeLoad(From, Event, To, path, filename) end --- Function that check if a file exists. local function _fileexists(name) - local f=io.open(name,"r") - if f~=nil then + local f=io.open(name,"r") + if f~=nil then io.close(f) return true else return false end end - + -- Set file name and path filename=filename or self.filename path = path or self.filepath - + -- Check io module is available. if not io then self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") @@ -2933,7 +2933,7 @@ function CSAR:onbeforeLoad(From, Event, To, path, filename) else self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename)) return false - --return self + --return self end end @@ -2957,11 +2957,11 @@ function CSAR:onafterLoad(From, Event, To, path, filename) f:close() return data end - + -- Set file name and path filename=filename or self.filename path = path or self.filepath - + -- Set path or default. if lfs then path=path or lfs.writedir() @@ -2976,39 +2976,39 @@ function CSAR:onafterLoad(From, Event, To, path, filename) local text=string.format("Loading CSAR state from file %s", filename) MESSAGE:New(text,10):ToAllIf(self.Debug) self:I(self.lid..text) - + local file=assert(io.open(filename, "rb")) - + local loadeddata = {} for line in file:lines() do - loadeddata[#loadeddata+1] = line + loadeddata[#loadeddata+1] = line end file:close() - + -- remove header table.remove(loadeddata, 1) - + for _id,_entry in pairs (loadeddata) do local dataset = UTILS.Split(_entry,",") -- 1=playerName,2=x,3=y,4=z,5=coalition,6=country,7=description,8=typeName,9=unitName,10=freq\n local playerName = dataset[1] - + local vec3 = {} vec3.x = tonumber(dataset[2]) vec3.y = tonumber(dataset[3]) vec3.z = tonumber(dataset[4]) local point = COORDINATE:NewFromVec3(vec3) - + local coalition = tonumber(dataset[5]) local country = tonumber(dataset[6]) local description = dataset[7] local typeName = dataset[8] local unitName = dataset[9] local freq = tonumber(dataset[10]) - - self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil) + + self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil) end - + return self end From 4b1888a34d4620655fc39deeba46ac3bc88bb7a7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Jul 2025 10:01:23 +0200 Subject: [PATCH 23/73] CSAR - Allow also the initial down message to be suppressed --- Moose Development/Moose/Ops/CSAR.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ce21f7121..56ec21ef5 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 May 2025 +-- Last Update July 2025 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -1199,7 +1199,7 @@ function CSAR:_EventHandler(EventData) -- all checks passed, get going. if self.csarUsePara == false or (self.csarUsePara and wetfeet ) then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") + self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, self.suppressmessages, "none") return self end @@ -1264,8 +1264,8 @@ function CSAR:_EventHandler(EventData) 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:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. - + 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 end end @@ -3005,8 +3005,8 @@ function CSAR:onafterLoad(From, Event, To, path, filename) local typeName = dataset[8] local unitName = dataset[9] local freq = tonumber(dataset[10]) - - self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil) + + self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, false, description, nil) end return self From f094716b73771caf0ad6525a33e91b7176a5b79a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Jul 2025 12:04:41 +0200 Subject: [PATCH 24/73] CTLD - Added option for Vehicle Formation when going to a MOVE zone. --- Moose Development/Moose/Ops/CTLD.lua | 35 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 51896bab7..c4f907190 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -867,6 +867,7 @@ do -- 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. it will be a 1 step solution. +-- my_ctld.VehicleMoveFormation = AI.Task.VehicleFormation.VEE -- When a group moves to a MOVE zone, then it takes this formation. Can be a table of formations, which are then randomly chosen. Defaults to "Vee". -- -- ## 2.1 CH-47 Chinook support -- @@ -1294,6 +1295,7 @@ CTLD = { LoadedGroupsTable = {}, keeploadtable = true, allowCATransport = false, + VehicleMoveFormation = AI.Task.VehicleFormation.VEE, } ------------------------------ @@ -1414,7 +1416,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.3.36" +CTLD.version="1.3.37" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1554,6 +1556,8 @@ function CTLD:New(Coalition, Prefixes, Alias) self.movetroopsdistance = 5000 self.troopdropzoneradius = 100 + self.VehicleMoveFormation = AI.Task.VehicleFormation.VEE + -- added support Hercules Mod self.enableHercules = false -- deprecated self.enableFixedWing = false @@ -4197,6 +4201,17 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult return self end +--- (Internal) Function to get a vehicle formation for a moving group +-- @param #CTLD self +-- @return #string Formation +function CTLD:_GetVehicleFormation() + local VehicleMoveFormation = self.VehicleMoveFormation or AI.Task.VehicleFormation.VEE + if type(self.VehicleMoveFormation)=="table" then + VehicleMoveFormation = self.VehicleMoveFormation[math.random(1,#self.VehicleMoveFormation)] + end + return VehicleMoveFormation +end + --- (Internal) Function to move group to WP zone. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The Group to move. @@ -4211,18 +4226,20 @@ function CTLD:_MoveGroupToZone(Group) -- yes, we can ;) local groupname = Group:GetName() local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE - local coordinate = zonecoord:GetVec2() + local formation = self:_GetVehicleFormation() + --local coordinate = zonecoord:GetVec2() Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) - Group:OptionROEOpenFirePossible() - Group:RouteToVec2(coordinate,5) + Group:OptionROEOpenFireWeaponFree() + Group:RouteGroundTo(zonecoord,5,formation) end return self 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. @@ -7134,6 +7151,16 @@ end local filepath = self.filepath self:__Save(interval,filepath,filename) end + + if type(self.VehicleMoveFormation) == "table" then + local Formations = {} + for _,_formation in pairs(self.VehicleMoveFormation) do + table.insert(Formations,_formation) + end + self.VehicleMoveFormation = nil + self.VehicleMoveFormation = Formations + end + return self end From 21a7023b7beb40cca9cfc72bd61d3c0497cb06f9 Mon Sep 17 00:00:00 2001 From: smiki Date: Tue, 29 Jul 2025 12:50:19 +0200 Subject: [PATCH 25/73] Removed getValueResourceByKey `UTILS.ShowPicture` and `UTILS.SetMissionBriefing` to use full file paths --- Moose Development/Moose/Utilities/Utils.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 11dcdcbb1..853575678 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4603,11 +4603,11 @@ end -- @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 --- @param #number HorizontalAlign Horizontal alignment of the picture, defaults to 1 (left), can be 0 (center) or 2 (right) --- @param #number VerticalAlign Vertical alignment of the picture, defaults to 1 (top), can be 0 (center) or 2 (bottom) +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom -- @param #number Size Size of the picture in percent, defaults to 100 --- @param #number SizeUnits Size units, defaults to 0 (percent), can be 1 (pixels) -function UTILS.ShowPicture(FileName, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPicture(FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) ClearView = ClearView or false StartDelay = StartDelay or 0 HorizontalAlign = HorizontalAlign or 1 @@ -4617,7 +4617,7 @@ function UTILS.ShowPicture(FileName, Duration, ClearView, StartDelay, Horizontal if ClearView then ClearView = "true" else ClearView = "false" end - net.dostring_in("mission", string.format("a_out_picture(getValueResourceByKey(\"%s\"), %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", FileName, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) + net.dostring_in("mission", string.format("a_out_picture(\"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) end --- Load a mission file. This will replace the current mission with the one given carrying along the online clients. @@ -4629,13 +4629,13 @@ end --- Set the mission briefing for a coalition. -- @param #number Coalition Briefing coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL -- @param #string Text Briefing text, can contain newlines, will be converted formatted properly for DCS --- @param #string Picture Picture filename, can be a file in the DEFAULT folder inside the .miz +-- @param #string Picture Picture file path, can be a file in the DEFAULT folder inside the .miz function UTILS.SetMissionBriefing(Coalition, Text, Picture) Text = Text or "" Text = Text:gsub("\n", "\\n") Picture = Picture or "" local coalName = string.lower(UTILS.GetCoalitionName(Coalition)) - net.dostring_in("mission", string.format("a_set_briefing(\"%s\", getValueResourceByKey(\"%s\"), \"%s\")", coalName, Picture, Text)) + net.dostring_in("mission", string.format("a_set_briefing(\"%s\", \"%s\", \"%s\")", coalName, Picture, Text)) end --- Show a helper gate at a DCS#Vec3 position From f735f1eb53c120ab168f359ff671f6c8ebf30019 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Jul 2025 17:38:31 +0200 Subject: [PATCH 26/73] CTLD - correct ground speed for routing --- Moose Development/Moose/Ops/CTLD.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index c4f907190..7b46d1439 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4231,8 +4231,8 @@ function CTLD:_MoveGroupToZone(Group) Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) - Group:OptionROEOpenFireWeaponFree() - Group:RouteGroundTo(zonecoord,5,formation) + Group:OptionROEOpenFire() + Group:RouteGroundTo(zonecoord,25,formation) end return self end From 324f4944b4e240db631bd89413a884a89f3be4bb Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 31 Jul 2025 08:54:16 +0200 Subject: [PATCH 27/73] [ADDED] `UTILS.ShowPicture` For all, coalition, country, group and unit [ADDED] `UTILS.ShowHelperGateForUnit` --- Moose Development/Moose/Utilities/Utils.lua | 108 +++++++++++++++++++- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 853575678..d5a4270f7 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4598,7 +4598,7 @@ function UTILS.DoStringIn(State,DoString) return net.dostring_in(State,DoString) end ---- Show a picture on the screen +--- Show a picture on the screen to all -- @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 @@ -4607,7 +4607,7 @@ end -- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom -- @param #number Size Size of the picture in percent, defaults to 100 -- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size -function UTILS.ShowPicture(FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) +function UTILS.ShowPictureToAll(FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) ClearView = ClearView or false StartDelay = StartDelay or 0 HorizontalAlign = HorizontalAlign or 1 @@ -4620,6 +4620,100 @@ function UTILS.ShowPicture(FilePath, Duration, ClearView, StartDelay, Horizontal net.dostring_in("mission", string.format("a_out_picture(\"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) 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 #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 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToCoalition(Coalition, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + local coalName = string.lower(UTILS.GetCoalitionName(Coalition)) + + net.dostring_in("mission", string.format("a_out_picture_s(\"%s\", \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", coalName, FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +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 #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 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToCountry(Country, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + net.dostring_in("mission", string.format("a_out_picture_c(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Country, FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +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 #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 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToGroup(Group, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + net.dostring_in("mission", string.format("a_out_picture_g(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Group:GetID(), FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +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 #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 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToGroup(Unit, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + net.dostring_in("mission", string.format("a_out_picture_u(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Unit:GetID(), FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +end + --- Load a mission file. This will replace the current mission with the one given carrying along the online clients. -- @param #string FileName Mission filename function UTILS.LoadMission(FileName) @@ -4640,11 +4734,19 @@ end --- Show a helper gate at a DCS#Vec3 position -- @param DCS#Vec3 pos The position --- @param number heading Heading in degrees, can be 0..359 degrees +-- @param #number heading Heading in degrees, can be 0..359 degrees function UTILS.ShowHelperGate(pos, heading) net.dostring_in("mission",string.format("a_show_helper_gate(%s, %s, %s, %f)", pos.x, pos.y, pos.z, math.rad(heading))) end +--- Show a helper gate for a unit. +-- @param Wrapper.Unit#UNIT Unit The unit to show the gate for +-- @param #number Flag Helper gate flag +function UTILS.ShowHelperGateForUnit(Unit, Flag) + net.dostring_in("mission",string.format("a_show_route_gates_for_unit(%d, \"%d\")", Unit:GetID(), Flag)) +end + + --- Shell a zone, zone must ME created -- @param #string name The name of the ME created zone -- @param #number power Equals kg of TNT, e.g. 75 From 9b217e1c9720993474b0fceb4b0898abe0a14398 Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 31 Jul 2025 08:57:20 +0200 Subject: [PATCH 28/73] [ADDED] `UTILS.ShowPicture` For all, coalition, country, group and unit [ADDED] `UTILS.ShowHelperGateForUnit` --- 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 d5a4270f7..afc3c3f7b 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4701,7 +4701,7 @@ end -- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom -- @param #number Size Size of the picture in percent, defaults to 100 -- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size -function UTILS.ShowPictureToGroup(Unit, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) +function UTILS.ShowPictureToUnit(Unit, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) ClearView = ClearView or false StartDelay = StartDelay or 0 HorizontalAlign = HorizontalAlign or 1 From 933000ffc7ac6c996a6ff0ad0b4827a33b6dfeb3 Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 31 Jul 2025 09:06:19 +0200 Subject: [PATCH 29/73] [ADDED] `UNIT:SetCarrierIlluminationMode` --- Moose Development/Moose/Utilities/Utils.lua | 6 ++++++ Moose Development/Moose/Wrapper/Unit.lua | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index afc3c3f7b..a9a76d535 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4746,6 +4746,12 @@ function UTILS.ShowHelperGateForUnit(Unit, Flag) net.dostring_in("mission",string.format("a_show_route_gates_for_unit(%d, \"%d\")", Unit:GetID(), Flag)) end +--- Set the carrier illumination mode. -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +-- @param #number UnitID Carrier unit ID ( UNIT:GetID() ) +-- @param #number Mode Illumination mode, can be -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +function UTILS.SetCarrierIlluminationMode(UnitID, Mode) + net.dostring_in("mission",string.format("a_set_carrier_illumination_mode(%d, %d)", UnitID, Mode)) +end --- Shell a zone, zone must ME created -- @param #string name The name of the ME created zone diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 20ce4837d..d005ebca2 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1931,3 +1931,10 @@ end function UNIT:SetLife(Percent) net.dostring_in("mission",string.format("a_unit_set_life_percentage(%d, %f)", self:GetID(), Percent)) end + +--- Set the carrier illumination mode. -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +-- @param #UNIT self +-- @param #number Mode Illumination mode, can be -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +function UNIT:SetCarrierIlluminationMode(Mode) + UTILS.SetCarrierIlluminationMode(self:GetID(), Mode) +end From b318e8ae13ae180b174e5431de8084dff4daa936 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 31 Jul 2025 09:47:54 +0200 Subject: [PATCH 30/73] #AIRBOSS - Added `:SetCarrierIllumination(Mode)` --- Moose Development/Moose/Ops/Airboss.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 7f2a4b6d8..c24f6720c 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -2412,6 +2412,16 @@ end -- USER API Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Set the carrier illumination mode. +-- @param #AIRBOSS self +-- @param #number Mode Options are: -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +-- @return #AIRBOSS self +function AIRBOSS:SetCarrierIllumination(Mode) + self.carrier:SetCarrierIlluminationMode(Mode) + return self +end + + --- Set welcome messages for players. -- @param #AIRBOSS self -- @param #boolean Switch If true, display welcome message to player. From 13fa8f373e37f6445952c6da4661b58873ff06b1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 1 Aug 2025 14:02:57 +0200 Subject: [PATCH 31/73] #MANTIS - added radar entry for Dog Ear and Nike --- Moose Development/Moose/Functional/Mantis.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 4b761e99f..cd3ee9cae 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -112,6 +112,7 @@ -- * 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, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2 +-- * Other Mods: Nike -- -- * 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" @@ -275,7 +276,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", - version = "0.9.32", + version = "0.9.33", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -392,7 +393,9 @@ MANTIS.SamData = { ["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" }, ["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" }, + ["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" }, } --- SAM data HDS From eb15fadcfeaca51d8c76064ed128b21bf292947e Mon Sep 17 00:00:00 2001 From: leka1986 Date: Sat, 2 Aug 2025 17:40:55 +0200 Subject: [PATCH 32/73] Added SetPartlyInside. if used, it the :Trigger will trigger as soon as any of the group units enteres the zone even if they are far apart. --- Moose Development/Moose/Core/Set.lua | 25 +++++++++++++++++++++++-- Moose Development/Moose/Core/Zone.lua | 20 +++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 7f863abf2..06981e95f 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -6691,6 +6691,8 @@ do -- SET_ZONE -- -- -- Stop watching after 1 hour -- zoneset:__TriggerStop(3600) + -- -- Call :SetPartlyInside() on any zone (or SET_ZONE) if you want GROUPs to count as inside when any of their units enters even if they are far apart. + -- -- Make sure to call :SetPartlyInside() before :Trigger()!. function SET_ZONE:Trigger(Objects) --self:I("Added Set_Zone Trigger") self:AddTransition("*","TriggerStart","TriggerRunning") @@ -6741,6 +6743,20 @@ do -- SET_ZONE -- @param Core.Zone#ZONE_BASE Zone The zone left. end + --- Toggle “partly-inside” handling for every zone in the set when those zones are used with :Trigger(). + -- * Call with no argument or **true** → enable for all. + -- * Call with **false** → disable again (handy if it was enabled before). + -- @param #SET_ZONE self + -- @return #SET_ZONE self + function SET_ZONE:SetPartlyInside(state) + for _,Zone in pairs(self.Set) do + if Zone.SetPartlyInside then + Zone:SetPartlyInside(state) + end + end + return self + end + --- (Internal) Check the assigned objects for being in/out of the zone -- @param #SET_ZONE self -- @param #boolean fromstart If true, do the init of the objects @@ -6776,8 +6792,13 @@ do -- SET_ZONE -- has not been tagged previously - wasn't in set! obj.TriggerInZone[_zone.ZoneName] = false end - -- is obj in zone? - local inzone = _zone:IsCoordinateInZone(obj:GetCoordinate()) + -- is obj in this zone? + local inzone + if _zone.PartlyInside and obj.ClassName == "GROUP" then + inzone = obj:IsAnyInZone(_zone) -- TRUE as soon as any unit is inside + else + inzone = _zone:IsCoordinateInZone(obj:GetCoordinate()) -- original centroid test + end --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) if inzone and not obj.TriggerInZone[_zone.ZoneName] then -- wasn't in zone before diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 6ed990101..6b0ac3d32 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -70,6 +70,7 @@ -- @field #table Table of any trigger zone properties from the ME. The key is the Name of the property, and the value is the property's Value. -- @field #number Surface Type of surface. Only determined at the center of the zone! -- @field #number Checktime Check every Checktime seconds, used for ZONE:Trigger() +-- @field #boolean PartlyInside When called, a GROUP is considered inside as soon as any of its units enters the zone even if they are far apart. -- @extends Core.Fsm#FSM @@ -612,6 +613,8 @@ end -- -- -- Stop watching the zone after 1 hour -- triggerzone:__TriggerStop(3600) +-- -- Call :SetPartlyInside() if you use SET_GROUP to count as inside when any of their units enters even when they are far apart. +-- -- Make sure to call :SetPartlyInside() before :Trigger()! function ZONE_BASE:Trigger(Objects) --self:I("Added Zone Trigger") self:SetStartState("TriggerStopped") @@ -680,6 +683,16 @@ function ZONE_BASE:Trigger(Objects) end + --- Toggle “partly-inside” handling for this zone. To be used before :Trigger(). + -- * Default:* flag is **false** until you call the method. + -- * Call with no argument or with **true** → enable. + -- * Call with **false** → disable again (handy if it was enabled before). + -- @param #ZONE_BASE self + -- @return #ZONE_BASE self + function ZONE_BASE:SetPartlyInside(state) + self.PartlyInside = state or not ( state == false ) + return self + end --- (Internal) Check the assigned objects for being in/out of the zone -- @param #ZONE_BASE self -- @param #boolean fromstart If true, do the init of the objects @@ -718,7 +731,12 @@ function ZONE_BASE:_TriggerCheck(fromstart) obj.TriggerInZone[self.ZoneName] = false end -- is obj in zone? - local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) + local inzone + if self.PartlyInside and obj.ClassName == "GROUP" then + inzone = obj:IsAnyInZone(self) -- TRUE if any unit is inside + else + inzone = self:IsCoordinateInZone(obj:GetCoordinate()) -- original barycentre test + end --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) if inzone and obj.TriggerInZone[self.ZoneName] then -- just count From c8d693c8e7b7bb12fac21b3b2605cd43af866ded Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:10:10 +0200 Subject: [PATCH 33/73] Update Airbase.lua Sinai add'l bases --- Moose Development/Moose/Wrapper/Airbase.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index a24c00cbe..cf96d6064 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -736,15 +736,19 @@ AIRBASE.SouthAtlantic={ -- * AIRBASE.Sinai.Kibrit_Air_Base -- * AIRBASE.Sinai.Kom_Awshim -- * AIRBASE.Sinai.Melez +-- * AIRBASE.Sinai.Mezzeh_Air_Base -- * AIRBASE.Sinai.Nevatim -- * AIRBASE.Sinai.Ovda -- * AIRBASE.Sinai.Palmachim -- * AIRBASE.Sinai.Quwaysina +-- * AIRBASE.Sinai.Rafic_Hariri_Intl +-- * AIRBASE.Sinai.Ramat_David -- * 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.Tabuk -- * AIRBASE.Sinai.Tel_Nof -- * AIRBASE.Sinai.Wadi_Abu_Rish -- * AIRBASE.Sinai.Wadi_al_Jandali @@ -784,15 +788,19 @@ AIRBASE.Sinai = { ["Kibrit_Air_Base"] = "Kibrit Air Base", ["Kom_Awshim"] = "Kom Awshim", ["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", + ["Tabuk"] = "Tabuk", ["Tel_Nof"] = "Tel Nof", ["Wadi_Abu_Rish"] = "Wadi Abu Rish", ["Wadi_al_Jandali"] = "Wadi al Jandali", From 029f7a3f5c3f74617937cf8b042d4a1a436f7e14 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 6 Aug 2025 12:27:28 +0200 Subject: [PATCH 34/73] Update Scoring.lua Better check for Scenery hits where target category is usually nil --- Moose Development/Moose/Functional/Scoring.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index c4a0c8e20..edab15328 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -1025,6 +1025,11 @@ function SCORING:_EventOnHit( Event ) TargetCategory = Event.TgtCategory TargetType = Event.TgtTypeName + -- Scenery hit + if (not TargetCategory) and TargetUNIT ~= nil and TargetUnit:IsInstanceOf("SCENERY") then + TargetCategory = Unit.Category.STRUCTURE + end + TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] TargetUnitCategory = _SCORINGCategory[TargetCategory] TargetUnitType = TargetType From 4fa63986dc4d94f2c97ac1e13877bda28e9e28e0 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 7 Aug 2025 11:16:07 +0200 Subject: [PATCH 35/73] Update Scoring.lua Further changes --- Moose Development/Moose/Functional/Scoring.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index edab15328..c3f4bd4b6 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -985,6 +985,7 @@ function SCORING:_EventOnHit( Event ) local TargetUnitCoalition = nil local TargetUnitCategory = nil local TargetUnitType = nil + local TargetIsScenery = false if Event.IniDCSUnit then @@ -1028,6 +1029,7 @@ function SCORING:_EventOnHit( Event ) -- Scenery hit if (not TargetCategory) and TargetUNIT ~= nil and TargetUnit:IsInstanceOf("SCENERY") then TargetCategory = Unit.Category.STRUCTURE + TargetIsScenery = true end TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] @@ -1122,17 +1124,22 @@ function SCORING:_EventOnHit( Event ) MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - else + elseif TargetIsScenery ~= true then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) + elseif TargetIsScenery == true then + MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object." .. " Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, + MESSAGE.Type.Update ) + :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) + :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) end self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) end else -- A scenery object was hit. - MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.", + MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit nothing special.", MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) From 674c6eec81a5492c6d21a38be32964efc83af116 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 7 Aug 2025 11:30:39 +0200 Subject: [PATCH 36/73] More randomness in functions using random coordinates --- Moose Development/Moose/Core/Point.lua | 4 +++- Moose Development/Moose/Core/Zone.lua | 9 +++++++++ Moose Development/Moose/Shapes/Circle.lua | 12 +++++++++++- Moose Development/Moose/Shapes/Polygon.lua | 1 + Moose Development/Moose/Shapes/Triangle.lua | 5 +++++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 09af3b47e..b17e40f27 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -777,7 +777,9 @@ do -- COORDINATE -- @return DCS#Vec2 Vec2 function COORDINATE:GetRandomVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) - + math.random() + math.random() + math.random() local Theta = 2 * math.pi * math.random() local Radials = math.random() + math.random() if Radials > 1 then diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 6b0ac3d32..104362abb 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1571,6 +1571,10 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) local Vec2 = self:GetVec2() local _inner = inner or 0 local _outer = outer or self:GetRadius() + + math.random() + math.random() + math.random() if surfacetypes and type(surfacetypes)~="table" then surfacetypes={surfacetypes} @@ -2936,6 +2940,11 @@ end function ZONE_POLYGON_BASE:GetRandomVec2() -- make sure we assign weights to the triangles based on their surface area, otherwise -- we'll be more likely to generate random points in smaller triangles + + math.random() + math.random() + math.random() + local weights = {} for _, triangle in pairs(self._Triangles) do weights[triangle] = triangle.SurfaceArea / self.SurfaceArea diff --git a/Moose Development/Moose/Shapes/Circle.lua b/Moose Development/Moose/Shapes/Circle.lua index 39461c522..7bdbb31c1 100644 --- a/Moose Development/Moose/Shapes/Circle.lua +++ b/Moose Development/Moose/Shapes/Circle.lua @@ -72,7 +72,7 @@ end --- Checks if a point is contained within the circle. -- @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 CIRCLE:ContainsPoint(point) if ((point.x - self.CenterVec2.x) ^ 2 + (point.y - self.CenterVec2.y) ^ 2) ^ 0.5 <= self.Radius then return true @@ -226,6 +226,11 @@ end --- Returns a random Vec2 within the circle. -- @return #table The random Vec2 function CIRCLE:GetRandomVec2() + + math.random() + math.random() + math.random() + local angle = math.random() * 2 * math.pi local rx = math.random(0, self.Radius) * math.cos(angle) + self.CenterVec2.x @@ -237,6 +242,11 @@ end --- Returns a random Vec2 on the border of the circle. -- @return #table The random Vec2 function CIRCLE:GetRandomVec2OnBorder() + + math.random() + math.random() + math.random() + local angle = math.random() * 2 * math.pi local rx = self.Radius * math.cos(angle) + self.CenterVec2.x diff --git a/Moose Development/Moose/Shapes/Polygon.lua b/Moose Development/Moose/Shapes/Polygon.lua index d0253fa04..e126a2002 100644 --- a/Moose Development/Moose/Shapes/Polygon.lua +++ b/Moose Development/Moose/Shapes/Polygon.lua @@ -352,6 +352,7 @@ end --- Returns a random Vec2 within the polygon. The Vec2 is weighted by the areas of the triangles that make up the polygon. -- @return #table The random Vec2 function POLYGON:GetRandomVec2() + local weights = {} for _, triangle in pairs(self.Triangles) do weights[triangle] = triangle.SurfaceArea / self.SurfaceArea diff --git a/Moose Development/Moose/Shapes/Triangle.lua b/Moose Development/Moose/Shapes/Triangle.lua index 747407d2e..e9a52e866 100644 --- a/Moose Development/Moose/Shapes/Triangle.lua +++ b/Moose Development/Moose/Shapes/Triangle.lua @@ -73,6 +73,11 @@ end -- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it -- @return #table The random Vec2 function TRIANGLE:GetRandomVec2(points) + + math.random() + math.random() + math.random() + points = points or self.Points local pt = {math.random(), math.random()} table.sort(pt) From 4c97d966a2038904b304dd92075b48b094269507 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 10 Aug 2025 13:20:22 +0200 Subject: [PATCH 37/73] #MSRS - align google voices catalog with new voice types. --- 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 35f15435a3a77e39d9e26a3f1f157f8681c6005d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Aug 2025 09:12:58 +0200 Subject: [PATCH 38/73] #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 4a04d7cce7b178ff4ea48cb480d705a331e2030e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Aug 2025 17:15:43 +0200 Subject: [PATCH 39/73] 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 b9cf1e46afcee679aa09b480d3451864da72a5e3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 14 Aug 2025 17:17:34 +0200 Subject: [PATCH 40/73] xx --- 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 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 From 67cb84455029531efe460e3b2e664c23ae07befb Mon Sep 17 00:00:00 2001 From: smiki Date: Sun, 24 Aug 2025 15:07:27 +0200 Subject: [PATCH 41/73] Validate and Reposition Ground Units algorithm [ADDED] UTILS.ValidateAndRepositionGroundUnits [ADDED] SPAWN:InitValidateAndRepositionGroundUnits [ADDED] CTLD.validateAndRepositionUnits --- Moose Development/Moose/Core/Spawn.lua | 24 +++- Moose Development/Moose/Ops/CTLD.lua | 8 +- Moose Development/Moose/Utilities/Utils.lua | 121 ++++++++++++++++++++ 3 files changed, 151 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index c81aea59d..6e95e285b 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1049,6 +1049,22 @@ function SPAWN:InitSetUnitAbsolutePositions(Positions) return self end + +--- Uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +-- @param #boolean OnOff Enable/disable the feature. +-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units. +-- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger. +-- @return #SPAWN +function SPAWN:InitValidateAndRepositionGroundUnits(OnOff, MaxRadius, Spacing) + self.SpawnValidateAndRepositionGroundUnits = OnOff + self.SpawnValidateAndRepositionGroundUnitsRadius = MaxRadius + self.SpawnValidateAndRepositionGroundUnitsSpacing = Spacing + return self +end + --- This method is rather complicated to understand. But I'll try to explain. -- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. @@ -1829,7 +1845,13 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if self.SpawnHiddenOnMap then SpawnTemplate.hidden=self.SpawnHiddenOnMap end - + + if self.SpawnValidateAndRepositionGroundUnits then + local units = SpawnTemplate.units + local gPos = { x = SpawnTemplate.x, y = SpawnTemplate.y } + UTILS.ValidateAndRepositionGroundUnits(gPos, units, self.SpawnValidateAndRepositionGroundUnitsRadius, self.SpawnValidateAndRepositionGroundUnitsSpacing) + end + -- Set country, coalition and category. SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 7b46d1439..40f2dc8e3 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1564,7 +1564,9 @@ function CTLD:New(Coalition, Prefixes, Alias) 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 - + + self.validateAndRepositionUnits = false -- 280 kph or 150kn eq 77 mps + -- message suppression self.suppressmessages = false @@ -3735,6 +3737,7 @@ function CTLD:_UnloadTroops(Group, Unit) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter],type) @@ -4181,11 +4184,13 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) --:InitRandomizeUnits(true,20,2) :InitDelayOff() + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :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() + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) end @@ -5211,6 +5216,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index a9a76d535..c7543b3a2 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4871,3 +4871,124 @@ function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) return {x=qx, y=qy} end + +--- This function uses Disposition and other fallback logic to find better ground positions for a group of ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +-- @param table Positions A table of DCS#Vec2 or DCS#Vec3, can be a units table from the group template. +-- @param DCS#Vec2 Anchor (Optional) DCS#Vec2 or DCS#Vec3 as anchor point to calculate offset of the units. +-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units. +-- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger. +function UTILS.ValidateAndRepositionGroundUnits(Anchor, Positions, MaxRadius, Spacing) + local units = Positions + Anchor = Anchor or UTILS.GetCenterPoint(units) + local gPos = { x = Anchor.x, y = Anchor.z or Anchor.y } + local maxRadius = 0 + local unitCount = 0 + for _, unit in pairs(units) do + local pos = { x = unit.x, y = unit.z or unit.y } + local dist = UTILS.VecDist2D(pos, gPos) + if dist > maxRadius then + maxRadius = dist + end + unitCount = unitCount + 1 + end + maxRadius = MaxRadius or math.max(maxRadius * 2, 10) + local spacing = Spacing or math.max(maxRadius * 0.05, 5) + if unitCount > 0 and maxRadius > 5 then + local spots = UTILS.GetSimpleZones(UTILS.Vec2toVec3(gPos), maxRadius, spacing, 1000) + if spots and #spots > 0 then + local validSpots = {} + for _, spot in pairs(spots) do -- Disposition sometimes returns points on roads, hence this filter. + if land.getSurfaceType(spot) == land.SurfaceType.LAND then + table.insert(validSpots, spot) + end + end + spots = validSpots + end + + local step = spacing + for _, unit in pairs(units) do + local pos = { x = unit.x, y = unit.z or unit.y } + local isOnLand = land.getSurfaceType(pos) == land.SurfaceType.LAND + local isValid = false + if spots and #spots > 0 then + local si = 1 + local sid = 0 + local closestDist = 100000000 + local closestSpot + for _, spot in pairs(spots) do + local dist = UTILS.VecDist2D(pos, spot) + if dist < closestDist then + local skip = false + for _, unit2 in pairs(units) do + local pos2 = { x = unit2.x, y = unit2.z or unit2.y } + local dist2 = UTILS.VecDist2D(spot, pos2) + if dist2 < spacing and isOnLand then + skip = true + break + end + end + if not skip then + closestDist = dist + closestSpot = spot + sid = si + end + end + si = si + 1 + end + if closestSpot then + if closestDist >= spacing then + pos = closestSpot + end + isValid = true + table.remove(spots, sid) + end + end + + -- Failsafe calculation + if not isValid and not isOnLand then + + local h = UTILS.HdgTo(pos, gPos) + local retries = 0 + while not isValid and retries < 500 do + + local dist = UTILS.VecDist2D(pos, gPos) + pos = UTILS.Vec2Translate(pos, step, h) + + local skip = false + for _, unit2 in pairs(units) do + if unit ~= unit2 then + local pos2 = { x = unit2.x, y = unit2.z or unit2.y } + local dist2 = UTILS.VecDist2D(pos, pos2) + if dist2 < 12 then + isValid = false + skip = true + break + end + end + end + + if not skip and dist > step and land.getSurfaceType(pos) == land.SurfaceType.LAND then + isValid = true + break + elseif dist <= step then + break + end + + retries = retries + 1 + end + end + + if isValid then + unit.x = pos.x + if unit.z then + unit.z = pos.y + else + unit.y = pos.y + end + end + end + end +end From 78b3efcf0095044067eda0838604f5a929d0a9d9 Mon Sep 17 00:00:00 2001 From: smiki Date: Sun, 24 Aug 2025 15:10:47 +0200 Subject: [PATCH 42/73] Validate and Reposition Ground Units algorithm [ADDED] UTILS.ValidateAndRepositionGroundUnits [ADDED] SPAWN:InitValidateAndRepositionGroundUnits [ADDED] CTLD.validateAndRepositionUnits --- Moose Development/Moose/Ops/CTLD.lua | 3 ++- Moose Development/Moose/Utilities/Utils.lua | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 40f2dc8e3..6b1bb8d60 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -868,7 +868,8 @@ do -- 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. it will be a 1 step solution. -- my_ctld.VehicleMoveFormation = AI.Task.VehicleFormation.VEE -- When a group moves to a MOVE zone, then it takes this formation. Can be a table of formations, which are then randomly chosen. Defaults to "Vee". --- +-- 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) +-- -- ## 2.1 CH-47 Chinook support -- -- The Chinook 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** diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index c7543b3a2..bdbff6b38 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4872,7 +4872,7 @@ function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) return {x=qx, y=qy} end ---- This function uses Disposition and other fallback logic to find better ground positions for a group of ground units. +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. --- NOTE: This is not a spawn randomizer. --- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. --- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. From 8169235d2fb944028f5c8ae7d04094f24b520bb3 Mon Sep 17 00:00:00 2001 From: smiki Date: Sun, 24 Aug 2025 16:27:55 +0200 Subject: [PATCH 43/73] [FIXED] Maintain valid unit positions --- Moose Development/Moose/Utilities/Utils.lua | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index bdbff6b38..989e9ef07 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4921,20 +4921,9 @@ function UTILS.ValidateAndRepositionGroundUnits(Anchor, Positions, MaxRadius, Sp for _, spot in pairs(spots) do local dist = UTILS.VecDist2D(pos, spot) if dist < closestDist then - local skip = false - for _, unit2 in pairs(units) do - local pos2 = { x = unit2.x, y = unit2.z or unit2.y } - local dist2 = UTILS.VecDist2D(spot, pos2) - if dist2 < spacing and isOnLand then - skip = true - break - end - end - if not skip then - closestDist = dist - closestSpot = spot - sid = si - end + closestDist = dist + closestSpot = spot + sid = si end si = si + 1 end From facac821305d6d406b48431f098f68cd56ea030b Mon Sep 17 00:00:00 2001 From: smiki Date: Sun, 24 Aug 2025 16:44:41 +0200 Subject: [PATCH 44/73] [ADDED] WAREHOUSE:SetValidateAndRepositionGroundUnits to use for Brigades --- Moose Development/Moose/Functional/Warehouse.lua | 14 ++++++++++++++ Moose Development/Moose/Utilities/Utils.lua | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 2486e74b6..19654ad5e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4247,6 +4247,16 @@ function WAREHOUSE:_AssetItemInfo(asset) self:T3({Template=asset.template}) end +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +--- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #boolean Enabled Enable/disable the feature. +function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled) + self.ValidateAndRepositionGroundUnits = Enabled +end + --- On after "NewAsset" event. A new asset has been added to the warehouse stock. -- @param #WAREHOUSE self -- @param #string From From state. @@ -5965,6 +5975,10 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, late template.y = coord.z template.alt = coord.y + if self.ValidateAndRepositionGroundUnits then + UTILS.ValidateAndRepositionGroundUnits(template.units) + end + -- Spawn group. local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 989e9ef07..cd94f71b2 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4876,11 +4876,11 @@ end --- NOTE: This is not a spawn randomizer. --- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. --- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. --- @param table Positions A table of DCS#Vec2 or DCS#Vec3, can be a units table from the group template. +-- @param #table Positions A table of DCS#Vec2 or DCS#Vec3, can be a units table from the group template. -- @param DCS#Vec2 Anchor (Optional) DCS#Vec2 or DCS#Vec3 as anchor point to calculate offset of the units. -- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units. -- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger. -function UTILS.ValidateAndRepositionGroundUnits(Anchor, Positions, MaxRadius, Spacing) +function UTILS.ValidateAndRepositionGroundUnits(Positions, Anchor, MaxRadius, Spacing) local units = Positions Anchor = Anchor or UTILS.GetCenterPoint(units) local gPos = { x = Anchor.x, y = Anchor.z or Anchor.y } From 9adf342dd84e9bcabc3890350cf2d1f90b255c9b Mon Sep 17 00:00:00 2001 From: smiki Date: Sun, 24 Aug 2025 16:45:54 +0200 Subject: [PATCH 45/73] [ADDED] WAREHOUSE:SetValidateAndRepositionGroundUnits to use for Brigades --- 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 19654ad5e..807f57f98 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -5975,9 +5975,9 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, late template.y = coord.z template.alt = coord.y - if self.ValidateAndRepositionGroundUnits then + if self.ValidateAndRepositionGroundUnits then UTILS.ValidateAndRepositionGroundUnits(template.units) - end + end -- Spawn group. local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP From 2c12cfe4fd018da77bee56c5932420ec7b77b4a7 Mon Sep 17 00:00:00 2001 From: smiki Date: Sun, 24 Aug 2025 17:06:45 +0200 Subject: [PATCH 46/73] [ADDED] WAREHOUSE:SetValidateAndRepositionGroundUnits to use for Brigades --- Moose Development/Moose/Core/Spawn.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 6e95e285b..0d58567a4 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1849,7 +1849,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if self.SpawnValidateAndRepositionGroundUnits then local units = SpawnTemplate.units local gPos = { x = SpawnTemplate.x, y = SpawnTemplate.y } - UTILS.ValidateAndRepositionGroundUnits(gPos, units, self.SpawnValidateAndRepositionGroundUnitsRadius, self.SpawnValidateAndRepositionGroundUnitsSpacing) + UTILS.ValidateAndRepositionGroundUnits(units, gPos, self.SpawnValidateAndRepositionGroundUnitsRadius, self.SpawnValidateAndRepositionGroundUnitsSpacing) end -- Set country, coalition and category. From cc7685161461e3e4430a83633e904897c72d4f30 Mon Sep 17 00:00:00 2001 From: smiki Date: Mon, 25 Aug 2025 23:08:25 +0200 Subject: [PATCH 47/73] [ADDED] `ValidateAndRepositionGroundUnits` to UNIT and GROUP respawns --- Moose Development/Moose/Wrapper/Group.lua | 14 ++++++++++++++ Moose Development/Moose/Wrapper/Unit.lua | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 89ff5f7c3..dc6cf3af1 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2231,6 +2231,10 @@ function GROUP:Respawn( Template, Reset ) --UTILS.PrintTableToLog(Template) + if self.ValidateAndRepositionGroundUnits then + UTILS.ValidateAndRepositionGroundUnits(Template.units) + end + -- Spawn new group. self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template) --_DATABASE:Spawn(Template) @@ -3192,3 +3196,13 @@ function GROUP:IsAAA() end return isAAA end + +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +--- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #boolean Enabled Enable/disable the feature. +function GROUP:SetValidateAndRepositionGroundUnits(Enabled) + self.ValidateAndRepositionGroundUnits = Enabled +end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index d005ebca2..9fd6963a1 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -377,6 +377,10 @@ function UNIT:ReSpawnAt(Coordinate, Heading) --self:T( SpawnGroupTemplate ) + if self.ValidateAndRepositionGroundUnits then + UTILS.ValidateAndRepositionGroundUnits(SpawnGroupTemplate.units) + end + _DATABASE:Spawn(SpawnGroupTemplate) end @@ -1938,3 +1942,13 @@ end function UNIT:SetCarrierIlluminationMode(Mode) UTILS.SetCarrierIlluminationMode(self:GetID(), Mode) end + +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +--- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #boolean Enabled Enable/disable the feature. +function UNIT:SetValidateAndRepositionGroundUnits(Enabled) + self.ValidateAndRepositionGroundUnits = Enabled +end From db053398d233f37e221bc20bc5d136871e60073c Mon Sep 17 00:00:00 2001 From: TeTeT Nimitz Date: Wed, 27 Aug 2025 13:12:50 +0200 Subject: [PATCH 48/73] Add support for Vietnam War Vessels carriers to Airboss: - CVA-31 Bon Homme Richard - Generic Essex with SCB-125 upgrade (angled Deck) - CVAN-65 Enterprise 1966 - CVN-65 Enterprise modern --- Moose Development/Moose/Ops/Airboss.lua | 65 +++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index c24f6720c..89f6f5b37 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1317,6 +1317,10 @@ AIRBOSS.AircraftCarrier={ -- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module] -- @field #string VINSON USS Carl Vinson (CVN-70) [Deprecated!] -- @field #string ESSEX Essex class carrier (e.g. USS Yorktown (CV-10)) [Magnitude 3 Carrier Module] +-- @field #string BONHOMMERICHARD USS Bon Homme Richard carrier [VWV Mod] +-- @field #string ESSEXSCB125 Generic Essex class carrier with angled deck (SCB-125 upgrade) [VWV Mod] +-- @field #string ENTERPRISE66 USS Enterprise in the 1966 configuration [VWV Mod] +-- @field #string ENTERPRISEMODERN USS Enterprise in a modern configuration [Derived VWV Mod] -- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier] -- @field #string INVINCIBLE HMS Invincible (R05) [V/STOL Carrier] -- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier] @@ -1331,8 +1335,12 @@ AIRBOSS.CarrierType = { TRUMAN = "CVN_75", STENNIS = "Stennis", FORRESTAL = "Forrestal", + ENTERPRISE66 = "USS Enterprise 1966", + ENTERPRISEMODERN = "cvn-65", VINSON = "VINSON", ESSEX = "Essex", + BONHOMMERICHARD = "USS Bon Homme Richard", + ESSEXSCB125 = "essex_scb125", HERMES = "HERMES81", INVINCIBLE = "hms_invincible", TARAWA = "LHA_Tarawa", @@ -2019,11 +2027,19 @@ function AIRBOSS:New( carriername, alias ) self:_InitNimitz() elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then self:_InitForrestal() + elseif self.carriertype == AIRBOSS.CarrierType.ENTERPRISE66 then + self:_InitEnterprise() + elseif self.carriertype == AIRBOSS.CarrierType.ENTERPRISEMODERN then + self:_InitEnterprise() elseif self.carriertype == AIRBOSS.CarrierType.VINSON then -- Carl Vinson is legacy now. self:_InitStennis() elseif self.carriertype == AIRBOSS.CarrierType.ESSEX then self:_InitEssex() + elseif self.carriertype == AIRBOSS.CarrierType.BONHOMMERICHARD then + self:_InitBonHommeRichard() + elseif self.carriertype == AIRBOSS.CarrierType.ESSEXSCB125 then + self:_InitEssexSCB125() elseif self.carriertype == AIRBOSS.CarrierType.HERMES then -- Hermes parameters. self:_InitHermes() @@ -4653,6 +4669,26 @@ function AIRBOSS:_InitForrestal() end +--- Init parameters for Enterprise carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitEnterprise() + -- Using Forrestal as template + self:_InitForrestal() + + self.carrierparam.sterndist = -164.30 + self.carrierparam.deckheight = 19.52 + + self.carrierparam.totlength = 335 + self.carrierparam.rwylength = 223 + + -- Wires. + self.carrierparam.wire1 = 57.7 + self.carrierparam.wire2 = 69.6 + self.carrierparam.wire3 = 79.5 + self.carrierparam.wire4 = 90.0 + +end + --- Init parameters for Essec class carriers. -- @param #AIRBOSS self function AIRBOSS:_InitEssex() @@ -4698,6 +4734,35 @@ function AIRBOSS:_InitEssex() end +--- Init parameters for CVA-31 Bon Homme Richard carriers. +-- @param #AIRBOSS self +function AIRBOSS:_InitBonHommeRichard() + -- Init Essex as default + self:_InitEssex() + + self.carrierparam.deckheight = 16.95 + + -- Landing runway. + -- from BHR EssexRunwayAndRoutes.lua + self.carrierparam.rwyangle = -11.4 + self.carrierparam.rwylength = 97 + self.carrierparam.rwywidth = 20 + + -- Wires. + self.carrierparam.wire1 = 40.4 -- Distance from stern to first wire. Original from Frank - 42 + self.carrierparam.wire2 = 45 + self.carrierparam.wire3 = 51 + self.carrierparam.wire4 = 58.1 +end + +--- Init parameters for Generic Essex SC125 class carriers. +-- @param #AIRBOSS self +function AIRBOSS:_InitEssexSCB125() + -- Init Bon Homme Richard as default + self:_InitBonHommeRichard() + +end + --- Init parameters for R12 HMS Hermes carrier. -- @param #AIRBOSS self function AIRBOSS:_InitHermes() From e003b91bbe0b3edf22da430b577d5950f71dc852 Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 28 Aug 2025 19:36:22 +0200 Subject: [PATCH 49/73] [ADDED] `SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff, MaxRadius)` --- Moose Development/Moose/Core/Spawn.lua | 1 + Moose Development/Moose/Core/SpawnStatic.lua | 22 +++++++++ Moose Development/Moose/Utilities/Utils.lua | 48 ++++++++++++++++++++ Moose Development/Moose/Wrapper/Group.lua | 1 + Moose Development/Moose/Wrapper/Unit.lua | 1 + 5 files changed, 73 insertions(+) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 0d58567a4..f58937439 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1054,6 +1054,7 @@ end --- NOTE: This is not a spawn randomizer. --- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area. --- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +-- @param #SPAWN self -- @param #boolean OnOff Enable/disable the feature. -- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units. -- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger. diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 344d1a5d7..c67d6ed7c 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -378,6 +378,20 @@ function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle) return self end +--- Uses Disposition and other fallback logic to find a better and valid ground spawn position. +--- NOTE: This is not a spawn randomizer. +--- It will try to a find clear ground location avoiding trees, water, roads, runways, map scenery, other statics and other units in the area. +--- Uses the initial position if it's a valid location. +-- @param #SPAWNSTATIC self +-- @param #boolean OnOff Enable/disable the feature. +-- @param #number MaxRadius (Optional) Max radius to search for a valid ground location in meters. Default is 10 times the max radius of the static. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff, MaxRadius) + self.SpawnValidateAndRepositionStatic = OnOff + self.ValidateAndRepositionStaticMaxRadius = MaxRadius + 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. @@ -544,6 +558,14 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- Add static to the game. local Static=nil --DCS#StaticObject + if self.ValidateAndRepositionStatic then + local validPos = UTILS.ValidateAndRepositionStatic(CountryID, Template.category, Template.type, Template, Template.shape_name, self.ValidateAndRepositionStaticMaxRadius) + if validPos then + Template.x = validPos.x + Template.y = validPos.y + end + end + if self.InitFarp then local TemplateGroup={} diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index cd94f71b2..1a2809b24 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4981,3 +4981,51 @@ function UTILS.ValidateAndRepositionGroundUnits(Positions, Anchor, MaxRadius, Sp end end end + +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +-- @param #table Positions A table of DCS#Vec2 or DCS#Vec3, can be a units table from the group template. +-- @param DCS#Vec2 Position DCS#Vec2 or DCS#Vec3 initial spawn location. +-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the static. +-- @return DCS#Vec2 Initial Position if it's valid, else a valid spawn position. nil if no valid position found. +function UTILS.ValidateAndRepositionStatic(Country, Category, Type, Position, ShapeName, MaxRadius) + local coord = COORDINATE:NewFromVec2(Position) + local st = SPAWNSTATIC:NewFromType(Type, Category, Country) + if ShapeName then + st:InitShape(ShapeName) + end + local sName = "s-"..timer.getTime().."-"..math.random(1,10000) + local tempStatic = st:SpawnFromCoordinate(coord, 0, sName) + if tempStatic then + local sRadius = tempStatic:GetBoundingRadius(2) or 3 + tempStatic:Destroy() + sRadius = sRadius * 0.5 + MaxRadius = MaxRadius or math.max(sRadius * 10, 100) + local positions = UTILS.GetSimpleZones(coord:GetVec3(), MaxRadius, sRadius, 20) + if positions and #positions > 0 then + local closestSpot + local closestDist = math.huge + for _, spot in pairs(positions) do -- Disposition sometimes returns points on roads, hence this filter. + if land.getSurfaceType(spot) == land.SurfaceType.LAND then + local dist = UTILS.VecDist2D(Position, spot) + if dist < closestDist then + closestDist = dist + closestSpot = spot + end + end + end + + if closestSpot then + if closestDist >= sRadius then + return closestSpot + else + return Position + end + end + end + end + + return nil +end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index dc6cf3af1..77380fb7b 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -3202,6 +3202,7 @@ end --- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. --- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. --- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #UNIT self -- @param #boolean Enabled Enable/disable the feature. function GROUP:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits = Enabled diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 9fd6963a1..9d2ab5cfd 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1948,6 +1948,7 @@ end --- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. --- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. --- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #UNIT self -- @param #boolean Enabled Enable/disable the feature. function UNIT:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits = Enabled From 323f09b06c85df89956b7d3766c0bf5213619ed1 Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 28 Aug 2025 19:38:05 +0200 Subject: [PATCH 50/73] [ADDED] `SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff, MaxRadius)` --- Moose Development/Moose/Core/SpawnStatic.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index c67d6ed7c..332e7e464 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -387,7 +387,7 @@ end -- @param #number MaxRadius (Optional) Max radius to search for a valid ground location in meters. Default is 10 times the max radius of the static. -- @return #SPAWNSTATIC self function SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff, MaxRadius) - self.SpawnValidateAndRepositionStatic = OnOff + self.ValidateAndRepositionStatic = OnOff self.ValidateAndRepositionStaticMaxRadius = MaxRadius return self end From 42ecdd3b1468d43e08be6c53acebc672d9b8a6ea Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 31 Aug 2025 13:24:31 +0200 Subject: [PATCH 51/73] #AIRBASE - Add Sinai Damascus Intl Airbase in the enumerator --- Moose Development/Moose/Wrapper/Airbase.lua | 100 ++++++++++---------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index cf96d6064..58b1fea1e 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -703,55 +703,56 @@ AIRBASE.SouthAtlantic={ --- Airbases of the Sinai map: -- --- * 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.Borg_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.Mezzeh_Air_Base --- * AIRBASE.Sinai.Nevatim --- * AIRBASE.Sinai.Ovda --- * AIRBASE.Sinai.Palmachim --- * AIRBASE.Sinai.Quwaysina --- * AIRBASE.Sinai.Rafic_Hariri_Intl --- * AIRBASE.Sinai.Ramat_David --- * 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.Tabuk --- * AIRBASE.Sinai.Tel_Nof --- * AIRBASE.Sinai.Wadi_Abu_Rish --- * AIRBASE.Sinai.Wadi_al_Jandali +-- * AIRBASE.SinaiMap.Abu_Rudeis +-- * AIRBASE.SinaiMap.Abu_Suwayr +-- * AIRBASE.SinaiMap.Al_Bahr_al_Ahmar +-- * AIRBASE.SinaiMap.Al_Ismailiyah +-- * AIRBASE.SinaiMap.Al_Khatatbah +-- * AIRBASE.SinaiMap.Al_Mansurah +-- * AIRBASE.SinaiMap.Al_Rahmaniyah_Air_Base +-- * AIRBASE.SinaiMap.As_Salihiyah +-- * AIRBASE.SinaiMap.AzZaqaziq +-- * AIRBASE.SinaiMap.Baluza +-- * AIRBASE.SinaiMap.Ben_Gurion +-- * AIRBASE.SinaiMap.Beni_Suef +-- * AIRBASE.SinaiMap.Bilbeis_Air_Base +-- * AIRBASE.SinaiMap.Bir_Hasanah +-- * AIRBASE.SinaiMap.Birma_Air_Base +-- * AIRBASE.SinaiMap.Borg_El_Arab_International_Airport +-- * AIRBASE.SinaiMap.Cairo_International_Airport +-- * AIRBASE.SinaiMap.Cairo_West +-- * AIRBASE.SinaiMap.Damascus_Intl +-- * AIRBASE.SinaiMap.Difarsuwar_Airfield +-- * AIRBASE.SinaiMap.El_Arish +-- * AIRBASE.SinaiMap.El_Gora +-- * AIRBASE.SinaiMap.El_Minya +-- * AIRBASE.SinaiMap.Fayed +-- * AIRBASE.SinaiMap.Gebel_El_Basur_Air_Base +-- * AIRBASE.SinaiMap.Hatzerim +-- * AIRBASE.SinaiMap.Hatzor +-- * AIRBASE.SinaiMap.Hurghada_International_Airport +-- * AIRBASE.SinaiMap.Inshas_Airbase +-- * AIRBASE.SinaiMap.Jiyanklis_Air_Base +-- * AIRBASE.SinaiMap.Kedem +-- * AIRBASE.SinaiMap.Kibrit_Air_Base +-- * AIRBASE.SinaiMap.Kom_Awshim +-- * AIRBASE.SinaiMap.Melez +-- * AIRBASE.SinaiMap.Mezzeh_Air_Base +-- * AIRBASE.SinaiMap.Nevatim +-- * AIRBASE.SinaiMap.Ovda +-- * AIRBASE.SinaiMap.Palmachim +-- * AIRBASE.SinaiMap.Quwaysina +-- * AIRBASE.SinaiMap.Rafic_Hariri_Intl +-- * AIRBASE.SinaiMap.Ramat_David +-- * AIRBASE.SinaiMap.Ramon_Airbase +-- * AIRBASE.SinaiMap.Ramon_International_Airport +-- * AIRBASE.SinaiMap.Sde_Dov +-- * AIRBASE.SinaiMap.Sharm_El_Sheikh_International_Airport +-- * AIRBASE.SinaiMap.St_Catherine +-- * AIRBASE.SinaiMap.Tabuk +-- * AIRBASE.SinaiMap.Tel_Nof +-- * AIRBASE.SinaiMap.Wadi_Abu_Rish +-- * AIRBASE.SinaiMap.Wadi_al_Jandali -- -- @field Sinai AIRBASE.Sinai = { @@ -773,6 +774,7 @@ AIRBASE.Sinai = { ["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", ["El_Arish"] = "El Arish", ["El_Gora"] = "El Gora", From 6c1907f7e0f14497e08c2ca08281de5119b655f4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 31 Aug 2025 13:27:24 +0200 Subject: [PATCH 52/73] GROUO correction in class self line 3205 --- Moose Development/Moose/Wrapper/Group.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 77380fb7b..6ff787553 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -3202,7 +3202,7 @@ end --- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. --- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. --- Uses UTILS.ValidateAndRepositionGroundUnits. --- @param #UNIT self +-- @param #GROUP self -- @param #boolean Enabled Enable/disable the feature. function GROUP:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits = Enabled From 873879ff794237212fe6f76f723a8d47506a00a3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 31 Aug 2025 18:22:17 +0200 Subject: [PATCH 53/73] #AIRBOSS - Slight tweak to EnableSRS() when no parameters are handed in. --- Moose Development/Moose/Ops/Airboss.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 89f6f5b37..70535cd0e 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -39,6 +39,8 @@ -- * [USS America](https://en.wikipedia.org/wiki/USS_America_\(LHA-6\)) (LHA-6) -- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) -- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_\(L02\)) (L02) +-- * BONHOMMERICHARD [VWV Mod] +-- * ENTERPRISE66 [VWV Mod] -- -- **Supported Aircraft:** -- @@ -1764,7 +1766,7 @@ AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version = "1.4.1" +AIRBOSS.version = "1.4.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3126,8 +3128,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 +3140,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 44f3c776eb0bad395b2520a89b2630a8345ef74b Mon Sep 17 00:00:00 2001 From: smiki Date: Mon, 1 Sep 2025 09:30:05 +0200 Subject: [PATCH 54/73] [FIXED] `UTILS.HdgTo to accept both Vec2 or Vec3` --- Moose Development/Moose/Utilities/Utils.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 1a2809b24..e4d85fbff 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1586,12 +1586,12 @@ function UTILS.HdgDiff(h1, h2) return math.abs(delta) end ---- Returns the heading from one vec3 to another vec3. --- @param DCS#Vec3 a From vec3. --- @param DCS#Vec3 b To vec3. +--- Returns the heading from one vec2/vec3 to another vec2/vec3. +-- @param DCS#Vec3 a From Vec2 or Vec3. +-- @param DCS#Vec3 b To Vec2 or Vec3. -- @return #number Heading in degrees. function UTILS.HdgTo(a, b) - local dz=b.z-a.z + local dz=(b.z or b.y) - (a.z or a.y) local dx=b.x-a.x local heading=math.deg(math.atan2(dz, dx)) if heading < 0 then @@ -4982,7 +4982,7 @@ function UTILS.ValidateAndRepositionGroundUnits(Positions, Anchor, MaxRadius, Sp end end ---- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- This function uses Disposition and other fallback logic to find better ground positions for statics. --- NOTE: This is not a spawn randomizer. --- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. --- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. From 7df90b2d302887cb052840688587b9d98ca5ff38 Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 3 Sep 2025 09:12:20 +0200 Subject: [PATCH 55/73] [ADDED] `GROUP:GetBoundingBox()` since `POSITIONABLE:GetBoundingBox()` is only for units --- Moose Development/Moose/Wrapper/Group.lua | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 6ff787553..cdc76a54d 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -3207,3 +3207,49 @@ end function GROUP:SetValidateAndRepositionGroundUnits(Enabled) self.ValidateAndRepositionGroundUnits = Enabled end + + +--- Get the bounding box of the group combining UNIT:GetBoundingBox() units. +-- @param #GROUP self +-- @return DCS#Box3 The bounding box of the GROUP. +-- @return #nil The GROUP does not have any alive units. +function GROUP:GetBoundingBox() + local bbox = { min = { x = math.huge, y = math.huge, z = math.huge }, + max = { x = -math.huge, y = -math.huge, z = -math.huge } + } + + local Units = self:GetUnits() or {} + if #Units == 0 then + return nil + end + + for _, unit in pairs(Units) do + if unit and unit:IsAlive() then + local ubox = unit:GetBoundingBox() + + if ubox then + if ubox.min.x < bbox.min.x then + bbox.min.x = ubox.min.x + end + if ubox.min.y < bbox.min.y then + bbox.min.y = ubox.min.y + end + if ubox.min.z < bbox.min.z then + bbox.min.z = ubox.min.z + end + + if ubox.max.x > bbox.max.x then + bbox.max.x = ubox.max.x + end + if ubox.max.y > bbox.max.y then + bbox.max.y = ubox.max.y + end + if ubox.max.z > bbox.max.z then + bbox.max.z = ubox.max.z + end + end + end + end + + return bbox +end From 09e5fca1a57c91b93cb8987f7d07a93bcddb4734 Mon Sep 17 00:00:00 2001 From: smiki Date: Thu, 11 Sep 2025 00:38:20 +0200 Subject: [PATCH 56/73] [FIXED] CTLD. Injected spawns not using `InitValidateAndRepositionGroundUnits` --- Moose Development/Moose/Ops/CTLD.lua | 3 +++ Moose Development/Moose/Wrapper/Group.lua | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6b1bb8d60..556b03038 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -6930,6 +6930,7 @@ end local alias = string.format("%s-%d", _template, math.random(1,100000)) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(randompositions,20,2) + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitDelayOff() :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) @@ -7083,12 +7084,14 @@ end if canmove then self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :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() + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index cdc76a54d..1c38db672 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -231,6 +231,7 @@ GROUP.Attribute = { GROUND_AAA="Ground_AAA", GROUND_SAM="Ground_SAM", GROUND_SHORAD="Ground_SHORAD", + GROUND_BALLISTICMISSILE="Ground_BallisticMissile", GROUND_OTHER="Ground_OtherGround", NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", NAVAL_WARSHIP="Naval_WarShip", @@ -2643,6 +2644,8 @@ function GROUP:GetAttribute() local artillery=self:HasAttribute("Artillery") local tank=self:HasAttribute("Old Tanks") or self:HasAttribute("Modern Tanks") or self:HasAttribute("Tanks") local aaa=self:HasAttribute("AAA") and (not self:HasAttribute("SAM elements")) + local ballisticMissile=artillery and self:HasAttribute("SS_missile") + local shorad=self:HasAttribute("SR SAM") local ewr=self:HasAttribute("EWR") local ifv=self:HasAttribute("IFV") local sam=self:HasAttribute("SAM elements") or self:HasAttribute("Optical Tracker") @@ -2684,6 +2687,8 @@ function GROUP:GetAttribute() attribute=GROUP.Attribute.GROUND_SAM elseif aaa then attribute=GROUP.Attribute.GROUND_AAA + elseif artillery and ballisticMissile then + attribute=GROUP.Attribute.GROUND_BALLISTICMISSILE elseif artillery then attribute=GROUP.Attribute.GROUND_ARTILLERY elseif tank then From 65d1c4187eff15d34465f37647c3e5dce5b0dbb5 Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 17 Sep 2025 20:19:17 +0200 Subject: [PATCH 57/73] [Fixed] attempt to index field 'Place' (a nil value) in LandingAfterEjection --- 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 c5893325b..7cdec63ac 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1508,7 +1508,9 @@ function EVENT:onEvent( Event ) else if Event.place:isExist() and Object.getCategory(Event.place) ~= Object.Category.SCENERY then Event.Place=AIRBASE:Find(Event.place) - Event.PlaceName=Event.Place:GetName() + if Event.Place then + Event.PlaceName=Event.Place:GetName() + end end end end From c5b0be5d21944fa5f9f2da18a013cdca1e19dd91 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:08:15 +0200 Subject: [PATCH 58/73] Update Airbase.lua Iraq Airbases --- Moose Development/Moose/Wrapper/Airbase.lua | 51 +++++++++++++-------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 58b1fea1e..23874b387 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -933,37 +933,52 @@ AIRBASE.Afghanistan = { --- Airbases of the Iraq map -- --- * AIRBASE.Iraq.Baghdad_International_Airport --- * AIRBASE.Iraq.Sulaimaniyah_International_Airport --- * AIRBASE.Iraq.Al_Sahra_Airport --- * AIRBASE.Iraq.Erbil_International_Airpor --- * AIRBASE.Iraq.Al_Taji_Airport -- * AIRBASE.Iraq.Al_Asad_Airbase +-- * AIRBASE.Iraq.Al_Kut_Airbase +-- * AIRBASE.Iraq.Al_Sahra_Airport -- * AIRBASE.Iraq.Al_Salam_Airbase --- * AIRBASE.Iraq.Balad_Airbase --- * AIRBASE.Iraq.Kirkuk_International_Airport --- * AIRBASE.Iraq.Bashur_Airport +-- * AIRBASE.Iraq.Al_Taji_Airport -- * AIRBASE.Iraq.Al_Taquddum_Airport --- * AIRBASE.Iraq.Qayyarah_Airfield_West +-- * AIRBASE.Iraq.Baghdad_International_Airport +-- * AIRBASE.Iraq.Balad_Airbase +-- * AIRBASE.Iraq.Bashur_Airport +-- * AIRBASE.Iraq.Erbil_International_Airport +-- * AIRBASE.Iraq.Sulaimaniyah_International_Airport +-- * AIRBASE.Iraq.H2_Airbase +-- * AIRBASE.Iraq.H3_Main_Airbase +-- * AIRBASE.Iraq.H3_Northwest_Airbase +-- * AIRBASE.Iraq.H3_Southwest_Airbase -- * AIRBASE.Iraq.K1_Base +-- * AIRBASE.Iraq.Kirkuk_International_Airport +-- * AIRBASE.Iraq.Mosul_International_Airport +-- * AIRBASE.Iraq.Qayyarah_Airfield_West +-- * AIRBASE.Iraq.Sulaimaniyah_International_Airport -- -- @field Iraq AIRBASE.Iraq = { - ["Baghdad_International_Airport"] = "Baghdad International Airport", - ["Sulaimaniyah_International_Airport"] = "Sulaimaniyah International Airport", - ["Al_Sahra_Airport"] = "Al-Sahra Airport", - ["Erbil_International_Airport"] = "Erbil International Airport", - ["Al_Taji_Airport"] = "Al-Taji Airport", +{ ["Al_Asad_Airbase"] = "Al-Asad Airbase", + ["Al_Kut_Airport"] = "Al-Kut Airport", + ["Al_Sahra_Airport"] = "Al-Sahra Airport", ["Al_Salam_Airbase"] = "Al-Salam Airbase", - ["Balad_Airbase"] = "Balad Airbase", - ["Kirkuk_International_Airport"] = "Kirkuk International Airport", - ["Bashur_Airport"] = "Bashur Airport", + ["Al_Taji_Airport"] = "Al-Taji Airport", ["Al_Taquddum_Airport"] = "Al-Taquddum Airport", - ["Qayyarah_Airfield_West"] = "Qayyarah Airfield West", + ["Baghdad_International_Airport"] = "Baghdad International Airport", + ["Balad_Airbase"] = "Balad Airbase", + ["Bashur_Airport"] = "Bashur Airport", + ["Erbil_International_Airport"] = "Erbil International Airport", + ["H2_Airbase"] = "H-2 Airbase", + ["H3_Main_Airbase"] = "H-3 Main Airbase", + ["H3_Northwest_Airbase"] = "H-3 Northwest Airbase", + ["H3_Southwest_Airbase"] = "H-3 Southwest Airbase", ["K1_Base"] = "K1 Base", + ["Kirkuk_International_Airport"] = "Kirkuk International Airport", + ["Mosul_International_Airport"] = "Mosul International Airport", + ["Qayyarah_Airfield_West"] = "Qayyarah Airfield West", + ["Sulaimaniyah_International_Airport"] = "Sulaimaniyah International Airport", } + --- Airbases of the Germany Cold War map -- * AIRBASE.GermanyCW.Airracing_Frankfurt -- * AIRBASE.GermanyCW.Airracing_Frankfurt From c2f0ce0fa2de38b0abfaa37ed73327da9307105e Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:13:44 +0200 Subject: [PATCH 59/73] Update Airbase.lua --- Moose Development/Moose/Wrapper/Airbase.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 23874b387..7b56f195b 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -956,7 +956,6 @@ AIRBASE.Afghanistan = { -- -- @field Iraq AIRBASE.Iraq = { -{ ["Al_Asad_Airbase"] = "Al-Asad Airbase", ["Al_Kut_Airport"] = "Al-Kut Airport", ["Al_Sahra_Airport"] = "Al-Sahra Airport", From 6fdf3957bdb88a7477410f6e63d9c9a1b621a720 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 23 Sep 2025 15:19:10 +0200 Subject: [PATCH 60/73] Fix for #Storage.lua some missing string concats --- Moose Development/Moose/Wrapper/Storage.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 4b445613c..4e6fbad2b 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -753,7 +753,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) + self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")) end end @@ -773,7 +773,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) + self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")) end end @@ -805,7 +805,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) + self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")) end end From 3beb98a5e9dbae24d9439e80c24e34c1e0bae0eb Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 23 Sep 2025 15:26:38 +0200 Subject: [PATCH 61/73] Update Storage.lua --- Moose Development/Moose/Wrapper/Storage.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 4e6fbad2b..5b5c23b3d 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -753,7 +753,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")) + self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end @@ -773,7 +773,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")) + self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end @@ -805,7 +805,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")) + self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end From ec0ff7afcd687e49d8ad2b17fd2e93342b727cd9 Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 24 Sep 2025 15:14:21 +0200 Subject: [PATCH 62/73] [Fixed] Zone Scans returns objects outside the zone --- Moose Development/Moose/Core/Zone.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 104362abb..c2deb6823 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1180,15 +1180,13 @@ 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 then + if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then -- Get object category. local ObjectCategory = Object.getCategory(ZoneObject) if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then - local CoalitionDCSUnit = ZoneObject:getCoalition() - local Include = false if not UnitCategories then -- Anything found is included. @@ -3308,14 +3306,12 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) local function EvaluateZone( ZoneObject ) - if ZoneObject then + if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then local ObjectCategory = Object.getCategory(ZoneObject) if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then - local CoalitionDCSUnit = ZoneObject:getCoalition() - local Include = false if not UnitCategories then -- Anything found is included. @@ -3347,7 +3343,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) end -- trying with box search - if ObjectCategory == Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint()) then + if ObjectCategory == Object.Category.SCENERY then local SceneryType = ZoneObject:getTypeName() local SceneryName = ZoneObject:getName() self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} From 8fb4d4c7c634d8602bdd14e4b9cb8804333b6b56 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 25 Sep 2025 12:13:29 +0200 Subject: [PATCH 63/73] #CTLD - add CratesPickedUp at one more point --- 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 aec69884dc2ca86b306ef4e14fd9fe649e90ccdf Mon Sep 17 00:00:00 2001 From: smiki Date: Fri, 26 Sep 2025 15:13:03 +0200 Subject: [PATCH 64/73] [ADDED] CTLD. UH-60L DAP default unit capability --- Moose Development/Moose/Ops/CTLD.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9dff3ecec..0c354709c 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1396,6 +1396,7 @@ CTLD.UnitTypeCapabilities = { ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers. --Actually it's longer, but the center coord is off-center of the model. ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats + ["UH-60L_DAP"] = {type="UH-60L_DAP", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 16, cargoweightlimit = 500}, -- UH-60L DAP is an attack helo but can do limited CSAR and CTLD ["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 ["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 From 362652ac6cdf9afef3e0ec80c73b67a0c04ba822 Mon Sep 17 00:00:00 2001 From: smiki Date: Fri, 26 Sep 2025 15:14:12 +0200 Subject: [PATCH 65/73] [ADDED] CSAR. UH-60L DAP aircraft type --- Moose Development/Moose/Ops/CSAR.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 56ec21ef5..d9d743d95 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -305,6 +305,7 @@ CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 CSAR.AircraftType["Bell-47"] = 2 CSAR.AircraftType["UH-60L"] = 10 +CSAR.AircraftType["UH-60L_DAP"] = 2 CSAR.AircraftType["AH-64D_BLK_II"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2 CSAR.AircraftType["MH-60R"] = 10 From b51e758516150d45a841a4d51d7373fb3e61f681 Mon Sep 17 00:00:00 2001 From: smiki Date: Tue, 30 Sep 2025 21:17:29 +0200 Subject: [PATCH 66/73] [ADDED] SET_OPSGROUP:CountAlive --- Moose Development/Moose/Core/Set.lua | 22 +++++++++++++++++++ .../Moose/Wrapper/Positionable.lua | 18 ++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 06981e95f..5ee275fd5 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -7865,6 +7865,28 @@ do -- SET_OPSGROUP return self end + --- Iterate the SET_OPSGROUP and count how many GROUPs and UNITs are alive. + -- @param #SET_GROUP self + -- @return #number The number of GROUPs alive. + -- @return #number The number of UNITs alive. + function SET_OPSGROUP:CountAlive() + local CountG = 0 + local CountU = 0 + + local Set = self:GetSet() + + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData and GroupData:IsAlive() then + CountG = CountG + 1 + -- Count Units. + CountU = CountU + GroupData:GetGroup():CountAliveUnits() + end + + end + + return CountG, CountU + end + --- Finds an OPSGROUP based on the group name. -- @param #SET_OPSGROUP self -- @param #string GroupName Name of the group. diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 65b145384..038eefe56 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -361,15 +361,17 @@ function POSITIONABLE:GetCoord() -- Get the current position. local PositionableVec3 = self:GetVec3() - if self.coordinate then - -- Update COORDINATE from 3D vector. - self.coordinate:UpdateFromVec3( PositionableVec3 ) - else - -- New COORDINATE. - self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 ) - end + if PositionableVec3 then + if self.coordinate then + -- Update COORDINATE from 3D vector. + self.coordinate:UpdateFromVec3( PositionableVec3 ) + else + -- New COORDINATE. + self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 ) + end - return self.coordinate + return self.coordinate + end end -- Error message. From 43b4a6834b021d999845a094f4ca2c9d9c6bb97f Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 1 Oct 2025 14:50:04 +0200 Subject: [PATCH 67/73] [FIXED] ZONE_POLYGON from RECT ME drawing rotation not taken into account. --- Moose Development/Moose/Core/Database.lua | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index a8c814260..20d6c94ce 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -577,13 +577,22 @@ do -- Zones and Pathlines -- For a rectangular polygon drawing, we have the width (y) and height (x). local w=objectData.width local h=objectData.height + local rotation = UTILS.ToRadian(objectData.angle or 0) - -- Create points from center using with and height (width for y and height for x is a bit confusing, but this is how ED implemented it). - local points={} - points[1]={x=vec2.x-h/2, y=vec2.y+w/2} --Upper left - points[2]={x=vec2.x+h/2, y=vec2.y+w/2} --Upper right - points[3]={x=vec2.x+h/2, y=vec2.y-w/2} --Lower right - points[4]={x=vec2.x-h/2, y=vec2.y-w/2} --Lower left + local dx = vec2.x + math.abs(w) + local dy = vec2.y + math.abs(h) + + local sinRot = math.sin(-rotation) + local cosRot = math.cos(-rotation) + dx = (dx / 2) + dy = (dy / 2) + + local points = { + { x = -dx * cosRot - (-dy * sinRot) + vec2.x, y = -dx * sinRot + (-dy * cosRot) + vec2.y }, + { x = dx * cosRot - (-dy * sinRot) + vec2.x, y = dx * sinRot + (-dy * cosRot) + vec2.y }, + { x = dx * cosRot - (dy * sinRot) + vec2.x, y = dx * sinRot + (dy * cosRot) + vec2.y }, + { x = -dx * cosRot - (dy * sinRot) + vec2.x, y = -dx * sinRot + (dy * cosRot) + vec2.y }, + } --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY") From f39236c8fd8954f71b9a4921d4b398f6436db5c9 Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 1 Oct 2025 14:59:59 +0200 Subject: [PATCH 68/73] [FIXED] ZONE_POLYGON from RECT ME drawing rotation not taken into account. --- Moose Development/Moose/Core/Database.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 20d6c94ce..e74461eca 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -579,13 +579,10 @@ do -- Zones and Pathlines local h=objectData.height local rotation = UTILS.ToRadian(objectData.angle or 0) - local dx = vec2.x + math.abs(w) - local dy = vec2.y + math.abs(h) - local sinRot = math.sin(-rotation) local cosRot = math.cos(-rotation) - dx = (dx / 2) - dy = (dy / 2) + local dx = w / 2 + local dy = h / 2 local points = { { x = -dx * cosRot - (-dy * sinRot) + vec2.x, y = -dx * sinRot + (-dy * cosRot) + vec2.y }, From aace98545aed59648b89197ae553efeafbe1dce0 Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 1 Oct 2025 15:07:18 +0200 Subject: [PATCH 69/73] [FIXED] ZONE_POLYGON from RECT ME drawing rotation not taken into account. --- Moose Development/Moose/Core/Database.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index e74461eca..b66ee1787 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -581,8 +581,8 @@ do -- Zones and Pathlines local sinRot = math.sin(-rotation) local cosRot = math.cos(-rotation) - local dx = w / 2 - local dy = h / 2 + local dx = h / 2 + local dy = w / 2 local points = { { x = -dx * cosRot - (-dy * sinRot) + vec2.x, y = -dx * sinRot + (-dy * cosRot) + vec2.y }, From 935b52c48984a21b8683ccd8b5475032ee08dd31 Mon Sep 17 00:00:00 2001 From: smiki Date: Wed, 1 Oct 2025 15:11:44 +0200 Subject: [PATCH 70/73] [FIXED] ZONE_POLYGON from RECT ME drawing rotation not taken into account. --- Moose Development/Moose/Core/Database.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index b66ee1787..e1a1e732d 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -579,8 +579,8 @@ do -- Zones and Pathlines local h=objectData.height local rotation = UTILS.ToRadian(objectData.angle or 0) - local sinRot = math.sin(-rotation) - local cosRot = math.cos(-rotation) + local sinRot = math.sin(rotation) + local cosRot = math.cos(rotation) local dx = h / 2 local dy = w / 2 From 5ae6495e6949b2a202f0dde1f7b716203d4cb5d1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 5 Oct 2025 13:51:55 +0200 Subject: [PATCH 71/73] #CSAR Added functionality to determine if a landing took place at a helo base (named "H ..." in newer maps). --- Moose Development/Moose/Ops/CSAR.lua | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 db138be5f3da8aa05384e142a22e8f2dd63003f4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 5 Oct 2025 13:52:22 +0200 Subject: [PATCH 72/73] #SCoRING - suppress autocreation of CSV files better --- Moose Development/Moose/Functional/Scoring.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 .. '"' .. ',' .. From 6e45ee558e6882ceaf331b26f73b82888a851441 Mon Sep 17 00:00:00 2001 From: smiki Date: Sun, 5 Oct 2025 16:57:33 +0200 Subject: [PATCH 73/73] [FIXED] SPAWNSTATIC not registering script spawned static templates therefore ReSpawn is not working. --- Moose Development/Moose/Core/SpawnStatic.lua | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 332e7e464..06a9179e1 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -633,16 +633,15 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) 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 + + 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 ) return mystatic end