From be062061d5292f77387bb0f1be88fe6f2983bfc9 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 2 Jan 2025 23:01:49 +0100 Subject: [PATCH 001/158] Update Auftrag.lua - Improved docs for ARTY --- Moose Development/Moose/Ops/Auftrag.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 0bdfcaa58..432ce4d8a 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2156,7 +2156,11 @@ end ]] ---- **[GROUND, NAVAL]** Create an ARTY mission. +--- **[GROUND, NAVAL]** Create an ARTY mission ("Fire at point" task). +-- +-- If the group has more than one weapon type supporting the "Fire at point" task, the employed weapon type can be set via the `AUFTRAG:SetWeaponType()` function. +-- +-- **Note** that it is recommended to set the weapon range via the `OPSGROUP:AddWeaponRange()` function as this cannot be retrieved from the DCS API. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Center of the firing solution. -- @param #number Nshots Number of shots to be fired. Default `#nil`. From 474f767e56873bc572c31c7b7574fa6ff5dd8982 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 4 Jan 2025 13:24:51 +0100 Subject: [PATCH 002/158] #CTLD - Clarify doc for Hook loadable stuff --- Moose Development/Moose/Ops/CTLD.lua | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index bde535a5d..73d870cc3 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Dec 2024 +-- Last Update Jan 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -805,7 +805,7 @@ do -- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. -- my_ctld.PackDistance = 35 -- Pack crates in this radius only -- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. --- my_ctld.dropAsCargoCrate = false -- Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. +-- my_ctld.dropAsCargoCrate = false -- Hercules only: Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. -- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. @@ -833,7 +833,7 @@ do -- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. -- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. -- my_ctld.RadioSoundFC3 = "beacon.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. --- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIc types for CTLD for the Chinook +-- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIC types for CTLD for the Chinook -- my_ctld.TroopUnloadDistGround = 5 -- If hovering, spawn dropped troops this far away in meters from the helo -- my_ctld.TroopUnloadDistHover = 1.5 -- If grounded, spawn dropped troops this far away in meters from the helo -- my_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules @@ -848,10 +848,11 @@ do -- -- ## 2.1.1 Moose CTLD created crate cargo -- --- Given the correct shape, Moose created cargo can be either loaded with the ground crew or via the F10 CTLD menu. **It is strongly recommend to either use the ground crew or CTLD to load/unload Moose created cargo**. Mix and match will not work here. --- Static shapes loadable *into* the Chinook are at the time of writing: +-- Given the correct shape, Moose created cargo can theoretically be either loaded with the ground crew or via the F10 CTLD menu. **It is strongly stated to avoid using shapes with +-- CTLD which can be Ground Crew loaded.** +-- Static shapes loadable *into* the Chinook and thus to **be avoided for CTLD** are at the time of writing: -- --- * Ammo crate (type "ammo_cargo") +-- * Ammo box (type "ammo_crate") -- * M117 bomb crate (type name "m117_cargo") -- * Dual shell fuel barrels (type name "barrels") -- * UH-1H net (type name "uh1h_cargo") @@ -860,12 +861,12 @@ do -- -- ## 2.1.2 Recommended settings -- --- my_ctld.basetype = "ammo_cargo" +-- my_ctld.basetype = "container_cargo" -- **DO NOT** change this to a base type which could also be loaded by F8/GC to avoid logic problems! -- my_ctld.forcehoverload = false -- no hover autoload, leads to cargo complications with ground crew created cargo items --- my_ctld.pilotmustopendoors = true -- crew must open back loading door 50% (horizontal) or more --- my_ctld.enableslingload = true -- will set cargo items as sling-loadable --- my_ctld.enableChinookGCLoading = true -- will effectively suppress the crate load and drop menus for CTLD for the Chinook --- my_ctld.movecratesbeforebuild = false -- cannot detect movement of crates at the moment +-- my_ctld.pilotmustopendoors = true -- crew must open back loading door 50% (horizontal) or more - watch out for NOT adding a back door gunner! +-- my_ctld.enableslingload = true -- will set cargo items as sling-loadable. +-- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIC types for CTLD for the Chinook. +-- my_ctld.movecratesbeforebuild = true -- leave as is at the pain of building crate still **inside** of the Hook. -- my_ctld.nobuildinloadzones = true -- don't build where you load. -- my_ctld.ChinookTroopCircleRadius = 5 -- Radius for troops dropping in a nice circle. Adjust to your planned squad size for the Chinook. -- From 4c1c36ef46148791af3ccda3071ae596ba1317c6 Mon Sep 17 00:00:00 2001 From: Shafik Date: Sat, 4 Jan 2025 16:16:03 +0200 Subject: [PATCH 003/158] [FIXED] `attempt to call method 'GetPlayerName' (a nil value)` --- Moose Development/Moose/Functional/CleanUp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua index 359922b5a..f4a391eef 100644 --- a/Moose Development/Moose/Functional/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -414,7 +414,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule() end end -- Clean Units which are waiting for a very long time in the CleanUpZone. - if CleanUpUnit and not CleanUpUnit:GetPlayerName() then + if CleanUpUnit and (CleanUpUnit.GetPlayerName == nil or not CleanUpUnit:GetPlayerName()) then local CleanUpUnitVelocity = CleanUpUnit:GetVelocityKMH() if CleanUpUnitVelocity < 1 then if CleanUpListData.CleanUpMoved then From 5fbe0d9a700c2ef76ef5dfa136621ad4e266c8c8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 4 Jan 2025 18:23:24 +0100 Subject: [PATCH 004/158] #ENUMS - Added items to the storage enumerator, some number corrections --- Moose Development/Moose/Utilities/Enums.lua | 140 ++++++++++++++++++-- 1 file changed, 130 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 7686630b7..59df545e1 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -1166,6 +1166,125 @@ ENUMS.Storage.weapons.bombs.AGM_62 = "weapons.bombs.AGM_62" ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE = "weapons.containers.{US_M10_SMOKE_TANK_WHITE}" ENUMS.Storage.weapons.missiles.MICA_T = "weapons.missiles.MICA_T" ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket" +-- 2025 +ENUMS.Storage.weapons.containers.LANTIRN = "weapons.containers.LANTIRN" +ENUMS.Storage.weapons.missiles.AGM_78B = "weapons.missiles.AGM_78B" +ENUMS.Storage.weapons.containers.uh_60l_pilot = "weapons.containers.uh-60l_pilot" +ENUMS.Storage.weapons.missiles.AIM_92E = "weapons.missiles.AIM-92E" +ENUMS.Storage.weapons.missiles.KD_63B = "weapons.missiles.KD_63B" +ENUMS.Storage.weapons.bombs.Type_200A = "weapons.bombs.Type_200A" +ENUMS.Storage.weapons.missiles.HB_AIM_7E_2 = "weapons.missiles.HB-AIM-7E-2" +ENUMS.Storage.weapons.containers.Spear = "weapons.containers.Spear" +ENUMS.Storage.weapons.missiles.LS_6 = "weapons.missiles.LS_6" +ENUMS.Storage.weapons.containers.HB_ALE_40_0_120 = "weapons.containers.HB_ALE_40_0_120" +ENUMS.Storage.weapons.containers.Fantasm = "weapons.containers.Fantasm" +ENUMS.Storage.weapons.nurs.FFAR_Mk61 = "weapons.nurs.FFAR_Mk61" +ENUMS.Storage.weapons.bombs.HB_F4E_GBU15V1 = "weapons.bombs.HB_F4E_GBU15V1" +ENUMS.Storage.weapons.containers.HB_F14_EXT_AN_APQ_167 = "weapons.containers.HB_F14_EXT_AN_APQ-167" +ENUMS.Storage.weapons.nurs.LWL_RP = "weapons.nurs.LWL_RP" +ENUMS.Storage.weapons.bombs.AGM_62_I = "weapons.bombs.AGM_62_I" +ENUMS.Storage.weapons.containers.ETHER = "weapons.containers.ETHER" +ENUMS.Storage.weapons.containers.TANGAZH = "weapons.containers.TANGAZH" +ENUMS.Storage.weapons.bombs.LYSBOMB_11086 = "weapons.bombs.LYSBOMB 11086" +ENUMS.Storage.weapons.containers.Stub_Wing = "weapons.containers.Stub_Wing" +ENUMS.Storage.weapons.missiles.AIM_9E = "weapons.missiles.AIM-9E" +ENUMS.Storage.weapons.missiles.C_701T = "weapons.missiles.C_701T" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP_100" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.missiles.CM_400AKG = "weapons.missiles.CM-400AKG" +ENUMS.Storage.weapons.missiles.C_802AK = "weapons.missiles.C_802AK" +ENUMS.Storage.weapons.missiles.KD_63 = "weapons.missiles.KD_63" +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike_Fast = "weapons.containers.HB_ORD_Pave_Spike_Fast" +ENUMS.Storage.weapons.missiles.SPIKE_ER2 = "weapons.missiles.SPIKE_ER2" +ENUMS.Storage.weapons.containers.KINGAL = "weapons.containers.KINGAL" +ENUMS.Storage.weapons.containers.LANTIRN_F14_TARGET = "weapons.containers.LANTIRN-F14-TARGET" +ENUMS.Storage.weapons.containers.SPS_141 = "weapons.containers.SPS-141" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU-3B_GROUP" +ENUMS.Storage.weapons.containers.HB_ALE_40_30_0 = "weapons.containers.HB_ALE_40_30_0" +ENUMS.Storage.weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL = "weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL" +ENUMS.Storage.weapons.containers.ALQ_184 = "weapons.containers.ALQ-184" +ENUMS.Storage.weapons.missiles.AGM_45B = "weapons.missiles.AGM_45B" +ENUMS.Storage.weapons.bombs.BLU_3_GROUP = "weapons.bombs.BLU-3_GROUP" +ENUMS.Storage.weapons.missiles.SPIKE_ER = "weapons.missiles.SPIKE_ER" +ENUMS.Storage.weapons.nurs.ARAKM70BAPPX = "weapons.nurs.ARAKM70BAPPX" +ENUMS.Storage.weapons.bombs.LYSBOMB_11088 = "weapons.bombs.LYSBOMB 11088" +ENUMS.Storage.weapons.bombs.LYSBOMB_11087 = "weapons.bombs.LYSBOMB 11087" +ENUMS.Storage.weapons.missiles.KD_20 = "weapons.missiles.KD_20" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank = "weapons.droptanks.HB_F-4E_EXT_WingTank" +ENUMS.Storage.weapons.missiles.Rb_04 = "weapons.missiles.Rb_04" +ENUMS.Storage.weapons.containers.AAQ_33 = "weapons.containers.AAQ-33" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank_EMPTY = "weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank_EMPTY" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R_EMPTY = "weapons.droptanks.HB_F-4E_EXT_WingTank_R_EMPTY" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_EMPTY = "weapons.droptanks.HB_F-4E_EXT_WingTank_EMPTY" +ENUMS.Storage.weapons.containers.uh_60l_copilot = "weapons.containers.uh-60l_copilot" +ENUMS.Storage.weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2 = "weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2" +ENUMS.Storage.weapons.containers.supply_m134 = "weapons.containers.supply_m134" +ENUMS.Storage.weapons.containers.Seahawk_Pylon = "weapons.containers.Seahawk_Pylon" +ENUMS.Storage.weapons.nurs.LWL_MPP = "weapons.nurs.LWL_MPP" +ENUMS.Storage.weapons.nurs.S_5KP = "weapons.nurs.S_5KP" +ENUMS.Storage.weapons.missiles.AIM_92J = "weapons.missiles.AIM-92J" +ENUMS.Storage.weapons.missiles.HB_AIM_7E = "weapons.missiles.HB-AIM-7E" +ENUMS.Storage.weapons.containers.ALQ_131 = "weapons.containers.ALQ-131" +ENUMS.Storage.weapons.containers.HB_F14_EXT_TARPS = "weapons.containers.HB_F14_EXT_TARPS" +ENUMS.Storage.weapons.containers.MH60_SOAR = "weapons.containers.MH60_SOAR" +ENUMS.Storage.weapons.missiles.YJ_83 = "weapons.missiles.YJ-83" +ENUMS.Storage.weapons.bombs.GBU_8_B = "weapons.bombs.GBU_8_B" +ENUMS.Storage.weapons.containers.HB_F14_EXT_ECA = "weapons.containers.HB_F14_EXT_ECA" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100" +ENUMS.Storage.weapons.nurs.M261_MPSM_Rocket = "weapons.nurs.M261_MPSM_Rocket" +ENUMS.Storage.weapons.droptanks.SEAHAWK_120_Fuel_Tank = "weapons.droptanks.SEAHAWK_120_Fuel_Tank" +ENUMS.Storage.weapons.containers.SHPIL = "weapons.containers.SHPIL" +ENUMS.Storage.weapons.bombs.GBU_39 = "weapons.bombs.GBU_39" +ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M" +ENUMS.Storage.weapons.containers.HB_ALE_40_15_90 = "weapons.containers.HB_ALE_40_15_90" +ENUMS.Storage.weapons.missiles.AIM_7E = "weapons.missiles.AIM-7E" +ENUMS.Storage.weapons.missiles.AIM_9P3 = "weapons.missiles.AIM-9P3" +ENUMS.Storage.weapons.missiles.AGM_12B = "weapons.missiles.AGM_12B" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank = "weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank" +ENUMS.Storage.weapons.containers.PAVETACK = "weapons.containers.PAVETACK" +ENUMS.Storage.weapons.missiles.LS_6_500 = "weapons.missiles.LS_6_500" +ENUMS.Storage.weapons.bombs.LYSBOMB_11089 = "weapons.bombs.LYSBOMB 11089" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU-4B_GROUP" +ENUMS.Storage.weapons.containers.ah_64d_radar = "weapons.containers.ah-64d_radar" +ENUMS.Storage.weapons.containers.F_18_LDT_POD = "weapons.containers.F-18-LDT-POD" +ENUMS.Storage.weapons.containers.HB_ALE_40_30_60 = "weapons.containers.HB_ALE_40_30_60" +ENUMS.Storage.weapons.bombs.LS_6_100 = "weapons.bombs.LS_6_100" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R = "weapons.droptanks.HB_F-4E_EXT_WingTank_R" +ENUMS.Storage.weapons.containers.SORBCIJA_R = "weapons.containers.SORBCIJA_R" +ENUMS.Storage.weapons.missiles.CATM_65K = "weapons.missiles.CATM_65K" +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike = "weapons.containers.HB_ORD_Pave_Spike" +ENUMS.Storage.weapons.containers.RobbieTank1 = "weapons.containers.RobbieTank1" +ENUMS.Storage.weapons.containers.SKY_SHADOW = "weapons.containers.SKY_SHADOW" +ENUMS.Storage.weapons.containers.SORBCIJA_L = "weapons.containers.SORBCIJA_L" +ENUMS.Storage.weapons.containers.Pavehawk = "weapons.containers.Pavehawk" +ENUMS.Storage.weapons.bombs.BLG66_EG = "weapons.bombs.BLG66_EG" +ENUMS.Storage.weapons.missiles.AGM_12C_ED = "weapons.missiles.AGM_12C_ED" +ENUMS.Storage.weapons.missiles.AIM_92C = "weapons.missiles.AIM-92C" +ENUMS.Storage.weapons.containers.MPS_410 = "weapons.containers.MPS-410" +ENUMS.Storage.weapons.missiles.HJ_12 = "weapons.missiles.HJ-12" +ENUMS.Storage.weapons.containers.AAQ_28_LITENING = "weapons.containers.AAQ-28_LITENING" +ENUMS.Storage.weapons.containers.F_18_FLIR_POD = "weapons.containers.F-18-FLIR-POD" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.containers.UH60L_Jayhawk = "weapons.containers.UH60L_Jayhawk" +ENUMS.Storage.weapons.containers.BOZ_100 = "weapons.containers.BOZ-100" +ENUMS.Storage.weapons.missiles.AGM_78A = "weapons.missiles.AGM_78A" +ENUMS.Storage.weapons.missiles.LAU_61_APKWS_M282 = "weapons.missiles.LAU_61_APKWS_M282" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU-4B_GROUP" +ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M" +ENUMS.Storage.weapons.missiles.AGM_12A = "weapons.missiles.AGM_12A" +ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Tank" +ENUMS.Storage.weapons.bombs.GBU_15_V_1_B = "weapons.bombs.GBU_15_V_1_B" +-- dupes with typos +ENUMS.Storage.weapons.bombs.BAP100 = "weapons.bombs.BAP_100" +ENUMS.Storage.weapons.bombs.BLU3B_GROUP = "weapons.bombs.BLU-3B_GROUP" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU_4B_GROUP" +ENUMS.Storage.weapons.nurs.S5M = "weapons.nurs.S-5M" -- Gazelle ENUMS.Storage.weapons.Gazelle.HMP400_100RDS = {4,15,46,1771} ENUMS.Storage.weapons.Gazelle.HMP400_200RDS = {4,15,46,1770} @@ -1177,16 +1296,16 @@ ENUMS.Storage.weapons.Gazelle.GIAT_M261_HEAP = {4,15,46,1765} ENUMS.Storage.weapons.Gazelle.GIAT_M261_APHE = {4,15,46,1764} ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680} ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679} --- Chinook -ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2476} -ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2477} -ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2478} -ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2482} -ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2483} -ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2484} -ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2479} -ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2480} -ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2481} +-- Chinook (changed) +ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2489} +ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2488} +ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2490} +ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2494} +ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2495} +ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2496} -- +ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2492} +ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2491} +ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2493} -- Huey ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right = {4,15,46,161} ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left = {4,15,46,160} @@ -1237,3 +1356,4 @@ ENUMS.FARPObjectTypeNamesAndShape ={ [ENUMS.FARPType.HELIPADSINGLE] = { TypeName="SINGLE_HELIPAD", ShapeName="FARP"}, [ENUMS.FARPType.PADSINGLE] = { TypeName="FARP_SINGLE_01", ShapeName="FARP_SINGLE_01"}, } + From 9bc067f2e879abf7659ee700519df06eab609990 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 4 Jan 2025 18:25:32 +0100 Subject: [PATCH 005/158] #STORAGE - Switched saving items from the storage inventory to using the enumerator (+~120 items), better handling of table IDs. --- Moose Development/Moose/Wrapper/Storage.lua | 39 +++++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 558368e98..3acaa9d16 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -203,7 +203,7 @@ STORAGE.Type = { --- STORAGE class version. -- @field #string version -STORAGE.version="0.1.4" +STORAGE.version="0.1.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -231,7 +231,7 @@ function STORAGE:New(AirbaseName) self.warehouse=self.airbase:getWarehouse() end - self.lid = string.format("STORAGE %s", AirbaseName) + self.lid = string.format("STORAGE %s | ", AirbaseName) return self end @@ -251,7 +251,7 @@ function STORAGE:NewFromStaticCargo(StaticCargoName) self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) end - self.lid = string.format("STORAGE %s", StaticCargoName) + self.lid = string.format("STORAGE %s | ", StaticCargoName) return self end @@ -271,7 +271,7 @@ function STORAGE:NewFromDynamicCargo(DynamicCargoName) self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) end - self.lid = string.format("STORAGE %s", DynamicCargoName) + self.lid = string.format("STORAGE %s | ", DynamicCargoName) return self end @@ -656,7 +656,6 @@ function STORAGE:SaveToFile(Path,Filename) for key,amount in pairs(lq) do DataLiquids = DataLiquids..tostring(key).."="..tostring(amount).."\n" end - --self:I(DataLiquids) UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids) if self.verbose and self.verbose > 0 then self:I(self.lid.."Saving Liquids to "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") @@ -668,7 +667,6 @@ function STORAGE:SaveToFile(Path,Filename) for key,amount in pairs(ac) do DataAircraft = DataAircraft..tostring(key).."="..tostring(amount).."\n" end - --self:I(DataAircraft) UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft) if self.verbose and self.verbose > 0 then self:I(self.lid.."Saving Aircraft to "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") @@ -677,9 +675,17 @@ function STORAGE:SaveToFile(Path,Filename) if UTILS.TableLength(wp) > 0 then DataWeapons = DataWeapons .."Weapons and Materiel in Storage:\n" - for key,amount in pairs(wp) do - DataWeapons = DataWeapons..tostring(key).."="..tostring(amount).."\n" + + for _,_category in pairs(ENUMS.Storage.weapons) do + for _,_key in pairs(_category) do + local amount = self:GetAmount(_key) + if type(_key) == "table" then + _key = "{"..table.concat(_key,",").."}" + end + DataWeapons = DataWeapons..tostring(_key).."="..tostring(amount).."\n" + end end + -- Gazelle table keys for key,amount in pairs(ENUMS.Storage.weapons.Gazelle) do amount = self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key]) @@ -705,7 +711,6 @@ function STORAGE:SaveToFile(Path,Filename) amount = self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key]) DataWeapons = DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n" end - --self:I(DataAircraft) UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons) if self.verbose and self.verbose > 0 then self:I(self.lid.."Saving Weapons to "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") @@ -777,14 +782,26 @@ function STORAGE:LoadFromFile(Path,Filename) local Ok,Weapons = UTILS.LoadFromFile(Path,Filename.."_Weapons.csv") if Ok then if self.verbose and self.verbose > 0 then - self:I(self.lid.."Loading _eapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") + self:I(self.lid.."Loading Weapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end for _id,_line in pairs(Weapons) do if string.find(_line,"Storage") == nil then local tbl=UTILS.Split(_line,"=") local wpname = tbl[1] local wpnumber = tonumber(tbl[2]) - self:SetAmount(wpname,wpnumber) + if string.find(wpname,"{") == 1 then + --self:I("Found a table: "..wpname) + wpname = string.gsub(wpname,"{","") + wpname = string.gsub(wpname,"}","") + local tbl = UTILS.Split(wpname,",") + local wptbl = {} + for _id,_key in ipairs(tbl) do + table.insert(wptbl,_id,_key) + end + self:SetAmount(wptbl,wpnumber) + else + self:SetAmount(wpname,wpnumber) + end end end else From 4c52509d6d57f8c58bfa6dcd5b7561ba83e5b383 Mon Sep 17 00:00:00 2001 From: Rolln Date: Sat, 4 Jan 2025 11:06:39 -0700 Subject: [PATCH 006/158] ENHANCEMENT: Adds a ceiling to the range. --- Moose Development/Moose/Functional/Range.lua | 45 +++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 05ec5e895..abc7f8b28 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -107,6 +107,9 @@ -- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor. -- @field #number Coalition Coalition side for the menu, if any. -- @field Core.Menu#MENU_MISSION menuF10root Specific user defined root F10 menu. +-- @field #number ceilingaltitude Range ceiling altitude in ft MSL. Aircraft above this altitude are not considered to be in the range. Default is 20000 ft. +-- @field #boolean ceilingenabled Range has a ceiling and is not unlimited. Default is false. + -- @extends Core.Fsm#FSM --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven @@ -273,6 +276,10 @@ -- -- Create a range object. -- GoldwaterRange=RANGE:New("Goldwater Range") -- +-- -- Set and enable the range ceiling altitude in feet MSL. If aircraft are above this altitude they are not considered to be in the range. +-- GoldwaterRange:SetRangeCeiling(20000) +-- GoldwaterRange:EnableRangeCeiling(true) +-- -- -- Distance between strafe target and foul line. You have to specify the names of the unit or static objects. -- -- Note that this could also be done manually by simply measuring the distance between the target and the foul line in the ME. -- GoldwaterRange:GetFoullineDistance("GWR Strafe Pit Left 1", "GWR Foul Line Left") @@ -358,6 +365,8 @@ RANGE = { targetpath = nil, targetprefix = nil, Coalition = nil, + ceilingaltitude = 20000, + ceilingenabled = false, } --- Default range parameters. @@ -1085,6 +1094,37 @@ function RANGE:SetRangeZone( zone ) return self end +--- Set range ceiling altitude in feet MSL. +-- @param #RANGE self +-- @param #number ceiling (optional) Ceiling altitude of the range in ft MSL. Default 20000ft MSL +-- @return #RANGE self +function RANGE:SetRangeCeiling( alt ) + self:T(self.lid.."SetRangeCeiling") + if alt and type(alt) == "number" then + self.ceilingaltitude=alt + else + self:E(self.lid.."Altitude either not provided or is not a number, using default setting (20000).") + self.ceilingaltitude=20000 + end + return self +end + +--- Enable range ceiling. Aircraft must be below the ceiling altitude to be considered in the range zone. +-- @param #RANGE self +-- @param #boolean enabled True if you would like to enable the ceiling check. If no value give, will Default to false. +-- @return #RANGE self +function RANGE:EnableRangeCeiling( enabled ) + self:T(self.lid.."EnableRangeCeiling") + if enabled and type(enabled) == "boolean" then + self.ceilingenabled=enabled + else + self:E(self.lid.."Enabled either not provide or is not a boolean, using default setting (false).") + self.ceilingenabled=false + end + + return self +end + --- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke. -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Red`. @@ -3112,7 +3152,10 @@ function RANGE:_CheckPlayers() if unit and unit:IsAlive() then - if unit:IsInZone( self.rangezone ) then + local unitalt = unit:GetAltitude(false) + local unitaltinfeet = UTILS.MetersToFeet(unitalt) + + if unit:IsInZone(self.rangezone) and (not self.ceilingenabled or unitaltinfeet < self.ceilingaltitude) then ------------------------------ -- Player INSIDE Range Zone -- From 8523b7e20a399aa296121f0a86886469c1a1c509 Mon Sep 17 00:00:00 2001 From: Shafik Date: Sun, 5 Jan 2025 10:47:14 +0200 Subject: [PATCH 007/158] [FIXED] `attempt to call method 'GetPlayerName' (a nil value)` --- Moose Development/Moose/Functional/CleanUp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua index 6bf5228f0..14616331f 100644 --- a/Moose Development/Moose/Functional/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -411,7 +411,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule() end end -- Clean Units which are waiting for a very long time in the CleanUpZone. - if CleanUpUnit and not CleanUpUnit:GetPlayerName() then + if CleanUpUnit and (CleanUpUnit.GetPlayerName == nil or not CleanUpUnit:GetPlayerName()) then local CleanUpUnitVelocity = CleanUpUnit:GetVelocityKMH() if CleanUpUnitVelocity < 1 then if CleanUpListData.CleanUpMoved then From e86069d39c19392e213aacadf3e90135e813731a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 5 Jan 2025 17:43:12 +0100 Subject: [PATCH 008/158] Smaller updates --- Moose Development/Moose/Core/Point.lua | 3 ++ Moose Development/Moose/Functional/Mantis.lua | 49 +++++++++++++++---- Moose Development/Moose/Ops/CSAR.lua | 23 +++++++-- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 964f8e1ee..7f2b3d558 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -970,6 +970,9 @@ do -- COORDINATE if not TargetCoordinate then return 1000000 end --local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} local a = self:GetVec2() + if not TargetCoordinate.ClassName then + TargetCoordinate=COORDINATE:NewFromVec3(TargetCoordinate) + end local b = TargetCoordinate:GetVec2() local norm=UTILS.VecDist2D(a,b) return norm diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 0124af4ba..28f511a91 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -60,6 +60,8 @@ -- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius. -- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range. -- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. +-- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. +-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White -- @extends Core.Base#BASE @@ -329,6 +331,8 @@ MANTIS = { autoshorad = true, ShoradGroupSet = nil, checkforfriendlies = false, + SmokeDecoy = false, + SmokeDecoyColor = SMOKECOLOR.White, } --- Advanced state enumerator @@ -590,7 +594,10 @@ do self.SkateZones = nil self.SkateNumber = 3 - self.shootandscoot = false + self.shootandscoot = false + + self.SmokeDecoy = false + self.SmokeDecoyColor = SMOKECOLOR.White self.UseEmOnOff = true if EmOnOff == false then @@ -602,6 +609,7 @@ do else self.advAwacs = false end + -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -663,7 +671,7 @@ do -- TODO Version -- @field #string version - self.version="0.8.22" + self.version="0.8.23" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -899,6 +907,16 @@ do return self end + --- Function to set Short Range SAMs to spit out smoke as decoy, if an enemy plane is in range. + -- @param #MANTIS self + -- @param #boolean Onoff Set to true for on and nil/false for off. + -- @param #number Color (Optional) Color to use, defaults to `SMOKECOLOR.White` + function MANTIS:SetSmokeDecoy(Onoff,Color) + self.SmokeDecoy = Onoff + self.SmokeDecoyColor = Color or SMOKECOLOR.White + return self + end + --- Function to set number of SAMs going active on a valid, detected thread -- @param #MANTIS self -- @param #number Short Number of short-range systems activated, defaults to 1. @@ -1550,18 +1568,18 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange,grpheight,type,blind = self:_GetSAMRange(grpname) - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) --table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG then - table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) --self:T("SAM "..grpname.." is type LONG") elseif type == MANTIS.SamType.MEDIUM then - table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) --self:T("SAM "..grpname.." is type MEDIUM") elseif type == MANTIS.SamType.SHORT then - table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) --self:T("SAM "..grpname.." is type SHORT") self.ShoradGroupSet:Add(grpname,group) if not self.autoshorad then @@ -1609,16 +1627,16 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind}) -- make the table lighter, as I don't really use the zone here + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG then - table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) --self:I({grpname,grprange, grpheight}) elseif type == MANTIS.SamType.MEDIUM then - table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) --self:I({grpname,grprange, grpheight}) elseif type == MANTIS.SamType.SHORT then - table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) --self:I({grpname,grprange, grpheight}) self.ShoradGroupSet:Add(grpname,group) if self.autoshorad then @@ -1688,6 +1706,7 @@ do local radius = _data[3] local height = _data[4] local blind = _data[5] * 1.25 + 1 + local shortsam = _data[6] == MANTIS.SamType.SHORT and true or false local samgroup = GROUP:FindByName(name) local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) local suppressed = self.SuppressedGroups[name] or false @@ -1713,6 +1732,16 @@ do self:__RedState(1,samgroup) self.SamStateTracker[name] = "RED" end + if shortsam == true and self.SmokeDecoy == true then + self:I("Smoking") + local units = samgroup:GetUnits() or {} + local smoke = self.SmokeDecoyColor or SMOKECOLOR.White + for _,unit in pairs(units) do + if unit and unit:IsAlive() then + unit:GetCoordinate():Smoke(smoke) + end + end + end -- link in to SHORAD if available -- DONE: Test integration fully if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 4bf18ac40..8cc55125c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Sep 2024 +-- Last Update Jan 2025 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -92,7 +92,7 @@ -- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal. -- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- mycsar.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. --- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. +-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. Will also try to add ZONE and STATIC objects with this prefix once at startup. -- mycsar.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- mycsar.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. -- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. @@ -313,7 +313,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.29" +CSAR.version="1.0.30" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -2425,7 +2425,22 @@ function CSAR:onafterStart(From, Event, To) self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end - self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also? + self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() + + local staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce() + local zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce() + + if staticmashes:Count() > 0 then + for _,_mash in pairs(staticmashes.Set) do + self.mash:AddObject(_mash) + end + end + + if zonemashes:Count() > 0 then + for _,_mash in pairs(zonemashes.Set) do + self.mash:AddObject(_mash) + end + end if not self.coordinate then local csarhq = self.mash:GetRandom() From 45dd7117c7adb94ed6621074d5fd99cf63dc183d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 5 Jan 2025 17:44:32 +0100 Subject: [PATCH 009/158] #CLIENTMENUMANAGER Add a retry loop if possibly client not yet exists in Database --- Moose Development/Moose/Core/ClientMenu.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/ClientMenu.lua b/Moose Development/Moose/Core/ClientMenu.lua index 59ec7169c..ee0b7e6f2 100644 --- a/Moose Development/Moose/Core/ClientMenu.lua +++ b/Moose Development/Moose/Core/ClientMenu.lua @@ -20,7 +20,7 @@ -- -- @module Core.ClientMenu -- @image Core_Menu.JPG --- last change: May 2024 +-- last change: Jan 2025 -- TODO ---------------------------------------------------------------------------------------------------------------- @@ -59,7 +59,7 @@ CLIENTMENU = { ClassName = "CLIENTMENUE", lid = "", - version = "0.1.2", + version = "0.1.3", name = nil, path = nil, group = nil, @@ -455,7 +455,7 @@ end -- @param #CLIENTMENUMANAGER self -- @param Core.Event#EVENTDATA EventData -- @return #CLIENTMENUMANAGER self -function CLIENTMENUMANAGER:_EventHandler(EventData) +function CLIENTMENUMANAGER:_EventHandler(EventData,Retry) self:T(self.lid.."_EventHandler: "..EventData.id) --self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName)) if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then @@ -468,6 +468,10 @@ function CLIENTMENUMANAGER:_EventHandler(EventData) if EventData.IniPlayerName and EventData.IniGroup then if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) + if not Retry then + -- try again in 2 secs + self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true) + end return self end --self:I(self.lid.."Join event for player: "..EventData.IniPlayerName) @@ -524,7 +528,7 @@ function CLIENTMENUMANAGER:InitAutoPropagation() self:HandleEvent(EVENTS.PilotDead, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:SetEventPriority(5) + self:SetEventPriority(6) return self end From 2635cf63456b6c74941e5c726501167125c5e39c Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:08:05 +0100 Subject: [PATCH 010/158] Update Spawn.lua #SPAWN Added option to use the center of the zone rather than a random postion when using InitRandomizeZone --- Moose Development/Moose/Core/Spawn.lua | 28 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index b188219f8..887da36a8 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1081,7 +1081,7 @@ function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) self.SpawnRandomizeTemplate = true for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeTemplate( SpawnGroupID ) + self:_RandomizeTemplate( SpawnGroupID, RandomizePositionInZone ) end return self @@ -1093,6 +1093,7 @@ end -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self -- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. +-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN -- @usage -- @@ -1111,11 +1112,11 @@ end -- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) -- -function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) +function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet,RandomizePositionInZone ) --self:F( { self.SpawnTemplatePrefix } ) local setnames = SpawnTemplateSet:GetSetNames() - self:InitRandomizeTemplate(setnames) + self:InitRandomizeTemplate(setnames,RandomizePositionInZone) return self end @@ -1125,7 +1126,8 @@ end -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned. +-- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned. +-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN -- @usage -- @@ -1141,12 +1143,12 @@ end -- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -- -function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) -- R2.3 +function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes, RandomizePositionInZone ) -- R2.3 --self:F( { self.SpawnTemplatePrefix } ) local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce() - self:InitRandomizeTemplateSet( SpawnTemplateSet ) + self:InitRandomizeTemplateSet( SpawnTemplateSet, RandomizePositionInZone ) return self end @@ -1166,6 +1168,7 @@ end --- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. -- @param #SPAWN self -- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects. +-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN self -- @usage -- @@ -1178,7 +1181,7 @@ end -- :InitRandomizeZones( ZoneTable ) -- :SpawnScheduled( 5, .5 ) -- -function SPAWN:InitRandomizeZones( SpawnZoneTable ) +function SPAWN:InitRandomizeZones( SpawnZoneTable, RandomizePositionInZone ) --self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) local temptable = {} @@ -1190,7 +1193,7 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable ) self.SpawnRandomizeZones = true for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeZones( SpawnGroupID ) + self:_RandomizeZones( SpawnGroupID, RandomizePositionInZone ) end return self @@ -3814,8 +3817,9 @@ end --- Private method that randomizes the @{Core.Zone}s where the Group will be spawned. -- @param #SPAWN self -- @param #number SpawnIndex +.. @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN self -function SPAWN:_RandomizeZones( SpawnIndex ) +function SPAWN:_RandomizeZones( SpawnIndex, RandomizePositionInZone) --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) if self.SpawnRandomizeZones then @@ -3829,7 +3833,11 @@ function SPAWN:_RandomizeZones( SpawnIndex ) --self:T2( "Preparing Spawn in Zone", SpawnZone:GetName() ) - local SpawnVec2 = SpawnZone:GetRandomVec2() + local SpawnVec2 = SpawnZone:GetVec2() + + if RandomizePositionInZone ~= false then + SpawnVec2 = SpawnZone:GetRandomVec2() + end --self:T2( { SpawnVec2 = SpawnVec2 } ) From dcdea166247ac4a508f8a55f4bee78cc9f5238d4 Mon Sep 17 00:00:00 2001 From: Rolln Date: Mon, 6 Jan 2025 06:15:35 -0700 Subject: [PATCH 011/158] #RANGE - Changed parameter naming to fix docs --- Moose Development/Moose/Functional/Range.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index abc7f8b28..4d07eeffa 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -1096,12 +1096,12 @@ end --- Set range ceiling altitude in feet MSL. -- @param #RANGE self --- @param #number ceiling (optional) Ceiling altitude of the range in ft MSL. Default 20000ft MSL +-- @param #number altitude (optional) Ceiling altitude of the range in ft MSL. Default 20000ft MSL -- @return #RANGE self -function RANGE:SetRangeCeiling( alt ) +function RANGE:SetRangeCeiling( altitude ) self:T(self.lid.."SetRangeCeiling") - if alt and type(alt) == "number" then - self.ceilingaltitude=alt + if altitude and type(altitude) == "number" then + self.ceilingaltitude=altitude else self:E(self.lid.."Altitude either not provided or is not a number, using default setting (20000).") self.ceilingaltitude=20000 From 04f8f6d512fa7e297fa9ac829ff01514ea8cfa97 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 6 Jan 2025 17:32:29 +0100 Subject: [PATCH 012/158] xx --- 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 887da36a8..89ff4d9fa 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3817,7 +3817,7 @@ end --- Private method that randomizes the @{Core.Zone}s where the Group will be spawned. -- @param #SPAWN self -- @param #number SpawnIndex -.. @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. +-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN self function SPAWN:_RandomizeZones( SpawnIndex, RandomizePositionInZone) --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) From 16ff1ac3e715000b9bd7bdeee59eb1f27f087cfa Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:21:26 +0100 Subject: [PATCH 013/158] Update Auftrag.lua #AUFTRAG Added some options for weapontype setting --- Moose Development/Moose/Ops/Auftrag.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 432ce4d8a..9000fd95a 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1719,15 +1719,16 @@ end -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 2000 ft. +-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options. -- @return #AUFTRAG self -function AUFTRAG:NewSTRIKE(Target, Altitude) +function AUFTRAG:NewSTRIKE(Target, Altitude, EngageWeaponType) local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE) mission:_TargetFromObject(Target) -- DCS Task options: - mission.engageWeaponType=ENUMS.WeaponFlag.Auto + mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) @@ -1750,15 +1751,16 @@ end -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. +-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options. -- @return #AUFTRAG self -function AUFTRAG:NewBOMBING(Target, Altitude) +function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType) local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) mission:_TargetFromObject(Target) -- DCS task options: - mission.engageWeaponType=ENUMS.WeaponFlag.Auto + mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) From 63c68d729b0e6be295fb0b346999bb75d1b22e72 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 8 Jan 2025 13:04:30 +0100 Subject: [PATCH 014/158] #SET_GROUP - fix for lost event if IniDCSGroup isn't filled --- Moose Development/Moose/Core/Set.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 96b629660..5cd4d8681 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1538,6 +1538,13 @@ do local size = 1 if Event.IniDCSGroup then size = Event.IniDCSGroup:getSize() + elseif Event.IniDCSGroupName then + local grp = Group.getByName(Event.IniDCSGroupName) + if grp then + size = grp:getSize() + end + elseif Object:IsAlive() then + size = Object:CountAliveUnits() end if size == 1 then -- Only remove if the last unit of the group was destroyed. self:Remove( ObjectName ) From 1182a5eb1eee33db4a87a8bb908897ecb08ef0e9 Mon Sep 17 00:00:00 2001 From: Shafik Date: Fri, 10 Jan 2025 21:40:03 +0200 Subject: [PATCH 015/158] [FIXED] `attempt to index field 'IniDCSGroup' (a nil value)` --- Moose Development/Moose/Core/Set.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index eeb7baf0e..f0efc0763 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -7962,7 +7962,7 @@ do -- SET_OPSGROUP function SET_OPSGROUP:_EventOnDeadOrCrash( Event ) --self:F( { Event } ) - if Event.IniDCSUnit then + if Event.IniDCSGroup then local ObjectName, Object = self:FindInDatabase( Event ) if ObjectName then if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed. From fbcc4ee32b6750371184f6f53c23c0d971cf5276 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 11 Jan 2025 10:14:03 +0100 Subject: [PATCH 016/158] Update Unit.lua GetSTN fix if template os nil --- Moose Development/Moose/Wrapper/Unit.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 9230eaeee..6bfd2eb45 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1777,7 +1777,8 @@ function UNIT:GetSTN() local VCN = nil -- VoiceCallsignNumber local FGL = false -- FlightGroupLeader local template = self:GetTemplate() - if template.AddPropAircraft then + if template then + if template.AddPropAircraft then if template.AddPropAircraft.STN_L16 then STN = template.AddPropAircraft.STN_L16 elseif template.AddPropAircraft.SADL_TN then @@ -1793,6 +1794,6 @@ function UNIT:GetSTN() if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then FGL = template.datalinks.SADL.settings.flightLead end - + end return STN, VCL, VCN, FGL end From acfe1a856ec76d354b4e66608b98b07142b3e110 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 11 Jan 2025 23:38:15 +0100 Subject: [PATCH 017/158] Update OpsGroup.lua - Added surface type check for ARTY mission - Improved search for coordinate in range - Added option to specify distance conversion for weapon ranges --- Moose Development/Moose/Ops/OpsGroup.lua | 81 ++++++++++++++++-------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 9f7a76c0b..c9a59be3c 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -512,7 +512,7 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="1.0.3" +OPSGROUP.version="1.0.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1333,8 +1333,9 @@ end -- @param Core.Point#COORDINATE TargetCoord Coordinate of the target. -- @param #number WeaponBitType Weapon type. -- @param Core.Point#COORDINATE RefCoord Reference coordinate. +-- @param #table SurfaceTypes Valid surfaces types of the coordinate. Default any (nil). -- @return Core.Point#COORDINATE Coordinate in weapon range -function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord) +function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord, SurfaceTypes) local coordInRange=nil --Core.Point#COORDINATE @@ -1343,35 +1344,58 @@ function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord) -- Get weapon range. local weapondata=self:GetWeaponData(WeaponBitType) + -- Heading intervals to search for a possible new coordinate in range. + local dh={0, -5, 5, -10, 10, -15, 15, -20, 20, -25, 25, -30, 30} + + -- Function that checks if the given surface type is valid.ss + local function _checkSurface(point) + if SurfaceTypes then + local stype=land.getSurfaceType(point) + for _,sf in pairs(SurfaceTypes) do + if sf==stype then + return true + end + end + return false + else + return true + end + end + if weapondata then -- Heading to target. - local heading=RefCoord:HeadingTo(TargetCoord) + local heading=TargetCoord:HeadingTo(RefCoord) -- Distance to target. local dist=RefCoord:Get2DDistance(TargetCoord) + + local range=nil + if dist>weapondata.RangeMax then + range=weapondata.RangeMax + self:T(self.lid..string.format("Out of max range = %.1f km by %.1f km for weapon %s", weapondata.RangeMax/1000, (weapondata.RangeMax-dist)/1000, tostring(WeaponBitType))) + elseif distweapondata.RangeMax then - - local d=(dist-weapondata.RangeMax)*1.05 - - -- New waypoint coord. - coordInRange=RefCoord:Translate(d, heading) - - -- Debug info. - self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(WeaponBitType))) - elseif dist Date: Sun, 12 Jan 2025 17:03:18 +0100 Subject: [PATCH 018/158] #CTLD - InjectTroops - if precision coords is true, do also not randomize unit positions. --- Moose Development/Moose/Ops/CTLD.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 73d870cc3..26295b17a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1352,7 +1352,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.22" +CTLD.version="1.1.23" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5583,11 +5583,12 @@ end if PreciseLocation then randomcoord = zone:GetCoordinate():GetVec2() end + local randompositions = not PreciseLocation for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - :InitRandomizeUnits(true,20,2) + :InitRandomizeUnits(randompositions,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then From f6e6dcac9a5a541cf2cf65f890fdeb09161ee742 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 12 Jan 2025 23:54:44 +0100 Subject: [PATCH 019/158] OPSGROUP ARTY - Fixed bug in surface type for coord in range - Improved search for coord in range - Added option to set mission waypoint --- Moose Development/Moose/Ops/Auftrag.lua | 1 + Moose Development/Moose/Ops/OpsGroup.lua | 15 ++++++--------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 9000fd95a..98d42aa37 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2185,6 +2185,7 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude) mission.optionAlarm=0 mission.missionFraction=0.0 + mission.missionWaypointRadius=0.0 -- Evaluate after 8 min. mission.dTevaluate=8*60 diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c9a59be3c..352e30045 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1345,12 +1345,12 @@ function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord, Sur local weapondata=self:GetWeaponData(WeaponBitType) -- Heading intervals to search for a possible new coordinate in range. - local dh={0, -5, 5, -10, 10, -15, 15, -20, 20, -25, 25, -30, 30} + local dh={0, -5, 5, -10, 10, -15, 15, -20, 20, -25, 25, -30, 30, -35, 35, -40, 40, -45, 45, -50, 50, -55, 55, -60, 60, -65, 65, -70, 70, -75, 75, -80, 80} - -- Function that checks if the given surface type is valid.ss + -- Function that checks if the given surface type is valid local function _checkSurface(point) if SurfaceTypes then - local stype=land.getSurfaceType(point) + local stype=point:GetSurfaceType() for _,sf in pairs(SurfaceTypes) do if sf==stype then return true @@ -1387,7 +1387,7 @@ function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord, Sur local h=heading+delta -- New waypoint coord. - coordInRange=TargetCoord:Translate(range*1.02, h) + coordInRange=TargetCoord:Translate(range, h) if _checkSurface(coordInRange) then break @@ -6100,13 +6100,12 @@ function OPSGROUP:RouteToMission(mission, delay) -- Target Coord. local targetcoord=mission:GetTargetCoordinate() - -- In range already? - local inRange=self:InWeaponRange(targetcoord, mission.engageWeaponType) + local inRange=self:InWeaponRange(targetcoord, mission.engageWeaponType, waypointcoord) if inRange then - waypointcoord=self:GetCoordinate(true) + --waypointcoord=self:GetCoordinate(true) else @@ -6183,8 +6182,6 @@ function OPSGROUP:RouteToMission(mission, delay) formation=ENUMS.Formation.Vehicle.OffRoad end - waypointcoord:MarkToAll("Bla Bla") - waypoint=ARMYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, formation, false) elseif self:IsNavygroup() then From 01b7575bcac36c2e839e50b46b8e7c2bfa43b37f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 13 Jan 2025 11:46:51 +0100 Subject: [PATCH 020/158] #AWACS - Fix a logic error: cancel the Escort mission when the replacement has arrived - Workaround for a situation where a player has a checkin-entry in the menu and a managed group entry at the same time - which should not be the case. - Added Escort options for formation and relative position behind the AWACS --- Moose Development/Moose/Ops/Awacs.lua | 50 ++++++++++++++++++++------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 5c2c015a0..3bc1b74d5 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -17,7 +17,7 @@ -- === -- -- ### Author: **applevangelist** --- @date Last Update Dec 2024 +-- @date Last Update Jan 2025 -- @module Ops.AWACS -- @image OPS_AWACS.jpg @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.68", -- #string + version = "0.2.69", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -2170,8 +2170,10 @@ end --- [User] Set AWACS Escorts Template -- @param #AWACS self -- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts. +-- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group` +-- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=500,y=0,z=500}` - 500m behind and to the left, no vertical separation. -- @return #AWACS self -function AWACS:SetEscort(EscortNumber) +function AWACS:SetEscort(EscortNumber,Formation,OffsetVector) self:T(self.lid.."SetEscort") if EscortNumber and EscortNumber > 0 then self.HasEscorts = true @@ -2180,6 +2182,8 @@ function AWACS:SetEscort(EscortNumber) self.HasEscorts = false self.EscortNumber = 0 end + self.EscortFormation = Formation + self.OffsetVec = OffsetVector or {x=500,y=0,z=500} return self end @@ -2234,12 +2238,24 @@ function AWACS:_StartEscorts(Shiftchange) local group = AwacsFG:GetGroup() local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds + local OffsetX = 500 + local OffsetY = 500 + local OffsetZ = 500 + if self.OffsetVec then + OffsetX = self.OffsetVec.x + OffsetY = self.OffsetVec.y + OffsetZ = self.OffsetVec.z + end for i=1,self.EscortNumber do - -- every - local escort = AUFTRAG:NewESCORT(group, {x= -100*((i + (i%2))/2), y=0, z=(100 + 100*((i + (i%2))/2))*(-1)^i},45,{"Air"}) + -- every + local escort = AUFTRAG:NewESCORT(group, {x= -OffsetX*((i + (i%2))/2), y=OffsetY, z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},45,{"Air"}) escort:SetRequiredAssets(1) escort:SetTime(nil,timeonstation) + if self.Escortformation then + escort:SetFormation(self.Escortformation) + end escort:SetMissionRange(self.MaxMissionRange) + self.AirWing:AddMission(escort) self.CatchAllMissions[#self.CatchAllMissions+1] = escort @@ -3642,7 +3658,7 @@ function AWACS:_CheckIn(Group) managedgroup.LastTasking = timer.getTime() GID = managedgroup.GID - self.ManagedGrps[self.ManagedGrpID]=managedgroup + self.ManagedGrps[self.ManagedGrpID]=managedgroup local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate()) local alphacheckbullstts = self:_ToStringBULLS(Group:GetCoordinate(),false,true) @@ -3909,6 +3925,12 @@ function AWACS:_SetClientMenus() checkin = checkin, } self.clientmenus:Push(menus,cgrpname) + -- catch errors - when this entry is built we should NOT have a managed entry + local GID,hasentry = self:_GetManagedGrpID(cgrp) + if hasentry then + -- this user is checked in but has the check in entry ... not good. + self:_CheckOut(cgrp,GID,true) + end end end else @@ -6065,6 +6087,7 @@ function AWACS:_CheckAwacsStatus() end end end + -------------------------------- -- AWACS -------------------------------- @@ -6213,12 +6236,13 @@ function AWACS:_CheckAwacsStatus() report:Add("====================") + local RESMission -- Check for replacement mission - if any if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG - ESmission = self.EscortMissionReplacement[i] - local esstatus = ESmission:GetState() - local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp) - local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds + RESMission = self.EscortMissionReplacement[i] + local esstatus = RESMission:GetState() + local RESMissiontime = (timer.getTime() - self.EscortsTimeStamp) + local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - RESMissiontime),0) -- seconds ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) @@ -6226,7 +6250,7 @@ function AWACS:_CheckAwacsStatus() report:Add(string.format("Auftrag Status: %s",esstatus)) report:Add(string.format("TOS Left: %d min",ESTOSLeft)) - local OpsGroups = ESmission:GetOpsGroups() + local OpsGroups = RESMission:GetOpsGroups() local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP if OpsGroup then local OpsName = OpsGroup:GetName() or "Unknown" @@ -6238,13 +6262,13 @@ function AWACS:_CheckAwacsStatus() report:Add("***** Cannot obtain (yet) this missions OpsGroup!") end - if ESmission:IsExecuting() then + if RESMission and RESMission:IsExecuting() then -- make the actual change in the queue self.ShiftChangeEscortsFlag = false self.ShiftChangeEscortsRequested = false -- cancel old mission if ESmission and ESmission:IsNotOver() then - ESmission:Cancel() + ESmission:__Cancel(1) end self.EscortMission[i] = self.EscortMissionReplacement[i] self.EscortMissionReplacement[i] = nil From 1b2522072947f5bbfa39d0f95a820dfbfeb7dbd8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 13 Jan 2025 12:38:53 +0100 Subject: [PATCH 021/158] #AWACS - make # of escorts required assets --- Moose Development/Moose/Ops/Awacs.lua | 32 ++++++++++++--------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 3bc1b74d5..fbaa1e52a 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.69", -- #string + version = "0.2.70", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -2171,9 +2171,10 @@ end -- @param #AWACS self -- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts. -- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group` --- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=500,y=0,z=500}` - 500m behind and to the left, no vertical separation. +-- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=-500,y=0,z=500}` - 500m behind and to the right, no vertical separation. +-- @param #number EscortEngageMaxDistance Escorts engage air targets max this NM away, defaults to 45NM. -- @return #AWACS self -function AWACS:SetEscort(EscortNumber,Formation,OffsetVector) +function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance) self:T(self.lid.."SetEscort") if EscortNumber and EscortNumber > 0 then self.HasEscorts = true @@ -2183,7 +2184,8 @@ function AWACS:SetEscort(EscortNumber,Formation,OffsetVector) self.EscortNumber = 0 end self.EscortFormation = Formation - self.OffsetVec = OffsetVector or {x=500,y=0,z=500} + self.OffsetVec = OffsetVector or {x=500,y=100,z=500} + self.EscortEngageMaxDistance = EscortEngageMaxDistance or 45 return self end @@ -2238,18 +2240,12 @@ function AWACS:_StartEscorts(Shiftchange) local group = AwacsFG:GetGroup() local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds - local OffsetX = 500 - local OffsetY = 500 - local OffsetZ = 500 - if self.OffsetVec then - OffsetX = self.OffsetVec.x - OffsetY = self.OffsetVec.y - OffsetZ = self.OffsetVec.z - end - for i=1,self.EscortNumber do + + --for i=1,self.EscortNumber do -- every - local escort = AUFTRAG:NewESCORT(group, {x= -OffsetX*((i + (i%2))/2), y=OffsetY, z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},45,{"Air"}) - escort:SetRequiredAssets(1) + --local escort = AUFTRAG:NewESCORT(group, {x= -OffsetX*((i + (i%2))/2), y=OffsetY, z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},45,{"Air"}) + local escort = AUFTRAG:NewESCORT(group,self.OffsetVec,self.EscortEngageMaxDistance,{"Air"}) + escort:SetRequiredAssets(self.EscortNumber) escort:SetTime(nil,timeonstation) if self.Escortformation then escort:SetFormation(self.Escortformation) @@ -2260,11 +2256,11 @@ function AWACS:_StartEscorts(Shiftchange) self.CatchAllMissions[#self.CatchAllMissions+1] = escort if Shiftchange then - self.EscortMissionReplacement[i] = escort + self.EscortMissionReplacement[1] = escort else - self.EscortMission[i] = escort + self.EscortMission[1] = escort end - end + --end return self end From b2dc7bc232074868fa46ea80871226998802fbb2 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 14 Jan 2025 10:36:12 +0100 Subject: [PATCH 022/158] ARTY v1.3.2 - Fixed bug that MLRS does not fire (ammo count if weapon type is auto) --- Moose Development/Moose/Functional/Artillery.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index c1745cac0..5b283ee20 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -695,7 +695,7 @@ ARTY.db={ --- Arty script version. -- @field #string version -ARTY.version="1.3.1" +ARTY.version="1.3.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3049,7 +3049,7 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target) local nfire=Narty local _type="shots" if target.weapontype==ARTY.WeaponType.Auto then - nfire=Narty + nfire=Nammo -- We take everything that is available _type="shots" elseif target.weapontype==ARTY.WeaponType.Cannon then nfire=Narty @@ -3070,6 +3070,8 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target) nfire=Nmissiles _type="cruise missiles" end + + --env.info(string.format("FF type=%s, Nrockets=%d, Nfire=%d target.nshells=%d", _type, Nrockets, nfire, target.nshells)) -- Adjust if less than requested ammo is left. target.nshells=math.min(target.nshells, nfire) From 7392cb9bf3e4967eda53bff3662ae6a9f116acf7 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:14:14 +0100 Subject: [PATCH 023/158] Update Awacs.lua #AWACS - clarify use of escort parameters and template --- Moose Development/Moose/Ops/Awacs.lua | 34 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index fbaa1e52a..b777054fe 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -184,7 +184,7 @@ do -- -- Add Escorts Squad (recommended, optional) -- --- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North") +-- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North") -- taking a template with 2 planes here, will result in a group of 2 escorts which can fly in formation escorting the AWACS. -- Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT}) -- Squad_Two:SetFuelLowRefuel(true) -- Squad_Two:SetFuelLowThreshold(0.3) @@ -232,8 +232,8 @@ do -- -- set up in the mission editor with a late activated helo named "Rock#ZONE_POLYGON". Note this also sets the BullsEye to be referenced as "Rock". -- -- The CAP station zone is called "Fremont". We will be on 255 AM. -- local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM ) --- -- set two escorts --- testawacs:SetEscort(2) +-- -- set one escort group; this example has two units in the template group, so they can fly a nice formation. +-- testawacs:SetEscort(1,ENUMS.Formation.FixedWing.FingerFour.Group,{x=-500,y=50,z=500},45) -- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm. -- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25) -- -- Set up SRS on port 5010 - change the below to your path and port @@ -2169,9 +2169,9 @@ end --- [User] Set AWACS Escorts Template -- @param #AWACS self --- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts. --- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group` --- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=-500,y=0,z=500}` - 500m behind and to the right, no vertical separation. +-- @param #number EscortNumber Number of fighther plane GROUPs to accompany this AWACS. 0 or nil means no escorts. If you want >1 plane in an escort group, you can either set the respective squadron grouping to the desired number, or use a template for escorts with >1 unit. +-- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group`. Formation is used on GROUP level, multiple groups of one unit will NOT conform to this formation. +-- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=-500,y=0,z=500}` - 500m behind (negative value) and to the right (negative for left), no vertical separation (positive over, negative under the AWACS flight). For multiple groups, the vectors will be slightly changed to avoid collisions. -- @param #number EscortEngageMaxDistance Escorts engage air targets max this NM away, defaults to 45NM. -- @return #AWACS self function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance) @@ -2240,12 +2240,20 @@ function AWACS:_StartEscorts(Shiftchange) local group = AwacsFG:GetGroup() local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds + local OffsetX = 500 + local OffsetY = 500 + local OffsetZ = 500 + if self.OffsetVec then + OffsetX = self.OffsetVec.x + OffsetY = self.OffsetVec.y + OffsetZ = self.OffsetVec.z + end - --for i=1,self.EscortNumber do + for i=1,self.EscortNumber do -- every - --local escort = AUFTRAG:NewESCORT(group, {x= -OffsetX*((i + (i%2))/2), y=OffsetY, z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},45,{"Air"}) - local escort = AUFTRAG:NewESCORT(group,self.OffsetVec,self.EscortEngageMaxDistance,{"Air"}) - escort:SetRequiredAssets(self.EscortNumber) + local escort = AUFTRAG:NewESCORT(group, {x= OffsetX*((i + (i%2))/2), y=OffsetY*((i + (i%2))/2), z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},self.EscortEngageMaxDistance,{"Air"}) + --local escort = AUFTRAG:NewESCORT(group,self.OffsetVec,self.EscortEngageMaxDistance,{"Air"}) + --escort:SetRequiredAssets(self.EscortNumber) escort:SetTime(nil,timeonstation) if self.Escortformation then escort:SetFormation(self.Escortformation) @@ -2256,11 +2264,11 @@ function AWACS:_StartEscorts(Shiftchange) self.CatchAllMissions[#self.CatchAllMissions+1] = escort if Shiftchange then - self.EscortMissionReplacement[1] = escort + self.EscortMissionReplacement[i] = escort else - self.EscortMission[1] = escort + self.EscortMission[i] = escort end - --end + end return self end From b522b38d31e5e4c0373cd83a9a644bc3806a8141 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 17 Jan 2025 09:21:48 +0100 Subject: [PATCH 024/158] #SCENERY - Some fixes for kissing getLife function on some objects, and life points being zero on some objects --- Moose Development/Moose/Wrapper/Scenery.lua | 25 ++++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index 2706aad71..eb69e7ab4 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -48,8 +48,8 @@ function SCENERY:Register( SceneryName, SceneryObject ) self.SceneryObject = SceneryObject - if self.SceneryObject then - self.Life0 = self.SceneryObject:getLife() + if self.SceneryObject and self.SceneryObject.getLife then -- fix some objects do not have all functions + self.Life0 = self.SceneryObject:getLife() or 0 else self.Life0 = 0 end @@ -59,7 +59,7 @@ function SCENERY:Register( SceneryName, SceneryObject ) return self end ---- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. +--- Returns the value of the scenery with the given PropertyName, or nil if no matching property exists. -- @param #SCENERY self -- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved. -- @return #string The Value of the QuadZone Property from the scenery assignment with the given PropertyName, or nil if absent. @@ -67,6 +67,14 @@ function SCENERY:GetProperty(PropertyName) return self.Properties[PropertyName] end +--- Checks if the value of the scenery with the given PropertyName exists. +-- @param #SCENERY self +-- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved. +-- @return #boolean Outcome True if it exists, else false. +function SCENERY:HasProperty(PropertyName) + return self.Properties[PropertyName] ~= nil and true or false +end + --- Returns the scenery Properties table. -- @param #SCENERY self -- @return #table The Key:Value table of QuadZone properties of the zone from the scenery assignment . @@ -97,7 +105,7 @@ function SCENERY:GetDCSObject() return self.SceneryObject end ---- Get current life points from the SCENERY Object. +--- Get current life points from the SCENERY Object. Note - Some scenery objects always have 0 life points. -- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120% -- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success -- criteria for a bombing task. @@ -105,7 +113,7 @@ end --@return #number life function SCENERY:GetLife() local life = 0 - if self.SceneryObject then + if self.SceneryObject and self.SceneryObject.getLife then life = self.SceneryObject:getLife() if life > self.Life0 then self.Life0 = math.floor(life * 1.2) @@ -121,7 +129,7 @@ function SCENERY:GetLife0() return self.Life0 or 0 end ---- Check if SCENERY Object is alive. +--- Check if SCENERY Object is alive. Note - Some scenery objects always have 0 life points. --@param #SCENERY self --@param #number Threshold (Optional) If given, SCENERY counts as alive above this relative life in percent (1..100). --@return #number life @@ -133,7 +141,7 @@ function SCENERY:IsAlive(Threshold) end end ---- Check if SCENERY Object is dead. +--- Check if SCENERY Object is dead. Note - Some scenery objects always have 0 life points. --@param #SCENERY self --@param #number Threshold (Optional) If given, SCENERY counts as dead below this relative life in percent (1..100). --@return #number life @@ -145,12 +153,13 @@ function SCENERY:IsDead(Threshold) end end ---- Get SCENERY relative life in percent, e.g. 75. +--- Get SCENERY relative life in percent, e.g. 75. Note - Some scenery objects always have 0 life points. --@param #SCENERY self --@return #number rlife function SCENERY:GetRelativeLife() local life = self:GetLife() local life0 = self:GetLife0() + if life == 0 or life0 == 0 then return 0 end local rlife = math.floor((life/life0)*100) return rlife end From 7f7999e3e5264800fc8cf1e07c0d05aa4a8ec53b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 17 Jan 2025 09:54:41 +0100 Subject: [PATCH 025/158] #CSAR - Make the list of downed pilots' coordinate dependent on _SETTINGS global or pilot --- Moose Development/Moose/Ops/CSAR.lua | 35 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 8cc55125c..e7805f25a 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -809,6 +809,8 @@ end -- @param #boolean noMessage -- @param #string _description Description -- @param #boolean forcedesc Use the description only for the pilot track entry +-- @return Wrapper.Group#GROUP PilotInField Pilot GROUP object +-- @return #string AliasName Alias display name function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc ) self:T(self.lid .. " _AddCsar") self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) @@ -878,7 +880,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. - return self + return _spawnedGroup, _alias end --- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. @@ -1829,8 +1831,9 @@ end --- (Internal) Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. +-- @param Wrapper.Unit#UNIT _Unit Requesting helo pilot unit -- @return #string Coordinates as Text -function CSAR:_GetPositionOfWounded(_woundedGroup) +function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit) self:T(self.lid .. " _GetPositionOfWounded") local _coordinate = _woundedGroup:GetCoordinate() local _coordinatesText = "None" @@ -1845,6 +1848,26 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end end + if _Unit and _Unit:GetPlayerName() then + local playername = _Unit:GetPlayerName() + if playername then + local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS + if settings then + self:T("Get Settings ok!") + if settings:IsA2G_MGRS() then + _coordinatesText = _coordinate:ToStringMGRS(settings) + elseif settings:IsA2G_LL_DMS() then + _coordinatesText = _coordinate:ToStringLLDMS(settings) + elseif settings:IsA2G_LL_DDM() then + _coordinatesText = _coordinate:ToStringLLDDM(settings) + elseif settings:IsA2G_BR() then + -- attention this is the distance from the ASKING unit to target, not from RECCE to target! + local startcoordinate = _Unit:GetCoordinate() + _coordinatesText = _coordinate:ToStringBR(startcoordinate,settings) + end + end + end + end return _coordinatesText end @@ -1870,13 +1893,17 @@ function CSAR:_DisplayActiveSAR(_unitName) self:T({Table=_value}) local _woundedGroup = _value.group if _woundedGroup and _value.alive then - local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup,_heli) local _helicoord = _heli:GetCoordinate() local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) local distancetext = "" - if _SETTINGS:IsImperial() then + local settings = _SETTINGS + if _heli:GetPlayerName() then + settings = _DATABASE:GetPlayerSettings(_heli:GetPlayerName()) or _SETTINGS + end + if settings:IsImperial() then distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) else distancetext = string.format("%.1fkm", _distance/1000.0) From 72bb23ed0d7111f92063073e332ef4cf5d621550 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 17 Jan 2025 09:55:12 +0100 Subject: [PATCH 026/158] xx --- .../Moose/Functional/Autolase.lua | 22 ++++- Moose Development/Moose/Ops/PlayerTask.lua | 85 +++++++++++++++++-- 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 423fc55fe..7a8545b79 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -74,7 +74,7 @@ -- @image Designation.JPG -- -- Date: 24 Oct 2021 --- Last Update: May 2024 +-- Last Update: Jan 2025 -- --- Class AUTOLASE -- @type AUTOLASE @@ -89,6 +89,7 @@ -- @field #table playermenus -- @field #boolean smokemenu -- @field #boolean threatmenu +-- @field #number RoundingPrecision -- @extends Ops.Intel#INTEL --- @@ -100,6 +101,7 @@ AUTOLASE = { alias = "", debug = false, smokemenu = true, + RoundingPrecision = 0, } --- Laser spot info @@ -118,7 +120,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.26" +AUTOLASE.version = "0.1.27" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -207,6 +209,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.playermenus = {} self.smokemenu = true self.threatmenu = true + self.RoundingPrecision = 0 -- Set some string id for output to DCS.log file. self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") @@ -600,6 +603,15 @@ function AUTOLASE:SetSmokeTargets(OnOff,Color) return self end +--- (User) Function to set rounding precision for BR distance output. +-- @param #AUTOLASE self +-- @param #number IDP Rounding precision before/after the decimal sign. Defaults to zero. Positive values round right of the decimal sign, negative ones left of the decimal sign. +-- @return #AUTOLASE self +function AUTOLASE:SetRoundingPrecsion(IDP) + self.RoundingPrecision = IDP or 0 + return self +end + --- (User) Show the "Switch smoke target..." menu entry for pilots. On by default. -- @param #AUTOLASE self -- @return #AUTOLASE self @@ -786,8 +798,12 @@ function AUTOLASE:ShowStatus(Group,Unit) locationstring = entry.coordinate:ToStringMGRS(settings) elseif settings:IsA2G_LL_DMS() then locationstring = entry.coordinate:ToStringLLDMS(settings) + elseif settings:IsA2G_LL_DDM() then + locationstring = entry.coordinate:ToStringLLDDM(settings) elseif settings:IsA2G_BR() then - locationstring = entry.coordinate:ToStringBR(Group:GetCoordinate() or Unit:GetCoordinate(),settings) + -- attention this is the distance from the ASKING unit to target, not from RECCE to target! + local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate() + locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision) end end end diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 1258462a7..f30bc6f11 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -98,7 +98,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.24" +PLAYERTASK.version="0.1.25" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -700,6 +700,15 @@ function PLAYERTASK:IsDone() return IsDone end +--- [User] Check if task is NOT done +-- @param #PLAYERTASK self +-- @return #boolean done +function PLAYERTASK:IsNotDone() + self:T(self.lid.."IsNotDone?") + local IsNotDone = not self:IsDone() + return IsNotDone +end + --- [User] Check if PLAYERTASK has clients assigned to it. -- @param #PLAYERTASK self -- @return #boolean hasclients @@ -1156,7 +1165,7 @@ function PLAYERTASK:onafterCancel(From, Event, To) self.TaskController:__TaskCancelled(-1,self) end self.timestamp = timer.getAbsTime() - self.FinalState = "Cancel" + self.FinalState = "Cancelled" self:__Done(-1) return self end @@ -1308,6 +1317,8 @@ do -- @field Core.ClientMenu#CLIENTMENU ActiveTopMenu -- @field Core.ClientMenu#CLIENTMENU ActiveInfoMenu -- @field Core.ClientMenu#CLIENTMENU MenuNoTask +-- @field #boolean InformationMenu Show Radio Info Menu +-- @field #number TaskInfoDuration How long to show the briefing info on the screen -- @extends Core.Fsm#FSM --- @@ -1663,6 +1674,8 @@ PLAYERTASKCONTROLLER = { UseTypeNames = false, Scoring = nil, MenuNoTask = nil, + InformationMenu = false, + TaskInfoDuration = 30, } --- @@ -1799,6 +1812,7 @@ PLAYERTASKCONTROLLER.Messages = { CRUISER = "Cruiser", DESTROYER = "Destroyer", CARRIER = "Aircraft Carrier", + RADIOS = "Radios", }, DE = { TASKABORT = "Auftrag abgebrochen!", @@ -1882,12 +1896,13 @@ PLAYERTASKCONTROLLER.Messages = { CRUISER = "Kreuzer", DESTROYER = "Zerstörer", CARRIER = "Flugzeugträger", + RADIOS = "Frequenzen", }, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.67" +PLAYERTASKCONTROLLER.version="0.1.69" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -1949,6 +1964,10 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.UseTypeNames = false + self.InformationMenu = false + + self.TaskInfoDuration = 30 + self.IsClientSet = false if ClientFilter and type(ClientFilter) == "table" and ClientFilter.ClassName and ClientFilter.ClassName == "SET_CLIENT" then @@ -2166,6 +2185,16 @@ function PLAYERTASKCONTROLLER:SetAllowFlashDirection(OnOff) return self end +--- [User] Set to show a menu entry to retrieve the radio frequencies used. +-- @param #PLAYERTASKCONTROLLER self +-- @param #boolean OnOff Set to `true` to switch on and `false` to switch off. Default is OFF. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetShowRadioInfoMenu(OnOff) + self:T(self.lid.."SetAllowRadioInfoMenu") + self.InformationMenu = OnOff + return self +end + --- [User] Do not show menu entries to smoke or flare targets -- @param #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self @@ -2261,7 +2290,7 @@ function PLAYERTASKCONTROLLER:_GetTextForSpeech(text) return text end ---- [User] Set repetition options for tasks +--- [User] Set repetition options for tasks. -- @param #PLAYERTASKCONTROLLER self -- @param #boolean OnOff Set to `true` to switch on and `false` to switch off (defaults to true) -- @param #number Repeats Number of repeats (defaults to 5) @@ -2279,6 +2308,16 @@ function PLAYERTASKCONTROLLER:SetTaskRepetition(OnOff, Repeats) return self end +--- [User] Set how long the briefing is shown on screen. +-- @param #PLAYERTASKCONTROLLER self +-- @param #number Seconds Duration in seconds. Defaults to 30 seconds. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetBriefingDuration(Seconds) + self:T(self.lid.."SetBriefingDuration") + self.TaskInfoDuration = Seconds or 30 + return self +end + --- [Internal] Send message to SET_CLIENT of players -- @param #PLAYERTASKCONTROLLER self -- @param #string Text the text to be send @@ -3464,6 +3503,32 @@ function PLAYERTASKCONTROLLER:_SwitchFlashing(Group, Client) return self end +function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client) + self:T(self.lid.."_ShowRadioInfo") + local playername, ttsplayername = self:_GetPlayerName(Client) + + if self.UseSRS then + local frequency = self.Frequency + local freqtext = "" + if type(frequency) == "table" then + freqtext = self.gettext:GetEntry("FREQUENCIES",self.locale) + freqtext = freqtext..table.concat(frequency,", ") + else + local freqt = self.gettext:GetEntry("FREQUENCY",self.locale) + freqtext = string.format(freqt,frequency) + end + + local switchtext = self.gettext:GetEntry("BROADCAST",self.locale) + + playername = ttsplayername or self:_GetTextForSpeech(playername) + --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) + local text = string.format(switchtext,playername,self.MenuName or self.Name,freqtext) + self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2,{Group},text,30,self.BCFrequency,self.BCModulation) + end + + return self +end + --- [Internal] Flashing directional info for a client -- @param #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self @@ -3683,7 +3748,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then - local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) + local m=MESSAGE:New(text,self.TaskInfoDuration or 30,"Tasking"):ToClient(Client) end return self end @@ -4037,6 +4102,11 @@ function PLAYERTASKCONTROLLER:_CreateJoinMenuTemplate() self.MenuNoTask = nil end + if self.InformationMenu then + local radioinfo = self.gettext:GetEntry("RADIOS",self.locale) + JoinTaskMenuTemplate:NewEntry(radioinfo,self.JoinTopMenu,self._ShowRadioInfo,self) + end + self.JoinTaskMenuTemplate = JoinTaskMenuTemplate return self @@ -4376,8 +4446,9 @@ end -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS; if you use a config file for MSRS, hand in nil here. -- @param #string AccessKey (Optional) Your Google API access key. This is necessary if DCS-gRPC is used as backend; if you use a config file for MSRS, hand in nil here. -- @param Core.Point#COORDINATE Coordinate Coordinate from which the controller radio is sending +-- @param #string Backend (Optional) MSRS Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC; if you use a config file for MSRS, hand in nil here. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate) +function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend) self:T(self.lid.."SetSRS") self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.Gender = Gender or MSRS.gender or "male" -- @@ -4393,7 +4464,7 @@ function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Cultu self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} -- self.BCModulation = self.Modulation -- set up SRS - self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation) + self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,Backend) self.SRS:SetCoalition(self.Coalition) self.SRS:SetLabel(self.MenuName or self.Name) self.SRS:SetGender(self.Gender) From 61b7b3ead66cf4416dbb8d4a3e599740ed9f529e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 18 Jan 2025 15:30:03 +0100 Subject: [PATCH 027/158] Smaller fixes --- Moose Development/Moose/Functional/Fox.lua | 2 +- Moose Development/Moose/Wrapper/Group.lua | 8 ++++++-- Moose Development/Moose/Wrapper/Positionable.lua | 9 ++++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 5ca6d4e51..38d8eb585 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -1060,7 +1060,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) -- Tracking info and init of last bomb position. local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName) - self:I(FOX.lid..text) + self:T(FOX.lid..text) MESSAGE:New(text, 10):ToAllIf(self.Debug) -- Loop over players. diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 3760a84fc..ef0561f84 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -3003,7 +3003,7 @@ end -- local callsign = mygroup:GetCustomCallSign(true,false,nil,function(groupname,playername) return string.match(playername,"([%a]+)$") end) -- function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,CustomFunction,...) - --self:I("GetCustomCallSign") + self:T("GetCustomCallSign") local callsign = "Ghost 1" if self:IsAlive() then @@ -3016,8 +3016,12 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,C local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9 local callnumberminor = string.char(string.byte(callnumber,2)) -- 1 local personalized = false - local playername = IsPlayer == true and self:GetPlayerName() or shortcallsign + --local playername = IsPlayer == true and self:GetPlayerName() or shortcallsign + local playername = shortcallsign + if IsPlayer then playername = self:GetPlayerName() end + + self:T2("GetCustomCallSign outcome = "..playername) if CustomFunction and IsPlayer then local arguments = arg or {} local callsign = CustomFunction(groupname,playername,unpack(arguments)) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 9d8c77873..df5fdcb37 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -110,14 +110,17 @@ function POSITIONABLE:Destroy( GenerateEvent ) if GenerateEvent and GenerateEvent == true then if self:IsAir() then + --self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),DCSObject) self:CreateEventCrash( timer.getTime(), DCSObject ) else + --self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),DCSObject) self:CreateEventDead( timer.getTime(), DCSObject ) end elseif GenerateEvent == false then -- Do nothing! else self:CreateEventRemoveUnit( timer.getTime(), DCSObject ) + --self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),DCSObject) end USERFLAG:New( UnitGroupName ):Set( 100 ) @@ -142,7 +145,11 @@ function POSITIONABLE:GetPosition() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() - + + if self:IsInstanceOf("GROUP") then + DCSPositionable = self:GetFirstUnitAlive():GetDCSObject() + end + if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition() self:T3( PositionablePosition ) From 8d87531464ecca3c4dce54bce62c9bc2cd529540 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 18 Jan 2025 15:32:08 +0100 Subject: [PATCH 028/158] #PLAYERRECCE - Add standard FOW for Kiowa --- Moose Development/Moose/Ops/PlayerRecce.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 7091139cd..3ada7882c 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -106,7 +106,7 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.1.24", + version = "0.1.25", ViewZone = {}, ViewZoneVisual = {}, ViewZoneLaser = {}, @@ -166,7 +166,8 @@ PLAYERRECCE.MaxViewDistance = { ["SA342Minigun"] = 8000, ["SA342L"] = 8000, ["Ka-50"] = 8000, - ["Ka-50_3"] = 8000, + ["Ka-50_3"] = 8000, + ["OH58D"] = 8000, } --- From bd074728fe5e66f96dde3a56d4bdaa56e029d2ab Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 18 Jan 2025 22:34:21 +0100 Subject: [PATCH 029/158] Update Artillery.lua - Update Arty DB --- .../Moose/Functional/Artillery.lua | 122 ++++++++++++------ 1 file changed, 80 insertions(+), 42 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 5b283ee20..afe21a14c 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -534,7 +534,7 @@ ARTY={ ClassName="ARTY", lid=nil, - Debug=false, + Debug=true, targets={}, moves={}, currentTarget=nil, @@ -619,63 +619,101 @@ ARTY.WeaponType={ } --- Database of common artillery unit properties. +-- @type ARTY.dbitem +-- @field #string displayname Name displayed in ME. +-- @field #number minrange Minimum firing range in meters. +-- @field #number maxrange Maximum firing range in meters. +-- @field #number reloadtime Reload time in seconds. + +--- Database of common artillery unit properties. +-- Table key is the "type name" and table value is and `ARTY.dbitem`. -- @type ARTY.db ARTY.db={ - ["2B11 mortar"] = { -- type "2B11 mortar" - minrange = 500, -- correct? - maxrange = 7000, -- 7 km - reloadtime = 30, -- 30 sec + ["2B11 mortar"] = { + displayname = "Mortar 2B11 120mm", + minrange = 500, -- correct? + maxrange = 7000, -- 7 km + reloadtime = 30, -- 30 sec }, - ["SPH 2S1 Gvozdika"] = { -- type "SAU Gvozdika" - minrange = 300, -- correct? - maxrange = 15000, -- 15 km - reloadtime = nil, -- unknown + ["SAU Gvozdika"] = { + displayname = "SPH 2S1 Gvozdika 122mm", + minrange = 300, -- correct? + maxrange = 15000, -- 15 km + reloadtime = nil, -- unknown }, - ["SPH 2S19 Msta"] = { --type "SAU Msta", alias "2S19 Msta" - minrange = 300, -- correct? - maxrange = 23500, -- 23.5 km - reloadtime = nil, -- unknown + ["SAU Msta"] = { + displayname = "SPH 2S19 Msta 152mm", + minrange = 300, -- correct? + maxrange = 23500, -- 23.5 km + reloadtime = nil, -- unknown }, - ["SPH 2S3 Akatsia"] = { -- type "SAU Akatsia", alias "2S3 Akatsia" - minrange = 300, -- correct? - maxrange = 17000, -- 17 km - reloadtime = nil, -- unknown + ["SAU Akatsia"] = { + displayname = "SPH 2S3 Akatsia 152mm", + minrange = 300, + maxrange = 17000, + reloadtime = nil, }, - ["SPH 2S9 Nona"] = { --type "SAU 2-C9" - minrange = 500, -- correct? - maxrange = 7000, -- 7 km - reloadtime = nil, -- unknown + ["SAU 2-C9"] = { + displayname = "SPM 2S9 Nona 120mm M", + minrange = 500, + maxrange = 7000, + reloadtime = nil, }, - ["SPH M109 Paladin"] = { -- type "M-109", alias "M109" - minrange = 300, -- correct? - maxrange = 22000, -- 22 km - reloadtime = nil, -- unknown + ["M-109"] = { + displayname = "SPH M109 Paladin 155mm", + minrange = 300, + maxrange = 22000, + reloadtime = nil, }, - ["SpGH Dana"] = { -- type "SpGH_Dana" - minrange = 300, -- correct? - maxrange = 18700, -- 18.7 km - reloadtime = nil, -- unknown + ["SpGH_Dana"] = { + displayname = "SPH Dana vz77 152mm", + minrange = 300, + maxrange = 18700, + reloadtime = nil, }, ["MLRS BM-21 Grad"] = { --type "Grad-URAL", alias "MLRS BM-21 Grad" - minrange = 5000, -- 5 km - maxrange = 19000, -- 19 km - reloadtime = 420, -- 7 min + minrange = 5000, + maxrange = 19000, + reloadtime = 420, }, ["MLRS 9K57 Uragan BM-27"] = { -- type "Uragan_BM-27" - minrange = 11500, -- 11.5 km - maxrange = 35800, -- 35.8 km - reloadtime = 840, -- 14 min + minrange = 11500, + maxrange = 35800, + reloadtime = 840, }, ["MLRS 9A52 Smerch"] = { -- type "Smerch" - minrange = 20000, -- 20 km - maxrange = 70000, -- 70 km - reloadtime = 2160, -- 36 min + minrange = 20000, + maxrange = 70000, + reloadtime = 2160, }, ["MLRS M270"] = { --type "MRLS", alias "M270 MRLS" - minrange = 10000, -- 10 km - maxrange = 32000, -- 32 km - reloadtime = 540, -- 9 min + minrange = 10000, + maxrange = 32000, + reloadtime = 540, }, + ["M12_GMC"] = { + displayname = "SPH M12 GMC 155mm", + minrange = 300, + maxrange = 18200, + reloadtime = nil, + }, + ["Wespe124"] = { + displayname = "SPH Sd.Kfz.124 Wespe 105mm", + minrange = 300, + maxrange = 7000, + reloadtime = nil, + }, + ["T155_Firtina"] = { + displayname = "SPH T155 Firtina 155mm", + minrange = 300, + maxrange = 41000, + reloadtime = nil, + }, + ["LeFH_18-40-105"] = { + minrange = 500, -- 500 m(?) + maxrange = 10500, -- 32 km + reloadtime = 540, -- 9 min + }, } --- Target. @@ -1923,7 +1961,7 @@ function ARTY:onafterStart(Controllable, From, Event, To) end -- Check if we have and arty type that is in the DB. - local _dbproperties=self:_CheckDB(self.DisplayName) + local _dbproperties=self:_CheckDB(self.Type) self:T({dbproperties=_dbproperties}) if _dbproperties~=nil then for property,value in pairs(_dbproperties) do From a3c13c8ceaad834b4cd838ec86885891d36c75ad Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 19 Jan 2025 17:41:02 +0100 Subject: [PATCH 030/158] #PLAYERTASK - added option for custom player callsigns #PLAYERRECCE - added option for custom player callsigns #PLAYERRECCE - added KIOWA support --- Moose Development/Moose/Ops/PlayerRecce.lua | 151 +++++++++++++------- Moose Development/Moose/Ops/PlayerTask.lua | 12 +- 2 files changed, 110 insertions(+), 53 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 3ada7882c..1b518dbe2 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -106,7 +106,7 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.1.25", + version = "0.1.26", ViewZone = {}, ViewZoneVisual = {}, ViewZoneLaser = {}, @@ -154,7 +154,8 @@ PLAYERRECCE.LaserRelativePos = { ["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 }, ["SA342L"] = { x = 1.7, y = 1.2, z = 0 }, ["Ka-50"] = { x = 6.1, y = -0.85 , z = 0 }, - ["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 } + ["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 }, + ["OH58D"] = {x = 0, y = 2.8, z = 0}, } --- @@ -179,7 +180,8 @@ PLAYERRECCE.Cameraheight = { ["SA342Minigun"] = 2.85, ["SA342L"] = 2.85, ["Ka-50"] = 0.5, - ["Ka-50_3"] = 0.5, + ["Ka-50_3"] = 0.5, + ["OH58D"] = 4.25, } --- @@ -191,7 +193,8 @@ PLAYERRECCE.CanLase = { ["SA342Minigun"] = false, -- no optics ["SA342L"] = true, ["Ka-50"] = true, - ["Ka-50_3"] = true, + ["Ka-50_3"] = true, + ["OH58D"] = false, -- has onboard and useable laser } --- @@ -547,7 +550,7 @@ function PLAYERRECCE:SetAttackSet(AttackSet) return self end ----[Internal] Check Gazelle camera in on +---[Internal] Check Helicopter camera in on -- @param #PLAYERRECCE self -- @param Wrapper.Client#CLIENT client -- @param #string playername @@ -563,6 +566,12 @@ function PLAYERRECCE:_CameraOn(client,playername) if vivihorizontal < -0.7 or vivihorizontal > 0.7 then camera = false end + elseif string.find(typename,"OH58") then + local dcsunit = Unit.getByName(client:GetName()) + local vivihorizontal = dcsunit:getDrawArgumentValue(528) or 0 -- Kiow + if vivihorizontal < -0.527 or vivihorizontal > 0.527 then + camera = false + end elseif string.find(typename,"Ka-50") then camera = true end @@ -570,6 +579,52 @@ function PLAYERRECCE:_CameraOn(client,playername) return camera end +--- [Internal] Get the view parameters from a Kiowa MMS camera +-- @param #PLAYERRECCE self +-- @param Wrapper.Unit#UNIT Kiowa +-- @return #number cameraheading in degrees. +-- @return #number cameranodding in degrees. +-- @return #number maxview in meters. +-- @return #boolean cameraison If true, camera is on, else off. +function PLAYERRECCE:_GetKiowaMMSSight(Kiowa) + self:T(self.lid.."_GetKiowaMMSSight") + local unit = Kiowa -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + local dcsunit = Unit.getByName(Kiowa:GetName()) + --[[ + shagrat — 01/01/2025 23:13 + Found the necessary ARGS for the Kiowa MMS angle and rotation: + Arg 527 vertical movement + 0 = neutral + -1.0 = max depression (30° max depression angle) + +1.0 = max elevation angle (30° max elevation angle) + + Arg 528 horizontal movement + 0 = forward (0 degr) + -0.25 = 90° left + -0.5 = rear (180°) left (max 190° = -0.527 + +0.25 = 90° right + +0.5 = 180° right (max 190° = 0.527) + --]] + local mmshorizontal = dcsunit:getDrawArgumentValue(528) or 0 + local mmsvertical = dcsunit:getDrawArgumentValue(527) or 0 + self:T(string.format("Kiowa MMS Arguments Read: H %.3f V %.3f",mmshorizontal,mmsvertical)) + local mmson = true + if mmshorizontal < -0.527 or mmshorizontal > 0.527 then mmson = false end + local horizontalview = mmshorizontal / 0.527 * 190 + local heading = unit:GetHeading() + local mmsheading = (heading+horizontalview)%360 + --local mmsyaw = mmsvertical * 30 + local mmsyaw = math.atan(mmsvertical)*40 + local maxview = self:_GetActualMaxLOSight(unit,mmsheading, mmsyaw,not mmson) + if maxview > 8000 then maxview = 8000 end + self:T(string.format("Kiowa MMS Heading %d, Yaw %d, MaxView %dm MMS On %s",mmsheading,mmsyaw,maxview,tostring(mmson))) + return mmsheading,mmsyaw,maxview,mmson + end + return 0,0,0,false +end + + --- [Internal] Get the view parameters from a Gazelle camera -- @param #PLAYERRECCE self -- @param Wrapper.Unit#UNIT Gazelle @@ -598,40 +653,15 @@ function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) vivioff = true return 0,0,0,false end - vivivertical = vivivertical / 1.10731 -- normalize + local horizontalview = vivihorizontal * -180 - local verticalview = vivivertical * 30 -- ca +/- 30° - --self:I(string.format("vivihorizontal=%.5f | vivivertical=%.5f",vivihorizontal,vivivertical)) - --self:I(string.format("horizontal=%.5f | vertical=%.5f",horizontalview,verticalview)) + --local verticalview = vivivertical * 30 -- ca +/- 30° + local verticalview = math.atan(vivivertical) + local heading = unit:GetHeading() local viviheading = (heading+horizontalview)%360 local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff) - --self:I(string.format("maxview=%.5f",maxview)) - -- visual skew - local factor = 3.15 - self.GazelleViewFactors = { - [1]=1.18, - [2]=1.32, - [3]=1.46, - [4]=1.62, - [5]=1.77, - [6]=1.85, - [7]=2.05, - [8]=2.05, - [9]=2.3, - [10]=2.3, - [11]=2.27, - [12]=2.27, - [13]=2.43, - } - local lfac = UTILS.Round(maxview,-2) - if lfac <= 1300 then - --factor = self.GazelleViewFactors[lfac/100] - factor = 3.15 - maxview = math.ceil((maxview*factor)/100)*100 - end if maxview > 8000 then maxview = 8000 end - --self:I(string.format("corrected maxview=%.5f",maxview)) return viviheading, verticalview,maxview, not vivioff end return 0,0,0,false @@ -652,20 +682,20 @@ function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff) if unit and unit:IsAlive() then local typename = unit:GetTypeName() maxview = self.MaxViewDistance[typename] or 8000 - local CamHeight = self.Cameraheight[typename] or 0 - if vnod < 0 then + local CamHeight = self.Cameraheight[typename] or 1 + if vnod < -2 then -- Looking down -- determine max distance we're looking at local beta = 90 - local gamma = math.floor(90-vnod) - local alpha = math.floor(180-beta-gamma) + local gamma = 90-math.abs(vnod) + local alpha = 90-gamma local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight local b = a / math.sin(math.rad(alpha)) local c = b * math.sin(math.rad(gamma)) maxview = c*1.2 -- +20% end end - return math.abs(maxview) + return math.ceil(math.abs(maxview)) end --- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. @@ -674,8 +704,10 @@ end -- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. -- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized -- callsigns from playername or group name. +-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername. -- @return #PLAYERRECCE self -function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign == false then self.ShortCallsign = false else @@ -683,6 +715,8 @@ function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTransla end self.Keepnumber = Keepnumber or false self.CallsignTranslations = CallsignTranslations + self.CallsignCustomFunc = CallsignCustomFunc + self.CallsignCustomArgs = arg or {} return self end @@ -818,6 +852,14 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) nod,maxview,camon = 10,1000,true angle = 10 maxview = self.MaxViewDistance[typename] or 5000 + elseif string.find(typename,"OH58") and camera then + --heading = unit:GetHeading() + nod,maxview,camon = 0,8000,true + heading,nod,maxview,camon = self:_GetKiowaMMSSight(unit) + angle = 8 + if maxview == 0 then + maxview = self.MaxViewDistance[typename] or 5000 + end else -- visual heading = unit:GetHeading() @@ -1362,6 +1404,7 @@ function PLAYERRECCE:_BuildMenus(Client) local client = _client -- Wrapper.Client#CLIENT if client and client:IsAlive() then local playername = client:GetPlayerName() + self:T("Menu for "..playername) if not self.UnitLaserCodes[playername] then self:_SetClientLaserCode(nil,nil,playername,1688) end @@ -1370,6 +1413,7 @@ function PLAYERRECCE:_BuildMenus(Client) end local group = client:GetGroup() if not self.ClientMenus[playername] then + self:T("Start Menubuild for "..playername) local canlase = self.CanLase[client:GetTypeName()] self.ClientMenus[playername] = MENU_GROUP:New(group,self.MenuName or self.Name or "RECCE") local txtonstation = self.OnStation[playername] and "ON" or "OFF" @@ -1586,6 +1630,15 @@ function PLAYERRECCE:EnableSmokeOwnPosition() return self end +--- [User] Enable auto lasing for the Kiowa OH-58D. +-- @param #PLAYERRECCE self +-- @return #PLAYERRECCE self +function PLAYERRECCE:EnableKiowaAutolase() + self:T(self.lid.."EnableKiowaAutolase") + self.CanLase.OH58D = true + return self +end + --- [User] Disable smoking of own position -- @param #PLAYERRECCE self -- @return #PLAYERRECCE @@ -1743,7 +1796,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.ReferencePoint then @@ -1752,7 +1805,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) end local text1 = "Party time!" local text2 = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext) - local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext) + local text2tts = string.format(" All stations, FACA %s on station at %s!",callsign, coordtext) text2tts = self:_GetTextForSpeech(text2tts) if self.debug then self:T(text2.."\n"..text2tts) @@ -1783,7 +1836,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.ReferencePoint then @@ -1923,7 +1976,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterIllumination(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -1966,7 +2019,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -2009,7 +2062,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -2053,7 +2106,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) @@ -2100,7 +2153,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterShack(From, Event, To, Client, Target) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) @@ -2147,7 +2200,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index f30bc6f11..dd9c18b52 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -2261,8 +2261,10 @@ end -- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. -- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized -- callsigns from playername or group name. +-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign == false then self.ShortCallsign = false else @@ -2270,6 +2272,8 @@ function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,Callsi end self.Keepnumber = Keepnumber or false self.CallsignTranslations = CallsignTranslations + self.CallsignCustomFunc = CallsignCustomFunc + self.CallsignCustomArgs = arg or {} return self end @@ -2481,7 +2485,7 @@ function PLAYERTASKCONTROLLER:_GetPlayerName(Client) if not self.customcallsigns[playername] then local playergroup = Client:GetGroup() if playergroup ~= nil then - ttsplayername = playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + ttsplayername = playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local newplayername = self:_GetTextForSpeech(ttsplayername) self.customcallsigns[playername] = newplayername ttsplayername = newplayername @@ -2621,7 +2625,7 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData) if self.customcallsigns[playername] then self.customcallsigns[playername] = nil end - playername = EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber) + playername = EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) end playername = self:_GetTextForSpeech(playername) --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) @@ -3641,7 +3645,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local pcoord = player:GetCoordinate() if pcoord:Get2DDistance(Coordinate) <= reachdist then inreach = true - local callsign = player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local playername = player:GetPlayerName() local islasing = no if self.PlayerRecce.CanLase[player:GetTypeName()] and self.PlayerRecce.AutoLase[playername] then From 92a05ca74a44b56c7c7c2cc1ea5c72821be13400 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 19 Jan 2025 18:22:21 +0100 Subject: [PATCH 031/158] #CTLD - If troops or vehicles have a stock set, you can only inject as many as there are in stock. Specifically, when using persistence, the load function will restrict to inject more objects than are in stock, each inject draws on the stock. --- Moose Development/Moose/Ops/CTLD.lua | 46 ++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 26295b17a..bfb47f706 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1352,7 +1352,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.23" +CTLD.version="1.1.24" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5512,14 +5512,18 @@ end local match = false local cgotbl = self.Cargo_Troops local name = cargo:GetName() + local CargoObject + local CargoName for _,_cgo in pairs (cgotbl) do local cname = _cgo:GetName() if name == cname then match = true + CargoObject = _cgo + CargoName = cname break end end - return match + return match, CargoObject, CargoName end local function Cruncher(group,typename,anzahl) @@ -5565,11 +5569,24 @@ end end end - if not IsTroopsMatch(cargo) then + local match,CargoObject,CargoName = IsTroopsMatch(cargo) + + if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert(self.Cargo_Troops,cargo) + table.insert(self.Cargo_Crates,cargo) + end + + if match and CargoObject then + local stock = CargoObject:GetStock() + if stock ~= -1 and stock ~= nil and stock == 0 then + -- stock empty + self:T(self.lid.."Stock of "..CargoName.." is empty. Cannot inject.") + return + else + CargoObject:RemoveStock(1) + end end local type = cargo:GetType() -- #CTLD_CARGO.Enum @@ -5637,14 +5654,18 @@ end local match = false local cgotbl = self.Cargo_Crates local name = cargo:GetName() + local CargoObject + local CargoName for _,_cgo in pairs (cgotbl) do local cname = _cgo:GetName() if name == cname then match = true + CargoObject = _cgo + CargoName = cname break end end - return match + return match,CargoObject,CargoName end local function Cruncher(group,typename,anzahl) @@ -5690,13 +5711,26 @@ end end end - if not IsVehicMatch(cargo) then + local match,CargoObject,CargoName = IsVehicMatch(cargo) + + if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 table.insert(self.Cargo_Crates,cargo) end + if match and CargoObject then + local stock = CargoObject:GetStock() + if stock ~= -1 and stock ~= nil and stock == 0 then + -- stock empty + self:T(self.lid.."Stock of "..CargoName.." is empty. Cannot inject.") + return + else + CargoObject:RemoveStock(1) + end + end + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then -- unload From 5957124e9e86e20529fddd23f5349b04c6d79564 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 19 Jan 2025 19:17:59 +0100 Subject: [PATCH 032/158] ARTY v1.3.3 - Added missing/new arty units to DB (min/max firing range) - Adjusted immobile speed check because some DCS units report a speed of 1 m/s --- .../Moose/Functional/Artillery.lua | 151 ++++++++++++------ 1 file changed, 99 insertions(+), 52 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index afe21a14c..ff352b555 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -534,7 +534,7 @@ ARTY={ ClassName="ARTY", lid=nil, - Debug=true, + Debug=false, targets={}, moves={}, currentTarget=nil, @@ -629,23 +629,95 @@ ARTY.WeaponType={ -- Table key is the "type name" and table value is and `ARTY.dbitem`. -- @type ARTY.db ARTY.db={ + ["LeFH_18-40-105"] = { + displayname = "FH LeFH-18 105mm", -- name displayed in the ME + minrange = 500, -- min range (green circle) in meters + maxrange = 10500, -- max range (red circle) in meters + reloadtime = nil, -- reload time in seconds + }, + ["M2A1-105"] = { + displayname = "FH M2A1 105mm", + minrange = 500, + maxrange = 11500, + reloadtime = nil, + }, + ["Pak40"] = { + displayname = "FH Pak 40 75mm", + minrange = 500, + maxrange = 3000, + reloadtime = nil, + }, + ["L118_Unit"] = { + displayname = "L118 Light Artillery Gun", + minrange = 500, + maxrange = 17500, + reloadtime = nil, + }, + ["Smerch"] = { + displayname = "MLRS 9A52 Smerch CM 300mm", + minrange = 20000, + maxrange = 70000, + reloadtime = 2160, + }, + ["Smerch_HE"] = { + displayname = "MLRS 9A52 Smerch HE 300mm", + minrange = 20000, + maxrange = 70000, + reloadtime = 2160, + }, + ["Uragan_BM-27"] = { + displayname = "MLRS 9K57 Uragan BM-27 220mm", + minrange = 11500, + maxrange = 35800, + reloadtime = 840, + }, + ["Grad-URAL"] = { + displayname = "MLRS BM-21 Grad 122mm", + minrange = 5000, + maxrange = 19000, + reloadtime = 420, + }, + ["HL_B8M1"] = { + displayname = "MLRS HL with B8M1 80mm", + minrange = 500, + maxrange = 5000, + reloadtime = nil, + }, + ["tt_B8M1"] = { + displayname = "MLRS LC with B8M1 80mm", + minrange = 500, + maxrange = 5000, + reloadtime = nil, + }, + ["MLRS"] = { + displayname = "MLRS M270 227mm", + minrange = 10000, + maxrange = 32000, + reloadtime = 540, + }, ["2B11 mortar"] = { displayname = "Mortar 2B11 120mm", - minrange = 500, -- correct? - maxrange = 7000, -- 7 km - reloadtime = 30, -- 30 sec + minrange = 500, + maxrange = 7000, + reloadtime = 30, + }, + ["PLZ05"] = { + displayname = "PLZ-05", + minrange = 500, + maxrange = 23500, + reloadtime = nil, }, ["SAU Gvozdika"] = { displayname = "SPH 2S1 Gvozdika 122mm", - minrange = 300, -- correct? - maxrange = 15000, -- 15 km - reloadtime = nil, -- unknown + minrange = 300, + maxrange = 15000, + reloadtime = nil, }, ["SAU Msta"] = { displayname = "SPH 2S19 Msta 152mm", - minrange = 300, -- correct? - maxrange = 23500, -- 23.5 km - reloadtime = nil, -- unknown + minrange = 300, + maxrange = 23500, + reloadtime = nil, }, ["SAU Akatsia"] = { displayname = "SPH 2S3 Akatsia 152mm", @@ -653,44 +725,18 @@ ARTY.db={ maxrange = 17000, reloadtime = nil, }, - ["SAU 2-C9"] = { - displayname = "SPM 2S9 Nona 120mm M", - minrange = 500, - maxrange = 7000, - reloadtime = nil, - }, - ["M-109"] = { - displayname = "SPH M109 Paladin 155mm", - minrange = 300, - maxrange = 22000, - reloadtime = nil, - }, ["SpGH_Dana"] = { displayname = "SPH Dana vz77 152mm", minrange = 300, maxrange = 18700, reloadtime = nil, }, - ["MLRS BM-21 Grad"] = { --type "Grad-URAL", alias "MLRS BM-21 Grad" - minrange = 5000, - maxrange = 19000, - reloadtime = 420, - }, - ["MLRS 9K57 Uragan BM-27"] = { -- type "Uragan_BM-27" - minrange = 11500, - maxrange = 35800, - reloadtime = 840, - }, - ["MLRS 9A52 Smerch"] = { -- type "Smerch" - minrange = 20000, - maxrange = 70000, - reloadtime = 2160, - }, - ["MLRS M270"] = { --type "MRLS", alias "M270 MRLS" - minrange = 10000, - maxrange = 32000, - reloadtime = 540, - }, + ["M-109"] = { + displayname = "SPH M109 Paladin 155mm", + minrange = 300, + maxrange = 22000, + reloadtime = nil, + }, ["M12_GMC"] = { displayname = "SPH M12 GMC 155mm", minrange = 300, @@ -708,11 +754,12 @@ ARTY.db={ minrange = 300, maxrange = 41000, reloadtime = nil, - }, - ["LeFH_18-40-105"] = { - minrange = 500, -- 500 m(?) - maxrange = 10500, -- 32 km - reloadtime = 540, -- 9 min + }, + ["SAU 2-C9"] = { + displayname = "SPM 2S9 Nona 120mm M", + minrange = 500, + maxrange = 7000, + reloadtime = nil, }, } @@ -733,7 +780,7 @@ ARTY.db={ --- Arty script version. -- @field #string version -ARTY.version="1.3.2" +ARTY.version="1.3.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -835,8 +882,8 @@ function ARTY:New(group, alias) -- Maximum speed in km/h. self.SpeedMax=group:GetSpeedMax() - -- Group is mobile or not (e.g. mortars). - if self.SpeedMax>1 then + -- Group is mobile or not (e.g. mortars). Some immobile units have a speed of 1 m/s = 3.6 km/h. So we check this number. + if self.SpeedMax>3.6 then self.ismobile=true else self.ismobile=false @@ -2007,8 +2054,8 @@ function ARTY:onafterStart(Controllable, From, Event, To) text=text..string.format("Type = %s\n", self.Type) text=text..string.format("Display Name = %s\n", self.DisplayName) text=text..string.format("Number of units = %d\n", self.IniGroupStrength) - text=text..string.format("Speed max = %d km/h\n", self.SpeedMax) - text=text..string.format("Speed default = %d km/h\n", self.Speed) + text=text..string.format("Speed max = %.1f km/h\n", self.SpeedMax) + text=text..string.format("Speed default = %.1f km/h\n", self.Speed) text=text..string.format("Is mobile = %s\n", tostring(self.ismobile)) text=text..string.format("Is cargo = %s\n", tostring(self.iscargo)) text=text..string.format("Min range = %.1f km\n", self.minrange/1000) From b03978cc3d0c636a6c25f1722ce58a75306bf52b Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 22 Jan 2025 09:43:57 +0100 Subject: [PATCH 033/158] Update Zone.lua Correct radius sphere search in ZONE_RADIUS:SearchZone() --- 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 7750851cb..ceca5fb02 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1415,7 +1415,7 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) id = world.VolumeType.SPHERE, params = { point = ZoneCoord:GetVec3(), - radius = ZoneRadius / 2, + radius = ZoneRadius, } } From 068a1ab99c2a41ab539299a4d326db831ce94271 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Jan 2025 10:29:47 +0100 Subject: [PATCH 034/158] #CTLD - Added `OnAfterLoaded` which gives you access to the rebuild troops and vehicles in a table of loaded groups, each entry is a table with three values: Group, TimeStamp and CargoType --- Moose Development/Moose/Ops/CTLD.lua | 64 +++++++++++++++++++++------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index bfb47f706..c73600e44 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -980,7 +980,7 @@ do -- -- ## 3.5 OnAfterCratesDropped -- --- This function is called when a player has deployed crates to a DROP zone: +-- This function is called when a player has deployed crates: -- -- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- ... your code here ... @@ -1242,6 +1242,8 @@ CTLD = { TroopUnloadDistHoverHook = 5, TroopUnloadDistHover = 1.5, UserSetGroup = nil, + LoadedGroupsTable = {}, + keeploadtable = true, } ------------------------------ @@ -1352,7 +1354,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.24" +CTLD.version="1.1.25" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1419,7 +1421,8 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event. self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event. - self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Loaded", "*") -- CTLD load event. self:AddTransition("*", "Save", "*") -- CTLD save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -1520,6 +1523,8 @@ function CTLD:New(Coalition, Prefixes, Alias) self.filepath = nil self.saveinterval = 600 self.eventoninject = true + self.keeploadtable = true + self.LoadedGroupsTable = {} -- sub categories self.usesubcats = false @@ -1832,6 +1837,14 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". + --- FSM Function OnAfterLoaded. + -- @function [parent=#CTLD] OnAfterLoaded + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table LoadedGroups Table of loaded groups, each entry is a table with three values: Group, TimeStamp and CargoType. + --- FSM Function OnAfterSave. -- @function [parent=#CTLD] OnAfterSave -- @param #CTLD self @@ -3510,10 +3523,9 @@ function CTLD:_UnloadTroops(Group, Unit) local rad = 2.5+(tempcount*2) local Positions = self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - --:InitRandomizeUnits(true,20,2) - --:InitHeading(heading) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter],type) end -- template loop @@ -3934,10 +3946,12 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) --:InitRandomizeUnits(true,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) end if Repair then @@ -5496,6 +5510,7 @@ end -- @param #table Surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another! -- @param #string Structure (Optional) String object describing the current structure of the injected group; mainly for load/save to keep current state setup. + -- @param #number TimeStamp (Optional) Timestamp used internally on re-loading from disk. -- @return #CTLD self -- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type @@ -5504,7 +5519,7 @@ end -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectTroops(dropzone,InjectTroopsType,{land.SurfaceType.LAND}) - function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation,Structure) + function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation,Structure,TimeStamp) self:T(self.lid.." InjectTroops") local cargo = Cargo -- #CTLD_CARGO @@ -5607,6 +5622,7 @@ end self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(randompositions,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) @@ -5624,6 +5640,11 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end + if self.keeploadtables and TimeStamp then + local cargotype = cargo.CargoType + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) + end + if self.eventoninject then self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter],type) end @@ -5638,6 +5659,7 @@ end -- @param #table Surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another! -- @param #string Structure (Optional) String object describing the current structure of the injected group; mainly for load/save to keep current state setup. + -- @param #number TimeStamp (Optional) Timestamp used internally on re-loading from disk. -- @return #CTLD self -- @usage Use this function to pre-populate the field with Vehicles or FOB at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type @@ -5646,7 +5668,7 @@ end -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectVehicles(dropzone,InjectVehicleType) - function CTLD:InjectVehicles(Zone,Cargo,Surfacetypes,PreciseLocation,Structure) + function CTLD:InjectVehicles(Zone,Cargo,Surfacetypes,PreciseLocation,Structure,TimeStamp) self:T(self.lid.." InjectVehicles") local cargo = Cargo -- #CTLD_CARGO @@ -5752,10 +5774,12 @@ end self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) end @@ -5763,6 +5787,11 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end + if self.keeploadtables and TimeStamp then + local cargotype = cargo.CargoType + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) + end + if self.eventoninject then self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) end @@ -6180,7 +6209,7 @@ end --local data = "LoadedData = {\n" - local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure,StaticCategory,StaticType,StaticShape\n" + local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure,StaticCategory,StaticType,StaticShape,SpawnTime\n" local n = 0 for _,_grp in pairs(grouptable) do local group = _grp -- Wrapper.Group#GROUP @@ -6213,6 +6242,7 @@ end for typen,anzahl in pairs (structure) do strucdata = strucdata .. typen .. "=="..anzahl..";" end + local spawntime = group.spawntime or timer.getTime()+n if type(cgotemp) == "table" then local templates = "{" @@ -6224,8 +6254,8 @@ end end local location = group:GetVec3() - local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s\n" - ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata,scat,stype,sshape or "none") + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s,%f\n" + ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata,scat,stype,sshape or "none",spawntime) data = data .. txt end end @@ -6378,10 +6408,10 @@ end -- remove header table.remove(loadeddata, 1) - + local n=0 for _id,_entry in pairs (loadeddata) do local dataset = UTILS.Split(_entry,",") - -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=Structure,11=StaticCategory,12=StaticType,13=StaticShape + -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=Structure,11=StaticCategory,12=StaticType,13=StaticShape,14=Timestamp local groupname = dataset[1] local vec2 = {} vec2.x = tonumber(dataset[2]) @@ -6394,6 +6424,8 @@ end local StaticCategory = dataset[11] local StaticType = dataset[12] local StaticShape = dataset[13] + n=n+1 + local timestamp = tonumber(dataset[14]) or timer.getTime()+n if type(groupname) == "string" and groupname ~= "STATIC" then cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") @@ -6408,10 +6440,10 @@ end if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) - self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure) + self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) - self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure) + self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) end elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) @@ -6433,7 +6465,9 @@ end end end end - + if self.keeploadtable then -- keeploadtables + self:__Loaded(1,self.LoadedGroupsTable) + end return self end end -- end do From 48bc41873acdcdc02c1bd66fc1e2cde1477976ba Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 24 Jan 2025 12:21:44 +0100 Subject: [PATCH 035/158] #STORAGE - Added small helper to get Syria "H" Helipad warehouses via a zone --- Moose Development/Moose/Wrapper/Storage.lua | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 3acaa9d16..4b445613c 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -227,7 +227,7 @@ function STORAGE:New(AirbaseName) self.airbase=Airbase.getByName(AirbaseName) - if Airbase.getWarehouse then + if Airbase.getWarehouse and self.airbase then self.warehouse=self.airbase:getWarehouse() end @@ -840,6 +840,35 @@ function STORAGE:StopAutoSave() return self end +--- Try to find the #STORAGE object of one of the many "H"-Helipads in Syria. You need to put a (small, round) zone on top of it, because the name is not unique(!). +-- @param #STORAGE self +-- @param #string ZoneName The name of the zone where to find the helipad. +-- @return #STORAGE self or nil if not found. +function STORAGE:FindSyriaHHelipadWarehouse(ZoneName) + local findzone = ZONE:New(ZoneName) + local base = world.getAirbases() + for i = 1, #base do + local info = {} + --info.desc = Airbase.getDesc(base[i]) + info.callsign = Airbase.getCallsign(base[i]) + info.id = Airbase.getID(base[i]) + --info.cat = Airbase.getCategory(base[i]) + info.point = Airbase.getPoint(base[i]) + info.coordinate = COORDINATE:NewFromVec3(info.point) + info.DCSObject = base[i] + --if Airbase.getUnit(base[i]) then + --info.unitId = Airbase.getUnit(base[i]):getID() + --end + if info.callsign == "H" and findzone:IsCoordinateInZone(info.coordinate) then + info.warehouse = info.DCSObject:getWarehouse() + info.Storage = STORAGE:New(info.callsign..info.id) + info.Storage.airbase = info.DCSObject + info.Storage.warehouse = info.warehouse + return info.Storage + end + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Private Functions From 1b4033cfce34c13c444ca53dff303f3064862f71 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 25 Jan 2025 15:04:33 +0100 Subject: [PATCH 036/158] CTLD Small fix for PlayerTask ExtractTroops --- Moose Development/Moose/Ops/CTLD.lua | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index c73600e44..b2e85552e 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1354,7 +1354,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.25" +CTLD.version="1.1.26" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5969,6 +5969,27 @@ end -- @return #CTLD self function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) self:T({From, Event, To}) + if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then + local playername = Unit:GetPlayerName() + local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0) + local dropvec2 = dropcoord:GetVec2() + self.PlayerTaskQueue:ForEach( + function (Task) + local task = Task -- Ops.PlayerTask#PLAYERTASK + local subtype = task:GetSubType() + -- right subtype? + if Event == subtype and not task:IsDone() then + local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2) then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end + end + end + end + ) + end return self end From 66a1fa8af570c4741bdd4a3a91e70be1d360cf2a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 25 Jan 2025 15:54:29 +0100 Subject: [PATCH 037/158] #CTLD - small fix for LoadesGroupsTable inserts --- Moose Development/Moose/Ops/CTLD.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index b2e85552e..9577a419a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -5640,7 +5640,8 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end - if self.keeploadtables and TimeStamp then + if self.keeploadtable and TimeStamp ~= nil then + self:T2("Inserting: "..cargo.CargoType) local cargotype = cargo.CargoType table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) end @@ -5787,7 +5788,8 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end - if self.keeploadtables and TimeStamp then + if self.keeploadtable and TimeStamp ~= nil then + self:T2("Inserting: "..cargo.CargoType) local cargotype = cargo.CargoType table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) end @@ -6446,7 +6448,8 @@ end local StaticType = dataset[12] local StaticShape = dataset[13] n=n+1 - local timestamp = tonumber(dataset[14]) or timer.getTime()+n + local timestamp = tonumber(dataset[14]) or (timer.getTime()+n) + self:T2("TimeStamp = "..timestamp) if type(groupname) == "string" and groupname ~= "STATIC" then cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") From 00f5fde5a8a10d4f172ff5dc91255b9206ed75ff Mon Sep 17 00:00:00 2001 From: Shafik Date: Sun, 26 Jan 2025 00:19:19 +0200 Subject: [PATCH 038/158] [FIXED] `bad argument #2 to 'format' (number expected, got nil)` --- Moose Development/Moose/Ops/Commander.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 596eb5661..b7d30f298 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -1774,8 +1774,10 @@ function COMMANDER:RecruitAssetsForMission(Mission) MaxWeight=cohort.cargobayLimit end end - - self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight)) + + if MaxWeight then + self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight)) + end end local legions=self.legions @@ -2165,4 +2167,4 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 25a9a0120a5e1fe113eb5eda7965a6f8c723f47f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Jan 2025 13:29:18 +0100 Subject: [PATCH 039/158] #CTLD - If stock is set, show stock number in menu entries - Corrected table build for GetStockCrates/Troops/Statics --- Moose Development/Moose/Ops/CTLD.lua | 41 ++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9577a419a..1c36ce65d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1354,7 +1354,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.26" +CTLD.version="1.1.27" --- Instantiate a new CTLD. -- @param #CTLD self @@ -4127,18 +4127,28 @@ function CTLD:_RefreshF10Menus() local entry = _entry -- #CTLD_CARGO local subcat = entry.Subcategory local noshow = entry.DontShowInMenu + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) + local menutext = entry.Name + if stock >= 0 then + menutext = menutext.." ["..stock.."]" + end + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) end end else for _,_entry in pairs(self.Cargo_Troops) do local entry = _entry -- #CTLD_CARGO local noshow = entry.DontShowInMenu + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + local menutext = entry.Name + if stock >= 0 then + menutext = menutext.." ["..stock.."]" + end + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops, self, _group, _unit, entry) end end end @@ -4164,12 +4174,16 @@ function CTLD:_RefreshF10Menus() local subcat = entry.Subcategory local noshow = entry.DontShowInMenu local zone = entry.Location + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end + if stock >= 0 then + menutext = menutext.."["..stock.."]" + end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) end end @@ -4178,12 +4192,16 @@ function CTLD:_RefreshF10Menus() local subcat = entry.Subcategory local noshow = entry.DontShowInMenu local zone = entry.Location + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end + if stock >= 0 then + menutext = menutext.."["..stock.."]" + end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) end end @@ -4192,12 +4210,16 @@ function CTLD:_RefreshF10Menus() local entry = _entry -- #CTLD_CARGO local noshow = entry.DontShowInMenu local zone = entry.Location + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end + if stock >= 0 then + menutext = menutext.."["..stock.."]" + end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) end end @@ -4205,12 +4227,16 @@ function CTLD:_RefreshF10Menus() local entry = _entry -- #CTLD_CARGO local noshow = entry.DontShowInMenu local zone = entry.Location + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end + if stock >= 0 then + menutext = menutext.."["..stock.."]" + end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) end end @@ -5369,7 +5395,8 @@ end local Stock = {} local gentroops = self.Cargo_Crates for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + --table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end @@ -5381,7 +5408,8 @@ end local Stock = {} local gentroops = self.Cargo_Troops for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + --table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end @@ -5414,7 +5442,8 @@ end local Stock = {} local gentroops = self.Cargo_Statics for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + -- table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end From 4ac57fce7a6c8110f4523d9ec5c2ecb605fb3720 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Jan 2025 13:54:28 +0100 Subject: [PATCH 040/158] #CTLD - Added `CTLD:GetGenericCargoObjectFromGroupName(GroupName)` to get the generic CTLD_CARGO entry from a group name. --- Moose Development/Moose/Ops/CTLD.lua | 32 ++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 1c36ce65d..75865e259 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2428,6 +2428,7 @@ end -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) local hoverload = self:CanHoverLoad(Unit) + local hassecondaries = false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) @@ -2542,7 +2543,7 @@ end end end -- clean up: - local hassecondaries = false + hassecondaries = false if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then for _,_key in pairs (Cargotype.Templates) do table.insert(secondarygroups,_key) @@ -5502,6 +5503,33 @@ end return self end + --- (User) Get a generic #CTLD_CARGO entry from a group name, works for Troops and Vehicles, FOB, i.e. everything that is spawned as a GROUP object. + -- @param #CTLD self + -- @param #string GroupName The name to use for the search + -- @return #CTLD_CARGO The cargo object or nil if not found + function CTLD:GetGenericCargoObjectFromGroupName(GroupName) + local Cargotype = nil + for k,v in pairs(self.Cargo_Troops) do + local comparison = "" + if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end + if comparison == GroupName then + Cargotype = v + break + end + end + if not Cargotype then + for k,v in pairs(self.Cargo_Crates) do + local comparison = "" + if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end + if comparison == GroupName then + Cargotype = v + break + end + end + end + return Cargotype + end + --- (Internal) Check on engineering teams -- @param #CTLD self -- @return #CTLD self @@ -5682,7 +5710,7 @@ end return self end - --- (User) Pre-populate vehicles in the field. + --- (User) Pre-populate vehicles in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. From b75fff60c8f00be027ba2a77729a27cd1f8265b2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Jan 2025 14:54:19 +0100 Subject: [PATCH 041/158] xx --- Moose Development/Moose/Ops/CTLD.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 75865e259..56a33010d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -839,6 +839,7 @@ do -- my_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules -- my_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook -- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook +-- 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. -- -- ## 2.1 CH-47 Chinook support -- @@ -1354,7 +1355,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.27" +CTLD.version="1.1.28" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1530,6 +1531,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.usesubcats = false self.subcats = {} self.subcatsTroop = {} + self.showstockinmenuitems = false -- disallow building in loadzones self.nobuildinloadzones = true @@ -4078,7 +4080,7 @@ function CTLD:_RefreshF10Menus() local menucount = 0 local menus = {} for _, _unitName in pairs(self.CtldUnits) do - if not self.MenusDone[_unitName] then + if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT if _unit then local _group = _unit:GetGroup() -- Wrapper.Group#GROUP @@ -4132,7 +4134,7 @@ function CTLD:_RefreshF10Menus() if not noshow then menucount = menucount + 1 local menutext = entry.Name - if stock >= 0 then + if stock >= 0 and self.showstockinmenuitems == true then menutext = menutext.." ["..stock.."]" end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) @@ -4146,7 +4148,7 @@ function CTLD:_RefreshF10Menus() if not noshow then menucount = menucount + 1 local menutext = entry.Name - if stock >= 0 then + if stock >= 0 and self.showstockinmenuitems == true then menutext = menutext.." ["..stock.."]" end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops, self, _group, _unit, entry) @@ -4182,7 +4184,7 @@ function CTLD:_RefreshF10Menus() if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end - if stock >= 0 then + if stock >= 0 and self.showstockinmenuitems == true then menutext = menutext.."["..stock.."]" end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) @@ -4200,7 +4202,7 @@ function CTLD:_RefreshF10Menus() if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end - if stock >= 0 then + if stock >= 0 and self.showstockinmenuitems == true then menutext = menutext.."["..stock.."]" end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) @@ -4218,7 +4220,7 @@ function CTLD:_RefreshF10Menus() if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end - if stock >= 0 then + if stock >= 0 and self.showstockinmenuitems == true then menutext = menutext.."["..stock.."]" end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) @@ -4235,7 +4237,7 @@ function CTLD:_RefreshF10Menus() if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end - if stock >= 0 then + if stock >= 0 and self.showstockinmenuitems == true then menutext = menutext.."["..stock.."]" end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) From 29111e1018fbb8f12f753cb5703b9ce3c67d0e09 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Jan 2025 14:55:21 +0100 Subject: [PATCH 042/158] xx --- Moose Development/Moose/Ops/CTLD.lua | 77 ++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9577a419a..56a33010d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -839,6 +839,7 @@ do -- my_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules -- my_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook -- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook +-- 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. -- -- ## 2.1 CH-47 Chinook support -- @@ -1354,7 +1355,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.26" +CTLD.version="1.1.28" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1530,6 +1531,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.usesubcats = false self.subcats = {} self.subcatsTroop = {} + self.showstockinmenuitems = false -- disallow building in loadzones self.nobuildinloadzones = true @@ -2428,6 +2430,7 @@ end -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) local hoverload = self:CanHoverLoad(Unit) + local hassecondaries = false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) @@ -2542,7 +2545,7 @@ end end end -- clean up: - local hassecondaries = false + hassecondaries = false if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then for _,_key in pairs (Cargotype.Templates) do table.insert(secondarygroups,_key) @@ -4077,7 +4080,7 @@ function CTLD:_RefreshF10Menus() local menucount = 0 local menus = {} for _, _unitName in pairs(self.CtldUnits) do - if not self.MenusDone[_unitName] then + if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT if _unit then local _group = _unit:GetGroup() -- Wrapper.Group#GROUP @@ -4127,18 +4130,28 @@ function CTLD:_RefreshF10Menus() local entry = _entry -- #CTLD_CARGO local subcat = entry.Subcategory local noshow = entry.DontShowInMenu + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) + local menutext = entry.Name + if stock >= 0 and self.showstockinmenuitems == true then + menutext = menutext.." ["..stock.."]" + end + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) end end else for _,_entry in pairs(self.Cargo_Troops) do local entry = _entry -- #CTLD_CARGO local noshow = entry.DontShowInMenu + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + local menutext = entry.Name + if stock >= 0 and self.showstockinmenuitems == true then + menutext = menutext.." ["..stock.."]" + end + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops, self, _group, _unit, entry) end end end @@ -4164,12 +4177,16 @@ function CTLD:_RefreshF10Menus() local subcat = entry.Subcategory local noshow = entry.DontShowInMenu local zone = entry.Location + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end + if stock >= 0 and self.showstockinmenuitems == true then + menutext = menutext.."["..stock.."]" + end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) end end @@ -4178,12 +4195,16 @@ function CTLD:_RefreshF10Menus() local subcat = entry.Subcategory local noshow = entry.DontShowInMenu local zone = entry.Location + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end + if stock >= 0 and self.showstockinmenuitems == true then + menutext = menutext.."["..stock.."]" + end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) end end @@ -4192,12 +4213,16 @@ function CTLD:_RefreshF10Menus() local entry = _entry -- #CTLD_CARGO local noshow = entry.DontShowInMenu local zone = entry.Location + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end + if stock >= 0 and self.showstockinmenuitems == true then + menutext = menutext.."["..stock.."]" + end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) end end @@ -4205,12 +4230,16 @@ function CTLD:_RefreshF10Menus() local entry = _entry -- #CTLD_CARGO local noshow = entry.DontShowInMenu local zone = entry.Location + local stock = _entry:GetStock() if not noshow then menucount = menucount + 1 local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if zone then menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) end + if stock >= 0 and self.showstockinmenuitems == true then + menutext = menutext.."["..stock.."]" + end menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) end end @@ -5369,7 +5398,8 @@ end local Stock = {} local gentroops = self.Cargo_Crates for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + --table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end @@ -5381,7 +5411,8 @@ end local Stock = {} local gentroops = self.Cargo_Troops for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + --table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end @@ -5414,7 +5445,8 @@ end local Stock = {} local gentroops = self.Cargo_Statics for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + -- table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end @@ -5473,6 +5505,33 @@ end return self end + --- (User) Get a generic #CTLD_CARGO entry from a group name, works for Troops and Vehicles, FOB, i.e. everything that is spawned as a GROUP object. + -- @param #CTLD self + -- @param #string GroupName The name to use for the search + -- @return #CTLD_CARGO The cargo object or nil if not found + function CTLD:GetGenericCargoObjectFromGroupName(GroupName) + local Cargotype = nil + for k,v in pairs(self.Cargo_Troops) do + local comparison = "" + if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end + if comparison == GroupName then + Cargotype = v + break + end + end + if not Cargotype then + for k,v in pairs(self.Cargo_Crates) do + local comparison = "" + if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end + if comparison == GroupName then + Cargotype = v + break + end + end + end + return Cargotype + end + --- (Internal) Check on engineering teams -- @param #CTLD self -- @return #CTLD self @@ -5653,7 +5712,7 @@ end return self end - --- (User) Pre-populate vehicles in the field. + --- (User) Pre-populate vehicles in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. From e26caa2f74edf1583dd3dab724b7cc60fd3370df Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Jan 2025 17:34:22 +0100 Subject: [PATCH 043/158] smaller fixes --- Moose Development/Moose/Core/Menu.lua | 14 ++++++++++++-- Moose Development/Moose/Ops/CTLD.lua | 13 +++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index 44c15b08d..aacb018a6 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -105,6 +105,7 @@ function MENU_INDEX:PrepareCoalition( CoalitionSide ) self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} end + --- -- @param Wrapper.Group#GROUP Group function MENU_INDEX:PrepareGroup( Group ) @@ -118,9 +119,11 @@ end function MENU_INDEX:HasMissionMenu( Path ) return self.MenuMission.Menus[Path] end + function MENU_INDEX:SetMissionMenu( Path, Menu ) self.MenuMission.Menus[Path] = Menu end + function MENU_INDEX:ClearMissionMenu( Path ) self.MenuMission.Menus[Path] = nil end @@ -128,9 +131,11 @@ end function MENU_INDEX:HasCoalitionMenu( Coalition, Path ) return self.Coalition[Coalition].Menus[Path] end + function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu ) self.Coalition[Coalition].Menus[Path] = Menu end + function MENU_INDEX:ClearCoalitionMenu( Coalition, Path ) self.Coalition[Coalition].Menus[Path] = nil end @@ -138,19 +143,24 @@ end function MENU_INDEX:HasGroupMenu( Group, Path ) if Group and Group:IsAlive() then local MenuGroupName = Group:GetName() - return self.Group[MenuGroupName].Menus[Path] + if self.Group[MenuGroupName] and self.Group[MenuGroupName].Menus and self.Group[MenuGroupName].Menus[Path] then + return self.Group[MenuGroupName].Menus[Path] + end end return nil end + function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) local MenuGroupName = Group:GetName() - Group:F({MenuGroupName=MenuGroupName,Path=Path}) + --Group:F({MenuGroupName=MenuGroupName,Path=Path}) self.Group[MenuGroupName].Menus[Path] = Menu end + function MENU_INDEX:ClearGroupMenu( Group, Path ) local MenuGroupName = Group:GetName() self.Group[MenuGroupName].Menus[Path] = nil end + function MENU_INDEX:Refresh( Group ) for MenuID, Menu in pairs( self.MenuMission.Menus ) do Menu:Refresh() diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 56a33010d..3ebcd67a2 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4094,7 +4094,12 @@ function CTLD:_RefreshF10Menus() --local nohookswitch = not (isHook and self.enableChinookGCLoading) local nohookswitch = true -- top menu + if _group.CTLDTopmenu then + _group.CTLDTopmenu:Remove() + _group.CTLDTopmenu = nil + end local topmenu = MENU_GROUP:New(_group,"CTLD",nil) + _group.CTLDTopmenu = topmenu local toptroops = nil local topcrates = nil if cantroops then @@ -5701,8 +5706,8 @@ end if self.keeploadtable and TimeStamp ~= nil then self:T2("Inserting: "..cargo.CargoType) - local cargotype = cargo.CargoType - table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) + local cargotype = type + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype, CargoName=name}) end if self.eventoninject then @@ -5849,8 +5854,8 @@ end if self.keeploadtable and TimeStamp ~= nil then self:T2("Inserting: "..cargo.CargoType) - local cargotype = cargo.CargoType - table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype}) + local cargotype = type + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype, CargoName=name}) end if self.eventoninject then From ff561410adc67c8f15f786c7a39dc55260a745d0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Jan 2025 17:34:36 +0100 Subject: [PATCH 044/158] xx --- Moose Development/Moose/Core/ClientMenu.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/ClientMenu.lua b/Moose Development/Moose/Core/ClientMenu.lua index ee0b7e6f2..5e7219add 100644 --- a/Moose Development/Moose/Core/ClientMenu.lua +++ b/Moose Development/Moose/Core/ClientMenu.lua @@ -57,7 +57,7 @@ --- -- @field #CLIENTMENU CLIENTMENU = { - ClassName = "CLIENTMENUE", + ClassName = "CLIENTMENU", lid = "", version = "0.1.3", name = nil, From 4d24eb82be5183b827f3b827c54b93ac67a6081e Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 27 Jan 2025 08:51:17 +0100 Subject: [PATCH 045/158] Update CTLD.lua #CTLD - Add the group name of the extracted group to TroopsExtracted FSM event --- Moose Development/Moose/Ops/CTLD.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3ebcd67a2..bdf3a3b5e 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -975,7 +975,7 @@ do -- -- This function is called when a player has re-boarded already deployed troops from the field: -- --- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) +-- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops, Troopname) -- ... your code here ... -- end -- @@ -1635,7 +1635,8 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. + -- @param Wrapper.Group#GROUP Troops extracted. + -- @param #string Troopname Name of the extracted group. -- @return #CTLD self --- FSM Function OnBeforeCratesPickedUp. @@ -1723,7 +1724,8 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. + -- @param Wrapper.Group#GROUP Troops extracted. + -- @param #string Troopname Name of the extracted group. -- @return #CTLD self --- FSM Function OnAfterCratesPickedUp. @@ -2533,7 +2535,8 @@ end self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!", 10, false, Group) self:_SendMessage("Troops boarding!", 10, false, Group) self:_UpdateUnitCargoMass(Unit) - self:__TroopsExtracted(running,Group, Unit, nearestGroup) + local groupname = nearestGroup:GetName() + self:__TroopsExtracted(running,Group, Unit, nearestGroup, groupname) local coord = Unit:GetCoordinate() or Group:GetCoordinate() -- Core.Point#COORDINATE local Point if coord then @@ -6032,8 +6035,9 @@ end -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @param #string Groupname Name of the extracted #GROUP. -- @return #CTLD self - function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) + function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops, Groupname) self:T({From, Event, To}) if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then local playername = Unit:GetPlayerName() From a55959dfbb3d0b90132000c40445c73a77513473 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:00:25 +0100 Subject: [PATCH 046/158] Update Base.lua Added property functions --- Moose Development/Moose/Core/Base.lua | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 5de2096a3..76671ffce 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -201,6 +201,7 @@ BASE = { States = {}, Debug = debug, Scheduler = nil, + Properties = {}, } -- @field #BASE.__ @@ -1109,6 +1110,31 @@ function BASE:ClearState( Object, StateName ) end end +--- Set one property of an object. +-- @param #BASE self +-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! +-- @param Value The value that is stored. Note that the value can be a #string, but it can also be any other type! +function BASE:SetProperty(Key,Value) + self.Properties = self.Properties or {} + self.Properties[Key] = Value +end + +--- Get one property of an object by the key. +-- @param #BASE self +-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! +-- @return Value The value that is stored. Note that the value can be a #string, but it can also be any other type! Nil if not found. +function BASE:GetProperty(Key) + self.Properties = self.Properties or {} + return self.Properties[Key] +end + +--- Get all of the properties of an object in a table. +-- @param #BASE self +-- @return #table of values, indexed by keys. +function BASE:GetProperties() + return self.Properties +end + -- Trace section -- Log a trace (only shown when trace is on) From bfb60b318eebab9f33a6bfc7f8c3b35a8e8f784e Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 27 Jan 2025 13:27:13 +0100 Subject: [PATCH 047/158] Update CTLD.lua update --- Moose Development/Moose/Ops/CTLD.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index bdf3a3b5e..c9440c657 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -6041,8 +6041,8 @@ end self:T({From, Event, To}) if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then local playername = Unit:GetPlayerName() - local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0) - local dropvec2 = dropcoord:GetVec2() + --local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0) + --local dropvec2 = dropcoord:GetVec2() self.PlayerTaskQueue:ForEach( function (Task) local task = Task -- Ops.PlayerTask#PLAYERTASK @@ -6050,7 +6050,7 @@ end -- right subtype? if Event == subtype and not task:IsDone() then local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... - if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2) then + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:GetProperty("Extractname") == Groupname then if task.Clients:HasUniqueID(playername) then -- success task:__Success(-1) From daffd7412acd68b8df2fbbff20530b05d2dc5fd2 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:44:35 +0100 Subject: [PATCH 048/158] Update Base.lua --- Moose Development/Moose/Core/Base.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 76671ffce..b1a12e740 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -1466,4 +1466,3 @@ function BASE:I( Arguments ) end end - From 6af836c118dffd49fdd1bf96efa82eeeef7dfaa7 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:45:39 +0100 Subject: [PATCH 049/158] Update CTLD.lua --- Moose Development/Moose/Ops/CTLD.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index c9440c657..5c83896a5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -5657,7 +5657,7 @@ end self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert(self.Cargo_Crates,cargo) + table.insert(self.Cargo_Troops,cargo) end if match and CargoObject then @@ -6050,7 +6050,9 @@ end -- right subtype? if Event == subtype and not task:IsDone() then local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... - if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:GetProperty("Extractname") == Groupname then + --self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")}) + local okaygroup = string.find(Groupname,task:GetProperty("ExtractName"),1,true) + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and okaygroup then if task.Clients:HasUniqueID(playername) then -- success task:__Success(-1) From 41dfaab82a7c7f2a5aa20e3598781c415daf010a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 27 Jan 2025 18:48:39 +0100 Subject: [PATCH 050/158] xx --- Moose Development/Moose/Ops/PlayerTask.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index dd9c18b52..a5a99b20c 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update Nov 2024 +-- @date Last Update Jan 2025 do @@ -1184,7 +1184,7 @@ function PLAYERTASK:onafterSuccess(From, Event, To) if self.TargetMarker then self.TargetMarker:Remove() end - if self.TaskController.Scoring then + if self.TaskController and self.TaskController.Scoring then local clients,count = self:GetClientObjects() if count > 0 then for _,_client in pairs(clients) do @@ -1242,7 +1242,7 @@ end do ------------------------------------------------------------------------------------------------------------------- -- PLAYERTASKCONTROLLER - -- TODO: PLAYERTASKCONTROLLER +-- TODO: PLAYERTASKCONTROLLER -- DONE Playername customized -- DONE Coalition-level screen info to SET based -- DONE Flash directions From 4fd55b1bd6413602adc5ac4d5d7deb9e7037a350 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 28 Jan 2025 08:31:17 +0100 Subject: [PATCH 051/158] Update Scenery.lua #SCENERY --- Moose Development/Moose/Wrapper/Scenery.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index eb69e7ab4..9c6b02516 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -91,6 +91,7 @@ function SCENERY:SetProperty(PropertyName, PropertyValue) self.Properties[PropertyName] = PropertyValue return self end + --- Obtain object name. --@param #SCENERY self --@return #string Name From 5f7a4f2bbb36ad27d8e23a0520f466bae3e3dafc Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 28 Jan 2025 20:22:45 +0100 Subject: [PATCH 052/158] Update Group.lua - Added way to get the coordinate of the group --- Moose Development/Moose/Wrapper/Group.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index ef0561f84..8b4465f87 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1221,7 +1221,14 @@ end -- @return Core.Point#COORDINATE The COORDINATE of the GROUP. function GROUP:GetCoordinate() - local Units = self:GetUnits() or {} + -- First try to get the 3D vector of the group. This uses + local vec3=self:GetVec3() + if vec3 then + local coord=COORDINATE:NewFromVec3(vec3) + return vec3 + end + + local Units = self:GetUnits() or {} for _,_unit in pairs(Units) do local FirstUnit = _unit -- Wrapper.Unit#UNIT From b96209666138c0274fedcd512b4a75df0a40cbd4 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 28 Jan 2025 20:29:04 +0100 Subject: [PATCH 053/158] Update RAT.lua - Tried to fix bug the Pnow is nil --- Moose Development/Moose/Functional/RAT.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 530d79053..560410ae2 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -3814,15 +3814,20 @@ function RAT:Status(message, forID) local N0units=group:GetInitialSize() -- Monitor travelled distance since last check. - local Pnow=coords - local Dtravel=Pnow:Get2DDistance(ratcraft.Pnow) - ratcraft.Pnow=Pnow + local Dtravel=0 + if coords and ratcraft.Pnow then + local Dtravel=coords:Get2DDistance(ratcraft.Pnow) + ratcraft.Pnow=coords + end -- Add up the travelled distance. ratcraft.Distance=ratcraft.Distance+Dtravel -- Distance remaining to destination. - local Ddestination=Pnow:Get2DDistance(ratcraft.destination:GetCoordinate()) + local Ddestination=-1 + if ratcraft.Pnow then + Ddestination=ratcraft.Pnow:Get2DDistance(ratcraft.destination:GetCoordinate()) + end -- Status report. if (forID and spawnindex==forID) or (not forID) then From d9948d1a19dce8440e32adb1722aa3e539970474 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 28 Jan 2025 20:29:35 +0100 Subject: [PATCH 054/158] Update Group.lua --- 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 8b4465f87..aedf6412d 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1225,7 +1225,7 @@ function GROUP:GetCoordinate() local vec3=self:GetVec3() if vec3 then local coord=COORDINATE:NewFromVec3(vec3) - return vec3 + return coord end local Units = self:GetUnits() or {} From ea776aeacc7e45c9d1b0b532673bee6854e45c4c Mon Sep 17 00:00:00 2001 From: Shafik Date: Wed, 29 Jan 2025 10:05:55 +0200 Subject: [PATCH 055/158] [FIXED] attempt to get length of local 'parking' (a nil value) --- Moose Development/Moose/Functional/Warehouse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 3c812686d..29682074f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -6047,7 +6047,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol else - if #parking<#template.units and not airstart then + if parking and #parking<#template.units and not airstart then local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units) self:_DebugMessage(text) return nil From 44ad362dbc64fc326e3df6f2c2a9e500ff5ce327 Mon Sep 17 00:00:00 2001 From: Shafik Date: Wed, 29 Jan 2025 10:06:19 +0200 Subject: [PATCH 056/158] [FIXED] attempt to index local 'CleanUpUnit' (a nil value) --- Moose Development/Moose/Functional/CleanUp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua index f4a391eef..82990f839 100644 --- a/Moose Development/Moose/Functional/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -387,7 +387,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule() local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT local CleanUpGroupName = CleanUpListData.CleanUpGroupName - if CleanUpUnit:IsAlive() ~= nil then + if CleanUpUnit and CleanUpUnit:IsAlive() ~= nil then if self:IsInAirbase( CleanUpUnit:GetVec2() ) then From 161bdc0413407b5e221e1a4e4865ba649539f316 Mon Sep 17 00:00:00 2001 From: Shafik Date: Wed, 29 Jan 2025 10:13:34 +0200 Subject: [PATCH 057/158] [FIXED] attempt to concatenate field 'IniGroupName' (a nil value) ?? --- Moose Development/Moose/Ops/Awacs.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index b777054fe..0533e13b0 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -1773,7 +1773,7 @@ function AWACS:_EventHandler(EventData) end end - if Event.id == EVENTS.PlayerLeaveUnit then --player left unit + if Event.id == EVENTS.PlayerLeaveUnit and Event.IniGroupName then --player left unit -- check known player? self:T("Player group left unit: " .. Event.IniGroupName) self:T("Player name left: " .. Event.IniPlayerName) From 09d53f7d8c901a96e5566dd928626286dc44ab1c Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:24:04 +0100 Subject: [PATCH 058/158] Update CTLD.lua #CTLD - Added HelicopterLost Event --- Moose Development/Moose/Ops/CTLD.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 5c83896a5..b5c7d9e19 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1422,6 +1422,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("*", "HelicopterLost", "*") -- CTLD lost event. self:AddTransition("*", "Load", "*") -- CTLD load event. self:AddTransition("*", "Loaded", "*") -- CTLD load event. self:AddTransition("*", "Save", "*") -- CTLD save event. @@ -1831,6 +1832,24 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. + + --- FSM Function OnBeforeHelicopterLost. + -- @function [parent=#CTLD] OnBeforeHelicopterLost + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param #string Unitname The name of the unit lost. + -- @param #table LostCargo Table of #CTLD_CARGO object which were aboard the helicopter/transportplane lost. Can be an empty table! + + --- FSM Function OnAfterHelicopterLost. + -- @function [parent=#CTLD] OnAfterHelicopterLost + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param #string Unitname The name of the unit lost. + -- @param #table LostCargo Table of #CTLD_CARGO object which were aboard the helicopter/transportplane lost. Can be an empty table! --- FSM Function OnAfterLoad. -- @function [parent=#CTLD] OnAfterLoad @@ -1972,6 +1991,10 @@ function CTLD:_EventHandler(EventData) elseif event.id == EVENTS.PlayerLeaveUnit or event.id == EVENTS.UnitLost then -- remove from pilot table local unitname = event.IniUnitName or "none" + if self.CtldUnits[unitname] then + local lostcargo = UTILS.DeepCopy(self.Loaded_Cargo[unitname] or {}) + self:__HelicopterLost(1,unitname,lostcargo) + end self.CtldUnits[unitname] = nil self.Loaded_Cargo[unitname] = nil self.MenusDone[unitname] = nil From 4aacdc156736e10a79e323ab07467cc2d119a63b Mon Sep 17 00:00:00 2001 From: Shafik Date: Wed, 29 Jan 2025 14:43:43 +0200 Subject: [PATCH 059/158] [FIXED] attempt to index local 'CleanUpUnit' (a nil value) --- Moose Development/Moose/Functional/CleanUp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua index 14616331f..5d975c3ca 100644 --- a/Moose Development/Moose/Functional/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -384,7 +384,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule() local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT local CleanUpGroupName = CleanUpListData.CleanUpGroupName - if CleanUpUnit:IsAlive() ~= nil then + if CleanUpUnit and CleanUpUnit:IsAlive() ~= nil then if self:IsInAirbase( CleanUpUnit:GetVec2() ) then From 5ef9bb2acda3e323817cf3909eb6b8f87fec59ac Mon Sep 17 00:00:00 2001 From: Shafik Date: Wed, 29 Jan 2025 14:44:31 +0200 Subject: [PATCH 060/158] [FIXED] attempt to get length of local 'parking' (a nil value) --- Moose Development/Moose/Functional/Warehouse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 3c812686d..29682074f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -6047,7 +6047,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol else - if #parking<#template.units and not airstart then + if parking and #parking<#template.units and not airstart then local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units) self:_DebugMessage(text) return nil From 99b93266ad466e417b0c0dbaff9ba791a042cbae Mon Sep 17 00:00:00 2001 From: Fedge <> Date: Wed, 29 Jan 2025 18:47:44 -0700 Subject: [PATCH 061/158] Fixes the AH-64D's FCR enum weapon definition. --- Moose Development/Moose/Utilities/Enums.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 59df545e1..ffd2a7deb 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -1327,7 +1327,7 @@ ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet = {4,5,9,490} ENUMS.Storage.weapons.OH58.Smk_Grenade_White = {4,5,9,492} ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow = {4,5,9,491} -- Apache -ENUMS.Storage.weapons.AH64D.AN_APG78 = {4,15,44,2138} +ENUMS.Storage.weapons.AH64D.AN_APG78 = {4,15,44,2114} ENUMS.Storage.weapons.AH64D.Internal_Aux_FuelTank = {1,3,43,1700} --- From 03c30f3ccef48b86e906f7224e7f8ffee24720c4 Mon Sep 17 00:00:00 2001 From: Fedge <> Date: Wed, 29 Jan 2025 18:48:34 -0700 Subject: [PATCH 062/158] Adds the 2 primary APKWS rockets as entries in the `ENUMS.Storage.weapons.missiles` table. I added them to `missiles`, rather than `nurs` because that's where they appear in the mission editor's storage window. --- Moose Development/Moose/Utilities/Enums.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index ffd2a7deb..a269fb972 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -1279,6 +1279,8 @@ ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M" ENUMS.Storage.weapons.missiles.AGM_12A = "weapons.missiles.AGM_12A" ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Tank" ENUMS.Storage.weapons.bombs.GBU_15_V_1_B = "weapons.bombs.GBU_15_V_1_B" +ENUMS.Storage.weapons.missiles.HYDRA_70_M151_APKWS = {4,4,8,292} +ENUMS.Storage.weapons.missiles.HYDRA_70_M282_APKWS = {4,4,8,293} -- dupes with typos ENUMS.Storage.weapons.bombs.BAP100 = "weapons.bombs.BAP_100" ENUMS.Storage.weapons.bombs.BLU3B_GROUP = "weapons.bombs.BLU-3B_GROUP" From 4b16e94eaf31d2c630988a3d0b3f78749d98725f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 30 Jan 2025 13:19:11 +0100 Subject: [PATCH 063/158] #CTLD - Added function for the game designer to get an overview of stock and alive groups, if they want to manage the stock: `CTLD:_CountStockPlusInHeloPlusAliveGroups()` --- Moose Development/Moose/Ops/CTLD.lua | 165 +++++++++++++++++++++++++-- 1 file changed, 158 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index b5c7d9e19..62cbb8a25 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -56,6 +56,7 @@ do -- @field #string StaticType Individual type if set. -- @field #string StaticCategory Individual static category if set. -- @field #list<#string> TypeNames Table of unit types able to pick this cargo up. +-- @field #number Stock0 Initial stock, if any given. -- @extends Core.Base#BASE --- @@ -73,6 +74,7 @@ CTLD_CARGO = { HasBeenDropped = false, PerCrateMass = 0, Stock = nil, + Stock0 = nil, Mark = nil, DontShowInMenu = false, Location = nil, @@ -131,6 +133,7 @@ CTLD_CARGO = { self.HasBeenDropped = Dropped or false --#boolean self.PerCrateMass = PerCrateMass or 0 -- #number self.Stock = Stock or nil --#number + self.Stock0 = Stock or nil --#number self.Mark = nil self.Subcategory = Subcategory or "Other" self.DontShowInMenu = DontShowInMenu or false @@ -321,7 +324,7 @@ CTLD_CARGO = { --- Get Stock. -- @param #CTLD_CARGO self - -- @return #number Stock + -- @return #number Stock or -1 if unlimited. function CTLD_CARGO:GetStock() if self.Stock then return self.Stock @@ -330,6 +333,28 @@ CTLD_CARGO = { end end + --- Get Stock0. + -- @param #CTLD_CARGO self + -- @return #number Stock0 or -1 if unlimited. + function CTLD_CARGO:GetStock0() + if self.Stock0 then + return self.Stock0 + else + return -1 + end + end + + --- Get relative Stock. + -- @param #CTLD_CARGO self + -- @return #number Stock Percentage like 75, or -1 if unlimited. + function CTLD_CARGO:GetRelativeStock() + if self.Stock and self.Stock0 then + return math.floor((self.Stock/self.Stock0)*100) + else + return -1 + end + end + --- Add Stock. -- @param #CTLD_CARGO self -- @param #number Number to add, none if nil. @@ -986,6 +1011,14 @@ do -- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- ... your code here ... -- end +-- +--- ## 3.6 OnAfterHelicopterLost +-- +-- This function is called when a player has deployed left a unit or crashed/died: +-- +-- function my_ctld:OnAfterHelicopterLost(From, Event, To, Unitname, Cargotable) +-- ... your code here ... +-- end -- -- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired -- @@ -1355,7 +1388,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.28" +CTLD.version="1.1.29" --- Instantiate a new CTLD. -- @param #CTLD self @@ -5313,6 +5346,119 @@ end self.EngineersInField = engtable return self end + + --- User - Count both the stock and groups in the field for available cargo types. Counts only limited cargo items and only troops and vehicle/FOB crates! + -- @param #CTLD self + -- @return #table Table A table of contents with numbers. + -- @usage + -- The index is the unique cargo name. + -- Each entry in the returned table contains a table with the following entries: + -- + -- { + -- Stock0 -- number of original stock when the cargo entry was created. + -- Stock -- number of currently available stock. + -- StockR -- relative number of available stock, e.g. 75 (percent). + -- Infield -- number of groups alive in the field of this kind. + -- Inhelo -- number of troops/crates in any helo alive. Can be with decimals < 1 if e.g. you have cargo that need 4 crates, but you have 2 loaded. + -- Sum -- sum is stock + infield + inhelo. + -- } + function CTLD:_CountStockPlusInHeloPlusAliveGroups() + local Troopstable = {} + -- generics + for _id,_cargo in pairs(self.Cargo_Crates) do + local generic = _cargo -- #CTLD_CARGO + local genname = generic:GetName() + if generic and generic:GetStock0() > 0 and not Troopstable[genname] then + Troopstable[genname] = { + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 0, + Inhelo = 0, + Sum = generic:GetStock(), + } + end + end + --- + for _id,_cargo in pairs(self.Cargo_Troops) do + local generic = _cargo -- #CTLD_CARGO + local genname = generic:GetName() + if generic and generic:GetStock0() > 0 and not Troopstable[genname] then + Troopstable[genname] = { + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 0, + Inhelo = 0, + Sum = generic:GetStock(), + } + end + end + -- Troops & Built Crates + for _index, _group in pairs (self.DroppedTroops) do + if _group and _group:IsAlive() then + self:T("Looking at ".._group:GetName() .. " in the field") + local generic = self:GetGenericCargoObjectFromGroupName(_group:GetName()) -- #CTLD_CARGO + if generic then + local genname = generic:GetName() + self:T("Found Generic "..genname .. " in the field. Adding.") + if generic:GetStock0() > 0 then -- don't count unlimited stock + if not Troopstable[genname] then + Troopstable[genname] = { + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 1, + Inhelo = 0, + Sum = generic:GetStock()+1, + } + else + Troopstable[genname].Infield = Troopstable[genname].Infield + 1 + Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + Troopstable[genname].Inhelo + end + end + else + self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) + end + end + end + -- Helos + for _unitname,_loaded in pairs(self.Loaded_Cargo) do + local _unit = UNIT:FindByName(_unitname) + if _unit and _unit:IsAlive() then + local unitname = _unit:GetName() + local loadedcargo = self.Loaded_Cargo[unitname].Cargo or {} + for _,_cgo in pairs (loadedcargo) do + local cargo = _cgo -- #CTLD_CARGO + local type = cargo.CargoType + local gname = cargo.Name + local gcargo = self:_FindCratesCargoObject(gname) or self:_FindTroopsCargoObject(gname) + self:T("Looking at ".. gname .. " in the helo - type = "..type) + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS or type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then + -- valid troops/engineers + if gcargo and gcargo:GetStock0() > 0 then -- don't count unlimited stock + self:T("Adding ".. gname .. " in the helo - type = "..type) + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then + Troopstable[gname].Inhelo = Troopstable[gname].Inhelo + 1 + end + if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then + -- maybe multiple crates of the same type + local counting = gcargo.CratesNeeded + local added = 1 + if counting > 1 then + added = added/counting + end + Troopstable[gname].Inhelo = Troopstable[gname].Inhelo + added + end + Troopstable[gname].Sum = Troopstable[gname].Infield + Troopstable[gname].Stock + Troopstable[gname].Inhelo + end + end + end + end + end + return Troopstable + end + --- User - function to add stock of a certain troops type -- @param #CTLD self @@ -5542,19 +5688,24 @@ end -- @return #CTLD_CARGO The cargo object or nil if not found function CTLD:GetGenericCargoObjectFromGroupName(GroupName) local Cargotype = nil + local template = GroupName + if string.find(template,"#") then + template = string.gsub(GroupName,"#(%d+)$","") + end + template = string.gsub(template,"-(%d+)$","") for k,v in pairs(self.Cargo_Troops) do local comparison = "" if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end - if comparison == GroupName then + if comparison == template then Cargotype = v break end end if not Cargotype then - for k,v in pairs(self.Cargo_Crates) do + for k,v in pairs(self.Cargo_Crates) do -- #number, #CTLD_CARGO local comparison = "" if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end - if comparison == GroupName then + if comparison == template and v.CargoType ~= CTLD_CARGO.Enum.REPAIR then Cargotype = v break end @@ -5679,7 +5830,7 @@ end if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter - cargo.Stock = 1 + --cargo.Stock = 1 table.insert(self.Cargo_Troops,cargo) end @@ -5829,7 +5980,7 @@ end if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter - cargo.Stock = 1 + --cargo.Stock = 1 table.insert(self.Cargo_Crates,cargo) end From 35348d9b81974022a142c47ac551f0d471ebb310 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 30 Jan 2025 18:32:25 +0100 Subject: [PATCH 064/158] #UTILS - Added UTILS.Weather for fog stuff --- Moose Development/Moose/Utilities/Utils.lua | 75 +++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index edf00b2f7..eeff81f26 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -4324,3 +4324,78 @@ end function UTILS.ScalarMult(vec, mult) return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult} end + +--- Utilities weather class for fog mainly. +-- @type UTILS.Weather +UTILS.Weather = {} + +--- Returns the current fog thickness in meters. Returns zero if fog is not present. +function UTILS.Weather.GetFogThickness() + return world.weather.getFogThickness() +end + +--- Sets the fog to the desired thickness in meters at sea level. +-- @param #number Thickness Thickness in meters. +-- Any fog animation will be discarded. +-- Valid range : 100 to 5000 meters +function UTILS.Weather.SetFogThickness(Thickness) + local value = Thickness + if value < 100 then value = 100 + elseif value > 5000 then value = 5000 end + return world.weather.setFogThickness(value) +end + +--- Removes the fog. +function UTILS.Weather.RemoveFog() + return world.weather.setFogThickness(0) +end + +--- Gets the maximum visibility distance of the current fog setting. +-- Returns 0 if no fog is present. +function UTILS.Weather.GetFogVisibilityDistanceMax() + return world.weather.getFogVisibilityDistance() +end + +--- Sets the maximum visibility at sea level in meters. +-- @param #number Thickness Thickness in meters. +-- Limit: 100 to 100000 +function UTILS.Weather.SetFogVisibilityDistance(Thickness) + local value = Thickness + if value < 100 then value = 100 + elseif value > 100000 then value = 100000 end + return world.weather.setFogVisibilityDistance(value) +end + +--- Uses data from the passed table to change the fog visibility and thickness over a desired timeframe. This allows for a gradual increase/decrease of fog values rather than abruptly applying the values. +-- Animation Key Format: {time, visibility, thickness} +-- @param #table AnimationKeys Table of AnimationKey tables +-- @usage +-- Time: in seconds 0 to infinity +-- Time is relative to when the function was called. Time value for each key must be larger than the previous key. If time is set to 0 then the fog will be applied to the corresponding visibility and thickness values at that key. Any time value greater than 0 will result in the current fog being inherited and changed to the first key. +-- Visibility: in meters 100 to 100000 +-- Thickness: in meters 100 to 5000 +-- The speed at which the visibility and thickness changes is based on the time between keys and the values that visibility and thickness are being set to. +-- +-- When the function is passed an empty table {} or nil the fog animation will be discarded and whatever the current thickness and visibility are set to will remain. +-- +-- The following will set the fog in the mission to disappear in 1 minute. +-- +-- UTILS.Weather.SetFogAnimation({ {60, 0, 0} }) +-- +-- The following will take 1 hour to get to the first fog setting, it will maintain that fog setting for another hour, then lightly removes the fog over the 2nd and 3rd hour, the completely removes the fog after 3 hours and 3 minutes from when the function was called. +-- +-- UTILS.Weather.SetFogAnimation({ +-- {3600, 10000, 3000}, -- one hour to get to that fog setting +-- {7200, 10000, 3000}, -- will maintain for 2 hours +-- {10800, 20000, 2000}, -- at 3 hours visibility will have been increased while thickness decreases slightly +-- {12600, 0, 0}, -- at 3:30 after the function was called the fog will be completely removed. +-- }) +-- +function UTILS.Weather.SetFogAnimation(AnimationKeys) + return world.weather.setFogAnimation(AnimationKeys) +end + +--- The fog animation will be discarded and whatever the current thickness and visibility are set to will remain +function UTILS.Weather.StopFogAnimation() + return world.weather.setFogAnimation({}) +end From d06e44d37b11b3bd6b8f5ac0b3db3c80744c20cd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 30 Jan 2025 18:32:39 +0100 Subject: [PATCH 065/158] #CTLD small additions --- Moose Development/Moose/Ops/CTLD.lua | 40 +++++++++++++++++++--------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 62cbb8a25..180b2c2e6 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -5349,6 +5349,8 @@ end --- User - Count both the stock and groups in the field for available cargo types. Counts only limited cargo items and only troops and vehicle/FOB crates! -- @param #CTLD self + -- @param #boolean Restock If true, restock the cargo and troop items. + -- @param #number Threshold Percentage below which to restock, used in conjunction with Restock (must be true). Defaults to 75 (percent). -- @return #table Table A table of contents with numbers. -- @usage -- The index is the unique cargo name. @@ -5361,8 +5363,9 @@ end -- Infield -- number of groups alive in the field of this kind. -- Inhelo -- number of troops/crates in any helo alive. Can be with decimals < 1 if e.g. you have cargo that need 4 crates, but you have 2 loaded. -- Sum -- sum is stock + infield + inhelo. + -- GenericCargo -- this filed holds the generic CTLD_CARGO object which drives the available stock. Only populated if Restock is true. -- } - function CTLD:_CountStockPlusInHeloPlusAliveGroups() + function CTLD:_CountStockPlusInHeloPlusAliveGroups(Restock,Threshold) local Troopstable = {} -- generics for _id,_cargo in pairs(self.Cargo_Crates) do @@ -5377,6 +5380,9 @@ end Inhelo = 0, Sum = generic:GetStock(), } + if Restock == true then + Troopstable[genname].GenericCargo = generic + end end end --- @@ -5392,6 +5398,9 @@ end Inhelo = 0, Sum = generic:GetStock(), } + if Restock == true then + Troopstable[genname].GenericCargo = generic + end end end -- Troops & Built Crates @@ -5403,19 +5412,8 @@ end local genname = generic:GetName() self:T("Found Generic "..genname .. " in the field. Adding.") if generic:GetStock0() > 0 then -- don't count unlimited stock - if not Troopstable[genname] then - Troopstable[genname] = { - Stock0 = generic:GetStock0(), - Stock = generic:GetStock(), - StockR = generic:GetRelativeStock(), - Infield = 1, - Inhelo = 0, - Sum = generic:GetStock()+1, - } - else Troopstable[genname].Infield = Troopstable[genname].Infield + 1 Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + Troopstable[genname].Inhelo - end end else self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) @@ -5455,7 +5453,19 @@ end end end end + end + -- Restock? + if Restock == true then + local threshold = Threshold or 75 + for _name,_data in pairs(Troopstable) do + if _data.StockR and _data.StockR < threshold then + if _data.GenericCargo then + _data.GenericCargo:SetStock(_data.Stock0) -- refill to start level + end + end + end end + -- Return return Troopstable end @@ -5473,6 +5483,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -5491,6 +5502,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -5509,6 +5521,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -5527,6 +5540,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self @@ -5545,6 +5559,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self @@ -5563,6 +5578,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self From 74520b13599571523b966a8c2070cfd0bd09a5d1 Mon Sep 17 00:00:00 2001 From: Shafik Date: Fri, 31 Jan 2025 10:30:18 +0200 Subject: [PATCH 066/158] [FIXED] attempt to index a nil value (`AWACS:_CheckAwacsStatus` -> `OPSGROUP:GetCallsignName` -> `GROUP:IsPlayer`) --- Moose Development/Moose/Wrapper/Group.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index aedf6412d..e72822977 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -757,7 +757,11 @@ end -- @param #GROUP self -- @return #boolean If true, group is associated with a client or player slot. function GROUP:IsPlayer() - return self:GetUnit(1):IsPlayer() + local unit = self:GetUnit(1) + if unit then + return unit:IsPlayer() + end + return false end --- Returns the UNIT wrapper object with number UnitNumber. If it doesn't exist, tries to return the next available unit. From f0fe1b431df2a23a4d8bef6fa7dbf403658279da Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 31 Jan 2025 14:26:28 +0100 Subject: [PATCH 067/158] #SPAWN - Add optional waiting time to InitRepeatOnLanding --- Moose Development/Moose/Core/Spawn.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 89ff4d9fa..42b8bab38 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1278,6 +1278,7 @@ end --- Respawn group after landing. -- @param #SPAWN self +-- @param #number WaitingTime Wait this many seconds before despawning the alive group after landing. Defaults to 3 . -- @return #SPAWN self -- @usage -- @@ -1285,15 +1286,16 @@ end -- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. -- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) -- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnLanding() +-- :InitRepeatOnLanding(20) -- :Spawn() -- -function SPAWN:InitRepeatOnLanding() +function SPAWN:InitRepeatOnLanding(WaitingTime) --self:F( { self.SpawnTemplatePrefix } ) self:InitRepeat() self.RepeatOnEngineShutDown = false self.RepeatOnLanding = true + self.RepeatOnLandingTime = (WaitingTime and WaitingTime > 3) and WaitingTime or 3 return self end @@ -4064,7 +4066,7 @@ function SPAWN:_OnLand( EventData ) -- self:ReSpawn( SpawnGroupIndex ) -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. - SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, self.RepeatOnLandingTime or 3 ) end end end From d25a723fc7f31876602af0b6e49cd7d7aaddb883 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 1 Feb 2025 18:39:51 +0100 Subject: [PATCH 068/158] #MESSAGE - add parameter for SRS Backend --- Moose Development/Moose/Core/Message.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index a452ab582..4165bdc57 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -464,6 +464,7 @@ _MESSAGESRS = {} -- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest). -- @param #string Label (optional) Label, defaults to "MESSAGE" or the Message Category set. -- @param Core.Point#COORDINATE Coordinate (optional) Coordinate this messages originates from. +-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only @@ -471,7 +472,7 @@ _MESSAGESRS = {} -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- -function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate) +function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend) _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" @@ -489,6 +490,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G _MESSAGESRS.MSRS:SetCoordinate(Coordinate) end + if Backend then + _MESSAGESRS.MSRS:SetBackend(Backend) + end + _MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB" _MESSAGESRS.MSRS:SetCulture(Culture) From d1e31a3652f6180adf22107cc3c608ba6614243d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 1 Feb 2025 18:40:26 +0100 Subject: [PATCH 069/158] #PLAYERRECCE - add parameter for SRS Backend option --- Moose Development/Moose/Ops/PlayerRecce.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 1b518dbe2..a88685565 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -1551,8 +1551,9 @@ end -- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS. -- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest) -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS +-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC -- @return #PLAYERRECCE self -function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) +function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend) self:T(self.lid.."SetSRS") self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.Gender = Gender or MSRS.gender or "male" -- @@ -1574,6 +1575,9 @@ function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,V self.SRS:SetCulture(self.Culture) self.SRS:SetPort(self.Port) self.SRS:SetVolume(self.Volume) + if Backend then + self.SRS:SetBackend(Backend) + end if self.PathToGoogleKey then self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey) self.SRS:SetProvider(MSRS.Provider.GOOGLE) From db1779e1db7dc43d0779da0a51f65d410035e099 Mon Sep 17 00:00:00 2001 From: Shafik Date: Sun, 2 Feb 2025 11:42:40 +0200 Subject: [PATCH 070/158] [ADDED] Set AI On/Off for units --- Moose Development/Moose/Wrapper/Unit.lua | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 6bfd2eb45..42d271143 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1797,3 +1797,42 @@ function UNIT:GetSTN() end return STN, VCL, VCN, FGL end + +do -- AI methods + + --- Turns the AI On or Off for the UNIT. + -- @param #UNIT self + -- @param #boolean AIOnOff The value true turns the AI On, the value false turns the AI Off. + -- @return #UNIT The UNIT. + function UNIT:SetAIOnOff( AIOnOff ) + + local DCSUnit = self:GetDCSObject() -- DCS#Group + + if DCSUnit then + local DCSController = DCSUnit:getController() -- DCS#Controller + if DCSController then + DCSController:setOnOff( AIOnOff ) + return self + end + end + + return nil + end + + --- Turns the AI On for the UNIT. + -- @param #UNIT self + -- @return #UNIT The UNIT. + function UNIT:SetAIOn() + + return self:SetAIOnOff( true ) + end + + --- Turns the AI Off for the UNIT. + -- @param #UNIT self + -- @return #UNIT The UNIT. + function UNIT:SetAIOff() + + return self:SetAIOnOff( false ) + end + +end From cc057744ba3d796a4accf6050e35049416ec7643 Mon Sep 17 00:00:00 2001 From: Shafik Date: Sun, 2 Feb 2025 12:46:55 +0200 Subject: [PATCH 071/158] [ADDED] `UNIT:IsSAM()` and `UNIT:IsAAA()` to use used in their respective GROUP functions. --- Moose Development/Moose/Wrapper/Group.lua | 16 +- Moose Development/Moose/Wrapper/Unit.lua | 2162 +++++++++++---------- 2 files changed, 1125 insertions(+), 1053 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index e72822977..17f23ed3c 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -3152,7 +3152,7 @@ function GROUP:IsSAM() local units = self:GetUnits() for _,_unit in pairs(units or {}) do local unit = _unit -- Wrapper.Unit#UNIT - if unit:HasSEAD() and unit:IsGround() and (not unit:HasAttribute("Mobile AAA")) then + if unit:IsSAM() then issam = true break end @@ -3162,18 +3162,16 @@ end --- [GROUND] Determine if a GROUP has a AAA unit, i.e. has no radar or optical tracker but the AAA = true or the "Mobile AAA" = true attribute. -- @param #GROUP self --- @return #boolean IsSAM True if AAA, else false +-- @return #boolean IsAAA True if AAA, else false function GROUP:IsAAA() - local issam = false + local isAAA = false local units = self:GetUnits() for _,_unit in pairs(units or {}) do local unit = _unit -- Wrapper.Unit#UNIT - local desc = unit:GetDesc() or {} - local attr = desc.attributes or {} - if unit:HasSEAD() then return false end - if attr["AAA"] or attr["SAM related"] then - issam = true + if unit:IsAAA() then + isAAA = true + break end end - return issam + return isAAA end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 42d271143..780616d7e 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -92,10 +92,10 @@ -- -- @field #UNIT UNIT = { - ClassName="UNIT", - UnitName=nil, - GroupName=nil, - DCSUnit = nil, + ClassName = "UNIT", + UnitName = nil, + GroupName = nil, + DCSUnit = nil, } @@ -108,34 +108,34 @@ UNIT = { -- Registration. - + --- Create a new UNIT from DCSUnit. -- @param #UNIT self -- @param #string UnitName The name of the DCS unit. -- @return #UNIT self -function UNIT:Register( UnitName ) +function UNIT:Register(UnitName) - -- Inherit CONTROLLABLE. - local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) --#UNIT - - -- Set unit name. - self.UnitName = UnitName - - local unit=Unit.getByName(self.UnitName) - - if unit then - local group = unit:getGroup() - if group then - self.GroupName=group:getName() - self.groupId = group:getID() + -- Inherit CONTROLLABLE. + local self = BASE:Inherit(self, CONTROLLABLE:New(UnitName)) --#UNIT + + -- Set unit name. + self.UnitName = UnitName + + local unit = Unit.getByName(self.UnitName) + + if unit then + local group = unit:getGroup() + if group then + self.GroupName = group:getName() + self.groupId = group:getID() + end + self.DCSUnit = unit end - self.DCSUnit = unit - end - - -- Set event prio. - self:SetEventPriority( 3 ) - - return self + + -- Set event prio. + self:SetEventPriority(3) + + return self end -- Reference methods. @@ -144,23 +144,23 @@ end -- @param #UNIT self -- @param DCS#Unit DCSUnit An existing DCS Unit object reference. -- @return #UNIT self -function UNIT:Find( DCSUnit ) - if DCSUnit then - local UnitName = DCSUnit:getName() - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound - end - return nil +function UNIT:Find(DCSUnit) + if DCSUnit then + local UnitName = DCSUnit:getName() + local UnitFound = _DATABASE:FindUnit(UnitName) + return UnitFound + end + return nil end --- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. -- @param #UNIT self -- @param #string UnitName The Unit Name. -- @return #UNIT self -function UNIT:FindByName( UnitName ) - - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound +function UNIT:FindByName(UnitName) + + local UnitFound = _DATABASE:FindUnit(UnitName) + return UnitFound end --- Find the first(!) UNIT matching using patterns. Note that this is **a lot** slower than `:FindByName()`! @@ -175,17 +175,17 @@ end -- -- using a pattern -- local unit = UNIT:FindByMatching( ".%d.%d$" ) -- -- will return the first group found ending in "-1-1" to "-9-9", but not e.g. "-10-1" -function UNIT:FindByMatching( Pattern ) - local GroupFound = nil - - for name,group in pairs(_DATABASE.UNITS) do - if string.match(name, Pattern ) then - GroupFound = group - break +function UNIT:FindByMatching(Pattern) + local GroupFound = nil + + for name, group in pairs(_DATABASE.UNITS) do + if string.match(name, Pattern) then + GroupFound = group + break + end end - end - - return GroupFound + + return GroupFound end --- Find all UNIT objects matching using patterns. Note that this is **a lot** slower than `:FindByName()`! @@ -200,24 +200,24 @@ end -- -- using a pattern -- local unittable = UNIT:FindAllByMatching( ".%d.%d$" ) -- -- will return the all units found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10" -function UNIT:FindAllByMatching( Pattern ) - local GroupsFound = {} - - for name,group in pairs(_DATABASE.UNITS) do - if string.match(name, Pattern ) then - GroupsFound[#GroupsFound+1] = group +function UNIT:FindAllByMatching(Pattern) + local GroupsFound = {} + + for name, group in pairs(_DATABASE.UNITS) do + if string.match(name, Pattern) then + GroupsFound[#GroupsFound + 1] = group + end end - end - - return GroupsFound + + return GroupsFound end --- Return the name of the UNIT. -- @param #UNIT self -- @return #string The UNIT name. function UNIT:Name() - - return self.UnitName + + return self.UnitName end --[[ @@ -241,28 +241,28 @@ end -- @return DCS#Unit The DCS Group. function UNIT:GetDCSObject() - -- FF: Added checks that DCSObject exists because otherwise there were problems when respawning the unit right after it was initially spawned (e.g. teleport in OPSGROUP). - -- Got "Unit does not exit" after coalition.addGroup() when trying to access unit data because LastCallDCSObject<=1. - if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime()-self.LastCallDCSObject>1) or (self.DCSObject==nil) or (self.DCSObject:isExist()==false) then + -- FF: Added checks that DCSObject exists because otherwise there were problems when respawning the unit right after it was initially spawned (e.g. teleport in OPSGROUP). + -- Got "Unit does not exit" after coalition.addGroup() when trying to access unit data because LastCallDCSObject<=1. + if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) or (self.DCSObject == nil) or (self.DCSObject:isExist() == false) then - -- Get DCS group. - local DCSUnit = Unit.getByName( self.UnitName ) + -- Get DCS group. + local DCSUnit = Unit.getByName(self.UnitName) + + if DCSUnit then + self.LastCallDCSObject = timer.getTime() + self.DCSObject = DCSUnit + return DCSUnit + else + self.DCSObject = nil + self.LastCallDCSObject = nil + end - if DCSUnit then - self.LastCallDCSObject = timer.getTime() - self.DCSObject = DCSUnit - return DCSUnit else - self.DCSObject = nil - self.LastCallDCSObject = nil + return self.DCSObject end - - else - return self.DCSObject - end - - --self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.UnitName))) - return nil + + --self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.UnitName))) + return nil end --- Returns the unit altitude above sea level in meters. @@ -270,22 +270,22 @@ end -- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. -- @return #number The height of the group or nil if is not existing or alive. function UNIT:GetAltitude(FromGround) - - local DCSUnit = self:GetDCSObject() - if DCSUnit then - local altitude = 0 - local point = DCSUnit:getPoint() --DCS#Vec3 - altitude = point.y - if FromGround then - local land = land.getHeight( { x = point.x, y = point.z } ) or 0 - altitude = altitude - land + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local altitude = 0 + local point = DCSUnit:getPoint() --DCS#Vec3 + altitude = point.y + if FromGround then + local land = land.getHeight({ x = point.x, y = point.z }) or 0 + altitude = altitude - land + end + return altitude end - return altitude - end - return nil - + return nil + end --- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group. @@ -299,85 +299,85 @@ end -- @param #UNIT self -- @param Core.Point#COORDINATE Coordinate The position where to Spawn the new Unit at. -- @param #number Heading The heading of the unit respawn. -function UNIT:ReSpawnAt( Coordinate, Heading ) +function UNIT:ReSpawnAt(Coordinate, Heading) - --self:T( self:Name() ) - local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) - --self:T( SpawnGroupTemplate ) + --self:T( self:Name() ) + local SpawnGroupTemplate = UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name())) + --self:T( SpawnGroupTemplate ) - local SpawnGroup = self:GetGroup() - --self:T( { SpawnGroup = SpawnGroup } ) - - if SpawnGroup then - - local Vec3 = SpawnGroup:GetVec3() - SpawnGroupTemplate.x = Coordinate.x - SpawnGroupTemplate.y = Coordinate.z - - --self:F( #SpawnGroupTemplate.units ) - for UnitID, UnitData in pairs( SpawnGroup:GetUnits() or {} ) do - local GroupUnit = UnitData -- #UNIT - --self:F( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y - SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x - SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z - SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading - --self:F( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) - end - end - end - - for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do - --self:T( { UnitTemplateData.name, self:Name() } ) - SpawnGroupTemplate.units[UnitTemplateID].unitId = nil - if UnitTemplateData.name == self:Name() then - --self:T("Adjusting") - SpawnGroupTemplate.units[UnitTemplateID].alt = Coordinate.y - SpawnGroupTemplate.units[UnitTemplateID].x = Coordinate.x - SpawnGroupTemplate.units[UnitTemplateID].y = Coordinate.z - SpawnGroupTemplate.units[UnitTemplateID].heading = Heading - --self:F( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) - else - --self:F( SpawnGroupTemplate.units[UnitTemplateID].name ) - local GroupUnit = UNIT:FindByName( SpawnGroupTemplate.units[UnitTemplateID].name ) -- #UNIT - if GroupUnit and GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - UnitTemplateData.alt = GroupUnitVec3.y - UnitTemplateData.x = GroupUnitVec3.x - UnitTemplateData.y = GroupUnitVec3.z - UnitTemplateData.heading = GroupUnitHeading - else - if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then - --self:T("nilling") - SpawnGroupTemplate.units[UnitTemplateID].delete = true + local SpawnGroup = self:GetGroup() + --self:T( { SpawnGroup = SpawnGroup } ) + + if SpawnGroup then + + local Vec3 = SpawnGroup:GetVec3() + SpawnGroupTemplate.x = Coordinate.x + SpawnGroupTemplate.y = Coordinate.z + + --self:F( #SpawnGroupTemplate.units ) + for UnitID, UnitData in pairs(SpawnGroup:GetUnits() or {}) do + local GroupUnit = UnitData -- #UNIT + --self:F( GroupUnit:GetName() ) + if GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y + SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x + SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z + SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading + --self:F( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) + end end - end end - end - -- Remove obscolete units from the group structure - local i = 1 - while i <= #SpawnGroupTemplate.units do - - local UnitTemplateData = SpawnGroupTemplate.units[i] - --self:T( UnitTemplateData.name ) - - if UnitTemplateData.delete then - table.remove( SpawnGroupTemplate.units, i ) - else - i = i + 1 + for UnitTemplateID, UnitTemplateData in pairs(SpawnGroupTemplate.units) do + --self:T( { UnitTemplateData.name, self:Name() } ) + SpawnGroupTemplate.units[UnitTemplateID].unitId = nil + if UnitTemplateData.name == self:Name() then + --self:T("Adjusting") + SpawnGroupTemplate.units[UnitTemplateID].alt = Coordinate.y + SpawnGroupTemplate.units[UnitTemplateID].x = Coordinate.x + SpawnGroupTemplate.units[UnitTemplateID].y = Coordinate.z + SpawnGroupTemplate.units[UnitTemplateID].heading = Heading + --self:F( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) + else + --self:F( SpawnGroupTemplate.units[UnitTemplateID].name ) + local GroupUnit = UNIT:FindByName(SpawnGroupTemplate.units[UnitTemplateID].name) -- #UNIT + if GroupUnit and GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + UnitTemplateData.alt = GroupUnitVec3.y + UnitTemplateData.x = GroupUnitVec3.x + UnitTemplateData.y = GroupUnitVec3.z + UnitTemplateData.heading = GroupUnitHeading + else + if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then + --self:T("nilling") + SpawnGroupTemplate.units[UnitTemplateID].delete = true + end + end + end end - end - - SpawnGroupTemplate.groupId = nil - - --self:T( SpawnGroupTemplate ) - _DATABASE:Spawn( SpawnGroupTemplate ) + -- Remove obscolete units from the group structure + local i = 1 + while i <= #SpawnGroupTemplate.units do + + local UnitTemplateData = SpawnGroupTemplate.units[i] + --self:T( UnitTemplateData.name ) + + if UnitTemplateData.delete then + table.remove(SpawnGroupTemplate.units, i) + else + i = i + 1 + end + end + + SpawnGroupTemplate.groupId = nil + + --self:T( SpawnGroupTemplate ) + + _DATABASE:Spawn(SpawnGroupTemplate) end @@ -386,17 +386,17 @@ end -- @param #UNIT self -- @return #boolean `true` if Unit is activated. `nil` The DCS Unit is not existing or alive. function UNIT:IsActive() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local UnitIsActive = DCSUnit:isActive() - return UnitIsActive - end + local DCSUnit = self:GetDCSObject() - return nil + if DCSUnit then + + local UnitIsActive = DCSUnit:isActive() + return UnitIsActive + end + + return nil end --- Returns if the unit is exists in the mission. @@ -406,14 +406,14 @@ end -- @return #boolean Returns `true` if unit exists in the mission. function UNIT:IsExist() - local DCSUnit = self:GetDCSObject() -- DCS#Unit - - if DCSUnit then - local exists = DCSUnit:isExist() - return exists - end - - return nil + local DCSUnit = self:GetDCSObject() -- DCS#Unit + + if DCSUnit then + local exists = DCSUnit:isExist() + return exists + end + + return nil end --- Returns if the Unit is alive. @@ -423,80 +423,86 @@ end -- @param #UNIT self -- @return #boolean Returns `true` if Unit is alive and active, `false` if it exists but is not active and `nil` if the object does not exist or DCS `isExist` function returns false. function UNIT:IsAlive() - --self:F3( self.UnitName ) + --self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() -- DCS#Unit - - if DCSUnit and DCSUnit:isExist() then - local UnitIsAlive = DCSUnit:isActive() - return UnitIsAlive - end - - return nil + local DCSUnit = self:GetDCSObject() -- DCS#Unit + + if DCSUnit and DCSUnit:isExist() then + local UnitIsAlive = DCSUnit:isActive() + return UnitIsAlive + end + + return nil end --- Returns if the Unit is dead. -- @param #UNIT self -- @return #boolean `true` if Unit is dead, else false or nil if the unit does not exist function UNIT:IsDead() - return not self:IsAlive() + return not self:IsAlive() end --- Returns the Unit's callsign - the localized string. -- @param #UNIT self -- @return #string The Callsign of the Unit. function UNIT:GetCallsign() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCallSign = DCSUnit:getCallsign() - if UnitCallSign == "" then - UnitCallSign = DCSUnit:getName() + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCallSign = DCSUnit:getCallsign() + if UnitCallSign == "" then + UnitCallSign = DCSUnit:getName() + end + return UnitCallSign end - return UnitCallSign - end - - --self:F( self.ClassName .. " " .. self.UnitName .. " not found!" ) - return nil + + --self:F( self.ClassName .. " " .. self.UnitName .. " not found!" ) + return nil end --- Check if an (air) unit is a client or player slot. Information is retrieved from the group template. -- @param #UNIT self -- @return #boolean If true, unit is associated with a client or player slot. function UNIT:IsPlayer() - - -- Get group. - local group=self:GetGroup() - - if not group then return false end - - -- Units of template group. - local template = group:GetTemplate() - - if (template == nil) or (template.units == nil ) then - local DCSObject = self:GetDCSObject() - if DCSObject then - if DCSObject:getPlayerName() ~= nil then return true else return false end - else - return false - end - end - - local units=template.units - - -- Get numbers. - for _,unit in pairs(units) do - - -- Check if unit name matach and skill is Client or Player. - if unit.name==self:GetName() and (unit.skill=="Client" or unit.skill=="Player") then - return true + + -- Get group. + local group = self:GetGroup() + + if not group then + return false end - end - - return false + -- Units of template group. + local template = group:GetTemplate() + + if (template == nil) or (template.units == nil) then + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:getPlayerName() ~= nil then + return true + else + return false + end + else + return false + end + end + + local units = template.units + + -- Get numbers. + for _, unit in pairs(units) do + + -- Check if unit name matach and skill is Client or Player. + if unit.name == self:GetName() and (unit.skill == "Client" or unit.skill == "Player") then + return true + end + + end + + return false end @@ -505,32 +511,32 @@ end -- @return #string Player Name -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetPlayerName() - --self:F( self.UnitName ) + --self:F( self.UnitName ) - local DCSUnit = self:GetDCSObject() -- DCS#Unit - - if DCSUnit then - - local PlayerName = DCSUnit:getPlayerName() - -- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696 --- if PlayerName == nil or PlayerName == "" then --- local PlayerCategory = DCSUnit:getDesc().category --- if PlayerCategory == Unit.Category.GROUND_UNIT or PlayerCategory == Unit.Category.SHIP then --- PlayerName = "Player" .. DCSUnit:getID() --- end --- end --- -- Good code --- if PlayerName == nil then --- PlayerName = nil --- else --- if PlayerName == "" then --- PlayerName = "Player" .. DCSUnit:getID() --- end --- end - return PlayerName - end + local DCSUnit = self:GetDCSObject() -- DCS#Unit - return nil + if DCSUnit then + + local PlayerName = DCSUnit:getPlayerName() + -- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696 + -- if PlayerName == nil or PlayerName == "" then + -- local PlayerCategory = DCSUnit:getDesc().category + -- if PlayerCategory == Unit.Category.GROUND_UNIT or PlayerCategory == Unit.Category.SHIP then + -- PlayerName = "Player" .. DCSUnit:getID() + -- end + -- end + -- -- Good code + -- if PlayerName == nil then + -- PlayerName = nil + -- else + -- if PlayerName == "" then + -- PlayerName = "Player" .. DCSUnit:getID() + -- end + -- end + return PlayerName + end + + return nil end @@ -539,11 +545,11 @@ end -- @return #boolean If true, unit is a player or client aircraft function UNIT:IsClient() - if _DATABASE.CLIENTS[self.UnitName] then - return true - end + if _DATABASE.CLIENTS[self.UnitName] then + return true + end - return false + return false end --- Get the CLIENT of the unit @@ -551,23 +557,23 @@ end -- @return Wrapper.Client#CLIENT function UNIT:GetClient() - local client=_DATABASE.CLIENTS[self.UnitName] + local client = _DATABASE.CLIENTS[self.UnitName] - if client then - return client - end + if client then + return client + end - return nil + return nil end --- [AIRPLANE] Get the NATO reporting name of a UNIT. Currently airplanes only! --@param #UNIT self --@return #string NatoReportingName or "Bogey" if unknown. function UNIT:GetNatoReportingName() - - local typename = self:GetTypeName() - return UTILS.GetReportingName(typename) - + + local typename = self:GetTypeName() + return UTILS.GetReportingName(typename) + end @@ -579,16 +585,16 @@ end -- @return #number The Unit number. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetNumber() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitNumber = DCSUnit:getNumber() - return UnitNumber - end + local DCSUnit = self:GetDCSObject() - return nil + if DCSUnit then + local UnitNumber = DCSUnit:getNumber() + return UnitNumber + end + + return nil end @@ -596,16 +602,16 @@ end -- @param #UNIT self -- @return #number Speed in km/h. function UNIT:GetSpeedMax() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local Desc = self:GetDesc() - - if Desc then - local SpeedMax = Desc.speedMax - return SpeedMax*3.6 - end + local Desc = self:GetDesc() - return 0 + if Desc then + local SpeedMax = Desc.speedMax + return SpeedMax * 3.6 + end + + return 0 end --- Returns the unit's max range in meters derived from the DCS descriptors. @@ -613,21 +619,21 @@ end -- @param #UNIT self -- @return #number Range in meters. function UNIT:GetRange() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local Desc = self:GetDesc() - - if Desc then - local Range = Desc.range --This is in kilometers (not meters) for some reason. But should check again! - if Range then - Range=Range*1000 -- convert to meters. - else - Range=10000000 --10.000 km if no range + local Desc = self:GetDesc() + + if Desc then + local Range = Desc.range --This is in kilometers (not meters) for some reason. But should check again! + if Range then + Range = Range * 1000 -- convert to meters. + else + Range = 10000000 --10.000 km if no range + end + return Range end - return Range - end - return nil + return nil end --- Check if the unit is refuelable. Also retrieves the refuelling system (boom or probe) if applicable. @@ -635,18 +641,18 @@ end -- @return #boolean If true, unit is refuelable (checks for the attribute "Refuelable"). -- @return #number Refueling system (if any): 0=boom, 1=probe. function UNIT:IsRefuelable() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local refuelable=self:HasAttribute("Refuelable") - - local system=nil - - local Desc=self:GetDesc() - if Desc and Desc.tankerType then - system=Desc.tankerType - end + local refuelable = self:HasAttribute("Refuelable") - return refuelable, system + local system = nil + + local Desc = self:GetDesc() + if Desc and Desc.tankerType then + system = Desc.tankerType + end + + return refuelable, system end --- Check if the unit is a tanker. Also retrieves the refuelling system (boom or probe) if applicable. @@ -654,41 +660,41 @@ end -- @return #boolean If true, unit is a tanker (checks for the attribute "Tankers"). -- @return #number Refueling system (if any): 0=boom, 1=probe. function UNIT:IsTanker() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local tanker=self:HasAttribute("Tankers") - - local system=nil - - if tanker then - - local Desc=self:GetDesc() - if Desc and Desc.tankerType then - system=Desc.tankerType - end - - local typename=self:GetTypeName() - - -- Some hard coded data as this is not in the descriptors... - if typename=="IL-78M" then - system=1 --probe - elseif typename=="KC130" or typename=="KC130J" then - system=1 --probe - elseif typename=="KC135BDA" then - system=1 --probe - elseif typename=="KC135MPRS" then - system=1 --probe - elseif typename=="S-3B Tanker" then - system=1 --probe - elseif typename=="KC_10_Extender" then - system=1 --probe - elseif typename=="KC_10_Extender_D" then - system=0 --boom - end - - end + local tanker = self:HasAttribute("Tankers") - return tanker, system + local system = nil + + if tanker then + + local Desc = self:GetDesc() + if Desc and Desc.tankerType then + system = Desc.tankerType + end + + local typename = self:GetTypeName() + + -- Some hard coded data as this is not in the descriptors... + if typename == "IL-78M" then + system = 1 --probe + elseif typename == "KC130" or typename == "KC130J" then + system = 1 --probe + elseif typename == "KC135BDA" then + system = 1 --probe + elseif typename == "KC135MPRS" then + system = 1 --probe + elseif typename == "S-3B Tanker" then + system = 1 --probe + elseif typename == "KC_10_Extender" then + system = 1 --probe + elseif typename == "KC_10_Extender_D" then + system = 0 --boom + end + + end + + return tanker, system end --- Check if the unit can supply ammo. Currently, we have @@ -703,21 +709,21 @@ end -- @return #boolean If `true`, unit can supply ammo. function UNIT:IsAmmoSupply() - -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. - local typename=self:GetTypeName() - - if typename=="M 818" then - -- Blue ammo truck. - return true - elseif typename=="Ural-375" then - -- Red ammo truck. - return true - elseif typename=="ZIL-135" then - -- Red ammo truck. Checked that it can also provide ammo. - return true - end + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename = self:GetTypeName() - return false + if typename == "M 818" then + -- Blue ammo truck. + return true + elseif typename == "Ural-375" then + -- Red ammo truck. + return true + elseif typename == "ZIL-135" then + -- Red ammo truck. Checked that it can also provide ammo. + return true + end + + return false end --- Check if the unit can supply fuel. Currently, we have @@ -733,41 +739,41 @@ end -- @return #boolean If `true`, unit can supply fuel. function UNIT:IsFuelSupply() - -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. - local typename=self:GetTypeName() - - if typename=="M978 HEMTT Tanker" then - return true - elseif typename=="ATMZ-5" then - return true - elseif typename=="ATMZ-10" then - return true - elseif typename=="ATZ-5" then - return true - end + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename = self:GetTypeName() - return false + if typename == "M978 HEMTT Tanker" then + return true + elseif typename == "ATMZ-5" then + return true + elseif typename == "ATMZ-10" then + return true + elseif typename == "ATZ-5" then + return true + end + + return false end --- Returns the unit's group if it exists and nil otherwise. -- @param Wrapper.Unit#UNIT self -- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist. function UNIT:GetGroup() - --self:F2( self.UnitName ) - local UnitGroup = GROUP:FindByName(self.GroupName) - if UnitGroup then - return UnitGroup - else - local DCSUnit = self:GetDCSObject() - if DCSUnit then - local grp = DCSUnit:getGroup() - if grp then - local UnitGroup = GROUP:FindByName( grp:getName() ) + --self:F2( self.UnitName ) + local UnitGroup = GROUP:FindByName(self.GroupName) + if UnitGroup then return UnitGroup - end + else + local DCSUnit = self:GetDCSObject() + if DCSUnit then + local grp = DCSUnit:getGroup() + if grp then + local UnitGroup = GROUP:FindByName(grp:getName()) + return UnitGroup + end + end end - end - return nil + return nil end --- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. @@ -777,39 +783,39 @@ end -- @return #string The name of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetPrefix() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) - --self:T3( UnitPrefix ) - return UnitPrefix - end - - return nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitPrefix = string.match(self.UnitName, ".*#"):sub(1, -2) + --self:T3( UnitPrefix ) + return UnitPrefix + end + + return nil end --- Returns the Unit's ammunition. -- @param #UNIT self -- @return DCS#Unit.Ammo Table with ammuntion of the unit (or nil). This can be a complex table! function UNIT:GetAmmo() - --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - if DCSUnit then - --local status, unitammo = pcall( - -- function() + --self:F2( self.UnitName ) + local DCSUnit = self:GetDCSObject() + if DCSUnit then + --local status, unitammo = pcall( + -- function() -- local UnitAmmo = DCSUnit:getAmmo() -- return UnitAmmo - --end - --) - --if status then - --return unitammo - --end - local UnitAmmo = DCSUnit:getAmmo() - return UnitAmmo - end - return nil + --end + --) + --if status then + --return unitammo + --end + local UnitAmmo = DCSUnit:getAmmo() + return UnitAmmo + end + return nil end @@ -818,11 +824,11 @@ end -- @param #number mass to set cargo to -- @return #UNIT self function UNIT:SetUnitInternalCargo(mass) - local DCSUnit = self:GetDCSObject() - if DCSUnit then - trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass) - end - return self + local DCSUnit = self:GetDCSObject() + if DCSUnit then + trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass) + end + return self end --- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has. @@ -837,165 +843,177 @@ end -- @return #number Number of tank HE shells left (for tanks, if applicable) function UNIT:GetAmmunition() - -- Init counter. - local nammo=0 - local nshells=0 - local nrockets=0 - local nmissiles=0 - local nbombs=0 - local narti=0 - local nAPshells = 0 - local nHEshells = 0 + -- Init counter. + local nammo = 0 + local nshells = 0 + local nrockets = 0 + local nmissiles = 0 + local nbombs = 0 + local narti = 0 + local nAPshells = 0 + local nHEshells = 0 - local unit=self + local unit = self - -- Get ammo table. - local ammotable=unit:GetAmmo() + -- Get ammo table. + local ammotable = unit:GetAmmo() - if ammotable then + if ammotable then - local weapons=#ammotable - - -- Loop over all weapons. - for w=1,weapons do + local weapons = #ammotable - -- Number of current weapon. - local Nammo=ammotable[w]["count"] + -- Loop over all weapons. + for w = 1, weapons do - -- Type name of current weapon. - local Tammo=ammotable[w]["desc"]["typeName"] + -- Number of current weapon. + local Nammo = ammotable[w]["count"] - --local _weaponString = UTILS.Split(Tammo,"%.") - --local _weaponName = _weaponString[#_weaponString] + -- Type name of current weapon. + local Tammo = ammotable[w]["desc"]["typeName"] - -- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3 - local Category=ammotable[w].desc.category + --local _weaponString = UTILS.Split(Tammo,"%.") + --local _weaponName = _weaponString[#_weaponString] - -- Get missile category: Weapon.MissileCategory AAM=1, SAM=2, BM=3, ANTI_SHIP=4, CRUISE=5, OTHER=6 - local MissileCategory=nil - if Category==Weapon.Category.MISSILE then - MissileCategory=ammotable[w].desc.missileCategory - end + -- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3 + local Category = ammotable[w].desc.category - -- We are specifically looking for shells or rockets here. - if Category==Weapon.Category.SHELL then + -- Get missile category: Weapon.MissileCategory AAM=1, SAM=2, BM=3, ANTI_SHIP=4, CRUISE=5, OTHER=6 + local MissileCategory = nil + if Category == Weapon.Category.MISSILE then + MissileCategory = ammotable[w].desc.missileCategory + end + + -- We are specifically looking for shells or rockets here. + if Category == Weapon.Category.SHELL then + + -- Add up all shells. + nshells = nshells + Nammo + + if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass > 0 then + narti = narti + Nammo + end + + if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName, "_AP", 1, true) then + nAPshells = nAPshells + Nammo + end + + if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName, "_HE", 1, true) then + nHEshells = nHEshells + Nammo + end + + elseif Category == Weapon.Category.ROCKET then + + -- Add up all rockets. + nrockets = nrockets + Nammo + + elseif Category == Weapon.Category.BOMB then + + -- Add up all rockets. + nbombs = nbombs + Nammo + + elseif Category == Weapon.Category.MISSILE then + + + -- Add up all missiles (category 5) + if MissileCategory == Weapon.MissileCategory.AAM then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.ANTI_SHIP then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.BM then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.OTHER then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.SAM then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.CRUISE then + nmissiles = nmissiles + Nammo + end + + end - -- Add up all shells. - nshells=nshells+Nammo - - if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass > 0 then - narti=narti+Nammo end - - if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName,"_AP",1,true) then - nAPshells = nAPshells+Nammo - end - - if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName,"_HE",1,true) then - nHEshells = nHEshells+Nammo - end - - elseif Category==Weapon.Category.ROCKET then - - -- Add up all rockets. - nrockets=nrockets+Nammo - - elseif Category==Weapon.Category.BOMB then - - -- Add up all rockets. - nbombs=nbombs+Nammo - - elseif Category==Weapon.Category.MISSILE then - - - -- Add up all missiles (category 5) - if MissileCategory==Weapon.MissileCategory.AAM then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.BM then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.OTHER then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.SAM then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.CRUISE then - nmissiles=nmissiles+Nammo - end - - end - end - end - -- Total amount of ammunition. - nammo=nshells+nrockets+nmissiles+nbombs + -- Total amount of ammunition. + nammo = nshells + nrockets + nmissiles + nbombs - return nammo, nshells, nrockets, nbombs, nmissiles, narti, nAPshells, nHEshells + return nammo, nshells, nrockets, nbombs, nmissiles, narti, nAPshells, nHEshells end --- Checks if a tank still has AP shells. -- @param #UNIT self -- @return #boolean HasAPShells function UNIT:HasAPShells() - local _,_,_,_,_,_,shells = self:GetAmmunition() - if shells > 0 then return true else return false end + local _, _, _, _, _, _, shells = self:GetAmmunition() + if shells > 0 then + return true + else + return false + end end --- Get number of AP shells from a tank. -- @param #UNIT self -- @return #number Number of AP shells function UNIT:GetAPShells() - local _,_,_,_,_,_,shells = self:GetAmmunition() - return shells or 0 + local _, _, _, _, _, _, shells = self:GetAmmunition() + return shells or 0 end --- Get number of HE shells from a tank. -- @param #UNIT self -- @return #number Number of HE shells function UNIT:GetHEShells() - local _,_,_,_,_,_,_,shells = self:GetAmmunition() - return shells or 0 + local _, _, _, _, _, _, _, shells = self:GetAmmunition() + return shells or 0 end --- Checks if a tank still has HE shells. -- @param #UNIT self -- @return #boolean HasHEShells function UNIT:HasHEShells() - local _,_,_,_,_,_,_,shells = self:GetAmmunition() - if shells > 0 then return true else return false end + local _, _, _, _, _, _, _, shells = self:GetAmmunition() + if shells > 0 then + return true + else + return false + end end --- Checks if an artillery unit still has artillery shells. -- @param #UNIT self -- @return #boolean HasArtiShells function UNIT:HasArtiShells() - local _,_,_,_,_,shells = self:GetAmmunition() - if shells > 0 then return true else return false end + local _, _, _, _, _, shells = self:GetAmmunition() + if shells > 0 then + return true + else + return false + end end --- Get number of artillery shells from an artillery unit. -- @param #UNIT self -- @return #number Number of artillery shells function UNIT:GetArtiShells() - local _,_,_,_,_,shells = self:GetAmmunition() - return shells or 0 + local _, _, _, _, _, shells = self:GetAmmunition() + return shells or 0 end --- Returns the unit sensors. -- @param #UNIT self -- @return DCS#Unit.Sensors Table of sensors. function UNIT:GetSensors() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSensors = DCSUnit:getSensors() - return UnitSensors - end - - return nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSensors = DCSUnit:getSensors() + return UnitSensors + end + + return nil end -- Need to add here a function per sensortype @@ -1004,41 +1022,41 @@ end --- Returns if the unit has sensors of a certain type. -- @param #UNIT self -- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. -function UNIT:HasSensors( ... ) - --self:F2( arg ) +function UNIT:HasSensors(...) + --self:F2( arg ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) - return HasSensors - end - - return nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local HasSensors = DCSUnit:hasSensors(unpack(arg)) + return HasSensors + end + + return nil end --- Returns if the unit is SEADable. -- @param #UNIT self -- @return #boolean returns true if the unit is SEADable. function UNIT:HasSEAD() - --self:F2() + --self:F2() - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSEADAttributes = DCSUnit:getDesc().attributes - - local HasSEAD = false - if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or - UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true or - UnitSEADAttributes["Optical Tracker"] and UnitSEADAttributes["Optical Tracker"] == true - then - HasSEAD = true + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSEADAttributes = DCSUnit:getDesc().attributes + + local HasSEAD = false + if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or + UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true or + UnitSEADAttributes["Optical Tracker"] and UnitSEADAttributes["Optical Tracker"] == true + then + HasSEAD = true + end + return HasSEAD end - return HasSEAD - end - - return nil + + return nil end --- Returns two values: @@ -1049,32 +1067,32 @@ end -- @return #boolean Indicates if at least one of the unit's radar(s) is on. -- @return DCS#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. function UNIT:GetRadar() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() - return UnitRadarOn, UnitRadarObject - end - - return nil, nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() + return UnitRadarOn, UnitRadarObject + end + + return nil, nil end --- Returns relative amount of fuel (from 0.0 to 1.0) the UNIT has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. -- @param #UNIT self -- @return #number The relative amount of fuel (from 0.0 to 1.0) or *nil* if the DCS Unit is not existing or alive. function UNIT:GetFuel() - --self:F3( self.UnitName ) + --self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitFuel = DCSUnit:getFuel() - return UnitFuel - end - - return nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitFuel = DCSUnit:getFuel() + return UnitFuel + end + + return nil end @@ -1082,18 +1100,18 @@ end -- @param #UNIT self -- @return #list A list of one @{Wrapper.Unit}. function UNIT:GetUnits() - --self:F3( { self.UnitName } ) - local DCSUnit = self:GetDCSObject() + --self:F3( { self.UnitName } ) + local DCSUnit = self:GetDCSObject() - local Units = {} - - if DCSUnit then - Units[1] = UNIT:Find( DCSUnit ) - -self:T3( Units ) - return Units - end + local Units = {} - return nil + if DCSUnit then + Units[1] = UNIT:Find(DCSUnit) + - self:T3(Units) + return Units + end + + return nil end @@ -1101,60 +1119,60 @@ end -- @param #UNIT self -- @return #number The Unit's health value or -1 if unit does not exist any more. function UNIT:GetLife() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit and DCSUnit:isExist() then - local UnitLife = DCSUnit:getLife() - return UnitLife - end - - return -1 + local DCSUnit = self:GetDCSObject() + + if DCSUnit and DCSUnit:isExist() then + local UnitLife = DCSUnit:getLife() + return UnitLife + end + + return -1 end --- Returns the Unit's initial health. -- @param #UNIT self -- @return #number The Unit's initial health value or 0 if unit does not exist any more. function UNIT:GetLife0() - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife0 = DCSUnit:getLife0() - return UnitLife0 - end - - return 0 + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitLife0 = DCSUnit:getLife0() + return UnitLife0 + end + + return 0 end --- Returns the unit's relative health. -- @param #UNIT self -- @return #number The Unit's relative health value, i.e. a number in [0,1] or -1 if unit does not exist any more. function UNIT:GetLifeRelative() - --self:F2(self.UnitName) + --self:F2(self.UnitName) - if self and self:IsAlive() then - local life0=self:GetLife0() - local lifeN=self:GetLife() - return lifeN/life0 - end - - return -1 + if self and self:IsAlive() then + local life0 = self:GetLife0() + local lifeN = self:GetLife() + return lifeN / life0 + end + + return -1 end --- Returns the unit's relative damage, i.e. 1-life. -- @param #UNIT self -- @return #number The Unit's relative health value, i.e. a number in [0,1] or 1 if unit does not exist any more. function UNIT:GetDamageRelative() - --self:F2(self.UnitName) + --self:F2(self.UnitName) - if self and self:IsAlive() then - return 1-self:GetLifeRelative() - end - - return 1 + if self and self:IsAlive() then + return 1 - self:GetLifeRelative() + end + + return 1 end --- Returns the current value for an animation argument on the external model of the given object. @@ -1165,14 +1183,14 @@ end -- @return #number Value of the animation argument [-1, 1]. If draw argument value is invalid for the unit in question a value of 0 will be returned. function UNIT:GetDrawArgumentValue(AnimationArgument) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local value = DCSUnit:getDrawArgumentValue(AnimationArgument or 0) - return value - end - - return 0 + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local value = DCSUnit:getDrawArgumentValue(AnimationArgument or 0) + return value + end + + return 0 end --- Returns the category of the #UNIT from descriptor. Returns one of @@ -1186,38 +1204,38 @@ end -- @param #UNIT self -- @return #number Unit category from `getDesc().category`. function UNIT:GetUnitCategory() - --self:F3( self.UnitName ) + --self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() - if DCSUnit then - return DCSUnit:getDesc().category - end - - return nil + local DCSUnit = self:GetDCSObject() + if DCSUnit then + return DCSUnit:getDesc().category + end + + return nil end --- Returns the category name of the #UNIT. -- @param #UNIT self -- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship function UNIT:GetCategoryName() - --self:F3( self.UnitName ) + --self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() - if DCSUnit then - local CategoryNames = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicopter", - [Unit.Category.GROUND_UNIT] = "Ground Unit", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - local UnitCategory = DCSUnit:getDesc().category - --self:T3( UnitCategory ) + local DCSUnit = self:GetDCSObject() + if DCSUnit then + local CategoryNames = { + [Unit.Category.AIRPLANE] = "Airplane", + [Unit.Category.HELICOPTER] = "Helicopter", + [Unit.Category.GROUND_UNIT] = "Ground Unit", + [Unit.Category.SHIP] = "Ship", + [Unit.Category.STRUCTURE] = "Structure", + } + local UnitCategory = DCSUnit:getDesc().category + --self:T3( UnitCategory ) - return CategoryNames[UnitCategory] - end + return CategoryNames[UnitCategory] + end - return nil + return nil end @@ -1279,125 +1297,150 @@ end function UNIT:GetThreatLevel() - local ThreatLevel = 0 - local ThreatText = "" - - local Descriptor = self:GetDesc() - - if Descriptor then - - local Attributes = Descriptor.attributes - - if self:IsGround() then - - local ThreatLevels = { - [1] = "Unarmed", - [2] = "Infantry", - [3] = "Old Tanks & APCs", - [4] = "Tanks & IFVs without ATGM", - [5] = "Tanks & IFV with ATGM", - [6] = "Modern Tanks", - [7] = "AAA", - [8] = "IR Guided SAMs", - [9] = "SR SAMs", - [10] = "MR SAMs", - [11] = "LR SAMs" - } - - - if Attributes["LR SAM"] then ThreatLevel = 10 - elseif Attributes["MR SAM"] then ThreatLevel = 9 - elseif Attributes["SR SAM"] and - not Attributes["IR Guided SAM"] then ThreatLevel = 8 - elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and - Attributes["IR Guided SAM"] then ThreatLevel = 7 - elseif Attributes["AAA"] then ThreatLevel = 6 - elseif Attributes["Modern Tanks"] then ThreatLevel = 5 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - Attributes["ATGM"] then ThreatLevel = 4 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - not Attributes["ATGM"] then ThreatLevel = 3 - elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then ThreatLevel = 2 - elseif Attributes["Infantry"] or Attributes["EWR"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - if self:IsAir() then - - local ThreatLevels = { - [1] = "Unarmed", - [2] = "Tanker", - [3] = "AWACS", - [4] = "Transport Helicopter", - [5] = "UAV", - [6] = "Bomber", - [7] = "Strategic Bomber", - [8] = "Attack Helicopter", - [9] = "Battleplane", - [10] = "Multirole Fighter", - [11] = "Fighter" - } - - - if Attributes["Fighters"] then ThreatLevel = 10 - elseif Attributes["Multirole fighters"] then ThreatLevel = 9 - elseif Attributes["Interceptors"] then ThreatLevel = 9 - elseif Attributes["Battleplanes"] then ThreatLevel = 8 - elseif Attributes["Battle airplanes"] then ThreatLevel = 8 - elseif Attributes["Attack helicopters"] then ThreatLevel = 7 - elseif Attributes["Strategic bombers"] then ThreatLevel = 6 - elseif Attributes["Bombers"] then ThreatLevel = 5 - elseif Attributes["UAVs"] then ThreatLevel = 4 - elseif Attributes["Transport helicopters"] then ThreatLevel = 3 - elseif Attributes["AWACS"] then ThreatLevel = 2 - elseif Attributes["Tankers"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - if self:IsShip() then - - --["Aircraft Carriers"] = {"Heavy armed ships",}, - --["Cruisers"] = {"Heavy armed ships",}, - --["Destroyers"] = {"Heavy armed ships",}, - --["Frigates"] = {"Heavy armed ships",}, - --["Corvettes"] = {"Heavy armed ships",}, - --["Heavy armed ships"] = {"Armed ships", "Armed Air Defence", "HeavyArmoredUnits",}, - --["Light armed ships"] = {"Armed ships","NonArmoredUnits"}, - --["Armed ships"] = {"Ships"}, - --["Unarmed ships"] = {"Ships","HeavyArmoredUnits",}, - - local ThreatLevels = { - [1] = "Unarmed ship", - [2] = "Light armed ships", - [3] = "Corvettes", - [4] = "", - [5] = "Frigates", - [6] = "", - [7] = "Cruiser", - [8] = "", - [9] = "Destroyer", - [10] = "", - [11] = "Aircraft Carrier" - } - - - if Attributes["Aircraft Carriers"] then ThreatLevel = 10 - elseif Attributes["Destroyers"] then ThreatLevel = 8 - elseif Attributes["Cruisers"] then ThreatLevel = 6 - elseif Attributes["Frigates"] then ThreatLevel = 4 - elseif Attributes["Corvettes"] then ThreatLevel = 2 - elseif Attributes["Light armed ships"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - end + local ThreatLevel = 0 + local ThreatText = "" - return ThreatLevel, ThreatText + local Descriptor = self:GetDesc() + + if Descriptor then + + local Attributes = Descriptor.attributes + + if self:IsGround() then + + local ThreatLevels = { + [1] = "Unarmed", + [2] = "Infantry", + [3] = "Old Tanks & APCs", + [4] = "Tanks & IFVs without ATGM", + [5] = "Tanks & IFV with ATGM", + [6] = "Modern Tanks", + [7] = "AAA", + [8] = "IR Guided SAMs", + [9] = "SR SAMs", + [10] = "MR SAMs", + [11] = "LR SAMs" + } + + if Attributes["LR SAM"] then + ThreatLevel = 10 + elseif Attributes["MR SAM"] then + ThreatLevel = 9 + elseif Attributes["SR SAM"] and + not Attributes["IR Guided SAM"] then + ThreatLevel = 8 + elseif (Attributes["SR SAM"] or Attributes["MANPADS"]) and + Attributes["IR Guided SAM"] then + ThreatLevel = 7 + elseif Attributes["AAA"] then + ThreatLevel = 6 + elseif Attributes["Modern Tanks"] then + ThreatLevel = 5 + elseif (Attributes["Tanks"] or Attributes["IFV"]) and + Attributes["ATGM"] then + ThreatLevel = 4 + elseif (Attributes["Tanks"] or Attributes["IFV"]) and + not Attributes["ATGM"] then + ThreatLevel = 3 + elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then + ThreatLevel = 2 + elseif Attributes["Infantry"] or Attributes["EWR"] then + ThreatLevel = 1 + end + + ThreatText = ThreatLevels[ThreatLevel + 1] + end + + if self:IsAir() then + + local ThreatLevels = { + [1] = "Unarmed", + [2] = "Tanker", + [3] = "AWACS", + [4] = "Transport Helicopter", + [5] = "UAV", + [6] = "Bomber", + [7] = "Strategic Bomber", + [8] = "Attack Helicopter", + [9] = "Battleplane", + [10] = "Multirole Fighter", + [11] = "Fighter" + } + + if Attributes["Fighters"] then + ThreatLevel = 10 + elseif Attributes["Multirole fighters"] then + ThreatLevel = 9 + elseif Attributes["Interceptors"] then + ThreatLevel = 9 + elseif Attributes["Battleplanes"] then + ThreatLevel = 8 + elseif Attributes["Battle airplanes"] then + ThreatLevel = 8 + elseif Attributes["Attack helicopters"] then + ThreatLevel = 7 + elseif Attributes["Strategic bombers"] then + ThreatLevel = 6 + elseif Attributes["Bombers"] then + ThreatLevel = 5 + elseif Attributes["UAVs"] then + ThreatLevel = 4 + elseif Attributes["Transport helicopters"] then + ThreatLevel = 3 + elseif Attributes["AWACS"] then + ThreatLevel = 2 + elseif Attributes["Tankers"] then + ThreatLevel = 1 + end + + ThreatText = ThreatLevels[ThreatLevel + 1] + end + + if self:IsShip() then + + --["Aircraft Carriers"] = {"Heavy armed ships",}, + --["Cruisers"] = {"Heavy armed ships",}, + --["Destroyers"] = {"Heavy armed ships",}, + --["Frigates"] = {"Heavy armed ships",}, + --["Corvettes"] = {"Heavy armed ships",}, + --["Heavy armed ships"] = {"Armed ships", "Armed Air Defence", "HeavyArmoredUnits",}, + --["Light armed ships"] = {"Armed ships","NonArmoredUnits"}, + --["Armed ships"] = {"Ships"}, + --["Unarmed ships"] = {"Ships","HeavyArmoredUnits",}, + + local ThreatLevels = { + [1] = "Unarmed ship", + [2] = "Light armed ships", + [3] = "Corvettes", + [4] = "", + [5] = "Frigates", + [6] = "", + [7] = "Cruiser", + [8] = "", + [9] = "Destroyer", + [10] = "", + [11] = "Aircraft Carrier" + } + + if Attributes["Aircraft Carriers"] then + ThreatLevel = 10 + elseif Attributes["Destroyers"] then + ThreatLevel = 8 + elseif Attributes["Cruisers"] then + ThreatLevel = 6 + elseif Attributes["Frigates"] then + ThreatLevel = 4 + elseif Attributes["Corvettes"] then + ThreatLevel = 2 + elseif Attributes["Light armed ships"] then + ThreatLevel = 1 + end + + ThreatText = ThreatLevels[ThreatLevel + 1] + end + end + + return ThreatLevel, ThreatText end @@ -1408,25 +1451,25 @@ end -- @return #UNIT self function UNIT:Explode(power, delay) - -- Default. - power=power or 100 - - local DCSUnit = self:GetDCSObject() - if DCSUnit then - - -- Check if delay or not. - if delay and delay>0 then - -- Delayed call. - SCHEDULER:New(nil, self.Explode, {self, power}, delay) - else - -- Create an explotion at the coordinate of the unit. - self:GetCoordinate():Explosion(power) + -- Default. + power = power or 100 + + local DCSUnit = self:GetDCSObject() + if DCSUnit then + + -- Check if delay or not. + if delay and delay > 0 then + -- Delayed call. + SCHEDULER:New(nil, self.Explode, { self, power }, delay) + else + -- Create an explotion at the coordinate of the unit. + self:GetCoordinate():Explosion(power) + end + + return self end - - return self - end - - return nil + + return nil end -- Is functions @@ -1439,25 +1482,25 @@ end -- @param Radius The radius in meters with the DCS Unit in the centre. -- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. -function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - --self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) +function UNIT:OtherUnitInRadius(AwaitUnit, Radius) + --self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitVec3 = self:GetVec3() - local AwaitUnitVec3 = AwaitUnit:GetVec3() - - if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then - --self:T3( "true" ) - return true - else - --self:T3( "false" ) - return false + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitVec3 = self:GetVec3() + local AwaitUnitVec3 = AwaitUnit:GetVec3() + + if (((UnitVec3.x - AwaitUnitVec3.x) ^ 2 + (UnitVec3.z - AwaitUnitVec3.z) ^ 2) ^ 0.5 <= Radius) then + --self:T3( "true" ) + return true + else + --self:T3( "false" ) + return false + end end - end - return nil + return nil end @@ -1469,22 +1512,22 @@ end --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. -function UNIT:IsFriendly( FriendlyCoalition ) - --self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCoalition = DCSUnit:getCoalition() - --self:T3( { UnitCoalition, FriendlyCoalition } ) - - local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) - - --self:F( IsFriendlyResult ) - return IsFriendlyResult - end - - return nil +function UNIT:IsFriendly(FriendlyCoalition) + --self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCoalition = DCSUnit:getCoalition() + --self:T3( { UnitCoalition, FriendlyCoalition } ) + + local IsFriendlyResult = (UnitCoalition == FriendlyCoalition) + + --self:F( IsFriendlyResult ) + return IsFriendlyResult + end + + return nil end --- Returns if the unit is of a ship category. @@ -1492,21 +1535,21 @@ end -- @param #UNIT self -- @return #boolean Ship category evaluation result. function UNIT:IsShip() - --self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - --self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - - local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) - - --self:T3( IsShipResult ) - return IsShipResult - end - - return nil + --self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + --self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) + + local IsShipResult = (UnitDescriptor.category == Unit.Category.SHIP) + + --self:T3( IsShipResult ) + return IsShipResult + end + + return nil end --- Returns true if the UNIT is in the air. @@ -1514,143 +1557,147 @@ end -- @param #boolean NoHeloCheck If true, no additonal checks for helos are performed. -- @return #boolean Return true if in the air or #nil if the UNIT is not existing or alive. function UNIT:InAir(NoHeloCheck) - --self:F2( self.UnitName ) + --self:F2( self.UnitName ) - -- Get DCS unit object. - local DCSUnit = self:GetDCSObject() --DCS#Unit - - if DCSUnit then + -- Get DCS unit object. + local DCSUnit = self:GetDCSObject() --DCS#Unit - -- Get DCS result of whether unit is in air or not. - local UnitInAir = DCSUnit:inAir() + if DCSUnit then - -- Get unit category. - local UnitCategory = DCSUnit:getDesc().category + -- Get DCS result of whether unit is in air or not. + local UnitInAir = DCSUnit:inAir() - -- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not. - -- This is a workaround since DCS currently does not acknowledge that helos land on buildings. - -- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier! - if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER and (not NoHeloCheck) then - local VelocityVec3 = DCSUnit:getVelocity() - local Velocity = UTILS.VecNorm(VelocityVec3) - local Coordinate = DCSUnit:getPoint() - local LandHeight = land.getHeight( { x = Coordinate.x, y = Coordinate.z } ) - local Height = Coordinate.y - LandHeight - if Velocity < 1 and Height <= 60 then - UnitInAir = false - end - end - - --self:T3( UnitInAir ) - return UnitInAir - end - - return nil -end + -- Get unit category. + local UnitCategory = DCSUnit:getDesc().category -do -- Event Handling - - --- Subscribe to a DCS Event. - -- @param #UNIT self - -- @param Core.Event#EVENTS EventID Event ID. - -- @param #function EventFunction (Optional) The function to be called when the event occurs for the unit. - -- @return #UNIT self - function UNIT:HandleEvent(EventID, EventFunction) - - self:EventDispatcher():OnEventForUnit(self:GetName(), EventFunction, self, EventID) - - return self - end - - --- UnSubscribe to a DCS event. - -- @param #UNIT self - -- @param Core.Event#EVENTS EventID Event ID. - -- @return #UNIT self - function UNIT:UnHandleEvent(EventID) - - --self:EventDispatcher():RemoveForUnit( self:GetName(), self, EventID ) - - -- Fixes issue #1365 https://github.com/FlightControl-Master/MOOSE/issues/1365 - self:EventDispatcher():RemoveEvent(self, EventID) - - return self - end - - --- Reset the subscriptions. - -- @param #UNIT self - -- @return #UNIT - function UNIT:ResetEvents() - - self:EventDispatcher():Reset( self ) - - return self - end - -end - -do -- Detection - - --- Returns if a unit is detecting the TargetUnit. - -- @param #UNIT self - -- @param #UNIT TargetUnit - -- @return #boolean true If the TargetUnit is detected by the unit, otherwise false. - function UNIT:IsDetected( TargetUnit ) --R2.1 - - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = self:IsTargetDetected( TargetUnit:GetDCSObject() ) - - return TargetIsDetected - end - - --- Returns if a unit has Line of Sight (LOS) with the TargetUnit. - -- @param #UNIT self - -- @param #UNIT TargetUnit - -- @return #boolean true If the TargetUnit has LOS with the unit, otherwise false. - function UNIT:IsLOS( TargetUnit ) --R2.1 - - local IsLOS = self:GetPointVec3():IsLOS( TargetUnit:GetPointVec3() ) - - return IsLOS - end - - --- Forces the unit to become aware of the specified target, without the unit manually detecting the other unit itself. - -- Applies only to a Unit Controller. Cannot be used at the group level. - -- @param #UNIT self - -- @param #UNIT TargetUnit The unit to be known. - -- @param #boolean TypeKnown The target type is known. If *false*, the type is not known. - -- @param #boolean DistanceKnown The distance to the target is known. If *false*, distance is unknown. - function UNIT:KnowUnit(TargetUnit, TypeKnown, DistanceKnown) - - -- Defaults. - if TypeKnown~=false then - TypeKnown=true - end - if DistanceKnown~=false then - DistanceKnown=true - end - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local Controller = DCSControllable:getController() --self:_GetController() - - if Controller then - - local object=TargetUnit:GetDCSObject() - - if object then - - self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s", self:GetName(), TargetUnit:GetName(), tostring(TypeKnown), tostring(DistanceKnown))) - - Controller:knowTarget(object, TypeKnown, DistanceKnown) - + -- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not. + -- This is a workaround since DCS currently does not acknowledge that helos land on buildings. + -- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier! + if UnitInAir == true and UnitCategory == Unit.Category.HELICOPTER and (not NoHeloCheck) then + local VelocityVec3 = DCSUnit:getVelocity() + local Velocity = UTILS.VecNorm(VelocityVec3) + local Coordinate = DCSUnit:getPoint() + local LandHeight = land.getHeight({ x = Coordinate.x, y = Coordinate.z }) + local Height = Coordinate.y - LandHeight + if Velocity < 1 and Height <= 60 then + UnitInAir = false + end end - - end - + + --self:T3( UnitInAir ) + return UnitInAir + end + + return nil +end + +do + -- Event Handling + + --- Subscribe to a DCS Event. + -- @param #UNIT self + -- @param Core.Event#EVENTS EventID Event ID. + -- @param #function EventFunction (Optional) The function to be called when the event occurs for the unit. + -- @return #UNIT self + function UNIT:HandleEvent(EventID, EventFunction) + + self:EventDispatcher():OnEventForUnit(self:GetName(), EventFunction, self, EventID) + + return self + end + + --- UnSubscribe to a DCS event. + -- @param #UNIT self + -- @param Core.Event#EVENTS EventID Event ID. + -- @return #UNIT self + function UNIT:UnHandleEvent(EventID) + + --self:EventDispatcher():RemoveForUnit( self:GetName(), self, EventID ) + + -- Fixes issue #1365 https://github.com/FlightControl-Master/MOOSE/issues/1365 + self:EventDispatcher():RemoveEvent(self, EventID) + + return self + end + + --- Reset the subscriptions. + -- @param #UNIT self + -- @return #UNIT + function UNIT:ResetEvents() + + self:EventDispatcher():Reset(self) + + return self + end + +end + +do + -- Detection + + --- Returns if a unit is detecting the TargetUnit. + -- @param #UNIT self + -- @param #UNIT TargetUnit + -- @return #boolean true If the TargetUnit is detected by the unit, otherwise false. + function UNIT:IsDetected(TargetUnit) + --R2.1 + + local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = self:IsTargetDetected(TargetUnit:GetDCSObject()) + + return TargetIsDetected + end + + --- Returns if a unit has Line of Sight (LOS) with the TargetUnit. + -- @param #UNIT self + -- @param #UNIT TargetUnit + -- @return #boolean true If the TargetUnit has LOS with the unit, otherwise false. + function UNIT:IsLOS(TargetUnit) + --R2.1 + + local IsLOS = self:GetPointVec3():IsLOS(TargetUnit:GetPointVec3()) + + return IsLOS + end + + --- Forces the unit to become aware of the specified target, without the unit manually detecting the other unit itself. + -- Applies only to a Unit Controller. Cannot be used at the group level. + -- @param #UNIT self + -- @param #UNIT TargetUnit The unit to be known. + -- @param #boolean TypeKnown The target type is known. If *false*, the type is not known. + -- @param #boolean DistanceKnown The distance to the target is known. If *false*, distance is unknown. + function UNIT:KnowUnit(TargetUnit, TypeKnown, DistanceKnown) + + -- Defaults. + if TypeKnown ~= false then + TypeKnown = true + end + if DistanceKnown ~= false then + DistanceKnown = true + end + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local Controller = DCSControllable:getController() --self:_GetController() + + if Controller then + + local object = TargetUnit:GetDCSObject() + + if object then + + self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s", self:GetName(), TargetUnit:GetName(), tostring(TypeKnown), tostring(DistanceKnown))) + + Controller:knowTarget(object, TypeKnown, DistanceKnown) + + end + + end + + end + end - - end end @@ -1659,26 +1706,26 @@ end -- @return #table Table of the unit template (deep copy) or #nil. function UNIT:GetTemplate() - local group=self:GetGroup() - - local name=self:GetName() - - if group then - local template=group:GetTemplate() - - if template then - - for _,unit in pairs(template.units) do - - if unit.name==name then - return UTILS.DeepCopy(unit) + local group = self:GetGroup() + + local name = self:GetName() + + if group then + local template = group:GetTemplate() + + if template then + + for _, unit in pairs(template.units) do + + if unit.name == name then + return UTILS.DeepCopy(unit) + end + end + end - end - - end - end - - return nil + end + + return nil end @@ -1694,13 +1741,13 @@ end -- @return #table Payload table (deep copy) or #nil. function UNIT:GetTemplatePayload() - local unit=self:GetTemplate() - - if unit then - return unit.payload - end - - return nil + local unit = self:GetTemplate() + + if unit then + return unit.payload + end + + return nil end --- Get the pylons table from a unit's template. This can be a complex table depending on the weapons the unit is carrying. @@ -1708,13 +1755,13 @@ end -- @return #table Table of pylons (deepcopy) or #nil. function UNIT:GetTemplatePylons() - local payload=self:GetTemplatePayload() - - if payload then - return payload.pylons - end + local payload = self:GetTemplatePayload() - return nil + if payload then + return payload.pylons + end + + return nil end --- Get the fuel of the unit from its template. @@ -1722,13 +1769,13 @@ end -- @return #number Fuel of unit in kg. function UNIT:GetTemplateFuel() - local payload=self:GetTemplatePayload() - - if payload then - return payload.fuel - end + local payload = self:GetTemplatePayload() - return nil + if payload then + return payload.fuel + end + + return nil end --- GROUND - Switch on/off radar emissions of a unit. @@ -1736,32 +1783,32 @@ end -- @param #boolean switch If true, emission is enabled. If false, emission is disabled. -- @return #UNIT self function UNIT:EnableEmission(switch) - --self:F2( self.UnitName ) - - local switch = switch or false - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - DCSUnit:enableEmission(switch) + --self:F2( self.UnitName ) - end + local switch = switch or false - return self + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:enableEmission(switch) + + end + + return self end --- Get skill from Unit. -- @param #UNIT self -- @return #string Skill String of skill name. function UNIT:GetSkill() - --self:F2( self.UnitName ) - local name = self.UnitName - local skill = "Random" - if _DATABASE.Templates.Units[name] and _DATABASE.Templates.Units[name].Template and _DATABASE.Templates.Units[name].Template.skill then - skill = _DATABASE.Templates.Units[name].Template.skill or "Random" - end - return skill + --self:F2( self.UnitName ) + local name = self.UnitName + local skill = "Random" + if _DATABASE.Templates.Units[name] and _DATABASE.Templates.Units[name].Template and _DATABASE.Templates.Units[name].Template.skill then + skill = _DATABASE.Templates.Units[name].Template.skill or "Random" + end + return skill end --- Get Link16 STN or SADL TN and other datalink info from Unit, if any. @@ -1771,68 +1818,95 @@ end -- @return #string VCN Voice Callsign Number or nil if not set/capable. -- @return #string Lead If true, unit is Flight Lead, else false or nil. function UNIT:GetSTN() - --self:F2(self.UnitName) - local STN = nil -- STN/TN - local VCL = nil -- VoiceCallsignLabel - local VCN = nil -- VoiceCallsignNumber - local FGL = false -- FlightGroupLeader - local template = self:GetTemplate() - if template then - if template.AddPropAircraft then - if template.AddPropAircraft.STN_L16 then - STN = template.AddPropAircraft.STN_L16 - elseif template.AddPropAircraft.SADL_TN then - STN = template.AddPropAircraft.SADL_TN + --self:F2(self.UnitName) + local STN = nil -- STN/TN + local VCL = nil -- VoiceCallsignLabel + local VCN = nil -- VoiceCallsignNumber + local FGL = false -- FlightGroupLeader + local template = self:GetTemplate() + if template then + if template.AddPropAircraft then + if template.AddPropAircraft.STN_L16 then + STN = template.AddPropAircraft.STN_L16 + elseif template.AddPropAircraft.SADL_TN then + STN = template.AddPropAircraft.SADL_TN + end + VCN = template.AddPropAircraft.VoiceCallsignNumber + VCL = template.AddPropAircraft.VoiceCallsignLabel + end + if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then + FGL = template.datalinks.Link16.settings.flightLead + end + -- A10CII + if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then + FGL = template.datalinks.SADL.settings.flightLead + end end - VCN = template.AddPropAircraft.VoiceCallsignNumber - VCL = template.AddPropAircraft.VoiceCallsignLabel - end - if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then - FGL = template.datalinks.Link16.settings.flightLead - end - -- A10CII - if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then - FGL = template.datalinks.SADL.settings.flightLead - end - end - return STN, VCL, VCN, FGL + return STN, VCL, VCN, FGL end -do -- AI methods +do + -- AI methods - --- Turns the AI On or Off for the UNIT. - -- @param #UNIT self - -- @param #boolean AIOnOff The value true turns the AI On, the value false turns the AI Off. - -- @return #UNIT The UNIT. - function UNIT:SetAIOnOff( AIOnOff ) + --- Turns the AI On or Off for the UNIT. + -- @param #UNIT self + -- @param #boolean AIOnOff The value true turns the AI On, the value false turns the AI Off. + -- @return #UNIT The UNIT. + function UNIT:SetAIOnOff(AIOnOff) - local DCSUnit = self:GetDCSObject() -- DCS#Group + local DCSUnit = self:GetDCSObject() -- DCS#Group - if DCSUnit then - local DCSController = DCSUnit:getController() -- DCS#Controller - if DCSController then - DCSController:setOnOff( AIOnOff ) - return self - end + if DCSUnit then + local DCSController = DCSUnit:getController() -- DCS#Controller + if DCSController then + DCSController:setOnOff(AIOnOff) + return self + end + end + + return nil end - return nil - end + --- Turns the AI On for the UNIT. + -- @param #UNIT self + -- @return #UNIT The UNIT. + function UNIT:SetAIOn() - --- Turns the AI On for the UNIT. - -- @param #UNIT self - -- @return #UNIT The UNIT. - function UNIT:SetAIOn() + return self:SetAIOnOff(true) + end - return self:SetAIOnOff( true ) - end + --- Turns the AI Off for the UNIT. + -- @param #UNIT self + -- @return #UNIT The UNIT. + function UNIT:SetAIOff() - --- Turns the AI Off for the UNIT. - -- @param #UNIT self - -- @return #UNIT The UNIT. - function UNIT:SetAIOff() - - return self:SetAIOnOff( false ) - end + return self:SetAIOnOff(false) + end end + +--- [GROUND] Determine if a UNIT is a SAM unit, i.e. has radar or optical tracker and is no mobile AAA. +-- @param #UNIT self +-- @return #boolean IsSAM True if SAM, else false +function UNIT:IsSAM() + if self:HasSEAD() and self:IsGround() and (not self:HasAttribute("Mobile AAA")) then + return true + end + return false +end + +--- [GROUND] Determine if a UNIT is a AAA unit, i.e. has no radar or optical tracker but the AAA = true or the "Mobile AAA" = true attribute. +-- @param #UNIT self +-- @return #boolean IsAAA True if AAA, else false +function UNIT:IsAAA() + local unit = self -- Wrapper.Unit#UNIT + local desc = unit:GetDesc() or {} + local attr = desc.attributes or {} + if unit:HasSEAD() then + return false + end + if attr["AAA"] or attr["SAM related"] then + return true + end + return false +end From e87dc525b91cedd5ae94d0ddc6b14f9c4cc1f5d8 Mon Sep 17 00:00:00 2001 From: Shafik Date: Sun, 2 Feb 2025 13:25:53 +0200 Subject: [PATCH 072/158] [FIXED] Group not turning off due to conflicting condition checking true and false (`if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then`) [FIXED] Units turning on for same coalition aircrafts in radius --- Moose Development/Moose/Functional/Tiresias.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index 83cc02bba..c210a22dc 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -288,7 +288,7 @@ function TIRESIAS:_InitGroups() } end if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias.invisible and grp.Tiresias.invisible == false then + if grp.Tiresias.invisible == false then grp:SetCommandInvisible(true) grp.Tiresias.invisible = true if SwitchAAA then @@ -315,7 +315,7 @@ function TIRESIAS:_InitGroups() } end if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then + if grp.Tiresias and grp.Tiresias.invisible == false then grp:SetCommandInvisible(true) grp:SetAIOff() grp.Tiresias.invisible = true @@ -336,7 +336,7 @@ function TIRESIAS:_InitGroups() } end if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then + if grp.Tiresias and grp.Tiresias.invisible == false then grp:SetCommandInvisible(true) grp.Tiresias.invisible = true end @@ -392,7 +392,8 @@ function TIRESIAS:_SwitchOnGroups(group,radius) ground:ForEachGroupAlive( function(grp) local name = grp:GetName() - if grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then + if grp:GetCoalition() ~= group:GetCoalition() + and grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then if grp.Tiresias.invisible == true then grp:SetCommandInvisible(false) grp.Tiresias.invisible = false From 2744befbab59bc4b2ba77cf301b9a0c17d4d50ff Mon Sep 17 00:00:00 2001 From: Shafik Date: Sun, 2 Feb 2025 13:27:03 +0200 Subject: [PATCH 073/158] [FIXED] Group not turning off due to conflicting condition checking true and false (`if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then`) [FIXED] Units turning on for same coalition aircrafts in radius --- Moose Development/Moose/Functional/Tiresias.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index c210a22dc..74c2b930e 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -319,6 +319,7 @@ function TIRESIAS:_InitGroups() grp:SetCommandInvisible(true) grp:SetAIOff() grp.Tiresias.invisible = true + grp.Tiresias.AIOff = true end end --BASE:I(string.format("Init/Switch off Vehicle %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) From 905b442e9b4df71db1d5266ca8eb8ee250dbb4c1 Mon Sep 17 00:00:00 2001 From: Shafik Date: Mon, 3 Feb 2025 09:03:43 +0200 Subject: [PATCH 074/158] [FIXED] attempt to index field (a nil value) --- Moose Development/Moose/Functional/CleanUp.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua index 5d975c3ca..4b909dcd5 100644 --- a/Moose Development/Moose/Functional/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -354,7 +354,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event ) self:F({Event}) - if Event.IniDCSUnit and Event.IniCategory == Object.Category.UNIT then + if Event.IniDCSUnit and Event.IniUnit and Event.IniCategory == Object.Category.UNIT then if self.CleanUpList[Event.IniDCSUnitName] == nil then if self:IsInAirbase( Event.IniUnit:GetVec2() ) then self:AddForCleanUp( Event.IniUnit, Event.IniDCSUnitName ) @@ -362,7 +362,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event ) end end - if Event.TgtDCSUnit and Event.TgtCategory == Object.Category.UNIT then + if Event.TgtDCSUnit and Event.TgtUnit and Event.TgtCategory == Object.Category.UNIT then if self.CleanUpList[Event.TgtDCSUnitName] == nil then if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then self:AddForCleanUp( Event.TgtUnit, Event.TgtDCSUnitName ) From 3d0b7b9267219eb54637ca21901d18b5ec003423 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 6 Feb 2025 08:50:33 +0100 Subject: [PATCH 075/158] #AUTOLASE - Add offset parameters for `autolase:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)})` --- .../Moose/Functional/Autolase.lua | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 7a8545b79..1969fcbf9 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -74,7 +74,7 @@ -- @image Designation.JPG -- -- Date: 24 Oct 2021 --- Last Update: Jan 2025 +-- Last Update: Feb 2025 -- --- Class AUTOLASE -- @type AUTOLASE @@ -90,6 +90,7 @@ -- @field #boolean smokemenu -- @field #boolean threatmenu -- @field #number RoundingPrecision +-- @field #table smokeoffset -- @extends Ops.Intel#INTEL --- @@ -120,7 +121,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.27" +AUTOLASE.version = "0.1.28" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -193,6 +194,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.reporttimelong = 30 self.smoketargets = false self.smokecolor = SMOKECOLOR.Red + self.smokeoffset = nil self.notifypilots = true self.targetsperrecce = {} self.RecceUnits = {} @@ -211,6 +213,8 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.threatmenu = true self.RoundingPrecision = 0 + self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) + -- Set some string id for output to DCS.log file. self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") @@ -614,9 +618,15 @@ end --- (User) Show the "Switch smoke target..." menu entry for pilots. On by default. -- @param #AUTOLASE self +-- @param #table Offset (Optional) Define an offset for the smoke, i.e. not directly on the unit itself, angle is degrees and distance is meters. E.g. `autolase:EnableSmokeMenu({Angle=30,Distance=20})` -- @return #AUTOLASE self -function AUTOLASE:EnableSmokeMenu() +function AUTOLASE:EnableSmokeMenu(Offset) self.smokemenu = true + if Offset then + self.smokeoffset = {} + self.smokeoffset.Distance = Offset.Distance or math.random(10,20) + self.smokeoffset.Angle = Offset.Angle or math.random(0,359) + end return self end @@ -625,6 +635,7 @@ end -- @return #AUTOLASE self function AUTOLASE:DisableSmokeMenu() self.smokemenu = false + self.smokeoffset = nil return self end @@ -1097,6 +1108,9 @@ function AUTOLASE:onafterMonitor(From, Event, To) } if self.smoketargets then local coord = unit:GetCoordinate() + if self.smokeoffset then + coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true) + end local color = self:GetSmokeColor(reccename) coord:Smoke(color) end From f26d8d68220a9d6249939704c7b4fa3a73fef408 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 6 Feb 2025 08:52:05 +0100 Subject: [PATCH 076/158] Update Autolase.lua Added option to set offset smoke parameter --- .../Moose/Functional/Autolase.lua | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 7a8545b79..1969fcbf9 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -74,7 +74,7 @@ -- @image Designation.JPG -- -- Date: 24 Oct 2021 --- Last Update: Jan 2025 +-- Last Update: Feb 2025 -- --- Class AUTOLASE -- @type AUTOLASE @@ -90,6 +90,7 @@ -- @field #boolean smokemenu -- @field #boolean threatmenu -- @field #number RoundingPrecision +-- @field #table smokeoffset -- @extends Ops.Intel#INTEL --- @@ -120,7 +121,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.27" +AUTOLASE.version = "0.1.28" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -193,6 +194,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.reporttimelong = 30 self.smoketargets = false self.smokecolor = SMOKECOLOR.Red + self.smokeoffset = nil self.notifypilots = true self.targetsperrecce = {} self.RecceUnits = {} @@ -211,6 +213,8 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.threatmenu = true self.RoundingPrecision = 0 + self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) + -- Set some string id for output to DCS.log file. self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") @@ -614,9 +618,15 @@ end --- (User) Show the "Switch smoke target..." menu entry for pilots. On by default. -- @param #AUTOLASE self +-- @param #table Offset (Optional) Define an offset for the smoke, i.e. not directly on the unit itself, angle is degrees and distance is meters. E.g. `autolase:EnableSmokeMenu({Angle=30,Distance=20})` -- @return #AUTOLASE self -function AUTOLASE:EnableSmokeMenu() +function AUTOLASE:EnableSmokeMenu(Offset) self.smokemenu = true + if Offset then + self.smokeoffset = {} + self.smokeoffset.Distance = Offset.Distance or math.random(10,20) + self.smokeoffset.Angle = Offset.Angle or math.random(0,359) + end return self end @@ -625,6 +635,7 @@ end -- @return #AUTOLASE self function AUTOLASE:DisableSmokeMenu() self.smokemenu = false + self.smokeoffset = nil return self end @@ -1097,6 +1108,9 @@ function AUTOLASE:onafterMonitor(From, Event, To) } if self.smoketargets then local coord = unit:GetCoordinate() + if self.smokeoffset then + coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true) + end local color = self:GetSmokeColor(reccename) coord:Smoke(color) end From c79b5c37c4f958f7a85167b51afe8e31a55fccf9 Mon Sep 17 00:00:00 2001 From: Shafik Date: Thu, 6 Feb 2025 13:16:03 +0200 Subject: [PATCH 077/158] [ADDED] `UNIT:IsEWR()` --- Moose Development/Moose/Wrapper/Unit.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 780616d7e..38b2fe74b 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1895,6 +1895,21 @@ function UNIT:IsSAM() return false end +--- [GROUND] Determine if a UNIT is a EWR unit +-- @param #UNIT self +-- @return #boolean IsEWR True if EWR, else false +function UNIT:IsEWR() + if self:IsGround() then + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local attrs = DCSUnit:getDesc().attributes + return attrs["EWR"] == true + end + end + return false +end + --- [GROUND] Determine if a UNIT is a AAA unit, i.e. has no radar or optical tracker but the AAA = true or the "Mobile AAA" = true attribute. -- @param #UNIT self -- @return #boolean IsAAA True if AAA, else false From f74d25b31ceeec6dbb98b03152bae544ef5c95e9 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 8 Feb 2025 14:29:35 +0100 Subject: [PATCH 078/158] Update CTLD.lua Option to unload single cargo items by @lekaa --- Moose Development/Moose/Ops/CTLD.lua | 871 +++++++++++++++++++-------- 1 file changed, 628 insertions(+), 243 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 180b2c2e6..7e3fadcfd 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2356,6 +2356,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) + self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group, Unit, Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() @@ -2590,6 +2591,7 @@ end self.Loaded_Cargo[unitname] = loaded self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!", 10, false, Group) self:_SendMessage("Troops boarding!", 10, false, Group) + self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) local groupname = nearestGroup:GetName() self:__TroopsExtracted(running,Group, Unit, nearestGroup, groupname) @@ -3639,6 +3641,7 @@ function CTLD:_UnloadTroops(Group, Unit) end self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded + self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) else if IsHerc then @@ -3721,6 +3724,7 @@ function CTLD:_UnloadCrates(Group, Unit) self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group,Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) @@ -4093,26 +4097,26 @@ end -- @return #CTLD self function CTLD:_RefreshF10Menus() self:T(self.lid .. " _RefreshF10Menus") - local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP - local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - -- rebuild units table + + -- 1) Gather all the pilot groups from our Set + local PlayerSet = self.PilotGroups + local PlayerTable = PlayerSet:GetSetObjects() + + -- 2) Rebuild the self.CtldUnits table local _UnitList = {} - for _key, _group in pairs (PlayerTable) do - local _unit = _group:GetFirstUnitAlive() -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players - if _unit then - if _unit:IsAlive() and _unit:IsPlayer() then - if _unit:IsHelicopter() or (self:IsHercules(_unit) and self.enableHercules) then --ensure no stupid unit entries here - local unitName = _unit:GetName() - _UnitList[unitName] = unitName - else - local unitName = _unit:GetName() - _UnitList[unitName] = nil - end - end -- end isAlive - end -- end if _unit - end -- end for + for _, groupObj in pairs(PlayerTable) do + local firstUnit = groupObj:GetFirstUnitAlive() + if firstUnit then + if firstUnit:IsPlayer() then + if firstUnit:IsHelicopter() or (self.enableHercules and self:IsHercules(firstUnit)) then + local _unit = firstUnit:GetName() + _UnitList[_unit] = _unit + end + end + end + end self.CtldUnits = _UnitList - + -- subcats? if self.usesubcats then for _id,_cargo in pairs(self.Cargo_Crates) do @@ -4134,207 +4138,555 @@ function CTLD:_RefreshF10Menus() end end end - - -- build unit menus + local menucount = 0 - local menus = {} + local menus = {} for _, _unitName in pairs(self.CtldUnits) do - if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then - local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT - if _unit then - local _group = _unit:GetGroup() -- Wrapper.Group#GROUP + if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then + local _unit = UNIT:FindByName(_unitName) + if _unit and _unit:IsAlive() then + local _group = _unit:GetGroup() if _group then - -- get chopper capabilities - local unittype = _unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitTypeCapabilities - local cantroops = capabilities.troops - local cancrates = capabilities.crates - local isHook = self:IsHook(_unit) - --local nohookswitch = not (isHook and self.enableChinookGCLoading) + local capabilities = self:_GetUnitCapabilities(_unit) + local cantroops = capabilities.troops + local cancrates = capabilities.crates + local unittype = _unit:GetTypeName() + local isHook = self:IsHook(_unit) local nohookswitch = true - -- top menu + --local nohookswitch = not (isHook and self.enableChinookGCLoading) + -- Clear old topmenu if it existed if _group.CTLDTopmenu then _group.CTLDTopmenu:Remove() _group.CTLDTopmenu = nil end - local topmenu = MENU_GROUP:New(_group,"CTLD",nil) - _group.CTLDTopmenu = topmenu local toptroops = nil local topcrates = nil + local topmenu = MENU_GROUP:New(_group, "CTLD", nil) + _group.CTLDTopmenu = topmenu + if cantroops then - toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) - end - if cancrates then - topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) - end - local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) - local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit) - local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) - local smoketopmenu = MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu) - local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokeself = MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu) - local smokeselfred = MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Red) - local smokeselfblue = MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Blue) - local smokeselfgreen = MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Green) - local smokeselforange = MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Orange) - local smokeselfwhite = MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.White) - local flaremenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) - local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true) - local beaconself = MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() - -- sub menus - -- sub menu troops management - if cantroops then - local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) - if self.usesubcats then - local subcatmenus = {} - for _name,_entry in pairs(self.subcatsTroop) do - subcatmenus[_name] = MENU_GROUP:New(_group,_name,troopsmenu) - end - for _,_entry in pairs(self.Cargo_Troops) do - local entry = _entry -- #CTLD_CARGO - local subcat = entry.Subcategory - local noshow = entry.DontShowInMenu - local stock = _entry:GetStock() - if not noshow then - menucount = menucount + 1 - local menutext = entry.Name - if stock >= 0 and self.showstockinmenuitems == true then - menutext = menutext.." ["..stock.."]" - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) - end - end - else - for _,_entry in pairs(self.Cargo_Troops) do - local entry = _entry -- #CTLD_CARGO - local noshow = entry.DontShowInMenu - local stock = _entry:GetStock() - if not noshow then - menucount = menucount + 1 - local menutext = entry.Name - if stock >= 0 and self.showstockinmenuitems == true then - menutext = menutext.." ["..stock.."]" - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,troopsmenu,self._LoadTroops, self, _group, _unit, entry) - end - end - end - local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() - local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() - end - -- sub menu crates management - if cancrates then - if nohookswitch then - local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) - end - local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) - local packmenu = MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) - local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) + local toptroops = MENU_GROUP:New(_group, "Manage Troops", topmenu) + local troopsmenu = MENU_GROUP:New(_group, "Load troops", toptroops) + _group.MyTopTroopsMenu = toptroops if self.usesubcats then local subcatmenus = {} - for _name,_entry in pairs(self.subcats) do - subcatmenus[_name] = MENU_GROUP:New(_group,_name,cratesmenu) + for catName, _ in pairs(self.subcatsTroop) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, troopsmenu) end - for _,_entry in pairs(self.Cargo_Crates) do - local entry = _entry -- #CTLD_CARGO - local subcat = entry.Subcategory - local noshow = entry.DontShowInMenu - local zone = entry.Location - local stock = _entry:GetStock() - if not noshow then - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - if zone then - menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) - end - if stock >= 0 and self.showstockinmenuitems == true then - menutext = menutext.."["..stock.."]" - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) - end - end - for _,_entry in pairs(self.Cargo_Statics) do - local entry = _entry -- #CTLD_CARGO - local subcat = entry.Subcategory - local noshow = entry.DontShowInMenu - local zone = entry.Location - local stock = _entry:GetStock() - if not noshow then - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - if zone then - menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) - end - if stock >= 0 and self.showstockinmenuitems == true then - menutext = menutext.."["..stock.."]" - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + for _, cargoObj in pairs(self.Cargo_Troops) do + if not cargoObj.DontShowInMenu then + local stock = cargoObj:GetStock() + local menutext = cargoObj.Name + if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, menutext, subcatmenus[cargoObj.Subcategory], self._LoadTroops, self, _group, _unit, cargoObj) end end else - for _,_entry in pairs(self.Cargo_Crates) do - local entry = _entry -- #CTLD_CARGO - local noshow = entry.DontShowInMenu - local zone = entry.Location - local stock = _entry:GetStock() - if not noshow then - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - if zone then - menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) - end - if stock >= 0 and self.showstockinmenuitems == true then - menutext = menutext.."["..stock.."]" - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) - end - end - for _,_entry in pairs(self.Cargo_Statics) do - local entry = _entry -- #CTLD_CARGO - local noshow = entry.DontShowInMenu - local zone = entry.Location - local stock = _entry:GetStock() - if not noshow then - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - if zone then - menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) - end - if stock >= 0 and self.showstockinmenuitems == true then - menutext = menutext.."["..stock.."]" - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + for _, cargoObj in pairs(self.Cargo_Troops) do + if not cargoObj.DontShowInMenu then + local stock = cargoObj:GetStock() + local menutext = cargoObj.Name + if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, menutext, troopsmenu, self._LoadTroops, self, _group, _unit, cargoObj) + end end end - listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) - local removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) - local unloadmenu - if nohookswitch then - unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh() + MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() + MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() + local uName=_unit:GetName() + local loadedData=self.Loaded_Cargo[uName] + if loadedData and loadedData.Cargo then + for i,cargoObj in ipairs(loadedData.Cargo) do + if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) and not cargoObj:WasDropped() then + local name=cargoObj:GetName() or "Unknown" + local needed=cargoObj:GetCratesNeeded() or 1 + local cID=cargoObj:GetID() + local line=string.format("Drop: %s",name,needed,cID) + MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh() + end + end end + end + if cancrates then + local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) + local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates) + _group.MyTopCratesMenu = topcrates + + -- Show "Load crates" on 1 line + MENU_GROUP_COMMAND:New(_group, "Load crates", topcrates, self._LoadCratesNearby, self, _group, _unit) + + MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) + local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) + MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) + + -- Build the “Get Crates” sub-menu items + if self.usesubcats then + local subcatmenus = {} + for catName, _ in pairs(self.subcats) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu) + end + for _, cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) + end + end + for _, cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) + end + end + else + for _, cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) + end + end + for _, cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) + end + end + end + + MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) + if not self.nobuildmenu then - local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) - local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() - elseif unloadmenu then - unloadmenu:Refresh() + MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() + MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) + end + -- Drop Crates sub-menu + local dropCratesMenu = MENU_GROUP:New(_group, "Drop Crates", topcrates) + MENU_GROUP_COMMAND:New(_group, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, _group, _unit) + + local uName = _unit:GetName() + local loadedData = self.Loaded_Cargo[uName] + if loadedData and loadedData.Cargo then + local cargoByName = {} + for _, cgo in pairs(loadedData.Cargo) do + if cgo and (not cgo:WasDropped()) then + local cname = cgo:GetName() + local cneeded = cgo:GetCratesNeeded() + cargoByName[cname] = cargoByName[cname] or { count=0, needed=cneeded } + cargoByName[cname].count = cargoByName[cname].count + 1 + end + end + for name, info in pairs(cargoByName) do + local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) + MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrate, self, _group, _unit, name) + end + end + + + end + + ----------------------------------------------------- + -- Misc sub‐menus + ----------------------------------------------------- + MENU_GROUP_COMMAND:New(_group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Inventory", topmenu, self._ListInventory, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit) + + local smoketopmenu = MENU_GROUP:New(_group, "Smokes, Flares, Beacons", topmenu) + MENU_GROUP_COMMAND:New(_group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokeself = MENU_GROUP:New(_group, "Drop smoke now", smoketopmenu) + MENU_GROUP_COMMAND:New(_group, "Red smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Red) + MENU_GROUP_COMMAND:New(_group, "Blue smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Blue) + MENU_GROUP_COMMAND:New(_group, "Green smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Green) + MENU_GROUP_COMMAND:New(_group, "Orange smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Orange) + MENU_GROUP_COMMAND:New(_group, "White smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.White) + + MENU_GROUP_COMMAND:New(_group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() + + if self:IsHercules(_unit) then + MENU_GROUP_COMMAND:New(_group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + else + MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + end + + -- Mark we built the menu + self.MenusDone[_unitName] = true + + end -- if _group + end -- if _unit + else + self:T(self.lid .. " Menus already done for this group!") + end + end -- for all pilot units + + return self +end + +function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) + if not self.dropcratesanywhere then + local inzone, zoneName, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + if not self.debug then + return self + end + end + end + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) + if not self.debug then return self end + end + local grounded = not self:IsUnitInAir(Unit) + local hoverunload = self:IsCorrectHover(Unit) + local isHerc = self:IsHercules(Unit) + local isHook = self:IsHook(Unit) + if isHerc and not isHook then + hoverunload = self:IsCorrectFlightParameters(Unit) + end + if not grounded and not hoverunload then + if isHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + end + return self + end + local unitName = Unit:GetName() + local loadedData = self.Loaded_Cargo[unitName] + if not loadedData or not loadedData.Cargo then + self:_SendMessage("Nothing loaded!", 10, false, Group) + return self + end + local cargoList = loadedData.Cargo + local needed = 0 + for _, cObj in ipairs(cargoList) do + if cObj:GetName() == CrateName and not cObj:WasDropped() then + needed = cObj:GetCratesNeeded() or 1 + break + end + end + if needed < 1 then + self:_SendMessage(string.format("No %s crate found or already dropped!", CrateName), 10, false, Group) + return self + end + local matched = {} + for _, cObj in ipairs(cargoList) do + local t = cObj:GetType() + if t ~= CTLD_CARGO.Enum.TROOPS + and t ~= CTLD_CARGO.Enum.ENGINEERS + and t ~= CTLD_CARGO.Enum.GCLOADABLE + and (not cObj:WasDropped() or self.allowcratepickupagain) + and cObj:GetName() == CrateName + then + table.insert(matched, cObj) + end + end + if #matched < needed then + self:_SendMessage(string.format("You only have %d of %d %s crates needed!", #matched, needed, CrateName), 10, false, Group) + return self + end + local crateToUse = matched[1] + self:_GetCrates(Group, Unit, crateToUse, needed, true) + local used = 0 + for _, cObj in ipairs(matched) do + if used < needed then + used = used + 1 + cObj:SetWasDropped(true) + cObj:SetHasMoved(true) + end + end + local newList = {} + local newCratesCount = 0 + for _, cObj in ipairs(cargoList) do + if not cObj:WasDropped() then + table.insert(newList, cObj) + local ct = cObj:GetType() + if ct ~= CTLD_CARGO.Enum.TROOPS and ct ~= CTLD_CARGO.Enum.ENGINEERS then + newCratesCount = newCratesCount + 1 + end + end + end + loadedData.Cargo = newList + loadedData.Cratesloaded = newCratesCount + self.Loaded_Cargo[unitName] = loadedData + self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group,Unit) + return self +end + +function CTLD:_RefreshDropCratesMenu(Group, Unit) +local theGroup = Group +local theUnit = Unit +if not theGroup.CTLDTopmenu then return end +local topCrates = theGroup.MyTopCratesMenu +if not topCrates then return end +if topCrates.DropCratesMenu then topCrates.DropCratesMenu:Remove() end +local dropCratesMenu = MENU_GROUP:New(theGroup, "Drop Crates", topCrates) +topCrates.DropCratesMenu = dropCratesMenu +MENU_GROUP_COMMAND:New(theGroup, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, theGroup, theUnit) +local loadedData = self.Loaded_Cargo[Unit:GetName()] +if loadedData and loadedData.Cargo then +local cargoByName = {} +for _, cargoObj in pairs(loadedData.Cargo) do + if cargoObj and not cargoObj:WasDropped() then + local ctype = cargoObj:GetType() + if ctype ~= CTLD_CARGO.Enum.TROOPS and ctype ~= CTLD_CARGO.Enum.ENGINEERS and ctype ~= CTLD_CARGO.Enum.GCLOADABLE then + local cName = cargoObj:GetName() + local needed = cargoObj:GetCratesNeeded() or 1 + if not cargoByName[cName] then + cargoByName[cName] = {count = 0, needed = needed} + end + cargoByName[cName].count = cargoByName[cName].count + 1 + end + end +end +for name, info in pairs(cargoByName) do + local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) + MENU_GROUP_COMMAND:New(theGroup, line, dropCratesMenu, self._UnloadSingleCrate, self, theGroup, theUnit, name) +end +end +end + +function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) + self:T(self.lid .. " _UnloadSingleTroopByID for cargo ID " .. tostring(cargoID)) + + -- check if we are in LOAD zone + local droppingatbase = false + local canunload = true + + -- Door check + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) + if not self.debug then return self end + end + + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.LOAD) + if not inzone then + inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP) + end + if inzone then + droppingatbase = true + end + + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) -- if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + local IsHook = self:IsHook(Unit) + if IsHerc and (not IsHook) then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end + + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if not droppingatbase or self.debug then + + ------------------------------------------------------------------------ + -- (NEW CODE FOR SINGLE DROP) + -- Instead of dropping ALL troop cargo, we only drop the one matching cargoID. + ------------------------------------------------------------------------ + local loadedCargoData = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local cargoList = loadedCargoData.Cargo or {} + + local foundCargo = nil + for _, cargoObj in ipairs(cargoList) do + if (cargoObj:GetType() == CTLD_CARGO.Enum.TROOPS or cargoObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) + and not cargoObj:WasDropped() + and (cargoObj:GetID() == cargoID) + then + foundCargo = cargoObj + break + end + end + + if foundCargo then + local cType = foundCargo:GetType() + local name = foundCargo:GetName() or "none" + local temptable = foundCargo:GetTemplates() or {} + local zoneradius = self.troopdropzoneradius or 100 -- drop zone radius + local factor = 1 + if IsHerc then + factor = foundCargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping + zoneradius = Unit:GetVelocityMPS() or 100 + end + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname), Group, zoneradius * factor) + local randomcoord = zone:GetRandomCoordinate(10, 30 * factor) + local heading = Group:GetHeading() or 0 + -- Spawn troops left from us, closer when hovering, further off when landed + if hoverunload or grounded then + randomcoord = Group:GetCoordinate() + -- slightly left from us + local Angle = (heading + 270) % 360 + if IsHerc or IsHook then Angle = (heading + 180) % 360 end + local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround + if IsHerc then offset = self.TroopUnloadDistGroundHerc or 25 end + if IsHook then + offset = self.TroopUnloadDistGroundHook or 15 + if hoverunload and self.TroopUnloadDistHoverHook then + offset = self.TroopUnloadDistHoverHook or 5 end end - if self:IsHercules(_unit) then - local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() - else - local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + randomcoord:Translate(offset, Angle, nil, true) + end + + local tempcount = 0 + if IsHook then tempcount = self.ChinookTroopCircleRadius or 5 end -- 10m circle for the Chinook + for _, _template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + tempcount = tempcount + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + local rad = 2.5 + (tempcount * 2) + local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template) + + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) + :InitDelayOff() + :InitSetUnitAbsolutePositions(Positions) + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) + :SpawnFromVec2(randomcoord:GetVec2()) + + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) + end + foundCargo:SetWasDropped(true) + if cType == CTLD_CARGO.Enum.ENGINEERS then + self.Engineers = self.Engineers + 1 + local grpname = self.DroppedTroops[self.TroopCounter]:GetName() + self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) + self:_SendMessage(string.format("Dropped Engineers %s into action!", name), 10, false, Group) + else + self:_SendMessage(string.format("Dropped Troops %s into action!", name), 10, false, Group) + end + else + -- We did not find any troop cargo with that ID + self:_SendMessage(string.format("No troop cargo with ID %d found or already dropped!", cargoID), 10, false, Group) + end + + else + -- droppingatbase logic + self:_SendMessage("Troops have returned to base!", 10, false, Group) + self:__TroopsRTB(1, Group, Unit, zonename, zone) + + -------------------------------------------------------------------- + -- (NEW CODE FOR SINGLE DROP AT BASE) + -- If you want to return only the single cargo item with cargoID to stock + -- instead of returning all, you can do something similar here: + -------------------------------------------------------------------- + local loadedCargoData = self.Loaded_Cargo[unitname] or {} + local cargoList = loadedCargoData.Cargo or {} + for _, cObj in ipairs(cargoList) do + if (cObj:GetType() == CTLD_CARGO.Enum.TROOPS or cObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) + and (cObj:GetID() == cargoID) + then + -- Return this one cargo to stock + local cName = cObj:GetName() + local gentroops = self.Cargo_Troops + for _id, _troop in pairs(gentroops) do + if _troop.Name == cName then + local st = _troop:GetStock() + if st and tonumber(st) >= 0 then + _troop:AddStock() + end + end end - self.MenusDone[_unitName] = true - end -- end group - end -- end unit - else -- menu build check - self:T(self.lid .. " Menus already done for this group!") - end -- end menu build check - end -- end for + -- Mark it as dropped so we remove it from the loaded cargo + cObj:SetWasDropped(true) + end + end + end + + ------------------------------------------------------------------------ + -- cleanup load list + ------------------------------------------------------------------------ + local cargoList = self.Loaded_Cargo[unitname].Cargo + + -- 1) Remove all dropped cargo (iterate backward for table.remove) + for i = #cargoList, 1, -1 do + if cargoList[i]:WasDropped() then + table.remove(cargoList, i) + end + end + + -- 2) Recount + local troopsLoaded = 0 + local cratesLoaded = 0 + for _, cargo in ipairs(cargoList) do + local cType = cargo:GetType() + if cType == CTLD_CARGO.Enum.TROOPS or cType == CTLD_CARGO.Enum.ENGINEERS then + -- If each cargo item represents just 1 group (or “1 load of troops”): + troopsLoaded = troopsLoaded + 1 + -- If you track “troops loaded” by `CratesNeeded()`, + -- then do: troopsLoaded = troopsLoaded + cargo:GetCratesNeeded() + else + cratesLoaded = cratesLoaded + 1 + end + end + + self.Loaded_Cargo[unitname].Troopsloaded = troopsLoaded + self.Loaded_Cargo[unitname].Cratesloaded = cratesLoaded + + self:_RefreshDropTroopsMenu(Group, Unit) + + + else + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + end + end + return self - end +end + + + +-------------------- +function CTLD:_RefreshDropTroopsMenu(Group, Unit) + local theGroup=Group + local theUnit=Unit + if not theGroup.CTLDTopmenu then return end + local topTroops=theGroup.MyTopTroopsMenu + if not topTroops then return end + if topTroops.DropTroopsMenu then topTroops.DropTroopsMenu:Remove() end + local dropTroopsMenu=MENU_GROUP:New(theGroup,"Drop Troops",topTroops) + topTroops.DropTroopsMenu=dropTroopsMenu + MENU_GROUP_COMMAND:New(theGroup,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,theGroup,theUnit) + local loadedData=self.Loaded_Cargo[theUnit:GetName()] + if loadedData and loadedData.Cargo then + for i,cargoObj in ipairs(loadedData.Cargo) do + if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) + and not cargoObj:WasDropped() + then + local name=cargoObj:GetName()or"Unknown" + local size=cargoObj:GetCratesNeeded()or 1 + local cID=cargoObj:GetID() + local index = i + local line = string.format("Drop: %s (#%d)", name, index) + MENU_GROUP_COMMAND:New(theGroup,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,theGroup,theUnit,cID) + end + end + end +end + + --- [Internal] Function to check if a template exists in the mission. -- @param #CTLD self @@ -5367,105 +5719,138 @@ end -- } function CTLD:_CountStockPlusInHeloPlusAliveGroups(Restock,Threshold) local Troopstable = {} - -- generics - for _id,_cargo in pairs(self.Cargo_Crates) do - local generic = _cargo -- #CTLD_CARGO + for _id, _cargo in pairs(self.Cargo_Crates) do + local generic = _cargo local genname = generic:GetName() if generic and generic:GetStock0() > 0 and not Troopstable[genname] then Troopstable[genname] = { - Stock0 = generic:GetStock0(), - Stock = generic:GetStock(), - StockR = generic:GetRelativeStock(), - Infield = 0, - Inhelo = 0, - Sum = generic:GetStock(), - } + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 0, + Inhelo = 0, + CratesInfield = 0, + Sum = generic:GetStock(), + } if Restock == true then Troopstable[genname].GenericCargo = generic end end end - --- - for _id,_cargo in pairs(self.Cargo_Troops) do - local generic = _cargo -- #CTLD_CARGO + for _id, _cargo in pairs(self.Cargo_Troops) do + local generic = _cargo local genname = generic:GetName() if generic and generic:GetStock0() > 0 and not Troopstable[genname] then Troopstable[genname] = { - Stock0 = generic:GetStock0(), - Stock = generic:GetStock(), - StockR = generic:GetRelativeStock(), - Infield = 0, - Inhelo = 0, - Sum = generic:GetStock(), - } + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 0, + Inhelo = 0, + CratesInfield = 0, + Sum = generic:GetStock(), + } if Restock == true then Troopstable[genname].GenericCargo = generic end end end - -- Troops & Built Crates - for _index, _group in pairs (self.DroppedTroops) do + for _index, _group in pairs(self.DroppedTroops) do if _group and _group:IsAlive() then - self:T("Looking at ".._group:GetName() .. " in the field") - local generic = self:GetGenericCargoObjectFromGroupName(_group:GetName()) -- #CTLD_CARGO + self:T("Looking at " .. _group:GetName() .. " in the field") + local generic = self:GetGenericCargoObjectFromGroupName(_group:GetName()) if generic then - local genname = generic:GetName() - self:T("Found Generic "..genname .. " in the field. Adding.") - if generic:GetStock0() > 0 then -- don't count unlimited stock - Troopstable[genname].Infield = Troopstable[genname].Infield + 1 - Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + Troopstable[genname].Inhelo - end + local genname = generic:GetName() + self:T("Found Generic " .. genname .. " in the field. Adding.") + if generic:GetStock0() > 0 then + Troopstable[genname].Infield = Troopstable[genname].Infield + 1 + Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + Troopstable[genname].Inhelo + end else - self:E(self.lid.."Group without Cargo Generic: ".._group:GetName()) + self:E(self.lid .. "Group without Cargo Generic: " .. _group:GetName()) end end end - -- Helos - for _unitname,_loaded in pairs(self.Loaded_Cargo) do + for _unitname, _loaded in pairs(self.Loaded_Cargo) do local _unit = UNIT:FindByName(_unitname) if _unit and _unit:IsAlive() then local unitname = _unit:GetName() local loadedcargo = self.Loaded_Cargo[unitname].Cargo or {} - for _,_cgo in pairs (loadedcargo) do - local cargo = _cgo -- #CTLD_CARGO + for _, _cgo in pairs(loadedcargo) do + local cargo = _cgo local type = cargo.CargoType local gname = cargo.Name local gcargo = self:_FindCratesCargoObject(gname) or self:_FindTroopsCargoObject(gname) - self:T("Looking at ".. gname .. " in the helo - type = "..type) + self:T("Looking at " .. gname .. " in the helo - type = " .. type) if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS or type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then - -- valid troops/engineers - if gcargo and gcargo:GetStock0() > 0 then -- don't count unlimited stock - self:T("Adding ".. gname .. " in the helo - type = "..type) + if gcargo and gcargo:GetStock0() > 0 then + self:T("Adding " .. gname .. " in the helo - type = " .. type) if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then Troopstable[gname].Inhelo = Troopstable[gname].Inhelo + 1 end if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then - -- maybe multiple crates of the same type local counting = gcargo.CratesNeeded local added = 1 if counting > 1 then - added = added/counting + added = added / counting end Troopstable[gname].Inhelo = Troopstable[gname].Inhelo + added end - Troopstable[gname].Sum = Troopstable[gname].Infield + Troopstable[gname].Stock + Troopstable[gname].Inhelo + Troopstable[gname].Sum = Troopstable[gname].Infield + Troopstable[gname].Stock + Troopstable[gname].Inhelo + Troopstable[gname].CratesInfield end end end end - end - -- Restock? - if Restock == true then - local threshold = Threshold or 75 - for _name,_data in pairs(Troopstable) do - if _data.StockR and _data.StockR < threshold then - if _data.GenericCargo then - _data.GenericCargo:SetStock(_data.Stock0) -- refill to start level + end + if self.Spawned_Cargo then + -- First pass: just add fractional amounts for each crate on the ground + for i = #self.Spawned_Cargo, 1, -1 do + local cargo = self.Spawned_Cargo[i] + if cargo and cargo:GetPositionable() and cargo:GetPositionable():IsAlive() then + local genname = cargo:GetName() + local gcargo = self:_FindCratesCargoObject(genname) + if Troopstable[genname] and gcargo and gcargo:GetStock0() > 0 then + local needed = gcargo.CratesNeeded or 1 + local added = 1 + if needed > 1 then + added = added / needed + end + Troopstable[genname].CratesInfield = Troopstable[genname].CratesInfield + added + Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + + Troopstable[genname].Inhelo + Troopstable[genname].CratesInfield + end + end + end + for i = #self.Spawned_Cargo, 1, -1 do + local cargo = self.Spawned_Cargo[i] + if cargo and cargo:GetPositionable() and cargo:GetPositionable():IsAlive() then + local genname = cargo:GetName() + if Troopstable[genname] then + if Troopstable[genname].Inhelo == 0 and Troopstable[genname].CratesInfield < 1 then + Troopstable[genname].CratesInfield = 0 + Troopstable[genname].Sum = Troopstable[genname].Stock + cargo:GetPositionable():Destroy(false) + table.remove(self.Spawned_Cargo, i) + local leftover = Troopstable[genname].Stock0 - (Troopstable[genname].Infield + Troopstable[genname].Inhelo + Troopstable[genname].CratesInfield) + if leftover < Troopstable[genname].Stock then + Troopstable[genname].Stock = leftover + end + Troopstable[genname].Sum = Troopstable[genname].Stock + Troopstable[genname].Infield + Troopstable[genname].Inhelo + Troopstable[genname].CratesInfield + end end end end end - -- Return + if Restock == true then + local threshold = Threshold or 75 + for _name,_data in pairs(Troopstable) do + if _data.StockR and _data.StockR < threshold then + if _data.GenericCargo then + _data.GenericCargo:SetStock(_data.Stock0) -- refill to start level + end + end + end + end return Troopstable end From 693c2a730f699a20ff31dbdd373d38a254893f2c Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:26:38 +0100 Subject: [PATCH 079/158] Update CTLD.lua Added self:_RefreshDropCratesMenu(Group, Unit) to function CTLD:_LoadCratesNearby(Group, Unit) --- Moose Development/Moose/Ops/CTLD.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 7e3fadcfd..bab258232 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3192,8 +3192,9 @@ function CTLD:_LoadCratesNearby(Group, Unit) end end self.Loaded_Cargo[unitname] = loaded - self:_UpdateUnitCargoMass(Unit) - -- clean up real world crates + self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group, Unit) + -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) end end From 96337cc5df289591ca79486b24642cb343f6008e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Feb 2025 11:48:22 +0100 Subject: [PATCH 080/158] #CTLD --- 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 7e3fadcfd..4befbf1a3 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3193,6 +3193,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) end self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group,Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) end From 5ad54648ab2ca03c20e963c48282748f20abb75d Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Sun, 9 Feb 2025 11:51:34 +0100 Subject: [PATCH 081/158] Update CTLD.lua Removed the block from partly drop 1. It's not helpful for the user to have 1 incomplete a set of crates onboard. With this line removed, If someone loads 2 items, and second item is not completed, due to crate limit or due weight, player can unload the uncomplete set. --- Moose Development/Moose/Ops/CTLD.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index bab258232..7d3f76070 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4397,10 +4397,6 @@ function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) table.insert(matched, cObj) end end - if #matched < needed then - self:_SendMessage(string.format("You only have %d of %d %s crates needed!", #matched, needed, CrateName), 10, false, Group) - return self - end local crateToUse = matched[1] self:_GetCrates(Group, Unit, crateToUse, needed, true) local used = 0 From c3ccecdd4afa62df1582e6a6fbf825b88ce105dc Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Feb 2025 12:01:57 +0100 Subject: [PATCH 082/158] #AWACS - Escort vector catch for noobs not reading the description --- Moose Development/Moose/Ops/Awacs.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 0533e13b0..09aa3324b 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.70", -- #string + version = "0.2.71", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -2244,9 +2244,9 @@ function AWACS:_StartEscorts(Shiftchange) local OffsetY = 500 local OffsetZ = 500 if self.OffsetVec then - OffsetX = self.OffsetVec.x - OffsetY = self.OffsetVec.y - OffsetZ = self.OffsetVec.z + OffsetX = self.OffsetVec.x or 500 + OffsetY = self.OffsetVec.y or 500 + OffsetZ = self.OffsetVec.z or 500 end for i=1,self.EscortNumber do From 9487a5ae9177b8645657ce33d979faab4bc8cb3f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Feb 2025 12:05:18 +0100 Subject: [PATCH 083/158] #CTLD --- Moose Development/Moose/Ops/CTLD.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4befbf1a3..24eb26bae 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4397,10 +4397,6 @@ function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) table.insert(matched, cObj) end end - if #matched < needed then - self:_SendMessage(string.format("You only have %d of %d %s crates needed!", #matched, needed, CrateName), 10, false, Group) - return self - end local crateToUse = matched[1] self:_GetCrates(Group, Unit, crateToUse, needed, true) local used = 0 From 1d08bcf2e0ebac3518ad94b89653c3941c67c13c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Feb 2025 12:24:16 +0100 Subject: [PATCH 084/158] #CTLD docu --- Moose Development/Moose/Ops/CTLD.lua | 96 +++++++++++++++++----------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 24eb26bae..067d28f37 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Jan 2025 +-- Last Update Feb 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -1388,7 +1388,7 @@ CTLD.UnitTypeCapabilities = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.29" +CTLD.version="1.1.30" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3202,6 +3202,9 @@ function CTLD:_LoadCratesNearby(Group, Unit) end --- (Internal) Function to clean up tracked cargo crates +-- @param #CTLD self +-- @param #list crateIdsToRemove Table of IDs +-- @return self function CTLD:_CleanupTrackedCrates(crateIdsToRemove) local existingcrates = self.Spawned_Cargo -- #table local newexcrates = {} @@ -3855,7 +3858,6 @@ end -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit - function CTLD:_PackCratesNearby(Group, Unit) self:T(self.lid .. " _PackCratesNearby") ----------------------------------------- @@ -4338,6 +4340,12 @@ function CTLD:_RefreshF10Menus() return self end +--- (Internal) Function to unload a single crate +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @param #string CrateName The name of the crate to unload +-- @return #CTLD self function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) if not self.dropcratesanywhere then local inzone, zoneName, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.DROP) @@ -4426,39 +4434,50 @@ function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) return self end +--- (Internal) Function to refresh the menu for a single unit after crates dropped. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @return #CTLD self function CTLD:_RefreshDropCratesMenu(Group, Unit) -local theGroup = Group -local theUnit = Unit -if not theGroup.CTLDTopmenu then return end -local topCrates = theGroup.MyTopCratesMenu -if not topCrates then return end -if topCrates.DropCratesMenu then topCrates.DropCratesMenu:Remove() end -local dropCratesMenu = MENU_GROUP:New(theGroup, "Drop Crates", topCrates) -topCrates.DropCratesMenu = dropCratesMenu -MENU_GROUP_COMMAND:New(theGroup, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, theGroup, theUnit) -local loadedData = self.Loaded_Cargo[Unit:GetName()] -if loadedData and loadedData.Cargo then -local cargoByName = {} -for _, cargoObj in pairs(loadedData.Cargo) do - if cargoObj and not cargoObj:WasDropped() then - local ctype = cargoObj:GetType() - if ctype ~= CTLD_CARGO.Enum.TROOPS and ctype ~= CTLD_CARGO.Enum.ENGINEERS and ctype ~= CTLD_CARGO.Enum.GCLOADABLE then - local cName = cargoObj:GetName() - local needed = cargoObj:GetCratesNeeded() or 1 - if not cargoByName[cName] then - cargoByName[cName] = {count = 0, needed = needed} - end - cargoByName[cName].count = cargoByName[cName].count + 1 - end + local theGroup = Group + local theUnit = Unit + if not theGroup.CTLDTopmenu then return end + local topCrates = theGroup.MyTopCratesMenu + if not topCrates then return end + if topCrates.DropCratesMenu then topCrates.DropCratesMenu:Remove() end + local dropCratesMenu = MENU_GROUP:New(theGroup, "Drop Crates", topCrates) + topCrates.DropCratesMenu = dropCratesMenu + MENU_GROUP_COMMAND:New(theGroup, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, theGroup, theUnit) + local loadedData = self.Loaded_Cargo[Unit:GetName()] + if loadedData and loadedData.Cargo then + local cargoByName = {} + for _, cargoObj in pairs(loadedData.Cargo) do + if cargoObj and not cargoObj:WasDropped() then + local ctype = cargoObj:GetType() + if ctype ~= CTLD_CARGO.Enum.TROOPS and ctype ~= CTLD_CARGO.Enum.ENGINEERS and ctype ~= CTLD_CARGO.Enum.GCLOADABLE then + local cName = cargoObj:GetName() + local needed = cargoObj:GetCratesNeeded() or 1 + if not cargoByName[cName] then + cargoByName[cName] = {count = 0, needed = needed} + end + cargoByName[cName].count = cargoByName[cName].count + 1 + end + end + end + for name, info in pairs(cargoByName) do + local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) + MENU_GROUP_COMMAND:New(theGroup, line, dropCratesMenu, self._UnloadSingleCrate, self, theGroup, theUnit, name) + end end end -for name, info in pairs(cargoByName) do - local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) - MENU_GROUP_COMMAND:New(theGroup, line, dropCratesMenu, self._UnloadSingleCrate, self, theGroup, theUnit, name) -end -end -end +--- (Internal) Function to unload a single Troop group by ID. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @param #number cargoId the Cargo ID +-- @return #CTLD self function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) self:T(self.lid .. " _UnloadSingleTroopByID for cargo ID " .. tostring(cargoID)) @@ -4653,9 +4672,11 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) return self end - - --------------------- +--- (Internal) Function to refresh menu for troops on drop for a specific unit +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The requesting group. +-- @param Wrapper.Unit#UNIT Unit The requesting unit. +-- @return #CTLD self function CTLD:_RefreshDropTroopsMenu(Group, Unit) local theGroup=Group local theUnit=Unit @@ -4681,10 +4702,9 @@ function CTLD:_RefreshDropTroopsMenu(Group, Unit) end end end + return self end - - --- [Internal] Function to check if a template exists in the mission. -- @param #CTLD self -- @param #table temptable Table of string names @@ -5412,7 +5432,7 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) local distance = UTILS.MetersToNM(self.smokedistance) self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) end - return self + return self end --- User - Function to add/adjust unittype capabilities. From cad8f15b616e25e65af707cd6f7c711457aa46a3 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Sun, 9 Feb 2025 23:41:16 +0100 Subject: [PATCH 085/158] Update CTLD.lua Reworked the logic of the menu. Now it will Show be shown : 1. Ammo truck 2. Humvee 3. Ammo truck 1/2 -- 1/2 due to incomplete set. And for the troops Squad 8 (2) -- 2 set of squad 8, Selecting this will only deploy 1 set. Squad 16 Changed so when loading troops, it will state Squad 8 boarded. same for the extraction. I have tested it and it works Heart8reaker also tested it and no issues so far. --- Moose Development/Moose/Ops/CTLD.lua | 630 +++++++++++++++------------ 1 file changed, 348 insertions(+), 282 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 067d28f37..fe760cc83 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2355,7 +2355,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:_SendMessage("Troops boarded!", 10, false, Group) + self:_SendMessage(string.format("%s boarded!", cgoname), 10, false, Group) self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group, Unit, Cargotype) self:_UpdateUnitCargoMass(Unit) @@ -2589,8 +2589,8 @@ end loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!", 10, false, Group) - self:_SendMessage("Troops boarding!", 10, false, Group) + self:ScheduleOnce(running, self._SendMessage, self, string.format("%s boarded!", Cargotype.Name), 10, false, Group) + self:_SendMessage(string.format("%s boarding!", Cargotype.Name), 10, false, Group) self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) local groupname = nearestGroup:GetName() @@ -3099,23 +3099,21 @@ function CTLD:_LoadCratesNearby(Group, Unit) self:T(self.lid .. " _LoadCratesNearby") -- load crates into heli local group = Group -- Wrapper.Group#GROUP - local unit = Unit -- Wrapper.Unit#UNIT + local unit = Unit -- Wrapper.Unit#UNIT local unitname = unit:GetName() - -- see if this heli can load crates + -- see if this heli can load crates local unittype = unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities - --local capabilities = self.UnitTypeCapabilities[unittype] -- #CTLD.UnitTypeCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number local grounded = not self:IsUnitInAir(Unit) local canhoverload = self:CanHoverLoad(Unit) - + -- Door check if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) - if not self.debug then return self end + if not self.debug then return self end end - --- cases ------------------------------- -- Chopper can\'t do crates - bark & return -- Chopper can do crates - @@ -3123,77 +3121,97 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- --> hover or land if not forcedhover ----------------------------------------- if not cancrates then - self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) elseif self.forcehoverload and not canhoverload then - self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) + self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) elseif not grounded and not canhoverload then - self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) + self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) else - -- have we loaded stuff already? + -- have we loaded stuff already? local numberonboard = 0 - local massonboard = 0 - local loaded = {} + local loaded = {} if self.Loaded_Cargo[unitname] then - loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo - numberonboard = loaded.Cratesloaded or 0 - massonboard = self:_GetUnitCargoMass(Unit) + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 else - loaded = {} -- #CTLD.LoadedCargo + loaded = {} loaded.Troopsloaded = 0 loaded.Cratesloaded = 0 loaded.Cargo = {} end + -- get nearby crates - local finddist = self.CrateDistance or 35 - local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist,false,false) -- #table + local finddist = self.CrateDistance or 35 + local nearcrates, number = self:_FindCratesNearby(Group,Unit,finddist,false,false) self:T(self.lid .. " Crates found: " .. number) + if number == 0 and self.hoverautoloading then - return self -- exit + return self elseif number == 0 then - self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!", 10, false, Group) - return self -- exit + self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!", 10, false, Group) + return self elseif numberonboard == cratelimit then - self:_SendMessage("Sorry, we are fully loaded!", 10, false, Group) - return self -- exit + self:_SendMessage("Sorry, we are fully loaded!", 10, false, Group) + return self else - -- go through crates and load local capacity = cratelimit - numberonboard local crateidsloaded = {} - local loops = 0 - while loaded.Cratesloaded < cratelimit and loops < number do - loops = loops + 1 - local crateind = 0 - -- get crate with largest index - for _ind,_crate in pairs (nearcrates) do - if self.allowcratepickupagain then - if _crate:GetID() > crateind and _crate.Positionable ~= nil then - crateind = _crate:GetID() - end + local crateMap = {} + + for _, cObj in pairs(nearcrates) do + if not cObj:HasMoved() or self.allowcratepickupagain then + local cName = cObj:GetName() or "Unknown" + crateMap[cName] = crateMap[cName] or {} + table.insert(crateMap[cName], cObj) + end + end + for cName, crateList in pairs(crateMap) do + if capacity <= 0 then break end + + table.sort(crateList, function(a, b) return a:GetID() > b:GetID() end) + local needed = crateList[1]:GetCratesNeeded() or 1 + local totalFound = #crateList + local loadedHere = 0 + + while loaded.Cratesloaded < cratelimit and loadedHere < totalFound do + loadedHere = loadedHere + 1 + local crate = crateList[loadedHere] + if crate and crate.Positionable then + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + crate:SetWasDropped(false) + table.insert(loaded.Cargo, crate) + table.insert(crateidsloaded, crate:GetID()) + -- destroy crate + crate:GetPositionable():Destroy(false) + crate.Positionable = nil else - if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then - crateind = _crate:GetID() - end + loadedHere = loadedHere - 1 + break end end - -- load one if we found one - if crateind > 0 then - local crate = nearcrates[crateind] -- #CTLD_CARGO - loaded.Cratesloaded = loaded.Cratesloaded + 1 - crate:SetHasMoved(true) - crate:SetWasDropped(false) - table.insert(loaded.Cargo, crate) - table.insert(crateidsloaded,crate:GetID()) - -- destroy crate - crate:GetPositionable():Destroy(false) - crate.Positionable = nil - self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) - table.remove(nearcrates,crate:GetID()) - self:__CratesPickedUp(1, Group, Unit, crate) + + capacity = cratelimit - loaded.Cratesloaded + if loadedHere > 0 then + local fullSets = math.floor(loadedHere / needed) + local leftover = loadedHere % needed + + if needed > 1 then + if fullSets > 0 and leftover == 0 then + self:_SendMessage(string.format("Loaded %d %s.", fullSets, cName), 10, false, Group) + elseif fullSets > 0 and leftover > 0 then + self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cName, leftover), 10, false, Group) + else + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cName), 15, false, Group) + end + else + self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cName), 10, false, Group) + end end end self.Loaded_Cargo[unitname] = loaded - self:_UpdateUnitCargoMass(Unit) - self:_RefreshDropCratesMenu(Group,Unit) + self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group, Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) end @@ -3201,6 +3219,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) return self end + --- (Internal) Function to clean up tracked cargo crates -- @param #CTLD self -- @param #list crateIdsToRemove Table of IDs @@ -3858,6 +3877,7 @@ end -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit + function CTLD:_PackCratesNearby(Group, Unit) self:T(self.lid .. " _PackCratesNearby") ----------------------------------------- @@ -4294,7 +4314,7 @@ function CTLD:_RefreshF10Menus() end for name, info in pairs(cargoByName) do local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) - MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrate, self, _group, _unit, name) + MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) end end @@ -4344,22 +4364,52 @@ end -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. -- @param Wrapper.Unit#UNIT Unit The calling unit. --- @param #string CrateName The name of the crate to unload +-- @param #string setIndex The name of the crate to unload -- @return #CTLD self -function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) +function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) + self:T(self.lid .. " _UnloadSingleCrateSet") + + -- Check if we are in a drop zone (unless we drop anywhere) if not self.dropcratesanywhere then local inzone, zoneName, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.DROP) if not inzone then self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - if not self.debug then - return self + if not self.debug then + return self end end end + + -- Check if doors must be open if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) if not self.debug then return self end end + + -- Check if the crate grouping data is available + local unitName = Unit:GetName() + if not self.CrateGroupList or not self.CrateGroupList[unitName] then + self:_SendMessage("No crate groups found for this unit!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Find the selected chunk/set by index + local chunk = self.CrateGroupList[unitName][setIndex] + if not chunk then + self:_SendMessage("No crate set found or index invalid!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Check if the chunk is empty + if #chunk == 0 then + self:_SendMessage("No crate found in that set!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Check hover/airdrop/landed logic local grounded = not self:IsUnitInAir(Unit) local hoverunload = self:IsCorrectHover(Unit) local isHerc = self:IsHercules(Unit) @@ -4373,64 +4423,51 @@ function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end + if not self.debug then return self end return self end - local unitName = Unit:GetName() + + -- Get the first crate from this set + local crateObj = chunk[1] + if not crateObj then + self:_SendMessage("No crate found in that set!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Perform the actual "drop" spawn + local needed = crateObj:GetCratesNeeded() or 1 + self:_GetCrates(Group, Unit, crateObj, #chunk, true) + + -- Mark all crates in the chunk as dropped + for _, cObj in ipairs(chunk) do + cObj:SetWasDropped(true) + cObj:SetHasMoved(true) + end + + -- Rebuild the cargo list to remove the dropped crates local loadedData = self.Loaded_Cargo[unitName] - if not loadedData or not loadedData.Cargo then - self:_SendMessage("Nothing loaded!", 10, false, Group) - return self - end - local cargoList = loadedData.Cargo - local needed = 0 - for _, cObj in ipairs(cargoList) do - if cObj:GetName() == CrateName and not cObj:WasDropped() then - needed = cObj:GetCratesNeeded() or 1 - break - end - end - if needed < 1 then - self:_SendMessage(string.format("No %s crate found or already dropped!", CrateName), 10, false, Group) - return self - end - local matched = {} - for _, cObj in ipairs(cargoList) do - local t = cObj:GetType() - if t ~= CTLD_CARGO.Enum.TROOPS - and t ~= CTLD_CARGO.Enum.ENGINEERS - and t ~= CTLD_CARGO.Enum.GCLOADABLE - and (not cObj:WasDropped() or self.allowcratepickupagain) - and cObj:GetName() == CrateName - then - table.insert(matched, cObj) - end - end - local crateToUse = matched[1] - self:_GetCrates(Group, Unit, crateToUse, needed, true) - local used = 0 - for _, cObj in ipairs(matched) do - if used < needed then - used = used + 1 - cObj:SetWasDropped(true) - cObj:SetHasMoved(true) - end - end - local newList = {} - local newCratesCount = 0 - for _, cObj in ipairs(cargoList) do - if not cObj:WasDropped() then - table.insert(newList, cObj) - local ct = cObj:GetType() - if ct ~= CTLD_CARGO.Enum.TROOPS and ct ~= CTLD_CARGO.Enum.ENGINEERS then - newCratesCount = newCratesCount + 1 + if loadedData and loadedData.Cargo then + local newList = {} + local newCratesCount = 0 + for _, cObj in ipairs(loadedData.Cargo) do + if not cObj:WasDropped() then + table.insert(newList, cObj) + local ct = cObj:GetType() + if ct ~= CTLD_CARGO.Enum.TROOPS and ct ~= CTLD_CARGO.Enum.ENGINEERS then + newCratesCount = newCratesCount + 1 + end end end + loadedData.Cargo = newList + loadedData.Cratesloaded = newCratesCount + self.Loaded_Cargo[unitName] = loadedData end - loadedData.Cargo = newList - loadedData.Cratesloaded = newCratesCount - self.Loaded_Cargo[unitName] = loadedData + + -- Update cargo mass, refresh menu self:_UpdateUnitCargoMass(Unit) - self:_RefreshDropCratesMenu(Group,Unit) + self:_RefreshDropCratesMenu(Group, Unit) + return self end @@ -4445,29 +4482,67 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) if not theGroup.CTLDTopmenu then return end local topCrates = theGroup.MyTopCratesMenu if not topCrates then return end - if topCrates.DropCratesMenu then topCrates.DropCratesMenu:Remove() end + if topCrates.DropCratesMenu then + topCrates.DropCratesMenu:Remove() + end local dropCratesMenu = MENU_GROUP:New(theGroup, "Drop Crates", topCrates) topCrates.DropCratesMenu = dropCratesMenu MENU_GROUP_COMMAND:New(theGroup, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, theGroup, theUnit) + local loadedData = self.Loaded_Cargo[Unit:GetName()] - if loadedData and loadedData.Cargo then - local cargoByName = {} - for _, cargoObj in pairs(loadedData.Cargo) do - if cargoObj and not cargoObj:WasDropped() then - local ctype = cargoObj:GetType() - if ctype ~= CTLD_CARGO.Enum.TROOPS and ctype ~= CTLD_CARGO.Enum.ENGINEERS and ctype ~= CTLD_CARGO.Enum.GCLOADABLE then - local cName = cargoObj:GetName() - local needed = cargoObj:GetCratesNeeded() or 1 - if not cargoByName[cName] then - cargoByName[cName] = {count = 0, needed = needed} - end - cargoByName[cName].count = cargoByName[cName].count + 1 - end + if not loadedData or not loadedData.Cargo then return end + + local cargoByName = {} + for _, cObj in ipairs(loadedData.Cargo) do + if cObj and not cObj:WasDropped() then + local cType = cObj:GetType() + if cType ~= CTLD_CARGO.Enum.TROOPS and cType ~= CTLD_CARGO.Enum.ENGINEERS and cType ~= CTLD_CARGO.Enum.GCLOADABLE then + local name = cObj:GetName() or "Unknown" + cargoByName[name] = cargoByName[name] or {} + table.insert(cargoByName[name], cObj) end end - for name, info in pairs(cargoByName) do - local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) - MENU_GROUP_COMMAND:New(theGroup, line, dropCratesMenu, self._UnloadSingleCrate, self, theGroup, theUnit, name) + end + + self.CrateGroupList = self.CrateGroupList or {} + self.CrateGroupList[Unit:GetName()] = {} + + -- A single global line index for ALL crate names + local lineIndex = 1 + + for cName, list in pairs(cargoByName) do + local needed = list[1]:GetCratesNeeded() or 1 + table.sort(list, function(a,b) return a:GetID() < b:GetID() end) + + local i = 1 + while i <= #list do + local left = (#list - i + 1) + if left >= needed then + local chunk = {} + for n = i, i + needed - 1 do + table.insert(chunk, list[n]) + end + -- Now label uses the global lineIndex and increments after each chunk + local label = string.format("%d. %s", lineIndex, cName) + table.insert(self.CrateGroupList[Unit:GetName()], chunk) + local setIndex = #self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(theGroup, label, dropCratesMenu, self._UnloadSingleCrateSet, self, theGroup, theUnit, setIndex) + + i = i + needed + else + local chunk = {} + for n = i, #list do + table.insert(chunk, list[n]) + end + local label = string.format("%d. %s %d/%d", lineIndex, cName, left, needed) + table.insert(self.CrateGroupList[Unit:GetName()], chunk) + local setIndex = #self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(theGroup, label, dropCratesMenu, self._UnloadSingleCrateSet, self, theGroup, theUnit, setIndex) + + i = #list + 1 + end + + lineIndex = lineIndex + 1 end end end @@ -4476,21 +4551,12 @@ end -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. -- @param Wrapper.Unit#UNIT Unit The calling unit. --- @param #number cargoId the Cargo ID +-- @param #number chunkID the Cargo ID -- @return #CTLD self -function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) - self:T(self.lid .. " _UnloadSingleTroopByID for cargo ID " .. tostring(cargoID)) +function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) + self:T(self.lid .. " _UnloadSingleTroopByID chunkID=" .. tostring(chunkID)) - -- check if we are in LOAD zone local droppingatbase = false - local canunload = true - - -- Door check - if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) - if not self.debug then return self end - end - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.LOAD) if not inzone then inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP) @@ -4499,120 +4565,116 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) droppingatbase = true end - -- check for hover unload - local hoverunload = self:IsCorrectHover(Unit) -- if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) - local IsHook = self:IsHook(Unit) - if IsHerc and (not IsHook) then - -- no hover but airdrop here - hoverunload = self:IsCorrectFlightParameters(Unit) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) + if not self.debug then return self end end - -- check if we\'re landed + local hoverunload = self:IsCorrectHover(Unit) + local isHerc = self:IsHercules(Unit) + local isHook = self:IsHook(Unit) + if isHerc and not isHook then + hoverunload = self:IsCorrectFlightParameters(Unit) + end local grounded = not self:IsUnitInAir(Unit) - -- Get what we have loaded - local unitname = Unit:GetName() + local unitName = Unit:GetName() - if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if self.Loaded_Cargo[unitName] and (grounded or hoverunload) then if not droppingatbase or self.debug then - - ------------------------------------------------------------------------ - -- (NEW CODE FOR SINGLE DROP) - -- Instead of dropping ALL troop cargo, we only drop the one matching cargoID. - ------------------------------------------------------------------------ - local loadedCargoData = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - local cargoList = loadedCargoData.Cargo or {} - - local foundCargo = nil - for _, cargoObj in ipairs(cargoList) do - if (cargoObj:GetType() == CTLD_CARGO.Enum.TROOPS or cargoObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) - and not cargoObj:WasDropped() - and (cargoObj:GetID() == cargoID) - then - foundCargo = cargoObj - break - end + if not self.TroopsIDToChunk or not self.TroopsIDToChunk[chunkID] then + self:_SendMessage(string.format("No troop cargo chunk found for ID %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self end - if foundCargo then - local cType = foundCargo:GetType() - local name = foundCargo:GetName() or "none" - local temptable = foundCargo:GetTemplates() or {} - local zoneradius = self.troopdropzoneradius or 100 -- drop zone radius - local factor = 1 - if IsHerc then - factor = foundCargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping - zoneradius = Unit:GetVelocityMPS() or 100 + local chunk = self.TroopsIDToChunk[chunkID] + if not chunk or #chunk == 0 then + self:_SendMessage(string.format("Troop chunk is empty for ID %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self + end + + -- Drop ONLY the FIRST cargo in that chunk + local foundCargo = chunk[1] + if not foundCargo then + self:_SendMessage(string.format("No troop cargo at chunk %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self + end + + local cType = foundCargo:GetType() + local name = foundCargo:GetName() or "none" + local tmpl = foundCargo:GetTemplates() or {} + local zoneradius = self.troopdropzoneradius or 100 + local factor = 1 + if isHerc then + factor = foundCargo:GetCratesNeeded() or 1 + zoneradius = Unit:GetVelocityMPS() or 100 + end + local zone = ZONE_GROUP:New(string.format("Unload zone-%s", unitName), Group, zoneradius * factor) + local randomcoord = zone:GetRandomCoordinate(10, 30 * factor) + local heading = Group:GetHeading() or 0 + + if grounded or hoverunload then + randomcoord = Group:GetCoordinate() + local Angle = (heading + 270) % 360 + if isHerc or isHook then + Angle = (heading + 180) % 360 end - local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname), Group, zoneradius * factor) - local randomcoord = zone:GetRandomCoordinate(10, 30 * factor) - local heading = Group:GetHeading() or 0 - -- Spawn troops left from us, closer when hovering, further off when landed - if hoverunload or grounded then - randomcoord = Group:GetCoordinate() - -- slightly left from us - local Angle = (heading + 270) % 360 - if IsHerc or IsHook then Angle = (heading + 180) % 360 end - local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround - if IsHerc then offset = self.TroopUnloadDistGroundHerc or 25 end - if IsHook then - offset = self.TroopUnloadDistGroundHook or 15 - if hoverunload and self.TroopUnloadDistHoverHook then - offset = self.TroopUnloadDistHoverHook or 5 - end + local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround + if isHerc then + offset = self.TroopUnloadDistGroundHerc or 25 + end + if isHook then + offset = self.TroopUnloadDistGroundHook or 15 + if hoverunload and self.TroopUnloadDistHoverHook then + offset = self.TroopUnloadDistHoverHook or 5 end - randomcoord:Translate(offset, Angle, nil, true) end + randomcoord:Translate(offset, Angle, nil, true) + end - local tempcount = 0 - if IsHook then tempcount = self.ChinookTroopCircleRadius or 5 end -- 10m circle for the Chinook - for _, _template in pairs(temptable) do - self.TroopCounter = self.TroopCounter + 1 - tempcount = tempcount + 1 - local alias = string.format("%s-%d", _template, math.random(1,100000)) - local rad = 2.5 + (tempcount * 2) - local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template) - - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) - :InitDelayOff() - :InitSetUnitAbsolutePositions(Positions) - :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) - :SpawnFromVec2(randomcoord:GetVec2()) - - self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) - end - foundCargo:SetWasDropped(true) - if cType == CTLD_CARGO.Enum.ENGINEERS then - self.Engineers = self.Engineers + 1 - local grpname = self.DroppedTroops[self.TroopCounter]:GetName() - self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) - self:_SendMessage(string.format("Dropped Engineers %s into action!", name), 10, false, Group) - else - self:_SendMessage(string.format("Dropped Troops %s into action!", name), 10, false, Group) - end + local tempcount = 0 + if isHook then + tempcount = self.ChinookTroopCircleRadius or 5 + end + for _, _template in pairs(tmpl) do + self.TroopCounter = self.TroopCounter + 1 + tempcount = tempcount + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + local rad = 2.5 + (tempcount * 2) + local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) + :InitDelayOff() + :InitSetUnitAbsolutePositions(Positions) + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) + :SpawnFromVec2(randomcoord:GetVec2()) + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) + end + + foundCargo:SetWasDropped(true) + if cType == CTLD_CARGO.Enum.ENGINEERS then + self.Engineers = self.Engineers + 1 + self:_SendMessage(string.format("Dropped Engineers %s into action!", name), 10, false, Group) else - -- We did not find any troop cargo with that ID - self:_SendMessage(string.format("No troop cargo with ID %d found or already dropped!", cargoID), 10, false, Group) + self:_SendMessage(string.format("Dropped Troops %s into action!", name), 10, false, Group) + end + + table.remove(chunk, 1) + if #chunk == 0 then + self.TroopsIDToChunk[chunkID] = nil end else - -- droppingatbase logic - self:_SendMessage("Troops have returned to base!", 10, false, Group) + -- Return to base logic, remove ONLY the first cargo + self:_SendMessage("Troops have returned to base!", 10, false, Group) self:__TroopsRTB(1, Group, Unit, zonename, zone) - -------------------------------------------------------------------- - -- (NEW CODE FOR SINGLE DROP AT BASE) - -- If you want to return only the single cargo item with cargoID to stock - -- instead of returning all, you can do something similar here: - -------------------------------------------------------------------- - local loadedCargoData = self.Loaded_Cargo[unitname] or {} - local cargoList = loadedCargoData.Cargo or {} - for _, cObj in ipairs(cargoList) do - if (cObj:GetType() == CTLD_CARGO.Enum.TROOPS or cObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) - and (cObj:GetID() == cargoID) - then - -- Return this one cargo to stock - local cName = cObj:GetName() + if self.TroopsIDToChunk and self.TroopsIDToChunk[chunkID] then + local chunk = self.TroopsIDToChunk[chunkID] + if #chunk > 0 then + local firstObj = chunk[1] + local cName = firstObj:GetName() local gentroops = self.Cargo_Troops for _id, _troop in pairs(gentroops) do if _troop.Name == cName then @@ -4622,53 +4684,42 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) end end end - -- Mark it as dropped so we remove it from the loaded cargo - cObj:SetWasDropped(true) + firstObj:SetWasDropped(true) + table.remove(chunk, 1) + if #chunk == 0 then + self.TroopsIDToChunk[chunkID] = nil + end end end end - ------------------------------------------------------------------------ - -- cleanup load list - ------------------------------------------------------------------------ - local cargoList = self.Loaded_Cargo[unitname].Cargo - - -- 1) Remove all dropped cargo (iterate backward for table.remove) + local cargoList = self.Loaded_Cargo[unitName].Cargo for i = #cargoList, 1, -1 do if cargoList[i]:WasDropped() then table.remove(cargoList, i) end end - - -- 2) Recount local troopsLoaded = 0 local cratesLoaded = 0 for _, cargo in ipairs(cargoList) do - local cType = cargo:GetType() - if cType == CTLD_CARGO.Enum.TROOPS or cType == CTLD_CARGO.Enum.ENGINEERS then - -- If each cargo item represents just 1 group (or “1 load of troops”): + local cT = cargo:GetType() + if cT == CTLD_CARGO.Enum.TROOPS or cT == CTLD_CARGO.Enum.ENGINEERS then troopsLoaded = troopsLoaded + 1 - -- If you track “troops loaded” by `CratesNeeded()`, - -- then do: troopsLoaded = troopsLoaded + cargo:GetCratesNeeded() else cratesLoaded = cratesLoaded + 1 end end - - self.Loaded_Cargo[unitname].Troopsloaded = troopsLoaded - self.Loaded_Cargo[unitname].Cratesloaded = cratesLoaded - + self.Loaded_Cargo[unitName].Troopsloaded = troopsLoaded + self.Loaded_Cargo[unitName].Cratesloaded = cratesLoaded self:_RefreshDropTroopsMenu(Group, Unit) - - else - if IsHerc then + local isHerc = self:IsHercules(Unit) + if isHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end end - return self end @@ -4678,31 +4729,46 @@ end -- @param Wrapper.Unit#UNIT Unit The requesting unit. -- @return #CTLD self function CTLD:_RefreshDropTroopsMenu(Group, Unit) - local theGroup=Group - local theUnit=Unit + local theGroup = Group + local theUnit = Unit if not theGroup.CTLDTopmenu then return end - local topTroops=theGroup.MyTopTroopsMenu + local topTroops = theGroup.MyTopTroopsMenu if not topTroops then return end - if topTroops.DropTroopsMenu then topTroops.DropTroopsMenu:Remove() end - local dropTroopsMenu=MENU_GROUP:New(theGroup,"Drop Troops",topTroops) - topTroops.DropTroopsMenu=dropTroopsMenu - MENU_GROUP_COMMAND:New(theGroup,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,theGroup,theUnit) - local loadedData=self.Loaded_Cargo[theUnit:GetName()] - if loadedData and loadedData.Cargo then - for i,cargoObj in ipairs(loadedData.Cargo) do - if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) - and not cargoObj:WasDropped() - then - local name=cargoObj:GetName()or"Unknown" - local size=cargoObj:GetCratesNeeded()or 1 - local cID=cargoObj:GetID() - local index = i - local line = string.format("Drop: %s (#%d)", name, index) - MENU_GROUP_COMMAND:New(theGroup,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,theGroup,theUnit,cID) - end + if topTroops.DropTroopsMenu then + topTroops.DropTroopsMenu:Remove() + end + local dropTroopsMenu = MENU_GROUP:New(theGroup, "Drop Troops", topTroops) + topTroops.DropTroopsMenu = dropTroopsMenu + MENU_GROUP_COMMAND:New(theGroup, "Drop ALL troops", dropTroopsMenu, self._UnloadTroops, self, theGroup, theUnit) + + local loadedData = self.Loaded_Cargo[theUnit:GetName()] + if not loadedData or not loadedData.Cargo then return end + + -- Gather troop cargo by name + local troopsByName = {} + for _, cargoObj in ipairs(loadedData.Cargo) do + if cargoObj + and (cargoObj:GetType() == CTLD_CARGO.Enum.TROOPS or cargoObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) + and not cargoObj:WasDropped() + then + local name = cargoObj:GetName() or "Unknown" + troopsByName[name] = troopsByName[name] or {} + table.insert(troopsByName[name], cargoObj) end end - return self + + self.TroopsIDToChunk = self.TroopsIDToChunk or {} + + for tName, objList in pairs(troopsByName) do + table.sort(objList, function(a,b) return a:GetID() < b:GetID() end) + local count = #objList + + local chunkID = objList[1]:GetID() + self.TroopsIDToChunk[chunkID] = objList + + local label = string.format("Drop %s (%d)", tName, count) + MENU_GROUP_COMMAND:New(theGroup, label, dropTroopsMenu, self._UnloadSingleTroopByID, self, theGroup, theUnit, chunkID) + end end --- [Internal] Function to check if a template exists in the mission. From f44db275651be4898705dbb9eb8756431b3403ed Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 10 Feb 2025 18:02:42 +0100 Subject: [PATCH 086/158] #ATC_GROUND_UNIVERSAL * Correct usage of airbase names if given * Exclude FARPs and Ships --- .../Moose/Functional/ATC_Ground.lua | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 4703559b9..9456fa270 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -18,7 +18,7 @@ -- ### Author: FlightControl - Framework Design & Programming -- ### Refactoring to use the Runway auto-detection: Applevangelist -- @date August 2022 --- Last Update Oct 2024 +-- Last Update Feb 2025 -- -- === -- @@ -416,7 +416,7 @@ end -- @field #ATC_GROUND_UNIVERSAL ATC_GROUND_UNIVERSAL = { ClassName = "ATC_GROUND_UNIVERSAL", - Version = "0.0.1", + Version = "0.0.2", SetClient = nil, Airbases = nil, AirbaseList = nil, @@ -441,17 +441,25 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) self:T( { self.ClassName } ) self.Airbases = {} - - for _name,_ in pairs(_DATABASE.AIRBASES) do - self.Airbases[_name]={} - end self.AirbaseList = AirbaseList if not self.AirbaseList then self.AirbaseList = {} - for _name,_ in pairs(_DATABASE.AIRBASES) do - self.AirbaseList[_name]=_name + for _name,_base in pairs(_DATABASE.AIRBASES) do + -- DONE exclude FARPS and Ships + if _base and _base.isAirdrome == true then + self.AirbaseList[_name]=_name + self.Airbases[_name]={} + end + end + else + for _,_name in pairs(AirbaseList) do + -- DONE exclude FARPS and Ships + local airbase = _DATABASE:FindAirbase(_name) + if airbase and airbase.isAirdrome == true then + self.Airbases[_name]={} + end end end @@ -1447,11 +1455,10 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end - - -- @type ATC_GROUND_MARIANAISLANDS +--- +-- @type ATC_GROUND_MARIANAISLANDS -- @extends #ATC_GROUND - --- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND} -- From 31aa604fc40c159c9d02cc08b0fd5d6676fbc913 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:20:23 +0100 Subject: [PATCH 087/158] Update CTLD.lua Added _LoadSingleCrateSet Function Added _refreshLoadCratesMenu Function Added Event for Takeoff, Land. Changes to the RefreshF10menus function the menus does not gets deleted, and rebuild, but only what is inside them. So they will remain at the same position. tested multiple times --- Moose Development/Moose/Ops/CTLD.lua | 354 ++++++++++++++++++++------- 1 file changed, 268 insertions(+), 86 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index fe760cc83..876f440fc 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2021,6 +2021,13 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end return + elseif event.id == EVENTS.Land or event.id == EVENTS.Takeoff then + local unitname = event.IniUnitName + if self.CtldUnits[unitname] then + local _group = event.IniGroup + local _unit = event.IniUnit + self:_RefreshLoadCratesMenu(_group, _unit) + end elseif event.id == EVENTS.PlayerLeaveUnit or event.id == EVENTS.UnitLost then -- remove from pilot table local unitname = event.IniUnitName or "none" @@ -2845,7 +2852,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) text = string.format("Crates for %s have been dropped!",cratename) self:__CratesDropped(1, Group, Unit, droppedcargo) end - self:_SendMessage(text, 10, false, Group) + self:_SendMessage(text, 10, false, Group) + self:_RefreshLoadCratesMenu(Group, Unit) + return self end @@ -2973,31 +2982,32 @@ end -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self -function CTLD:_RemoveCratesNearby( _group, _unit) - self:T(self.lid .. " _RemoveCratesNearby") - local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(_group,_unit, finddist,true,true) -- #table - if number > 0 then - local text = REPORT:New("Removing Crates Found Nearby:") +function CTLD:_RemoveCratesNearby(_group, _unit) + self:T(self.lid.." _RemoveCratesNearby") + local finddist=self.CrateDistance or 35 + local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true,true) + if number>0 then + local removedIDs={} + local text=REPORT:New("Removing Crates Found Nearby:") text:Add("------------------------------------------------------------") - for _,_entry in pairs (crates) do - local entry = _entry -- #CTLD_CARGO - local name = entry:GetName() --#string - local dropped = entry:WasDropped() - if dropped then - text:Add(string.format("Crate for %s, %dkg removed",name, entry.PerCrateMass)) - else - text:Add(string.format("Crate for %s, %dkg removed",name, entry.PerCrateMass)) + for _,_entry in pairs(crates)do + local entry=_entry + local name=entry:GetName()or"none" + text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) + if entry:GetPositionable()then + entry:GetPositionable():Destroy(false) end - entry:GetPositionable():Destroy(false) + table.insert(removedIDs,entry:GetID()) end - if text:GetCount() == 1 then - text:Add(" N O N E") + if text:GetCount()==1 then + text:Add(" N O N E") end text:Add("------------------------------------------------------------") - self:_SendMessage(text:Text(), 30, true, _group) + self:_SendMessage(text:Text(),30,true,_group) + self:_CleanupTrackedCrates(removedIDs) + self:_RefreshLoadCratesMenu(_group,_unit) else - self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end return self end @@ -3212,6 +3222,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group, Unit) + self:_RefreshLoadCratesMenu(Group, Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) end @@ -3900,6 +3911,7 @@ function CTLD:_PackCratesNearby(Group, Unit) if (_entry.Templates[1] == _Template.GroupName) then -- check if the #CTLD_CARGO matches the template name _Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player + self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu return self end end @@ -4235,17 +4247,10 @@ function CTLD:_RefreshF10Menus() end if cancrates then local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) - local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates) _group.MyTopCratesMenu = topcrates - -- Show "Load crates" on 1 line - MENU_GROUP_COMMAND:New(_group, "Load crates", topcrates, self._LoadCratesNearby, self, _group, _unit) - - MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) - local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) - MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) - -- Build the “Get Crates” sub-menu items + local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates) if self.usesubcats then local subcatmenus = {} for catName, _ in pairs(self.subcats) do @@ -4290,15 +4295,24 @@ function CTLD:_RefreshF10Menus() end end - MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) - + local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) + _group.MyLoadCratesMenu=loadCratesMenu + MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) + + local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates) + topcrates.DropCratesMenu=dropCratesMenu + if not self.nobuildmenu then - MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() end - -- Drop Crates sub-menu - local dropCratesMenu = MENU_GROUP:New(_group, "Drop Crates", topcrates) - MENU_GROUP_COMMAND:New(_group, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, _group, _unit) + + local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) + MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) + + MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) local uName = _unit:GetName() local loadedData = self.Loaded_Cargo[uName] @@ -4317,10 +4331,10 @@ function CTLD:_RefreshF10Menus() MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) end end - - end + + ----------------------------------------------------- -- Misc sub‐menus ----------------------------------------------------- @@ -4349,6 +4363,8 @@ function CTLD:_RefreshF10Menus() -- Mark we built the menu self.MenusDone[_unitName] = true + self:_RefreshLoadCratesMenu(_group, _unit) + self:_RefreshDropCratesMenu(_group,_unit) end -- if _group end -- if _unit @@ -4360,6 +4376,168 @@ function CTLD:_RefreshF10Menus() return self end +--- (Internal) Function to refresh the menu for load crates. Triggered from land/getcrate/pack and more +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @return #CTLD self +function CTLD:_RefreshLoadCratesMenu(Group, Unit) + if not Group.MyLoadCratesMenu then return end + Group.MyLoadCratesMenu:RemoveSubMenus() + + local d = self.CrateDistance or 35 + local nearby, n = self:_FindCratesNearby(Group, Unit, d, true, true) + if n == 0 then + MENU_GROUP_COMMAND:New(Group, "No crates found! Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) + return + end + MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) + MENU_GROUP_COMMAND:New(Group, "Load ALL", Group.MyLoadCratesMenu, self._LoadCratesNearby, self, Group, Unit) + local cargoByName = {} + for _, crate in pairs(nearby) do + local cName = crate:GetName() + cargoByName[cName] = cargoByName[cName] or {} + table.insert(cargoByName[cName], crate) + end + + for cName, cList in pairs(cargoByName) do + local needed = cList[1]:GetCratesNeeded() or 1 + local found = #cList + + local line + if found >= needed then + line = string.format("Load %s", cName) + else + line = string.format("Load %s (%d/%d)", cName, found, needed) + end + MENU_GROUP_COMMAND:New(Group, line, Group.MyLoadCratesMenu, self._LoadSingleCrateSet, self, Group, Unit, cName) + end +end + +--- +-- Loads exactly `CratesNeeded` crates for one cargoName in range. +-- If "Ammo Truck" needs 2 crates, we pick up 2 if available. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #string cargoName The cargo name, e.g. "Ammo Truck" +function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName) + self:T(self.lid .. " _LoadSingleCrateSet cargoName=" .. (cargoName or "nil")) + + -- 1) Must be landed or hovering + local grounded = not self:IsUnitInAir(Unit) + local hover = self:CanHoverLoad(Unit) + if not grounded and not hover then + self:_SendMessage("You must land or hover to load crates!", 10, false, Group) + return self + end + + -- 2) Check door if required + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) + return self + end + + -- 3) Find crates with `cargoName` in range + local finddist = self.CrateDistance or 35 + local cratesNearby, number = self:_FindCratesNearby(Group, Unit, finddist, false, false) + if number == 0 then + self:_SendMessage("No crates found in range!", 10, false, Group) + return self + end + + local matchingCrates = {} + local needed = nil + for _, crateObj in pairs(cratesNearby) do + if crateObj:GetName() == cargoName then + needed = needed or crateObj:GetCratesNeeded() + table.insert(matchingCrates, crateObj) + end + end + if not needed then + self:_SendMessage(string.format("No \"%s\" crates found in range!", cargoName), 10, false, Group) + return self + end + + local found = #matchingCrates + + -- 4) Check capacity + local unitName = Unit:GetName() + local loadedData = self.Loaded_Cargo[unitName] or { Troopsloaded=0, Cratesloaded=0, Cargo={} } + local capabilities = self:_GetUnitCapabilities(Unit) + local capacity = capabilities.cratelimit or 0 + if loadedData.Cratesloaded >= capacity then + self:_SendMessage("No more capacity to load crates!", 10, false, Group) + return self + end + + -- decide how many we can actually load + local spaceLeft = capacity - loadedData.Cratesloaded + local toLoad = math.min(found, needed, spaceLeft) + if toLoad < 1 then + self:_SendMessage("Cannot load crates: either none found or no capacity left.", 10, false, Group) + return self + end + + -- 5) Load exactly `toLoad` crates + local crateIDsLoaded = {} + for i = 1, toLoad do + local crate = matchingCrates[i] + crate:SetHasMoved(true) + crate:SetWasDropped(false) + table.insert(loadedData.Cargo, crate) + loadedData.Cratesloaded = loadedData.Cratesloaded + 1 + local stObj = crate:GetPositionable() + if stObj and stObj:IsAlive() then + stObj:Destroy(false) + end + table.insert(crateIDsLoaded, crate:GetID()) + end + self.Loaded_Cargo[unitName] = loadedData + self:_UpdateUnitCargoMass(Unit) + + -- 6) Remove them from self.Spawned_Cargo + local newSpawned = {} + for _, cObj in ipairs(self.Spawned_Cargo) do + local keep = true + for i=1, toLoad do + if matchingCrates[i] and cObj:GetID() == matchingCrates[i]:GetID() then + keep = false + break + end + end + if keep then + table.insert(newSpawned, cObj) + end + end + self.Spawned_Cargo = newSpawned + + -- 7) Show final message, including a special note if capacity is now reached + local loadedHere = toLoad + if loadedHere < needed and loadedData.Cratesloaded >= capacity then + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!", loadedHere, needed, cargoName), 10, false, Group) + else + local fullSets = math.floor(loadedHere / needed) + local leftover = loadedHere % needed + if needed > 1 then + if fullSets > 0 and leftover == 0 then + self:_SendMessage(string.format("Loaded %d %s.", fullSets, cargoName), 10, false, Group) + elseif fullSets > 0 and leftover > 0 then + self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cargoName, leftover), 10, false, Group) + else + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cargoName), 15, false, Group) + end + else + self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cargoName), 10, false, Group) + end + end + + self:_RefreshLoadCratesMenu(Group, Unit) + self:_RefreshDropCratesMenu(Group, Unit) + return self +end + + --- (Internal) Function to unload a single crate -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. @@ -4467,7 +4645,7 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) -- Update cargo mass, refresh menu self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group, Unit) - + self:_RefreshLoadCratesMenu(Group, Unit) return self end @@ -4477,72 +4655,74 @@ end -- @param Wrapper.Unit#UNIT Unit The calling unit. -- @return #CTLD self function CTLD:_RefreshDropCratesMenu(Group, Unit) - local theGroup = Group - local theUnit = Unit - if not theGroup.CTLDTopmenu then return end - local topCrates = theGroup.MyTopCratesMenu + if not Group.CTLDTopmenu then return end + local topCrates = Group.MyTopCratesMenu if not topCrates then return end if topCrates.DropCratesMenu then - topCrates.DropCratesMenu:Remove() + topCrates.DropCratesMenu:RemoveSubMenus() + else + topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) end - local dropCratesMenu = MENU_GROUP:New(theGroup, "Drop Crates", topCrates) - topCrates.DropCratesMenu = dropCratesMenu - MENU_GROUP_COMMAND:New(theGroup, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, theGroup, theUnit) + local dropCratesMenu = topCrates.DropCratesMenu local loadedData = self.Loaded_Cargo[Unit:GetName()] - if not loadedData or not loadedData.Cargo then return end + if not loadedData or not loadedData.Cargo then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end - local cargoByName = {} - for _, cObj in ipairs(loadedData.Cargo) do + local cargoByName={} + local dropableCrates=0 + for _,cObj in ipairs(loadedData.Cargo) do if cObj and not cObj:WasDropped() then - local cType = cObj:GetType() - if cType ~= CTLD_CARGO.Enum.TROOPS and cType ~= CTLD_CARGO.Enum.ENGINEERS and cType ~= CTLD_CARGO.Enum.GCLOADABLE then - local name = cObj:GetName() or "Unknown" - cargoByName[name] = cargoByName[name] or {} - table.insert(cargoByName[name], cObj) + local cType=cObj:GetType() + if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then + local name=cObj:GetName()or"Unknown" + cargoByName[name]=cargoByName[name]or{} + table.insert(cargoByName[name],cObj) + dropableCrates=dropableCrates+1 end end end - self.CrateGroupList = self.CrateGroupList or {} - self.CrateGroupList[Unit:GetName()] = {} + if dropableCrates==0 then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end - -- A single global line index for ALL crate names - local lineIndex = 1 + MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) + self.CrateGroupList=self.CrateGroupList or{} + self.CrateGroupList[Unit:GetName()]={} - for cName, list in pairs(cargoByName) do - local needed = list[1]:GetCratesNeeded() or 1 - table.sort(list, function(a,b) return a:GetID() < b:GetID() end) - - local i = 1 - while i <= #list do - local left = (#list - i + 1) - if left >= needed then - local chunk = {} - for n = i, i + needed - 1 do - table.insert(chunk, list[n]) + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + local chunk={} + for n=i,i+needed-1 do + table.insert(chunk,list[n]) end - -- Now label uses the global lineIndex and increments after each chunk - local label = string.format("%d. %s", lineIndex, cName) - table.insert(self.CrateGroupList[Unit:GetName()], chunk) - local setIndex = #self.CrateGroupList[Unit:GetName()] - MENU_GROUP_COMMAND:New(theGroup, label, dropCratesMenu, self._UnloadSingleCrateSet, self, theGroup, theUnit, setIndex) - - i = i + needed + local label=string.format("%d. %s",lineIndex,cName) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=i+needed else - local chunk = {} - for n = i, #list do - table.insert(chunk, list[n]) + local chunk={} + for n=i,#list do + table.insert(chunk,list[n]) end - local label = string.format("%d. %s %d/%d", lineIndex, cName, left, needed) - table.insert(self.CrateGroupList[Unit:GetName()], chunk) - local setIndex = #self.CrateGroupList[Unit:GetName()] - MENU_GROUP_COMMAND:New(theGroup, label, dropCratesMenu, self._UnloadSingleCrateSet, self, theGroup, theUnit, setIndex) - - i = #list + 1 + local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=#list+1 end - - lineIndex = lineIndex + 1 + lineIndex=lineIndex+1 end end end @@ -6562,6 +6742,8 @@ end self:HandleEvent(EVENTS.DynamicCargoLoaded, self._EventHandler) self:HandleEvent(EVENTS.DynamicCargoUnloaded, self._EventHandler) self:HandleEvent(EVENTS.DynamicCargoRemoved, self._EventHandler) + self:HandleEvent(EVENTS.Land, self._EventHandler) + self:HandleEvent(EVENTS.Takeoff, self._EventHandler) self:__Status(-5) -- AutoSave From fd2d8a511912059d446aeae243aaba5ae7545502 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Sun, 16 Feb 2025 17:16:05 +0100 Subject: [PATCH 088/158] Update CTLD.lua Now in the listcargo, (cargo onboard) I will display CargoName and a number / number instead of listing each crate, 5 lines for 5 crates. Now 1 line for each CargoName and 2/2 or 3/3, etc. "Rescan" have been moved to only show up if it detects lesser then what it's needed when loading. --- Moose Development/Moose/Ops/CTLD.lua | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 876f440fc..9870a9d4a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3333,6 +3333,7 @@ function CTLD:_ListCargo(Group, Unit) local maxloadable = self:_GetMaxLoadableMass(Unit) local finddist = self.CrateDistance or 35 --local _,_,loadedgc,loadedno = self:_FindCratesNearby(Group,Unit,finddist,true) + if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 local no_crates = loadedcargo.Cratesloaded or 0 @@ -3346,7 +3347,7 @@ function CTLD:_ListCargo(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) + report:Add(string.format("Troop: %s size %d", cargo:GetName(), cargo:GetCratesNeeded())) end end if report:GetCount() == 4 then @@ -3355,18 +3356,25 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add(" -- CRATES --") local cratecount = 0 + local accumCrates = {} for _,_cargo in pairs(cargotable or {}) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add(string.format("Crate: %s size 1",cargo:GetName())) - cratecount = cratecount + 1 + local cName = cargo:GetName() + local needed = cargo:GetCratesNeeded() or 1 + accumCrates[cName] = accumCrates[cName] or {count=0, needed=needed} + accumCrates[cName].count = accumCrates[cName].count + 1 end if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then - report:Add(string.format("GC loaded Crate: %s size 1",cargo:GetName())) + report:Add(string.format("GC loaded Crate: %s size 1", cargo:GetName())) cratecount = cratecount + 1 end end + for cName, data in pairs(accumCrates) do + cratecount = cratecount + data.count + report:Add(string.format("Crate: %s %d/%d", cName, data.count, data.needed)) + end if cratecount == 0 then report:Add(" N O N E") end @@ -3387,13 +3395,12 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add("Total Mass: ".. loadedmass .. " kg. Loadable: "..maxloadable.." kg.") local text = report:Text() - self:_SendMessage(text, 30, true, Group) + self:_SendMessage(text, 30, true, Group) else - self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",trooplimit,cratelimit,maxloadable), 10, false, Group) + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs", trooplimit, cratelimit, maxloadable), 10, false, Group) end return self end - --- (Internal) Function to list loaded cargo. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -4391,7 +4398,6 @@ function CTLD:_RefreshLoadCratesMenu(Group, Unit) MENU_GROUP_COMMAND:New(Group, "No crates found! Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) return end - MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) MENU_GROUP_COMMAND:New(Group, "Load ALL", Group.MyLoadCratesMenu, self._LoadCratesNearby, self, Group, Unit) local cargoByName = {} for _, crate in pairs(nearby) do @@ -4408,6 +4414,7 @@ function CTLD:_RefreshLoadCratesMenu(Group, Unit) if found >= needed then line = string.format("Load %s", cName) else + MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) line = string.format("Load %s (%d/%d)", cName, found, needed) end MENU_GROUP_COMMAND:New(Group, line, Group.MyLoadCratesMenu, self._LoadSingleCrateSet, self, Group, Unit, cName) From a49bd23a2abe741b07b59ce1f1cc58fe8096e3fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 17 Feb 2025 08:35:25 +0100 Subject: [PATCH 089/158] #ATC_GROUND --- Moose Development/Moose/Functional/ATC_Ground.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 9456fa270..7d1669d51 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -457,7 +457,7 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) for _,_name in pairs(AirbaseList) do -- DONE exclude FARPS and Ships local airbase = _DATABASE:FindAirbase(_name) - if airbase and airbase.isAirdrome == true then + if airbase and (airbase.isAirdrome == true) then self.Airbases[_name]={} end end From f9ba96f228c13cd9dade916695516977bfbfe1a7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 18 Feb 2025 11:13:23 +0100 Subject: [PATCH 090/158] #MANTIS - New 4-Tier-Approach --- Moose Development/Moose/Functional/Mantis.lua | 167 ++++++++++++------ Moose Development/Moose/Functional/Shorad.lua | 4 +- 2 files changed, 115 insertions(+), 56 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 28f511a91..4ae7b68e3 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Jan 2025 +-- Last Update: Feb 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -62,6 +62,7 @@ -- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. -- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. -- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White +-- @field #number checkcounter Counter for SAM Table refreshes -- @extends Core.Base#BASE @@ -73,7 +74,7 @@ -- -- * Moose derived Modular, Automatic and Network capable Targeting and Interception System. -- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy. --- * **Automatic mode** (default since 0.8) can set-up your SAM site network automatically for you +-- * **Automatic mode** (default since 0.8) will set-up your SAM site network automatically for you -- * **Classic mode** behaves like before -- * Leverage evasiveness from SEAD, leverage attack range setting -- * Automatic setup of SHORAD based on groups of the class "short-range" @@ -88,6 +89,7 @@ -- * SAM sites, e.g. each **group name** begins with "Red SAM" -- * EWR network and AWACS, e.g. each **group name** begins with "Red EWR" and *not* e.g. "Red SAM EWR" (overlap with "Red SAM"), "Red EWR Awacs" will be found by "Red EWR" -- * SHORAD, e.g. each **group name** begins with "Red SHORAD" and *not" e.g. just "SHORAD" because you might also have "Blue SHORAD" +-- * Point Defense, e.g. each **group name** begins with "Red AAA" and *not" e.g. just "AAA" because you might also have "Blue AAA" -- -- It's important to get this right because of the nature of the filter-system in @{Core.Set#SET_GROUP}. Filters are "greedy", that is they -- will match *any* string that contains the search string - hence we need to avoid that SAMs, EWR and SHORAD step on each other\'s toes. @@ -194,26 +196,24 @@ -- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones) -- -- --- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target: +-- ### 2.1.2 Change the number of long-, mid- and short-range, point defense systems going live on a detected target: -- --- -- parameters are numbers. Defaults are 1,2,2,6 respectively --- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic) +-- -- parameters are numbers. Defaults are 1,2,2,6,6 respectively +-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) -- --- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range" +-- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "short-range" if the range is less than 5km or if the type is AAA. -- -- ### 2.1.4 Advanced features -- --- -- switch off auto mode **before** you start MANTIS. +-- -- Option to switch off auto mode **before** you start MANTIS (not recommended) -- mybluemantis.automode = false -- --- -- switch off auto shorad **before** you start MANTIS. --- mybluemantis.autoshorad = false --- --- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. +-- -- Option to set the scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. -- -- also see engagerange below. -- self.radiusscale[MANTIS.SamType.LONG] = 1.1 -- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 -- self.radiusscale[MANTIS.SamType.SHORT] = 1.3 +-- self.radiusscale[MANTIS.SamType.POINT] = 1.4 -- -- ### 2.1.5 Friendlies check in firing range -- @@ -242,9 +242,9 @@ -- -- Use this option if you want to make use of or allow advanced SEAD tactics. -- --- # 5. Integrate SHORAD [classic mode, not necessary in automode] +-- # 5. Integrate SHORAD [classic mode, not necessary in automode, not recommended for manual setup] -- --- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in +-- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs manually. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in -- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so: -- -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() @@ -298,6 +298,7 @@ MANTIS = { SAM_Table_Long = {}, SAM_Table_Medium = {}, SAM_Table_Short = {}, + SAM_Table_PointDef = {}, lid = "", Detection = nil, AWACS_Detection = nil, @@ -333,6 +334,7 @@ MANTIS = { checkforfriendlies = false, SmokeDecoy = false, SmokeDecoyColor = SMOKECOLOR.White, + checkcounter = 1, } --- Advanced state enumerator @@ -349,6 +351,7 @@ MANTIS.SamType = { SHORT = "Short", MEDIUM = "Medium", LONG = "Long", + POINT = "Point", } --- SAM data @@ -358,6 +361,7 @@ MANTIS.SamType = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamData = { ["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B @@ -369,16 +373,16 @@ MANTIS.SamData = { ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" }, ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"}, ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, - ["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, + ["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Point", Radar="Roland" }, ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, - ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, + ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" }, ["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" }, - ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Short", Radar="Tor 9A331" }, - ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, + ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true" }, + ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" }, ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, - ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Linebacker" }, + ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, @@ -386,7 +390,7 @@ MANTIS.SamData = { ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, - ["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="Igla" }, + ["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="Igla", Point="true" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, } @@ -398,6 +402,7 @@ MANTIS.SamData = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataHDS = { -- units from HDS Mod, multi launcher options is tricky -- group name MUST contain HDS to ID launcher type correctly! @@ -419,6 +424,7 @@ MANTIS.SamDataHDS = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataSMA = { -- units from SMA Mod (Sweedish Military Assets) -- https://forum.dcs.world/topic/295202-swedish-military-assets-for-dcs-by-currenthill/ @@ -432,7 +438,7 @@ MANTIS.SamDataSMA = { ["RBS103B SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" }, ["RBS103AM SMA"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, ["RBS103BM SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" }, - ["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" }, + ["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Point", Radar="LvKv9040",Point="true" }, } --- SAM data CH @@ -442,6 +448,7 @@ MANTIS.SamDataSMA = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataCH = { -- units from CH (Military Assets by Currenthill) -- https://www.currenthill.com/ @@ -458,20 +465,20 @@ MANTIS.SamDataCH = { ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, - ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_Centurion_C_RAM" }, - ["PGZ-09 CHM"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" }, + ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, + ["PGZ-09 CHM"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" }, ["S350-9M100 CHM"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, ["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" }, ["LAV-AD CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_LAVAD" }, ["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" }, - ["PGZ-95 CHM"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_PGZ95" }, - ["LD-3000 CHM"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="CH_LD3000_stationary" }, - ["LD-3000M CHM"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="CH_LD3000" }, + ["PGZ-95 CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" }, + ["LD-3000 CHM"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="CH_LD3000_stationary", Point="true" }, + ["LD-3000M CHM"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="CH_LD3000", Point="true" }, ["FlaRakRad CHM"] = { Range=8, Blindspot=1.5, Height=6, Type="Short", Radar="HQ17A" }, ["IRIS-T SLM CHM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, ["M903PAC2KAT1 CHM"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="CH_MIM104_M903_PAC2_KAT1" }, - ["Skynex CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Short", Radar="CH_SkynexHX" }, - ["Skyshield CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Short", Radar="CH_Skyshield_Gun" }, + ["Skynex CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" }, + ["Skyshield CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" }, ["WieselOzelot CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" }, ["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" }, ["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" }, @@ -486,7 +493,7 @@ MANTIS.SamDataCH = { ["RBS103B CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" }, ["RBS103AM CHM"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, ["RBS103BM CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" }, - ["Lvkv9040M CHM"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" }, + ["Lvkv9040M CHM"] = { Range=4, Blindspot=0, Height=2.5, Type="Point", Radar="LvKv9040", Point="true" }, } ----------------------------------------------------------------------- @@ -547,6 +554,7 @@ do self.SAM_Table_Long = {} self.SAM_Table_Medium = {} self.SAM_Table_Short = {} + self.SAM_Table_PointDef = {} self.dynamic = dynamic or false self.checkradius = 25000 self.grouping = 5000 @@ -579,6 +587,7 @@ do self.radiusscale[MANTIS.SamType.LONG] = 1.1 self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 self.radiusscale[MANTIS.SamType.SHORT] = 1.3 + self.radiusscale[MANTIS.SamType.POINT] = 1.4 --self.SAMCheckRanges = {} self.usezones = false self.AcceptZones = {} @@ -587,6 +596,7 @@ do self.maxlongrange = 1 self.maxmidrange = 2 self.maxshortrange = 2 + self.maxpointdefrange =6 self.maxclassic = 6 self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP @@ -669,9 +679,12 @@ do self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) end + -- counter for SAM table updates + self.checkcounter = 1 + -- TODO Version -- @field #string version - self.version="0.8.23" + self.version="0.9.24" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -923,13 +936,15 @@ do -- @param #number Mid Number of mid-range systems activated, defaults to 2. -- @param #number Long Number of long-range systems activated, defaults to 2. -- @param #number Classic (non-automode) Number of overall systems activated, defaults to 6. + -- @param #number Point Number of point defense and AAA systems activated, defaults to 6. -- @return #MANTIS self - function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic) + function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) self:T(self.lid .. "SetMaxActiveSAMs") self.maxclassic = Classic or 6 self.maxlongrange = Long or 1 self.maxmidrange = Mid or 2 self.maxshortrange = Short or 2 + self.maxpointdefrange= Point or 6 return self end @@ -1477,6 +1492,17 @@ do end if found then break end end + --- AAA or Point Defense + if not found then + local grp = GROUP:FindByName(grpname) + if (grp and grp:IsAlive() and grp:IsAAA()) or string.find(grpname,"AAA",1,true) then + range = 2000 + height = 2000 + blind = 50 + type = MANTIS.SamType.POINT + found = true + end + end if not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end @@ -1510,7 +1536,7 @@ do end --if self.automode then for idx,entry in pairs(self.SamData) do - self:T("ID = " .. idx) + self:T2("ID = " .. idx) if string.find(grpname,idx,1,true) then local _entry = entry -- #MANTIS.SamData type = _entry.Type @@ -1524,14 +1550,25 @@ do end end --end - -- secondary filter if not found + --- Secondary - AAA or Point Defense + if not found then + local grp = GROUP:FindByName(grpname) + if (grp and grp:IsAlive() and grp:IsAAA()) or string.find(grpname,"AAA",1,true) then + range = 2000 + height = 2000 + blind = 50 + type = MANTIS.SamType.POINT + found = true + end + end + --- Tertiary filter if not found if (not found) or HDSmod or SMAMod or CHMod then range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod) elseif not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end - if string.find(grpname,"SHORAD",1,true) then - type = MANTIS.SamType.SHORT -- force short on match + if found and string.find(grpname,"SHORAD",1,true) then + type = MANTIS.SamType.POINT -- force short on match end return range, height, type, blind end @@ -1550,6 +1587,7 @@ do local SAM_Tbl_lg = {} -- table of long range SAM defense zones local SAM_Tbl_md = {} -- table of mid range SAM defense zones local SAM_Tbl_sh = {} -- table of short range SAM defense zones + local SAM_Tbl_pt = {} -- table of point defense/AAA local SEAD_Grps = {} -- table of SAM names to make evasive local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc @@ -1573,18 +1611,22 @@ do if type == MANTIS.SamType.LONG then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) - --self:T("SAM "..grpname.." is type LONG") + self:T("SAM "..grpname.." is type LONG") elseif type == MANTIS.SamType.MEDIUM then table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) - --self:T("SAM "..grpname.." is type MEDIUM") + self:T("SAM "..grpname.." is type MEDIUM") elseif type == MANTIS.SamType.SHORT then table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) - --self:T("SAM "..grpname.." is type SHORT") + table.insert( SEAD_Grps, grpname ) + self:T("SAM "..grpname.." is type SHORT") + elseif type == MANTIS.SamType.POINT then + table.insert( SAM_Tbl_pt, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T("SAM "..grpname.." is type POINT") self.ShoradGroupSet:Add(grpname,group) if not self.autoshorad then table.insert( SEAD_Grps, grpname ) - end + end end self.SamStateTracker[grpname] = "GREEN" end @@ -1593,6 +1635,7 @@ do self.SAM_Table_Long = SAM_Tbl_lg self.SAM_Table_Medium = SAM_Tbl_md self.SAM_Table_Short = SAM_Tbl_sh + self.SAM_Table_PointDef = SAM_Tbl_pt -- make SAMs evasive local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD mysead:SetEngagementRange(engagerange) @@ -1616,7 +1659,8 @@ do local SAM_Tbl = {} -- table of SAM defense zones local SAM_Tbl_lg = {} -- table of long range SAM defense zones local SAM_Tbl_md = {} -- table of mid range SAM defense zones - local SAM_Tbl_sh = {} -- table of short range SAM defense zon + local SAM_Tbl_sh = {} -- table of short range SAM defense zones + local SAM_Tbl_pt = {} -- table of point defense/AAA local SEAD_Grps = {} -- table of SAM names to make evasive local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc @@ -1627,17 +1671,21 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) + local radaralive = group:IsSAM() table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) - if type == MANTIS.SamType.LONG then + if type == MANTIS.SamType.LONG and radaralive then table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) - --self:I({grpname,grprange, grpheight}) - elseif type == MANTIS.SamType.MEDIUM then + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.MEDIUM and radaralive then table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) - --self:I({grpname,grprange, grpheight}) - elseif type == MANTIS.SamType.SHORT then - table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) - --self:I({grpname,grprange, grpheight}) + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.SHORT and radaralive then + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.POINT or (not radaralive) then + table.insert( SAM_Tbl_pt, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T({grpname,grprange, grpheight}) self.ShoradGroupSet:Add(grpname,group) if self.autoshorad then self.Shorad.Groupset = self.ShoradGroupSet @@ -1649,6 +1697,7 @@ do self.SAM_Table_Long = SAM_Tbl_lg self.SAM_Table_Medium = SAM_Tbl_md self.SAM_Table_Short = SAM_Tbl_sh + self.SAM_Table_PointDef = SAM_Tbl_pt -- make SAMs evasive if self.mysead ~= nil then local mysead = self.mysead @@ -1692,7 +1741,9 @@ do -- @param #table detset Table of COORDINATES -- @param #boolean dlink Using DLINK -- @param #number limit of SAM sites to go active on a contact - -- @return #MANTIS self + -- @return #number instatusred + -- @return #number instatusgreen + -- @return #number activeshorads function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates") local switchedon = 0 @@ -1707,6 +1758,9 @@ do local height = _data[4] local blind = _data[5] * 1.25 + 1 local shortsam = _data[6] == MANTIS.SamType.SHORT and true or false + if not shortsam then + shortsam = _data[6] == MANTIS.SamType.POINT and true or false + end local samgroup = GROUP:FindByName(name) local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) local suppressed = self.SuppressedGroups[name] or false @@ -1732,8 +1786,9 @@ do self:__RedState(1,samgroup) self.SamStateTracker[name] = "RED" end + -- TODO doesn't work if shortsam == true and self.SmokeDecoy == true then - self:I("Smoking") + self:T("Smoking") local units = samgroup:GetUnits() or {} local smoke = self.SmokeDecoyColor or SMOKECOLOR.White for _,unit in pairs(units) do @@ -1778,7 +1833,7 @@ do end --end alive end --end check end --for loop - if self.debug then + if self.debug or self.verbose then for _,_status in pairs(self.SamStateTracker) do if _status == "GREEN" then instatusgreen=instatusgreen+1 @@ -1805,22 +1860,24 @@ do --get detected set local detset = detection:GetDetectedItemCoordinates() --self:T("Check:", {detset}) - -- randomly update SAM Table - local rand = math.random(1,100) - if rand > 65 then -- 1/3 of cases + -- update SAM Table evey 3 runs + if self.checkcounter%3 == 0 then self:_RefreshSAMTable() end + self.checkcounter = self.checkcounter + 1 local instatusred = 0 local instatusgreen = 0 local activeshorads = 0 -- switch SAMs on/off if (n)one of the detected groups is inside their reach if self.automode then local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxlongrange) + local instatusredl, instatusgreenl, activeshoradsl = self:_CheckLoop(samset,detset,dlink,self.maxlongrange) local samset = self.SAM_Table_Medium -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxmidrange) + local instatusredm, instatusgreenm, activeshoradsm = self:_CheckLoop(samset,detset,dlink,self.maxmidrange) local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxshortrange) + local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange) + local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height + instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange) else local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index dd0fd7637..68ffe9c56 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -443,7 +443,9 @@ do for _,_groups in pairs (shoradset) do local groupname = _groups:GetName() if string.find(groupname, tgtgrp, 1, true) then - returnname = true + if _groups:IsSAM() then + returnname = true + end end end return returnname From a520daeb5623c8ccef1532a66aae1d43153b4409 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:23:58 +0100 Subject: [PATCH 091/158] Update Range.lua attempt to index local 'target' (a nil value) Was using this build in another mission. Added a nilcheck. env.info('*** MOOSE GITHUB Commit Hash ID: 2025-02-14T06:13:08+01:00-24b320077721d45774acd56b25086ef6bdfb2e5a ***') --- Moose Development/Moose/Functional/Range.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 4d07eeffa..62b85e828 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -1933,7 +1933,7 @@ function RANGE:OnEventHit( EventData ) local _currentTarget = self.strafeStatus[_unitID] --#RANGE.StrafeStatus -- Player has rolled in on a strafing target. - if _currentTarget and target:IsAlive() then + if _currentTarget and target and target:IsAlive() then local playerPos = _unit:GetCoordinate() local targetPos = target:GetCoordinate() From 43eeaede656d4cafe07cef6c81371ebad9c0ea99 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 19 Feb 2025 17:32:25 +0100 Subject: [PATCH 092/158] #SET - Error output for `Add`if the ObjectName variable is empty #UNIT - Return zero is SpeedMax is nil --- Moose Development/Moose/Core/Set.lua | 9 ++++++++- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 5cd4d8681..2a9fb1e67 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -289,7 +289,14 @@ do -- SET_BASE -- Debug info. --self:T2( { ObjectName = ObjectName, Object = Object } ) - + + -- Error ahndling + if not ObjectName or ObjectName == "" then + self:E("SET_BASE:Add - Invalid ObjectName handed") + self:E({ObjectName=ObjectName, Object=Object}) + return self + end + -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then self:Remove( ObjectName, true ) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 38b2fe74b..10d59ed81 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -607,7 +607,7 @@ function UNIT:GetSpeedMax() local Desc = self:GetDesc() if Desc then - local SpeedMax = Desc.speedMax + local SpeedMax = Desc.speedMax or 0 return SpeedMax * 3.6 end From 8ef781a9ac6fd1f6ef2bdfe9a716924d3a295ca6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 21 Feb 2025 10:39:21 +0100 Subject: [PATCH 093/158] #SET_BASE - added `GetThreatLevelMax()` --- Moose Development/Moose/Core/Set.lua | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 2a9fb1e67..56ec9ecab 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -531,6 +531,21 @@ do -- SET_BASE return self.SomeIteratorLimit or self:Count() end + + --- Get max threat level of all objects in the SET. + -- @param #SET_BASE self + -- @return #number Max threat level found. + function SET_BASE:GetThreatLevelMax() + local ThreatMax = 0 + for _,_unit in pairs(self.Set or {}) do + local unit = _unit -- Wrapper.Unit#UNIT + local threat = unit.GetThreatLevel and unit:GetThreatLevel() or 0 + if threat > ThreatMax then + ThreatMax = threat + end + end + return ThreatMax + end --- Filters for the defined collection. -- @param #SET_BASE self @@ -7985,7 +8000,7 @@ function SET_OPSGROUP:_EventOnBirth(Event) function SET_OPSGROUP:_EventOnDeadOrCrash( Event ) --self:F( { Event } ) - if Event.IniDCSUnit then + if Event.IniDCSGroup then local ObjectName, Object = self:FindInDatabase( Event ) if ObjectName then if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed. From b9ad5b5ba764facfd08788a8e1ca92a3bfecef63 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 21 Feb 2025 10:42:43 +0100 Subject: [PATCH 094/158] #TARGET - add threat level determination for ZONE and OPSZONE --- Moose Development/Moose/Ops/OpsZone.lua | 2 ++ Moose Development/Moose/Ops/Target.lua | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 12c4b4608..bbcf4f97b 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -723,6 +723,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @return #OPSZONE self function OPSZONE:onafterStart(From, Event, To) -- Info. @@ -739,6 +740,7 @@ function OPSZONE:onafterStart(From, Event, To) self:HandleEvent(EVENTS.BaseCaptured) end + return self end --- Stop OPSZONE FSM. diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index e0b750679..fcc108087 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -387,6 +387,8 @@ function TARGET:AddObject(Object) if Object:IsInstanceOf("OPSGROUP") then self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object. + --elseif Object:IsInstanceOf("OPSZONE") then + --self:_AddObject(Object:GetZone()) else self:_AddObject(Object) end @@ -1296,11 +1298,27 @@ function TARGET:GetTargetThreatLevelMax(Target) return 0 elseif Target.Type==TARGET.ObjectType.ZONE then + + local zone = Target.Object -- Core.Zone#ZONE_RADIUS + local foundunits = {} + if zone:IsInstanceOf("ZONE_RADIUS") or zone:IsInstanceOf("ZONE_POLYGON") then + zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}) + foundunits = zone:GetScannedSetUnit() + else + foundunits = SET_UNIT:New():FilterZones({zone}):FilterOnce() + end + local ThreatMax = foundunits:GetThreatLevelMax() or 0 + return ThreatMax - return 0 + elseif Target.Type==TARGET.ObjectType.OPSZONE then + + local unitset = Target.Object:GetScannedUnitSet() -- Core.Set#SET_UNIT + local ThreatMax = unitset:GetThreatLevelMax() + return ThreatMax else self:E("ERROR: unknown target object type in GetTargetThreatLevel!") + return 0 end return self From 7552309a28231b3be8967b29e9037ff934466a6c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 21 Feb 2025 10:43:14 +0100 Subject: [PATCH 095/158] #AUTOLASE - option to make ground units aware of target to be lased --- .../Moose/Functional/Autolase.lua | 88 ++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 1969fcbf9..88cb6c9e6 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -91,6 +91,7 @@ -- @field #boolean threatmenu -- @field #number RoundingPrecision -- @field #table smokeoffset +-- @field #boolean increasegroundawareness -- @extends Ops.Intel#INTEL --- @@ -103,6 +104,7 @@ AUTOLASE = { debug = false, smokemenu = true, RoundingPrecision = 0, + increasegroundawareness = true, } --- Laser spot info @@ -121,7 +123,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.28" +AUTOLASE.version = "0.1.29" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -212,6 +214,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.smokemenu = true self.threatmenu = true self.RoundingPrecision = 0 + self.increasegroundawareness = true self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) @@ -326,6 +329,22 @@ function AUTOLASE:SetLaserCodes( LaserCodes ) return self end +--- [User] Improve ground unit detection by using a zone scan and LOS check. +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:EnableImproveGroundUnitsDetection() + self.increasegroundawareness = true + return self +end + +--- [User] Do not improve ground unit detection by using a zone scan and LOS check. +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:DisableImproveGroundUnitsDetection() + self.increasegroundawareness = false + return self +end + --- (Internal) Function to set pilot menu. -- @param #AUTOLASE self -- @return #AUTOLASE self @@ -694,7 +713,8 @@ function AUTOLASE:CleanCurrentLasing() local unit = recce:GetUnit(1) local name = unit:GetName() if not self.RecceUnits[name] then - self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime() } + local isground = (unit and unit.IsGround) and unit:IsGround() or false + self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground } end end end @@ -944,6 +964,65 @@ function AUTOLASE:CanLase(Recce,Unit) return canlase end +--- (Internal) Function to do a zone check per ground Recce and make found units and statics "known". +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:_Prescient() + -- self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground } + for _,_data in pairs(self.RecceUnits) do + -- ground units only + if _data.isground and _data.unit and _data.unit:IsAlive() then + local unit = _data.unit -- Wrapper.Unit#UNIT + local position = unit:GetCoordinate() -- Core.Point#COORDINATE + local needsinit = false + if position then + local lastposition = unit:GetProperty("lastposition") + -- property initiated? + if not lastposition then + unit:SetProperty("lastposition",position) + lastposition = position + needsinit = true + end + -- has moved? + local dist = position:Get2DDistance(lastposition) + -- refresh? + local TNow = timer.getAbsTime() + -- check + if dist > 10 or needsinit==true or TNow - _data.timestamp > 29 then + -- init scan objects + local hasunits,hasstatics,_,Units,Statics = position:ScanObjects(self.LaseDistance,true,true,false) + -- loop found units + if hasunits then + self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName()) + for _,_target in pairs(Units) do -- Wrapper.Unit#UNIT object here + local target = _target -- Wrapper.Unit#UNIT + if target and target:GetCoalition() ~= self.coalition then + if unit:IsLOS(target) and (not target:IsUnitDetected(unit))then + unit:KnowUnit(target,true,true) + end + end + end + end + -- loop found statics + if hasstatics then + self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) + for _,_static in pairs(Statics) do -- DCS static object here + local static = STATIC:Find(_static) + if static and static:GetCoalition() ~= self.coalition then + local IsLOS = position:IsLOS(static:GetCoordinate()) + if IsLOS then + unit:KnowUnit(static,true,true) + end + end + end + end + end + end + end + end + return self +end + ------------------------------------------------------------------- -- FSM Functions ------------------------------------------------------------------- @@ -956,6 +1035,9 @@ end -- @return #AUTOLASE self function AUTOLASE:onbeforeMonitor(From, Event, To) self:T({From, Event, To}) + if self.increasegroundawareness then + self:_Prescient() + end -- Check if group has detected any units. self:UpdateIntel() return self @@ -984,7 +1066,7 @@ function AUTOLASE:onafterMonitor(From, Event, To) local grp = contact.group local coord = contact.position local reccename = contact.recce or "none" - local threat = contact.threatlevel or 0 + local threat = contact.threatlevel or 0 local reccegrp = UNIT:FindByName(reccename) if reccegrp then local reccecoord = reccegrp:GetCoordinate() From 12b596a47f92e77ce1e11fec3cc4c4f8b55fe398 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Feb 2025 16:36:15 +0100 Subject: [PATCH 096/158] #MANTIS - if no EWR system is left over, MANTIS will switch on a random SR/TR each cycle to detect incoming enemies. --- Moose Development/Moose/Functional/Mantis.lua | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 4ae7b68e3..fd6dea106 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -148,6 +148,7 @@ -- **Location** is of highest importance here. Whilst AWACS in DCS has almost the "all seeing eye", EWR don't have that. Choose your location wisely, against a mountain backdrop or inside a valley even the best EWR system -- doesn't work well. Prefer higher-up locations with a good view; use F7 in-game to check where you actually placed your EWR and have a look around. Apart from the obvious choice, do also consider other radar units -- for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do. +-- **HINT** Set at least one EWR on invisible and immortal so MANTIS doesn't stop working. -- -- ## 1.2 SAM sites -- @@ -201,7 +202,7 @@ -- -- parameters are numbers. Defaults are 1,2,2,6,6 respectively -- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) -- --- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "short-range" if the range is less than 5km or if the type is AAA. +-- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "point" or if the range is less than 5km or if the type is AAA. -- -- ### 2.1.4 Advanced features -- @@ -684,7 +685,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.24" + self.version="0.9.25" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -1146,6 +1147,24 @@ do end return self end + + --- [Internal] Check if any EWR or AWACS is still alive + -- @param #MANTIS self + -- @return #boolean outcome + function MANTIS:_CheckAnyEWRAlive() + self:T(self.lid .. "_CheckAnyEWRAlive") + local alive = false + if self.EWR_Group:CountAlive() > 0 then + alive = true + end + if not alive and self.AWACS_Prefix then + local awacs = GROUP:FindByName(self.AWACS_Prefix) + if awacs and awacs:IsAlive() then + alive = true + end + end + return alive + end --- [Internal] Function to determine state of the advanced mode -- @param #MANTIS self @@ -1757,9 +1776,9 @@ do local radius = _data[3] local height = _data[4] local blind = _data[5] * 1.25 + 1 - local shortsam = _data[6] == MANTIS.SamType.SHORT and true or false + local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false if not shortsam then - shortsam = _data[6] == MANTIS.SamType.POINT and true or false + shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false end local samgroup = GROUP:FindByName(name) local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) @@ -2001,12 +2020,34 @@ do if not self.state2flag then self:_Check(self.Detection,self.DLink) end - - --[[ check Awacs - if self.advAwacs and not self.state2flag then - self:_Check(self.AWACS_Detection,false) + + local EWRAlive = self:_CheckAnyEWRAlive() + + local function FindSAMSRTR() + for i=1,1000 do + local randomsam = self.SAM_Group:GetRandom() + if randomsam and randomsam:IsAlive() then + if randomsam:IsSAM() then return randomsam end + end + end + end + + -- Switch on a random SR/TR if no EWR left over + if not EWRAlive then + local randomsam = FindSAMSRTR() -- Wrapper.Group#GROUP + if randomsam and randomsam:IsAlive() then + if self.UseEmOnOff then + randomsam:EnableEmission(true) + else + randomsam:OptionAlarmStateRed() + end + local name = randomsam:GetName() + if self.SamStateTracker[name] ~= "RED" then + self:__RedState(1,randomsam) + self.SamStateTracker[name] = "RED" + end + end end - --]] -- relocate HQ and EWR if self.autorelocate then @@ -2016,8 +2057,6 @@ do local halfintv = math.floor(timepassed / relointerval) - --self:T({timepassed=timepassed, halfintv=halfintv}) - if halfintv >= 1 then self.TimeStamp = timer.getAbsTime() self:_Relocate() From 2fc7a3b542b04ec115510f27463488ad554ce16d Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Sun, 23 Feb 2025 02:34:04 +0100 Subject: [PATCH 097/158] Update Controllable.lua Fixing this error Line 26056: attempt to index local '_coord' (a nil value) in this line, local _tocoord=_coord:GetRandomCoordinateInRadius(_radius,100) --- Moose Development/Moose/Wrapper/Controllable.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 362f6eb03..5dbd66ffd 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -4263,6 +4263,9 @@ function CONTROLLABLE:RelocateGroundRandomInRadius( speed, radius, onroad, short self:F2( { self.ControllableName } ) local _coord = self:GetCoordinate() + if not _coord then + return self + end local _radius = radius or 500 local _speed = speed or 20 local _tocoord = _coord:GetRandomCoordinateInRadius( _radius, 100 ) From 1c0a8d9380e65344a45005e1f887cabe100f46ee Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 23 Feb 2025 16:10:07 +0100 Subject: [PATCH 098/158] #SET_CLIENT - added option to handle CA slots #CONTROLLABLE - fix typos --- Moose Development/Moose/Core/Set.lua | 10 ++++++++++ Moose Development/Moose/Wrapper/Controllable.lua | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 56ec9ecab..f2b6e6d3c 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -4611,6 +4611,16 @@ do -- SET_CLIENT end return self end + + --- Make the SET handle CA slots **only** (GROUND units used by any player). Needs active filtering with `FilterStart()` + -- @param #SET_CLIENT self + -- @return #SET_CLIENT self + function SET_CLIENT:HandleCASlots() + self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) + self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) + self:FilterFunction(function(client) if client and client:IsAlive() and client:IsGround() then return true else return false end end) + return self + end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 5dbd66ffd..dd95b441e 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -973,7 +973,7 @@ end -- @param #number Frequency Radio frequency in MHz. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. -- @param #number Power (Optional) Power of the Radio in Watts. Defaults to 10. --- @param #UnitID UnitID (Optional, if your object is a UNIT) The UNIT ID this is for. +-- @param #number UnitID (Optional, if your object is a UNIT) The UNIT ID this is for. -- @param #number Delay (Optional) Delay in seconds before the frequency is set. Default is immediately. -- @return #CONTROLLABLE self function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay) @@ -4315,7 +4315,7 @@ function CONTROLLABLE:OptionDisperseOnAttack( Seconds ) end --- Returns if the unit is a submarine. --- @param #POSITIONABLE self +-- @param #CONTROLLABLE self -- @return #boolean Submarines attributes result. function CONTROLLABLE:IsSubmarine() self:F2() From d4e141f3c5f08a8a7ff629bb500c0bd312571cb4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 23 Feb 2025 16:11:36 +0100 Subject: [PATCH 099/158] #EASYGCICAP - use onbefore on foreign classes rather than OnAfter to avoid user overwrites --- Moose Development/Moose/Ops/EasyGCICAP.lua | 37 ++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index dc402157d..c5f0ebb0b 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -252,7 +252,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.16" +EASYGCICAP.version="0.1.17" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -348,9 +348,23 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch) return self end ---- Set Maximum of alive missions to stop airplanes spamming the map +--- Count alive missions in our internal stack. -- @param #EASYGCICAP self --- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Cap-Missions + Intercept-Missions + Alert5-Missionsm default is 6 +-- @return #number count +function EASYGCICAP:_CountAliveAuftrags() + local alive = 0 + for _,_auftrag in pairs(self.ListOfAuftrag) do + local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG + if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then + alive = alive + 1 + end + end + return alive +end + +--- Set Maximum of alive missions created by this instance to stop airplanes spamming the map +-- @param #EASYGCICAP self +-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Intercept-Missions + Alert5-Missions, default is 8 -- @return #EASYGCICAP self function EASYGCICAP:SetMaxAliveMissions(Maxiumum) self:T(self.lid.."SetMaxAliveMissions") @@ -585,7 +599,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local TankerInvisible = self.TankerInvisible - function CAP_Wing:OnAfterFlightOnMission(From, Event, To, Flightgroup, Mission) + function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission) local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP if DespawnAfterLanding then flightgroup:SetDespawnAfterLanding() @@ -615,7 +629,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) flightgroup:SetFuelLowRTB(true) Intel:AddAgent(flightgroup) if DespawnAfterHolding then - function flightgroup:OnAfterHolding(From,Event,To) + function flightgroup:onbeforeHolding(From,Event,To) self:Despawn(1,true) end end @@ -1177,7 +1191,7 @@ function EASYGCICAP:_AssignIntercept(Cluster) local wings = self.wings local ctlpts = self.ManagedCP - local MaxAliveMissions = self.MaxAliveMissions * self.capgrouping + local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping local nogozoneset = self.NoGoZoneSet local ReadyFlightGroups = self.ReadyFlightGroups @@ -1242,9 +1256,10 @@ function EASYGCICAP:_AssignIntercept(Cluster) -- Do we have a matching airwing? if targetairwing then local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort) + local missioncount = self:_CountAliveAuftrags() -- Enough airframes on mission already? self:T(self.lid.." Assets on Mission "..AssetCount) - if AssetCount <= MaxAliveMissions then + if missioncount < MaxAliveMissions then local repeats = repeatsonfailure local InterceptAuftrag = AUFTRAG:NewINTERCEPT(contact.group) :SetMissionRange(150) @@ -1312,7 +1327,7 @@ function EASYGCICAP:_StartIntel() self:_AssignIntercept(Cluster) end - function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster) + function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) AssignCluster(Cluster) end @@ -1429,12 +1444,14 @@ function EASYGCICAP:onafterStatus(From,Event,To) local text = "GCICAP "..self.alias text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock text = text.."\nThreats: "..threatcount - text = text.."\nMissions: "..capmission+interceptmission + text = text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission text = text.."\n - CAP: "..capmission - text = text.."\n - Intercept: "..interceptmission text = text.."\n - AWACS: "..awacsmission text = text.."\n - TANKER: "..tankermission text = text.."\n - Recon: "..reconmission + text = text.."\nSelf managed Missions:" + text = text.."\n - Mission Limit: "..self.MaxAliveMissions + text = text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags() MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug) end self:__Status(30) From 41b867a4ca53a4ef1e285c63fb7ac11333e60372 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 2 Mar 2025 12:40:00 +0100 Subject: [PATCH 100/158] #GROUP - ensure GROUP:GetCoordinate() returns a group heading data field --- Moose Development/Moose/Wrapper/Group.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 17f23ed3c..75ece35ee 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1227,11 +1227,14 @@ function GROUP:GetCoordinate() -- First try to get the 3D vector of the group. This uses local vec3=self:GetVec3() + local coord if vec3 then - local coord=COORDINATE:NewFromVec3(vec3) + coord=COORDINATE:NewFromVec3(vec3) + coord.Heading = self:GetHeading() or 0 return coord end + -- No luck try units and add Heading data local Units = self:GetUnits() or {} for _,_unit in pairs(Units) do @@ -1242,15 +1245,15 @@ function GROUP:GetCoordinate() local FirstUnitCoordinate = FirstUnit:GetCoordinate() if FirstUnitCoordinate then - local Heading = self:GetHeading() + local Heading = self:GetHeading() or 0 FirstUnitCoordinate.Heading = Heading return FirstUnitCoordinate end end end - -- no luck, try the API way + -- no luck, try the API way local DCSGroup = Group.getByName(self.GroupName) if DCSGroup then local DCSUnits = DCSGroup:getUnits() or {} @@ -1261,14 +1264,19 @@ function GROUP:GetCoordinate() if point then --self:I(point) local coord = COORDINATE:NewFromVec3(point) + coord.Heading = 0 + local munit = UNIT:Find(_unit) + if munit then + coord.Heading = munit:GetHeading() or 0 + end return coord end end end end - + BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) - + end From 0b8810f8b38ab61795bc3b15a6146ec4df713958 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 3 Mar 2025 14:16:47 +0100 Subject: [PATCH 101/158] Update ATIS.lua ATIS - Fix for Kola map when no sunset or sunrise arise and SRS is not used --- Moose Development/Moose/Ops/ATIS.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 32d43a1ca..548280fad 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -2049,12 +2049,14 @@ function ATIS:onafterBroadcast( From, Event, To ) local sunrise = coord:GetSunrise() --self:I(sunrise) local SUNRISE = "no time" + local NorthPolar = true if tostring(sunrise) ~= "N/S" and tostring(sunrise) ~= "N/R" then sunrise = UTILS.Split( sunrise, ":" ) SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] ) if self.useSRS then SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours ) end + NorthPolar = false end local sunset = coord:GetSunset() @@ -2066,6 +2068,7 @@ function ATIS:onafterBroadcast( From, Event, To ) if self.useSRS then SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours ) end + NorthPolar = false end --------------------------------- @@ -2405,7 +2408,7 @@ function ATIS:onafterBroadcast( From, Event, To ) local sunrise = self.gettext:GetEntry("SUNRISEAT",self.locale) --subtitle = string.format( "Sunrise at %s local time", SUNRISE ) subtitle = string.format( sunrise, SUNRISE ) - if not self.useSRS then + if not self.useSRS and == false then self:Transmission( self.Sound.SunriseAt, 0.5, subtitle ) self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 ) self:Transmission( self.Sound.TimeLocal, 0.2 ) @@ -2416,7 +2419,7 @@ function ATIS:onafterBroadcast( From, Event, To ) local sunset = self.gettext:GetEntry("SUNSETAT",self.locale) --subtitle = string.format( "Sunset at %s local time", SUNSET ) subtitle = string.format( sunset, SUNSET ) - if not self.useSRS then + if not self.useSRS and == false then self:Transmission( self.Sound.SunsetAt, 0.5, subtitle ) self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 ) self:Transmission( self.Sound.TimeLocal, 0.2 ) From 70e9d91bb5fd5e2eae8e3f1f63265f58dae36341 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 3 Mar 2025 14:19:11 +0100 Subject: [PATCH 102/158] Update ATIS.lua --- Moose Development/Moose/Ops/ATIS.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 548280fad..2366ff09b 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -2408,7 +2408,7 @@ function ATIS:onafterBroadcast( From, Event, To ) local sunrise = self.gettext:GetEntry("SUNRISEAT",self.locale) --subtitle = string.format( "Sunrise at %s local time", SUNRISE ) subtitle = string.format( sunrise, SUNRISE ) - if not self.useSRS and == false then + if not self.useSRS and NorthPolar == false then self:Transmission( self.Sound.SunriseAt, 0.5, subtitle ) self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 ) self:Transmission( self.Sound.TimeLocal, 0.2 ) @@ -2419,7 +2419,7 @@ function ATIS:onafterBroadcast( From, Event, To ) local sunset = self.gettext:GetEntry("SUNSETAT",self.locale) --subtitle = string.format( "Sunset at %s local time", SUNSET ) subtitle = string.format( sunset, SUNSET ) - if not self.useSRS and == false then + if not self.useSRS and NorthPolar == false then self:Transmission( self.Sound.SunsetAt, 0.5, subtitle ) self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 ) self:Transmission( self.Sound.TimeLocal, 0.2 ) From c808e4a4e21e554314198969517f0ab60818bf8f Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 5 Mar 2025 20:48:15 +0100 Subject: [PATCH 103/158] Update Airbase.lua - Added FighterAircraftSmall parking spot type --- Moose Development/Moose/Wrapper/Airbase.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 1a1d6566e..93d672750 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -926,11 +926,12 @@ AIRBASE.Iraq = { -- @field #number HelicopterOnly 40: Special spots for Helicopers. -- @field #number Shelter 68: Hardened Air Shelter. Currently only on Caucaus map. -- @field #number OpenMed 72: Open/Shelter air airplane only. +-- @field #number SmallSizeFigher 100: Tight spots for smaller type fixed wing aircraft, like the F-16. Example of these spots: 04, 05, 06 on Muwaffaq_Salti. A Viper sized plane can spawn here, but an A-10 or Strike Eagle can't -- @field #number OpenBig 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. -- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots. -- @field #number HelicopterUsable 216: Combines HelicopterOnly, OpenMed and OpenBig. --- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. --- @field #number SmallSizeFigher 100: Tight spots for smaller type fixed wing aircraft, like the F-16. Example of these spots: 04, 05, 06 on Muwaffaq_Salti. A Viper sized plane can spawn here, but an A-10 or Strike Eagle can't +-- @field #number FighterAircraft 244: Combines Shelter, OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. +-- @field #number FighterAircraftSmall 344: Combines Shelter, SmallsizeFighter, OpenMed and OpenBig spots. So effectively all spots usable by small fixed wing aircraft. AIRBASE.TerminalType = { Runway=16, HelicopterOnly=40, @@ -941,6 +942,7 @@ AIRBASE.TerminalType = { OpenMedOrBig=176, HelicopterUsable=216, FighterAircraft=244, + FighterAircraftSmall=344, } --- Status of a parking spot. @@ -2013,9 +2015,13 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) match=true end elseif termtype==AIRBASE.TerminalType.FighterAircraft then - if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then + if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then match=true end + elseif termtype==AIRBASE.TerminalType.FighterAircraftSmall then + if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then + match=true + end end return match From 45ebf9a3c7526769ee8183842e9850ae8f461978 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 6 Mar 2025 12:25:53 +0100 Subject: [PATCH 104/158] #ZONE_ELASTIC - Fix zone filling --- Moose Development/Moose/Core/Zone.lua | 187 +++++++++++++------------- 1 file changed, 95 insertions(+), 92 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index ceca5fb02..463629249 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -144,7 +144,7 @@ ZONE_BASE = { -- @return #ZONE_BASE self function ZONE_BASE:New( ZoneName ) local self = BASE:Inherit( self, FSM:New() ) - self:F( ZoneName ) + --self:F( ZoneName ) self.ZoneName = ZoneName @@ -157,7 +157,7 @@ end -- @param #ZONE_BASE self -- @return #string The name of the zone. function ZONE_BASE:GetName() - self:F2() + --self:F2() return self.ZoneName end @@ -167,7 +167,7 @@ end -- @param #string ZoneName The name of the zone. -- @return #ZONE_BASE function ZONE_BASE:SetName( ZoneName ) - self:F2() + --self:F2() self.ZoneName = ZoneName end @@ -177,7 +177,7 @@ end -- @param DCS#Vec2 Vec2 The Vec2 to test. -- @return #boolean true if the Vec2 is within the zone. function ZONE_BASE:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) + --self:F2( Vec2 ) return false end @@ -232,13 +232,13 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. function ZONE_BASE:GetPointVec2() - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) local Vec2 = self:GetVec2() local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - self:T2( { PointVec2 } ) + --self:T2( { PointVec2 } ) return PointVec2 end @@ -248,7 +248,7 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return DCS#Vec3 The Vec3 of the zone. function ZONE_BASE:GetVec3( Height ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) Height = Height or 0 @@ -256,7 +256,7 @@ function ZONE_BASE:GetVec3( Height ) local Vec3 = { x = Vec2.x, y = Height and Height or land.getHeight( self:GetVec2() ), z = Vec2.y } - self:T2( { Vec3 } ) + --self:T2( { Vec3 } ) return Vec3 end @@ -266,13 +266,13 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. function ZONE_BASE:GetPointVec3( Height ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) local Vec3 = self:GetVec3( Height ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2( { PointVec3 } ) + --self:T2( { PointVec3 } ) return PointVec3 end @@ -282,7 +282,7 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#COORDINATE The Coordinate of the zone. function ZONE_BASE:GetCoordinate( Height ) --R2.1 - self:F2(self.ZoneName) + --self:F2(self.ZoneName) local Vec3 = self:GetVec3( Height ) @@ -363,7 +363,7 @@ end --- Bound the zone boundaries with a tires. -- @param #ZONE_BASE self function ZONE_BASE:BoundZone() - self:F2() + --self:F2() end --- Set draw coalition of zone. @@ -510,7 +510,7 @@ end -- @param #ZONE_BASE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) - self:F2( SmokeColor ) + --self:F2( SmokeColor ) end @@ -519,7 +519,7 @@ end -- @param #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -- @return #ZONE_BASE self function ZONE_BASE:SetZoneProbability( ZoneProbability ) - self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) + --self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) self.ZoneProbability = ZoneProbability or 1 return self @@ -529,7 +529,7 @@ end -- @param #ZONE_BASE self -- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. function ZONE_BASE:GetZoneProbability() - self:F2() + --self:F2() return self.ZoneProbability end @@ -560,7 +560,7 @@ end -- -- The result should be that Zone1 would be more probable selected than Zone2. -- function ZONE_BASE:GetZoneMaybe() - self:F2() + --self:F2() local Randomization = math.random() if Randomization <= self.ZoneProbability then @@ -791,7 +791,7 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius, DoNotRegisterZone ) -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS - self:F( { ZoneName, Vec2, Radius } ) + --self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius self.Vec2 = Vec2 @@ -947,7 +947,7 @@ end -- @param #number AddOffSet (optional) The angle to be added for the smoking start position. -- @return #ZONE_RADIUS self function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) - self:F2( SmokeColor ) + --self:F2( SmokeColor ) local Point = {} local Vec2 = self:GetVec2() @@ -978,7 +978,7 @@ end -- @param #number AddHeight (optional) The height to be added for the smoke. -- @return #ZONE_RADIUS self function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) - self:F2( { FlareColor, Azimuth } ) + --self:F2( { FlareColor, Azimuth } ) local Point = {} local Vec2 = self:GetVec2() @@ -1004,9 +1004,9 @@ end -- @param #ZONE_RADIUS self -- @return DCS#Distance The radius of the zone. function ZONE_RADIUS:GetRadius() - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) - self:T2( { self.Radius } ) + --self:T2( { self.Radius } ) return self.Radius end @@ -1016,10 +1016,10 @@ end -- @param DCS#Distance Radius The radius of the zone. -- @return DCS#Distance The radius of the zone. function ZONE_RADIUS:SetRadius( Radius ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) self.Radius = Radius - self:T2( { self.Radius } ) + --self:T2( { self.Radius } ) return self.Radius end @@ -1028,9 +1028,9 @@ end -- @param #ZONE_RADIUS self -- @return DCS#Vec2 The location of the zone. function ZONE_RADIUS:GetVec2() - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) - self:T2( { self.Vec2 } ) + --self:T2( { self.Vec2 } ) return self.Vec2 end @@ -1040,11 +1040,11 @@ end -- @param DCS#Vec2 Vec2 The new location of the zone. -- @return DCS#Vec2 The new location of the zone. function ZONE_RADIUS:SetVec2( Vec2 ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) self.Vec2 = Vec2 - self:T2( { self.Vec2 } ) + --self:T2( { self.Vec2 } ) return self.Vec2 end @@ -1054,14 +1054,14 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return DCS#Vec3 The point of the zone. function ZONE_RADIUS:GetVec3( Height ) - self:F2( { self.ZoneName, Height } ) + --self:F2( { self.ZoneName, Height } ) Height = Height or 0 local Vec2 = self:GetVec2() local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - self:T2( { Vec3 } ) + --self:T2( { Vec3 } ) return Vec3 end @@ -1138,7 +1138,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData.Units[ZoneObject] = ZoneObject - self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) + --self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) end end @@ -1149,7 +1149,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( tostring(SceneryName), ZoneObject) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName] ) - self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) + --self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end @@ -1409,7 +1409,7 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) + --self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { id = world.VolumeType.SPHERE, @@ -1436,7 +1436,7 @@ end -- @param DCS#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) + --self:F2( Vec2 ) if not Vec2 then return false end @@ -1456,7 +1456,7 @@ end -- @param DCS#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) + --self:F2( Vec3 ) if not Vec3 then return false end local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) @@ -1521,11 +1521,11 @@ end -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2( inner, outer ) ) - self:T3( { PointVec2 } ) + --self:T3( { PointVec2 } ) return PointVec2 end @@ -1536,11 +1536,11 @@ end -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @return DCS#Vec3 The random location within the zone. function ZONE_RADIUS:GetRandomVec3( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) local Vec2 = self:GetRandomVec2( inner, outer ) - self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } ) + --self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } ) return { x = Vec2.x, y = self.y, z = Vec2.y } end @@ -1552,11 +1552,11 @@ end -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @return Core.Point#POINT_VEC3 The @{Core.Point#POINT_VEC3} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2( inner, outer ) ) - self:T3( { PointVec3 } ) + --self:T3( { PointVec3 } ) return PointVec3 end @@ -1685,7 +1685,7 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma T1=timer.getTime() - self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0)) + --self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0)) if found then return rcoord else return nil end @@ -1754,7 +1754,7 @@ function ZONE:New( ZoneName ) -- Create a new ZONE_RADIUS. local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius, true)) - self:F(ZoneName) + --self:F(ZoneName) -- Color of zone. self.Color={1, 0, 0, 0.15} @@ -1824,7 +1824,7 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) self.relative_to_unit = Offset.relative_to_unit or false end - self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) + --self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT self.LastVec2 = ZoneUNIT:GetVec2() @@ -1840,7 +1840,7 @@ end -- @param #ZONE_UNIT self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location and the offset, if any. function ZONE_UNIT:GetVec2() - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) local ZoneVec2 = self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -1873,7 +1873,7 @@ function ZONE_UNIT:GetVec2() return self.LastVec2 end - self:T2( { ZoneVec2 } ) + --self:T2( { ZoneVec2 } ) return nil end @@ -1882,7 +1882,7 @@ end -- @param #ZONE_UNIT self -- @return DCS#Vec2 The random location within the zone. function ZONE_UNIT:GetRandomVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local RandomVec2 = {} --local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! @@ -1896,7 +1896,7 @@ function ZONE_UNIT:GetRandomVec2() RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - self:T( { RandomVec2 } ) + --self:T( { RandomVec2 } ) return RandomVec2 end @@ -1906,7 +1906,7 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return DCS#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) Height = Height or 0 @@ -1914,7 +1914,7 @@ function ZONE_UNIT:GetVec3( Height ) local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - self:T2( { Vec3 } ) + --self:T2( { Vec3 } ) return Vec3 end @@ -1940,7 +1940,7 @@ ZONE_GROUP = { -- @return #ZONE_GROUP self function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius, true ) ) - self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) + --self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) self._.ZoneGROUP = ZoneGROUP self._.ZoneVec2Cache = self._.ZoneGROUP:GetVec2() @@ -1956,7 +1956,7 @@ end -- @param #ZONE_GROUP self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local ZoneVec2 = nil @@ -1967,7 +1967,7 @@ function ZONE_GROUP:GetVec2() ZoneVec2 = self._.ZoneVec2Cache end - self:T( { ZoneVec2 } ) + --self:T( { ZoneVec2 } ) return ZoneVec2 end @@ -1976,7 +1976,7 @@ end -- @param #ZONE_GROUP self -- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetRandomVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local Point = {} local Vec2 = self._.ZoneGROUP:GetVec2() @@ -1985,7 +1985,7 @@ function ZONE_GROUP:GetRandomVec2() Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - self:T( { Point } ) + --self:T( { Point } ) return Point end @@ -1996,11 +1996,11 @@ end -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. function ZONE_GROUP:GetRandomPointVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - self:T3( { PointVec2 } ) + --self:T3( { PointVec2 } ) return PointVec2 end @@ -2046,7 +2046,7 @@ function _ZONE_TRIANGLE:New(p1, p2, p3) end self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5 - + return self end @@ -2183,7 +2183,7 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, PointsArray } ) + --self:F( { ZoneName, PointsArray } ) if PointsArray then @@ -2351,7 +2351,7 @@ end -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_POLYGON_BASE:GetVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local Bounds = self:GetBoundingSquare() @@ -2434,9 +2434,9 @@ end -- @param #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:Flush() - self:F2() + --self:F2() - self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } ) + --self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } ) return self end @@ -2455,7 +2455,7 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) j = #self._.Polygon while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + --self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y @@ -2563,7 +2563,7 @@ function ZONE_POLYGON_BASE:ReFill(Color,Alpha) self.FillTriangles = {} end -- refill - for _, triangle in pairs(self._Triangles) do + for _,triangle in pairs(self._Triangles) do local draw_ids = triangle:Fill(coalition,color,alpha,nil) self.FillTriangles = draw_ids table.combine(self.DrawID, draw_ids) @@ -2707,7 +2707,7 @@ end -- @param #number Segments (Optional) Number of segments within boundary line. Default 10. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) - self:F2( SmokeColor ) + --self:F2( SmokeColor ) Segments=Segments or 10 @@ -2715,7 +2715,7 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) local j=#self._.Polygon while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + --self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y @@ -2740,7 +2740,7 @@ end -- @param #number AddHeight (optional) The height to be added for the smoke. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) - self:F2(FlareColor) + --self:F2(FlareColor) Segments=Segments or 10 @@ -2750,7 +2750,7 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) local j=#self._.Polygon while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + --self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y @@ -2773,7 +2773,7 @@ end -- @param DCS#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) + --self:F2( Vec2 ) if not Vec2 then return false end local Next local Prev @@ -2783,18 +2783,18 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) Prev = #self._.Polygon while Next <= #self._.Polygon do - self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } ) + --self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } ) if ( ( ( self._.Polygon[Next].y > Vec2.y ) ~= ( self._.Polygon[Prev].y > Vec2.y ) ) and ( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x ) ) then InPolygon = not InPolygon end - self:T2( { InPolygon = InPolygon } ) + --self:T2( { InPolygon = InPolygon } ) Prev = Next Next = Next + 1 end - self:T( { InPolygon = InPolygon } ) + --self:T( { InPolygon = InPolygon } ) return InPolygon end @@ -2803,7 +2803,7 @@ end -- @param DCS#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) + --self:F2( Vec3 ) if not Vec3 then return false end @@ -2838,11 +2838,11 @@ end -- @param #ZONE_POLYGON_BASE self -- @return @{Core.Point#POINT_VEC2} function ZONE_POLYGON_BASE:GetRandomPointVec2() - self:F2() + --self:F2() local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - self:T2( PointVec2 ) + --self:T2( PointVec2 ) return PointVec2 end @@ -2851,11 +2851,11 @@ end -- @param #ZONE_POLYGON_BASE self -- @return @{Core.Point#POINT_VEC3} function ZONE_POLYGON_BASE:GetRandomPointVec3() - self:F2() + --self:F2() local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) - self:T2( PointVec3 ) + --self:T2( PointVec3 ) return PointVec3 end @@ -2865,11 +2865,11 @@ end -- @param #ZONE_POLYGON_BASE self -- @return Core.Point#COORDINATE function ZONE_POLYGON_BASE:GetRandomCoordinate() - self:F2() + --self:F2() local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - self:T2( Coordinate ) + --self:T2( Coordinate ) return Coordinate end @@ -2886,7 +2886,7 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() local y2 = self._.Polygon[1].y for i = 2, #self._.Polygon do - self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) + --self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1 x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2 y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1 @@ -2909,7 +2909,7 @@ function ZONE_POLYGON_BASE:GetBoundingVec2() local y2 = self._.Polygon[1].y for i = 2, #self._.Polygon do - self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) + --self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1 x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2 y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1 @@ -2948,7 +2948,7 @@ function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, C Limit = #self._.Polygon end while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + --self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) if j ~= Limit then local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y @@ -3019,7 +3019,7 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup ) local GroupPoints = ZoneGroup:GetTaskRoute() local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, GroupPoints ) ) - self:F( { ZoneName, ZoneGroup, self._.Polygon } ) + --self:F( { ZoneName, ZoneGroup, self._.Polygon } ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) @@ -3035,7 +3035,7 @@ end function ZONE_POLYGON:NewFromPointsArray( ZoneName, PointsArray ) local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) ) - self:F( { ZoneName, self._.Polygon } ) + --self:F( { ZoneName, self._.Polygon } ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) @@ -3055,7 +3055,7 @@ function ZONE_POLYGON:NewFromGroupName( GroupName ) local GroupPoints = ZoneGroup:GetTaskRoute() local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( GroupName, GroupPoints ) ) - self:F( { GroupName, ZoneGroup, self._.Polygon } ) + --self:F( { GroupName, ZoneGroup, self._.Polygon } ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) @@ -3221,7 +3221,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) self.ScanData.Units[ZoneObject] = ZoneObject - self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) + --self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) end end @@ -3232,7 +3232,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName]) - self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) + --self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end @@ -3573,8 +3573,8 @@ do -- ZONE_ELASTIC function ZONE_ELASTIC:Update(Delay, Draw) -- Debug info. - self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName))) - + --self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName))) + -- Copy all points. local points=UTILS.DeepCopy(self.points or {}) @@ -3592,6 +3592,9 @@ do -- ZONE_ELASTIC -- Update polygon verticies from points. self._.Polygon=self:_ConvexHull(points) + + self._Triangles = self:_Triangulate() + self.SurfaceArea = self:_CalculateSurfaceArea() if Draw~=false then if self.DrawID or Draw==true then @@ -3987,7 +3990,7 @@ do -- ZONE_AIRBASE -- @param #ZONE_AIRBASE self -- @return DCS#Vec2 The location of the zone based on the AIRBASE location. function ZONE_AIRBASE:GetVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local ZoneVec2 = nil @@ -3998,7 +4001,7 @@ do -- ZONE_AIRBASE ZoneVec2 = self._.ZoneVec2Cache end - self:T( { ZoneVec2 } ) + --self:T( { ZoneVec2 } ) return ZoneVec2 end @@ -4009,11 +4012,11 @@ do -- ZONE_AIRBASE -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. function ZONE_AIRBASE:GetRandomPointVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - self:T3( { PointVec2 } ) + --self:T3( { PointVec2 } ) return PointVec2 end From 29c0017e806668097eb505b42d8f8c96699bfaa5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 6 Mar 2025 12:26:24 +0100 Subject: [PATCH 105/158] xx --- Moose Development/Moose/AI/AI_CAS.lua | 1 - Moose Development/Moose/Core/Fsm.lua | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index 7fb848d42..89b26b3fd 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -162,7 +162,6 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Engage. diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index e3fbd4f6c..af2e971fe 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -948,7 +948,8 @@ do -- FSM end do -- FSM_CONTROLLABLE - + + --- -- @type FSM_CONTROLLABLE -- @field Wrapper.Controllable#CONTROLLABLE Controllable -- @extends Core.Fsm#FSM @@ -1081,7 +1082,8 @@ do -- FSM_CONTROLLABLE end do -- FSM_PROCESS - + + --- -- @type FSM_PROCESS -- @field Tasking.Task#TASK Task -- @extends Core.Fsm#FSM_CONTROLLABLE From 3c710613a80ab2a6159ab5729737081886e071e8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 6 Mar 2025 14:51:49 +0100 Subject: [PATCH 106/158] CONTROLLABLE added HasIRMarker --- Moose Development/Moose/Wrapper/Controllable.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index dd95b441e..2623dc6d5 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1777,8 +1777,6 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, return DCSTask end --- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES - --- (AIR) Engaging targets of defined types. -- @param #CONTROLLABLE self -- @param DCS#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. @@ -5735,6 +5733,14 @@ function CONTROLLABLE:DisableIRMarkerForGroup() return self end +--- [GROUND] Check if an IR Spot exists. +-- @param #CONTROLLABLE self +-- @return #boolean outcome +function CONTROLLABLE:HasIRMarker() + if self.spot then return true end + return false +end + --- [Internal] This method is called by the scheduler after enabling the IR marker. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self From c00eff8b23af2bacb3b829710598198eee115d78 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 8 Mar 2025 21:37:43 +0100 Subject: [PATCH 107/158] AIRBASE - AIRBASE: Workaround for DCS bug that helipads have category of airdrome - SET_AIRBASE: Added FilterZones function --- Moose Development/Moose/Core/Set.lua | 40 +++++++++++ Moose Development/Moose/DCS.lua | 8 ++- Moose Development/Moose/Utilities/Utils.lua | 2 +- Moose Development/Moose/Wrapper/Airbase.lua | 76 ++++++++++++++++++--- 4 files changed, 114 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index f2b6e6d3c..3f4fa2e71 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -5403,6 +5403,7 @@ do -- SET_AIRBASE Airbases = {}, Filter = { Coalitions = nil, + Zones = nil, }, FilterMeta = { Coalitions = { @@ -5554,6 +5555,31 @@ do -- SET_AIRBASE end return self end + + --- Builds a set of airbase objects in zones. + -- @param #SET_AIRBASE self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_AIRBASE self + function SET_AIRBASE:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + --self:T((zonename) + self.Filter.Zones[zonename] = Zone + end + return self + end --- Starts the filtering. -- @param #SET_AIRBASE self @@ -5692,6 +5718,20 @@ do -- SET_AIRBASE --self:T(( { "Evaluated Category", MAirbaseCategory } ) MAirbaseInclude = MAirbaseInclude and MAirbaseCategory end + + if self.Filter.Zones and MAirbaseInclude then + local MAirbaseZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + --self:T(( "Zone:", ZoneName ) + local coord = MAirbase:GetCoordinate() + if coord and Zone:IsCoordinateInZone(coord) then + MAirbaseZone = true + end + --self:T(( { "Evaluated Zone", MSceneryZone } ) + end + MAirbaseInclude = MAirbaseInclude and MAirbaseZone + end + end if self.Filter.Functions and MAirbaseInclude then diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 44049cecf..8e01b8a73 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -630,9 +630,13 @@ do -- Object --- @function [parent=#Object] destroy -- @param #Object self - --- @function [parent=#Object] getCategory + --- Returns an enumerator of the category for the specific object. + -- The enumerator returned is dependent on the category of the object and how the function is called. + -- As of DCS 2.9.2 when this function is called on an Object, Unit, Weapon, or Airbase a 2nd value will be returned which details the object sub-category value. + -- @function [parent=#Object] getCategory -- @param #Object self - -- @return #Object.Category + -- @return #Object.Category The object category (1=UNIT, 2=WEAPON, 3=STATIC, 4=BASE, 5=SCENERY, 6=Cargo) + -- @return #number The subcategory of the passed object, e.g. Unit.Category if a unit object was passed. --- Returns type name of the Object. -- @function [parent=#Object] getTypeName diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index eeff81f26..626580dff 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -514,7 +514,7 @@ function UTILS.PrintTableToLog(table, indent, noprint) env.info(string.rep(" ", indent) .. tostring(k) .. " = {") end text = text ..string.rep(" ", indent) .. tostring(k) .. " = {\n" - text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1)).."\n" + text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1), noprint).."\n" if not noprint then env.info(string.rep(" ", indent) .. "},") end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 93d672750..21fd34fda 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -994,7 +994,7 @@ function AIRBASE:Register(AirbaseName) -- Debug info. --self:I({airbase=AirbaseName, descriptors=self.descriptors}) - + -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME @@ -1009,6 +1009,7 @@ if self.category==Airbase.Category.AIRDROME then self.isAirdrome=true elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01" then self.isHelipad=true + self.category=Airbase.Category.HELIPAD elseif self.category==Airbase.Category.SHIP then self.isShip=true -- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects() @@ -1024,20 +1025,33 @@ end -- Init Runways. self:_InitRunways() - + + -- Number of runways + local Nrunways=#self.runways + -- Set the active runways based on wind direction. - if self.isAirdrome then + if Nrunways>0 then self:SetActiveRunway() end -- Init parking spots. self:_InitParkingSpots() + + -- Some heliports identify as airdromes in the airbase category. This is buggy in the descriptors category but also in the getCategory() and getCategoryEx() functions. + if self.category==Airbase.Category.AIRDROME and (Nrunways==0 or self.NparkingTotal==self.NparkingTerminal[AIRBASE.TerminalType.HelicopterOnly]) then + self:E(string.format("WARNING: %s identifies as airdrome (category=0) but has no runways or just helo parking ==> will change to helipad (category=1)", self.AirbaseName)) + self.category=Airbase.Category.HELIPAD + self.isAirdrome=false + self.isHelipad=true + --self:GetCoordinate():MarkToAll("Helipad not airdrome") + end -- Get 2D position vector. local vec2=self:GetVec2() -- Init coordinate. self:GetCoordinate() + -- Storage. self.storage=_DATABASE:AddStorage(AirbaseName) @@ -1061,6 +1075,46 @@ end return self end +--- Get the category of this airbase. This is only a debug function because DCS 2.9 incorrectly returns heliports as airdromes. +-- @param #AIRBASE self +function AIRBASE:_GetCategory() + + local name=self.AirbaseName + + local static=StaticObject.getByName(name) + local airbase=Airbase.getByName(name) + local unit=Unit.getByName(name) + + local text=string.format("\n=====================================================") + text=text..string.format("\nAirbase %s:", name) + if static then + local oc, uc=static:getCategory() + local ex=static:getCategoryEx() + text=text..string.format("\nSTATIC: oc=%d, uc=%d, ex=%d", oc, uc, ex) + --text=text..UTILS.PrintTableToLog(static:getDesc(), nil, true) + text=text..string.format("\n--------------------------------------------------") + end + if unit then + local oc, uc=unit:getCategory() + local ex=unit:getCategoryEx() + text=text..string.format("\nUNIT: oc=%d, uc=%d, ex=%d", oc, uc, ex) + --text=text..UTILS.PrintTableToLog(unit:getDesc(), nil, true) + text=text..string.format("\n--------------------------------------------------") + end + if airbase then + local oc, uc=airbase:getCategory() + local ex=airbase:getCategoryEx() + text=text..string.format("\nAIRBASE: oc=%d, uc=%d, ex=%d", oc, uc, ex) + text=text..string.format("\n--------------------------------------------------") + text=text..UTILS.PrintTableToLog(airbase:getDesc(), nil, true) + end + + text=text..string.format("\n=====================================================") + + + env.info(text) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Reference methods ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1592,6 +1646,9 @@ function AIRBASE:_InitParkingSpots() self.parkingByID[park.TerminalID]=park table.insert(self.parking, park) end + + -- Runways are not included in total number of parking spots + self.NparkingTotal=self.NparkingTotal-self.NparkingTerminal[AIRBASE.TerminalType.Runway] return self end @@ -2079,11 +2136,6 @@ function AIRBASE:_InitRunways(IncludeInverse) -- Runway table. local Runways={} - if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then - self.runways={} - return {} - end - --- Function to create a runway data table. local function _createRunway(name, course, width, length, center) @@ -2169,7 +2221,7 @@ function AIRBASE:_InitRunways(IncludeInverse) -- Debug info. self:T2(runways) - if runways then + if runways and #runways>0 then -- Loop over runways. for _,rwy in pairs(runways) do @@ -2202,6 +2254,12 @@ function AIRBASE:_InitRunways(IncludeInverse) end end + + else + + -- No runways + self.runways={} + return {} end From 4976cd86f23460924c29a5c02cf0c154c469539e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 9 Mar 2025 14:36:38 +0100 Subject: [PATCH 108/158] ZONE_ELASTIC - function to remove vertices --- Moose Development/Moose/Core/Zone.lua | 34 +++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 463629249..32ec18222 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -2054,7 +2054,7 @@ end -- @param #_ZONE_TRIANGLE self -- @param #table pt The point to check -- @param #table points (optional) The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it --- @return #bool True if the point is contained, false otherwise +-- @return #boolean True if the point is contained, false otherwise function _ZONE_TRIANGLE:ContainsPoint(pt, points) points = points or self.Points @@ -3536,7 +3536,37 @@ do -- ZONE_ELASTIC return self end + + --- Remove a vertex (point) from the polygon. + -- @param #ZONE_ELASTIC self + -- @param DCS#Vec2 Vec2 Point in 2D (with x and y coordinates). + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:RemoveVertex2D(Vec2) + + local found = false + local findex = 0 + for _id,_vec2 in pairs(self.points) do + if _vec2.x == Vec2.x and _vec2.y == Vec2.y then + found = true + findex = _id + break + end + end + + if found == true and findex > 0 then + table.remove(self.points,findex) + end + return self + end + + --- Remove a vertex (point) from the polygon. + -- @param #ZONE_ELASTIC self + -- @param DCS#Vec3 Vec3 Point in 3D (with x, y and z coordinates). Only the x and z coordinates are used. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:RemoveVertex3D(Vec3) + return self:RemoveVertex2D({x=Vec3.x, y=Vec3.z}) + end --- Add a vertex (point) to the polygon. -- @param #ZONE_ELASTIC self @@ -3793,7 +3823,7 @@ end --- Checks if a point is contained within the oval. -- @param #ZONE_OVAL self -- @param #table point The point to check --- @return #bool True if the point is contained, false otherwise +-- @return #boolean True if the point is contained, false otherwise function ZONE_OVAL:IsVec2InZone(vec2) local cos, sin = math.cos, math.sin local dx = vec2.x - self.CenterVec2.x From f335ffc4ecb5a4f7a331a34b296760a98ff30530 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 10 Mar 2025 10:55:01 +0100 Subject: [PATCH 109/158] Update CTLD.lua Clarify hover min/max height is in meters --- 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 9870a9d4a..7f195ac60 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -831,8 +831,8 @@ do -- my_ctld.PackDistance = 35 -- Pack crates in this radius only -- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. -- my_ctld.dropAsCargoCrate = false -- Hercules only: Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. --- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. --- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load in meters. +-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load in meters. -- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering. -- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. -- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). From 56ec3920c591e584ebf5dd31db97db9d02be1ce0 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 10 Mar 2025 13:12:00 +0100 Subject: [PATCH 110/158] Update Controllable.lua IR Strobe . some improvements --- .../Moose/Wrapper/Controllable.lua | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 2623dc6d5..f2eba2f43 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -5693,15 +5693,22 @@ function CONTROLLABLE:DisableIRMarker() self:DisableIRMarkerForGroup() return end - - if self.spot then - self.spot:destroy() - self.spot = nil + + if self.spot then + self.spot:destroy() + self.spot = nil + end if self.timer and self.timer:IsRunning() then self.timer:Stop() self.timer = nil end + + if self.ClassName == "GROUP" then + self.IRMarkerGroup = nil + elseif self.ClassName == "UNIT" then + self.IRMarkerUnit = nil end + return self end @@ -5709,7 +5716,7 @@ end -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:EnableIRMarkerForGroup() - --sefl:F("EnableIRMarkerForGroup") + --self:F("EnableIRMarkerForGroup") if self.ClassName == "GROUP" then local units = self:GetUnits() or {} for _,_unit in pairs(units) do @@ -5729,6 +5736,7 @@ function CONTROLLABLE:DisableIRMarkerForGroup() for _,_unit in pairs(units) do _unit:DisableIRMarker() end + self.IRMarkerGroup = nil end return self end @@ -5737,10 +5745,18 @@ end -- @param #CONTROLLABLE self -- @return #boolean outcome function CONTROLLABLE:HasIRMarker() - if self.spot then return true end + if self.IRMarkerGroup == true or self.IRMarkerUnit == true then return true end return false end +--- [Internal] This method is called by the scheduler after disabling the IR marker. +function CONTROLLABLE._StopSpot(spot) + if spot then + spot:destroy() + spot=nil + end +end + --- [Internal] This method is called by the scheduler after enabling the IR marker. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self @@ -5756,13 +5772,16 @@ function CONTROLLABLE:_MarkerBlink() local _, _, unitBBHeight, _ = self:GetObjectSize() local unitPos = self:GetPositionVec3() - self.spot = Spot.createInfraRed( - self.DCSUnit, - { x = 0, y = (unitBBHeight + 1), z = 0 }, - { x = unitPos.x, y = (unitPos.y + unitBBHeight), z = unitPos.z } - ) - - local offTimer = TIMER:New(function() if self.spot then self.spot:destroy() end end) - offTimer:Start(0.5) + if not self.spot then + local spot = Spot.createInfraRed( + self.DCSUnit, + { x = 0, y = (unitBBHeight + 1), z = 0 }, + { x = unitPos.x, y = (unitPos.y + unitBBHeight), z = unitPos.z } + ) + self.spot = spot + local offTimer = nil + local offTimer = TIMER:New(CONTROLLABLE._StopSpot, spot) + offTimer:Start(0.5) + end return self end From ea23162ca9373428d8d04481bbbd798d0be262f9 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 Mar 2025 19:53:24 +0100 Subject: [PATCH 111/158] Update Spawn.lua - Enabled explicit parking spots in SpawnAtAirbase --- Moose Development/Moose/Core/Spawn.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 42b8bab38..33eb2e804 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2173,12 +2173,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT --self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) - --[[ elseif Parkingdata~=nil then - -- Parking data explicitly set by user as input parameter. + -- Parking data explicitly set by user as input parameter. (This was commented out for some unknown reason. But I need it this way.) nfree=#Parkingdata spots=Parkingdata - ]] else if ishelo then if termtype == nil then From 94ee76fe6268f908c2e6a696eb8ebbec6d7301ae Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 11 Mar 2025 10:52:16 +0100 Subject: [PATCH 112/158] #CONTROLLABLE - Improved IR Markers --- .../Moose/Wrapper/Controllable.lua | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index f2eba2f43..79d45ee33 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -5642,19 +5642,18 @@ end -- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. -- @return #CONTROLLABLE self function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime) - --sefl:F("NewIRMarker") - if self.ClassName == "GROUP" then + self:T2("NewIRMarker") + if self:IsInstanceOf("GROUP") then + if self.IRMarkerGroup == true then return end self.IRMarkerGroup = true self.IRMarkerUnit = false - elseif self.ClassName == "UNIT" then + elseif self:IsInstanceOf("UNIT") then + if self.IRMarkerUnit == true then return end self.IRMarkerGroup = false self.IRMarkerUnit = true end - - self.spot = nil - self.timer = nil - self.stoptimer = nil + self.Runtime = Runtime or 60 if EnableImmediately and EnableImmediately == true then self:EnableIRMarker(Runtime) end @@ -5667,19 +5666,23 @@ end -- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Else run until you call `myobject:DisableIRMarker()`. -- @return #CONTROLLABLE self function CONTROLLABLE:EnableIRMarker(Runtime) - --sefl:F("EnableIRMarker") + self:T2("EnableIRMarker") if self.IRMarkerGroup == nil then self:NewIRMarker(true,Runtime) return end - if (self.IRMarkerGroup == true) then - self:EnableIRMarkerForGroup() + if self:IsInstanceOf("GROUP") then + self:EnableIRMarkerForGroup(Runtime) return end - + + if self.timer and self.timer:IsRunning() then return self end + + local Runtime = Runtime or self.Runtime self.timer = TIMER:New(CONTROLLABLE._MarkerBlink, self) self.timer:Start(nil, 1 - math.random(1, 5) / 10 / 2, Runtime) -- start randomized + self.IRMarkerUnit = true return self end @@ -5688,14 +5691,13 @@ end -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:DisableIRMarker() - --sefl:F("DisableIRMarker") - if (self.IRMarkerGroup == true) then + self:T2("DisableIRMarker") + if self:IsInstanceOf("GROUP") then self:DisableIRMarkerForGroup() return end if self.spot then - self.spot:destroy() self.spot = nil end if self.timer and self.timer:IsRunning() then @@ -5703,9 +5705,9 @@ function CONTROLLABLE:DisableIRMarker() self.timer = nil end - if self.ClassName == "GROUP" then + if self:IsInstanceOf("GROUP") then self.IRMarkerGroup = nil - elseif self.ClassName == "UNIT" then + elseif self:IsInstanceOf("UNIT") then self.IRMarkerUnit = nil end @@ -5714,14 +5716,17 @@ end --- [GROUND] Enable the IR markers for a whole group. -- @param #CONTROLLABLE self +-- @param #number Runtime Runtime of the marker in seconds -- @return #CONTROLLABLE self -function CONTROLLABLE:EnableIRMarkerForGroup() - --self:F("EnableIRMarkerForGroup") - if self.ClassName == "GROUP" then +function CONTROLLABLE:EnableIRMarkerForGroup(Runtime) + self:T2("EnableIRMarkerForGroup") + if self:IsInstanceOf("GROUP") + then local units = self:GetUnits() or {} for _,_unit in pairs(units) do - _unit:EnableIRMarker() + _unit:EnableIRMarker(Runtime) end + self.IRMarkerGroup = true end return self end @@ -5730,8 +5735,8 @@ end -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:DisableIRMarkerForGroup() - --sefl:F("DisableIRMarkerForGroup") - if self.ClassName == "GROUP" then + self:T2("DisableIRMarkerForGroup") + if self:IsInstanceOf("GROUP") then local units = self:GetUnits() or {} for _,_unit in pairs(units) do _unit:DisableIRMarker() @@ -5745,15 +5750,15 @@ end -- @param #CONTROLLABLE self -- @return #boolean outcome function CONTROLLABLE:HasIRMarker() - if self.IRMarkerGroup == true or self.IRMarkerUnit == true then return true end + self:T2("HasIRMarker") + if self.timer and self.timer:IsRunning() then return true end return false end ---- [Internal] This method is called by the scheduler after disabling the IR marker. +--- [Internal] This method is called by the scheduler to blink the IR marker. function CONTROLLABLE._StopSpot(spot) if spot then spot:destroy() - spot=nil end end @@ -5761,7 +5766,7 @@ end -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:_MarkerBlink() - --sefl:F("_MarkerBlink") + self:T2("_MarkerBlink") if self:IsAlive() ~= true then self:DisableIRMarker() return @@ -5772,7 +5777,8 @@ function CONTROLLABLE:_MarkerBlink() local _, _, unitBBHeight, _ = self:GetObjectSize() local unitPos = self:GetPositionVec3() - if not self.spot then + if self.timer:IsRunning() then + self:T2("Create Spot") local spot = Spot.createInfraRed( self.DCSUnit, { x = 0, y = (unitBBHeight + 1), z = 0 }, From 6e9727e26516b3b3e158abab689340df9836ef0d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 11 Mar 2025 16:29:31 +0100 Subject: [PATCH 113/158] xx --- Moose Development/Moose/Wrapper/Controllable.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 79d45ee33..c158eb0c6 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -5639,7 +5639,7 @@ end --- [GROUND] Create and enable a new IR Marker for the given controllable UNIT or GROUP. -- @param #CONTROLLABLE self -- @param #boolean EnableImmediately (Optionally) If true start up the IR Marker immediately. Else you need to call `myobject:EnableIRMarker()` later on. --- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. +-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. Defaults to 60 seconds. -- @return #CONTROLLABLE self function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime) self:T2("NewIRMarker") @@ -5751,7 +5751,12 @@ end -- @return #boolean outcome function CONTROLLABLE:HasIRMarker() self:T2("HasIRMarker") - if self.timer and self.timer:IsRunning() then return true end + if self:IsInstanceOf("GROUP") then + local units = self:GetUnits() or {} + for _,_unit in pairs(units) do + if _unit.timer and _unit.timer:IsRunning() then return true end + end + elseif self.timer and self.timer:IsRunning() then return true end return false end From 87c436ba34cbe7efb0779f79df4a69f3ec255996 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 12 Mar 2025 08:42:38 +0100 Subject: [PATCH 114/158] #STTS - remove from tree --- Moose Development/Moose/Modules.lua | 1 - Moose Development/Moose/Ops/ATIS.lua | 2 +- Moose Development/Moose/Sound/SoundOutput.lua | 2 +- Moose Development/Moose/Utilities/STTS.lua | 259 ------------------ Moose Setup/Moose.files | 1 - 5 files changed, 2 insertions(+), 263 deletions(-) delete mode 100644 Moose Development/Moose/Utilities/STTS.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 58c779ca7..0de9d2a9f 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -2,7 +2,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Enums.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Utils.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Profiler.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Templates.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/STTS.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/FiFo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' ) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 2366ff09b..70f4f0848 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -19,7 +19,7 @@ -- * Option to present information in imperial or metric units -- * Runway length and airfield elevation (optional) -- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (optional) --- * SRS Simple-Text-To-Speech (STTS) integration (no sound files necessary) +-- * SRS Simple-Text-To-Speech (MSRS) integration (no sound files necessary) -- -- === -- diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index 552689fc8..638dc9aec 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -5,7 +5,7 @@ -- ## Features: -- -- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions --- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (STTS) +-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (MSRS) -- -- === -- diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua deleted file mode 100644 index 1a27696f1..000000000 --- a/Moose Development/Moose/Utilities/STTS.lua +++ /dev/null @@ -1,259 +0,0 @@ ---- **Utilities** - DCS Simple Text-To-Speech (STTS). --- --- --- @module Utilities.STTS --- @image MOOSE.JPG - ---- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world) --- @type STTS --- @field #string DIRECTORY Path of the SRS directory. - ---- Simple Text-To-Speech --- --- Version 0.4 - Compatible with SRS version 1.9.6.0+ --- --- # DCS Modification Required --- --- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitization. --- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)" --- Do this without DCS running to allow mission scripts to use os functions. --- --- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE* --- --- # USAGE: --- --- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialize it --- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission. --- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts. --- --- Example calls: --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2) --- --- Arguments in order are: --- --- * Message to say, make sure not to use a newline (\n) ! --- * Frequency in MHz --- * Modulation - AM/FM --- * Volume - 1.0 max, 0.5 half --- * Name of the transmitter - ATC, RockFM etc --- * Coalition - 0 spectator, 1 red 2 blue --- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed --- * OPTIONAL - Speed -10 to +10 --- * OPTIONAL - Gender male, female or neuter --- * OPTIONAL - Culture - en-US, en-GB etc --- * OPTIONAL - Voice - a specific voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line --- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly --- --- --- ## Example --- --- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB") --- --- ## Example --- --- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" --- --- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB") --- --- Arguments in order are: --- --- * FULL path to the MP3 OR OGG to play --- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations --- * Modulation - AM/FM - to use multiple --- * Volume - 1.0 max, 0.5 half --- * Name of the transmitter - ATC, RockFM etc --- * Coalition - 0 spectator, 1 red 2 blue --- --- ## Example --- --- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only --- --- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0) --- --- @field #STTS -STTS = { - ClassName = "STTS", - DIRECTORY = "", - SRS_PORT = 5002, - GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json", - EXECUTABLE = "DCS-SR-ExternalAudio.exe" -} - ---- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER -STTS.DIRECTORY = "D:/DCS/_SRS" - ---- LOCAL SRS PORT - DEFAULT IS 5002 -STTS.SRS_PORT = 5002 - ---- Google credentials file -STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" - ---- DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING -STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" - ---- Function for UUID. -function STTS.uuid() - local random = math.random - local template = 'yxxx-xxxxxxxxxxxx' - return string.gsub( template, '[xy]', function( c ) - local v = (c == 'x') and random( 0, 0xf ) or random( 8, 0xb ) - return string.format( '%x', v ) - end ) -end - ---- Round a number. --- @param #number x Number. --- @param #number n Precision. -function STTS.round( x, n ) - n = math.pow( 10, n or 0 ) - x = x * n - if x >= 0 then - x = math.floor( x + 0.5 ) - else - x = math.ceil( x - 0.5 ) - end - return x / n -end - ---- Function returns estimated speech time in seconds. --- Assumptions for time calc: 100 Words per min, average of 5 letters for english word so --- --- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second --- --- So length of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: --- --- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min --- --- @param #number length can also be passed as #string --- @param #number speed Defaults to 1.0 --- @param #boolean isGoogle We're using Google TTS -function STTS.getSpeechTime(length,speed,isGoogle) - - local maxRateRatio = 3 - - speed = speed or 1.0 - isGoogle = isGoogle or false - - local speedFactor = 1.0 - if isGoogle then - speedFactor = speed - else - if speed ~= 0 then - speedFactor = math.abs( speed ) * (maxRateRatio - 1) / 10 + 1 - end - if speed < 0 then - speedFactor = 1 / speedFactor - end - end - - local wpm = math.ceil( 100 * speedFactor ) - local cps = math.floor( (wpm * 5) / 60 ) - - if type( length ) == "string" then - length = string.len( length ) - end - - return length/cps --math.ceil(length/cps) -end - ---- Text to speech function. -function STTS.TextToSpeech( message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS ) - if os == nil or io == nil then - env.info( "[DCS-STTS] LUA modules os or io are sanitized. skipping. " ) - return - end - - speed = speed or 1 - gender = gender or "female" - culture = culture or "" - voice = voice or "" - coalition = coalition or "0" - name = name or "ROBOT" - volume = 1 - speed = 1 - - message = message:gsub( "\"", "\\\"" ) - - local cmd = string.format( "start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name ) - - if voice ~= "" then - cmd = cmd .. string.format( " -V \"%s\"", voice ) - else - - if culture ~= "" then - cmd = cmd .. string.format( " -l %s", culture ) - end - - if gender ~= "" then - cmd = cmd .. string.format( " -g %s", gender ) - end - end - - if googleTTS == true then - cmd = cmd .. string.format( " -G \"%s\"", STTS.GOOGLE_CREDENTIALS ) - end - - if speed ~= 1 then - cmd = cmd .. string.format( " -s %s", speed ) - end - - if volume ~= 1.0 then - cmd = cmd .. string.format( " -v %s", volume ) - end - - if point and type( point ) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL( point ) - - lat = STTS.round( lat, 4 ) - lon = STTS.round( lon, 4 ) - alt = math.floor( alt ) - - cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) - end - - cmd = cmd .. string.format( " -t \"%s\"", message ) - - if string.len( cmd ) > 255 then - local filename = os.getenv( 'TMP' ) .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" - local script = io.open( filename, "w+" ) - script:write( cmd .. " && exit" ) - script:close() - cmd = string.format( "\"%s\"", filename ) - timer.scheduleFunction( os.remove, filename, timer.getTime() + 1 ) - end - - if string.len( cmd ) > 255 then - env.info( "[DCS-STTS] - cmd string too long" ) - env.info( "[DCS-STTS] TextToSpeech Command :\n" .. cmd .. "\n" ) - end - os.execute( cmd ) - - return STTS.getSpeechTime( message, speed, googleTTS ) -end - ---- Play mp3 function. --- @param #string pathToMP3 Path to the sound file. --- @param #string freqs Frequencies, e.g. "305, 256". --- @param #string modulations Modulations, e.g. "AM, FM". --- @param #string volume Volume, e.g. "0.5". -function STTS.PlayMP3( pathToMP3, freqs, modulations, volume, name, coalition, point ) - - local cmd = string.format( "start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1" ) - - if point and type( point ) == "table" and point.x then - local lat, lon, alt = coord.LOtoLL( point ) - - lat = STTS.round( lat, 4 ) - lon = STTS.round( lon, 4 ) - alt = math.floor( alt ) - - cmd = cmd .. string.format( " -L %s -O %s -A %s", lat, lon, alt ) - end - - env.info( "[DCS-STTS] MP3/OGG Command :\n" .. cmd .. "\n" ) - os.execute( cmd ) - -end diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 1f41c41a6..6f79e6933 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -3,7 +3,6 @@ Utilities/Utils.lua Utilities/Enums.lua Utilities/Profiler.lua Utilities/Templates.lua -Utilities/STTS.lua Utilities/FiFo.lua Utilities/Socket.lua From 9cdf550432853817924a4bb6d486a16d84392f4c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 12 Mar 2025 08:45:08 +0100 Subject: [PATCH 115/158] #PLAYERTASKMANAGER - Allow use of multiple drones for lasing of precision bombing tasks --- Moose Development/Moose/Ops/PlayerTask.lua | 323 ++++++++++++++------- 1 file changed, 218 insertions(+), 105 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index a5a99b20c..673c5095c 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -1935,6 +1935,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.TaskQueue = FIFO:New() -- Utilities.FiFo#FIFO self.TasksPerPlayer = FIFO:New() -- Utilities.FiFo#FIFO self.PrecisionTasks = FIFO:New() -- Utilities.FiFo#FIFO + self.LasingDroneSet = SET_OPSGROUP:New() -- Core.Set#SET_OPSGROUP --self.PlayerMenu = {} -- #table self.FlashPlayer = {} -- #table self.AllowFlash = false @@ -2348,6 +2349,7 @@ end -- @param Core.Point#COORDINATE HoldingPoint (Optional) Point where the drone should initially circle. If not set, defaults to BullsEye of the coalition. -- @param #number Alt (Optional) Altitude in feet. Only applies if using a FLIGHTGROUP object! Defaults to 10000. -- @param #number Speed (Optional) Speed in knots. Only applies if using a FLIGHTGROUP object! Defaults to 120. +-- @param #number MaxTravelDist (Optional) Max distance to travel to traget. Only applies if using a FLIGHTGROUP object! Defaults to 100 NM. -- @return #PLAYERTASKCONTROLLER self -- @usage -- -- Set up precision bombing, FlightGroup as lasing unit @@ -2362,35 +2364,56 @@ end -- ArmyGroup:Activate() -- taskmanager:EnablePrecisionBombing(ArmyGroup,1688) -- -function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint, Alt, Speed) +function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) self:T(self.lid.."EnablePrecisionBombing") + + if not self.LasingDroneSet then + self.LasingDroneSet = SET_OPSGROUP:New() + end + + local LasingDrone -- Ops.FlightGroup#FLIGHTGROUP FlightGroup + if FlightGroup then if FlightGroup.ClassName and (FlightGroup.ClassName == "FLIGHTGROUP" or FlightGroup.ClassName == "ARMYGROUP")then -- ok we have a FG - self.LasingDrone = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP FlightGroup - self.LasingDrone.playertask = {} - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.id = 0 + LasingDrone = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP FlightGroup + self.precisionbombing = true - self.LasingDrone:SetLaser(LaserCode) - self.LaserCode = LaserCode or 1688 - self.LasingDroneTemplate = self.LasingDrone:_GetTemplate(true) - self.LasingDroneAlt = Alt or 10000 - self.LasingDroneSpeed = Speed or 120 + + LasingDrone.playertask = {} + LasingDrone.playertask.id = 0 + LasingDrone.playertask.busy = false + LasingDrone.playertask.lasercode = LaserCode or 1688 + LasingDrone:SetLaser(LasingDrone.playertask.lasercode) + LasingDrone.playertask.template = LasingDrone:_GetTemplate(true) + LasingDrone.playertask.alt = Alt or 10000 + LasingDrone.playertask.speed = Speed or 120 + LasingDrone.playertask.maxtravel = UTILS.NMToMeters(MaxTravelDist or 50) + -- let it orbit the BullsEye if FG - if self.LasingDrone:IsFlightgroup() then - self.LasingDroneIsFlightgroup = true + if LasingDrone:IsFlightgroup() then + --settings.IsFlightgroup = true local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.Coalition )) if HoldingPoint then BullsCoordinate = HoldingPoint end - local Orbit = AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,self.LasingDroneAlt,self.LasingDroneSpeed) - self.LasingDrone:AddMission(Orbit) - elseif self.LasingDrone:IsArmygroup() then - self.LasingDroneIsArmygroup = true + local Orbit = AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,Alt,Speed) + Orbit:SetMissionAltitude(Alt) + LasingDrone:AddMission(Orbit) + elseif LasingDrone:IsArmygroup() then + --settings.IsArmygroup = true local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.Coalition )) if HoldingPoint then BullsCoordinate = HoldingPoint end local Orbit = AUFTRAG:NewONGUARD(BullsCoordinate) - self.LasingDrone:AddMission(Orbit) + LasingDrone:AddMission(Orbit) end + + self.LasingDroneSet:AddObject(FlightGroup) + + elseif FlightGroup.ClassName and (FlightGroup.ClassName == "SET_OPSGROUP") then --SET_OPSGROUP + FlightGroup:ForEachGroup( + function(group) + self:EnablePrecisionBombing(group,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) + end + ) else self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!") end @@ -2401,6 +2424,20 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi return self end +--- [User] Convenience function - add done or ground allowing precision laser-guided bombing on statics and "high-value" ground units (MBT etc) +-- @param #PLAYERTASKCONTROLLER self +-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FlightGroup (e.g. drone) to be used for lasing (one unit in one group only). +-- Can optionally be handed as Ops.ArmyGroup#ARMYGROUP - **Note** might not find an LOS spot or get lost on the way. Cannot island-hop. +-- @param #number LaserCode The lasercode to be used. Defaults to 1688. +-- @param Core.Point#COORDINATE HoldingPoint (Optional) Point where the drone should initially circle. If not set, defaults to BullsEye of the coalition. +-- @param #number Alt (Optional) Altitude in feet. Only applies if using a FLIGHTGROUP object! Defaults to 10000. +-- @param #number Speed (Optional) Speed in knots. Only applies if using a FLIGHTGROUP object! Defaults to 120. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddPrecisionBombingOpsGroup(FlightGroup,LaserCode,HoldingPoint, Alt, Speed) + self:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) + return self +end + --- [User] Allow precision laser-guided bombing on statics and "high-value" ground units (MBT etc) with player units lasing. -- @param #PLAYERTASKCONTROLLER self @@ -2922,99 +2959,155 @@ end -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() self:T(self.lid.."_CheckPrecisionTasks") + self:T({count=self.PrecisionTasks:Count(),enabled=self.precisionbombing}) if self.PrecisionTasks:Count() > 0 and self.precisionbombing then - if not self.LasingDrone or self.LasingDrone:IsDead() then - -- we need a new drone - self:E(self.lid.."Lasing drone is dead ... creating a new one!") - if self.LasingDrone then - self.LasingDrone:_Respawn(1,nil,true) - else - -- DONE: Handle ArmyGroup - if self.LasingDroneIsFlightgroup then - local FG = FLIGHTGROUP:New(self.LasingDroneTemplate) - FG:Activate() - self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + + -- alive checks + self.LasingDroneSet:ForEachGroup( + function(LasingDrone) + if not LasingDrone or LasingDrone:IsDead() then + -- we need a new drone + self:E(self.lid.."Lasing drone is dead ... creating a new one!") + if LasingDrone then + LasingDrone:_Respawn(1,nil,true) else - local FG = ARMYGROUP:New(self.LasingDroneTemplate) - FG:Activate() - self:EnablePrecisionBombing(FG,self.LaserCode or 1688) - end - end - return self - end - -- do we have a lasing unit assigned? - if self.LasingDrone and self.LasingDrone:IsAlive() then - if self.LasingDrone.playertask and (not self.LasingDrone.playertask.busy) then - -- not busy, get a task - self:T(self.lid.."Sending lasing unit to target") - local task = self.PrecisionTasks:Pull() -- Ops.PlayerTask#PLAYERTASK - self.LasingDrone.playertask.id = task.PlayerTaskNr - self.LasingDrone.playertask.busy = true - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.reachmessage = false - -- move the drone to target - if self.LasingDroneIsFlightgroup then - self.LasingDrone:CancelAllMissions() - local auftrag = AUFTRAG:NewORBIT_CIRCLE(task.Target:GetCoordinate(),self.LasingDroneAlt,self.LasingDroneSpeed) - self.LasingDrone:AddMission(auftrag) - elseif self.LasingDroneIsArmygroup then - local tgtcoord = task.Target:GetCoordinate() - local tgtzone = ZONE_RADIUS:New("ArmyGroup-"..math.random(1,10000),tgtcoord:GetVec2(),3000) - local finalpos=nil -- Core.Point#COORDINATE - for i=1,50 do - finalpos = tgtzone:GetRandomCoordinate(2500,0,{land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.SHALLOW_WATER}) - if finalpos then - if finalpos:IsLOS(tgtcoord,0) then - break - end + --[[ + -- DONE: Handle ArmyGroup + if LasingDrone:IsFlightgroup() then + local FG = FLIGHTGROUP:New(LasingDroneTemplate) + FG:Activate() + self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + else + local FG = ARMYGROUP:New(LasingDroneTemplate) + FG:Activate() + self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + end -- if LasingDroneIsFlightgroup + --]] + end -- if LasingDrone + end -- if not LasingDrone + end -- function + ) + + local function SelectDrone(coord) + local selected = nil + local mindist = math.huge + local dist = math.huge + self.LasingDroneSet:ForEachGroup( + function(grp) + if grp.playertask and (not grp.playertask.busy) then + local gc = grp:GetCoordinate() + if coord and gc then + dist = coord:Get2DDistance(gc) + end + if dist < mindist then + selected = grp + mindist = dist end end - if finalpos then - self.LasingDrone:CancelAllMissions() - -- yeah we got one - local auftrag = AUFTRAG:NewARMOREDGUARD(finalpos,"Off road") - self.LasingDrone:AddMission(auftrag) - else - -- could not find LOS position! - self:E("***Could not find LOS position to post ArmyGroup for lasing!") - self.LasingDrone.playertask.id = 0 - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.reachmessage = false - end end - self.PrecisionTasks:Push(task,task.PlayerTaskNr) - elseif self.LasingDrone.playertask and self.LasingDrone.playertask.busy then + ) + return selected + end + + local task = self.PrecisionTasks:Pull() -- Ops.PlayerTask#PLAYERTASK + local taskpt = task.Target:GetCoordinate() + + local SelectedDrone = SelectDrone(taskpt) -- Ops.OpsGroup#OPSGROUP + + -- do we have a lasing unit assignable? + if SelectedDrone and SelectedDrone:IsAlive() then + if SelectedDrone.playertask and (not SelectedDrone.playertask.busy) then + -- not busy, get a task + self:T(self.lid.."Sending lasing unit to target") + local isassigned = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) + -- distance check + local startpoint = SelectedDrone:GetCoordinate() + local endpoint = task.Target:GetCoordinate() + local dist = math.huge + if startpoint and endpoint then + dist = startpoint:Get2DDistance(endpoint) + end + if dist <= SelectedDrone.playertask.maxtravel and (not isassigned) then + SelectedDrone.playertask.id = task.PlayerTaskNr + SelectedDrone.playertask.busy = true + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.reachmessage = false + -- move the drone to target + if SelectedDrone:IsFlightgroup() then + SelectedDrone:CancelAllMissions() + local auftrag = AUFTRAG:NewORBIT_CIRCLE(task.Target:GetCoordinate(),SelectedDrone.playertask.alt,SelectedDrone.playertask.speed) + SelectedDrone:AddMission(auftrag) + elseif SelectedDrone:IsArmygroup() then + local tgtcoord = task.Target:GetCoordinate() + local tgtzone = ZONE_RADIUS:New("ArmyGroup-"..math.random(1,10000),tgtcoord:GetVec2(),3000) + local finalpos=nil -- Core.Point#COORDINATE + for i=1,50 do + finalpos = tgtzone:GetRandomCoordinate(2500,0,{land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.SHALLOW_WATER}) + if finalpos then + if finalpos:IsLOS(tgtcoord,0) then + break + end + end + end + if finalpos then + SelectedDrone:CancelAllMissions() + -- yeah we got one + local auftrag = AUFTRAG:NewARMOREDGUARD(finalpos,"Off road") + SelectedDrone:AddMission(auftrag) + else + -- could not find LOS position! + self:E("***Could not find LOS position to post ArmyGroup for lasing!") + SelectedDrone.playertask.id = 0 + SelectedDrone.playertask.busy = false + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.reachmessage = false + end + end + else + self:T(self.lid.."Lasing unit too far from target") + end + + end + end + + self.PrecisionTasks:Push(task,task.PlayerTaskNr) + + + local function DronesWithTask(SelectedDrone) + -- handle drones with a task + if SelectedDrone.playertask and SelectedDrone.playertask.busy then -- drone is busy, set up laser when over target - local task = self.PrecisionTasks:ReadByID(self.LasingDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK + local task = self.PrecisionTasks:ReadByID(SelectedDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK self:T("Looking at Task: "..task.PlayerTaskNr.." Type: "..task.Type.." State: "..task:GetState()) if (not task) or task:GetState() == "Done" or task:GetState() == "Stopped" then -- we're done here - local task = self.PrecisionTasks:PullByID(self.LasingDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK + local task = self.PrecisionTasks:PullByID(SelectedDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK self:_CheckTaskQueue() task = nil - if self.LasingDrone:IsLasing() then - self.LasingDrone:__LaserOff(-1) + if SelectedDrone:IsLasing() then + SelectedDrone:__LaserOff(-1) end - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.id = 0 - self.LasingDrone.playertask.reachmessage = false + SelectedDrone.playertask.busy = false + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.id = 0 + SelectedDrone.playertask.reachmessage = false self:T(self.lid.."Laser Off") else -- not done yet - local dcoord = self.LasingDrone:GetCoordinate() + self:T(self.lid.."Not done yet") + local dcoord = SelectedDrone:GetCoordinate() local tcoord = task.Target:GetCoordinate() tcoord.y = tcoord.y + 2 local dist = dcoord:Get2DDistance(tcoord) + self:T(self.lid.."Dist "..dist) -- close enough? - if dist < 3000 and not self.LasingDrone:IsLasing() then + if dist < 3000 and not SelectedDrone:IsLasing() then self:T(self.lid.."Laser On") - self.LasingDrone:__LaserOn(-1,tcoord) - self.LasingDrone.playertask.inreach = true - if not self.LasingDrone.playertask.reachmessage then + SelectedDrone:__LaserOn(-1,tcoord) + SelectedDrone.playertask.inreach = true + if not SelectedDrone.playertask.reachmessage then --local textmark = self.gettext:GetEntry("FLARETASK",self.locale) - self.LasingDrone.playertask.reachmessage = true + SelectedDrone.playertask.reachmessage = true local clients = task:GetClients() local text = "" for _,playername in pairs(clients) do @@ -3022,7 +3115,7 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() local ttsplayername = playername if self.customcallsigns[playername] then ttsplayername = self.customcallsigns[playername] - end + end -- --text = string.format("%s, %s, pointer over target for task %03d, lasing!", playername, self.MenuName or self.Name, task.PlayerTaskNr) text = string.format(pointertext, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr) if not self.NoScreenOutput then @@ -3034,18 +3127,21 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() ) if client then local m = MESSAGE:New(text,15,"Tasking"):ToClient(client) - end - end - end + end -- + end -- + end -- if self.UseSRS then self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) - end - end - end - end - end - end - end + end -- + end -- + end -- + end -- end else + end -- end handle drones with a task + end -- end function + + self.LasingDroneSet:ForEachGroup(DronesWithTask) + + end -- return self end @@ -3558,6 +3654,22 @@ function PLAYERTASKCONTROLLER:_FlashInfo() return self end +--- [Internal] Find matching drone for precision bombing task, if any is assigned. +-- @param #PLAYERTASKCONTROLLER self +-- @param #number ID Task ID to look for +-- @return Ops.OpsGroup#OPSGROUP Drone +function PLAYERTASKCONTROLLER:_FindLasingDroneForTaskID(ID) + local drone = nil + self.LasingDroneSet:ForEachGroup( + function(grp) + if grp and grp:IsAlive() and grp.playertask and grp.playertask.id and grp.playertask.id == ID then + drone = grp + end + end + ) + return drone +end + --- [Internal] Show active task info -- @param #PLAYERTASKCONTROLLER self -- @param Ops.PlayerTask#PLAYERTASK Task @@ -3585,6 +3697,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local Elevation = Coordinate:GetLandHeight() or 0 -- meters local CoordText = "" local CoordTextLLDM = nil + local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else @@ -3620,14 +3733,14 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure) -- Prec bombing if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then - if self.LasingDrone and self.LasingDrone.playertask then + if LasingDrone and LasingDrone.playertask then local yes = self.gettext:GetEntry("YES",self.locale) local no = self.gettext:GetEntry("NO",self.locale) - local inreach = self.LasingDrone.playertask.inreach == true and yes or no - local islasing = self.LasingDrone:IsLasing() == true and yes or no + local inreach = LasingDrone.playertask.inreach == true and yes or no + local islasing = LasingDrone:IsLasing() == true and yes or no local prectext = self.gettext:GetEntry("POINTERTARGETREPORT",self.locale) prectext = string.format(prectext,inreach,islasing) - text = text .. prectext.." ("..self.LaserCode..")" + text = text .. prectext.." ("..LasingDrone.playertask.lasercode..")" end end -- Buddylasing @@ -3732,7 +3845,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText) -- POINTERTARGETLASINGTTS = ". Pointer over target and lasing." if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then - if self.LasingDrone.playertask.inreach and self.LasingDrone:IsLasing() then + if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing() then local lasingtext = self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) ttstext = ttstext .. lasingtext end From 31d0410284364c177d7e2eefbe6a30fc3e35a337 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 12 Mar 2025 08:56:13 +0100 Subject: [PATCH 116/158] #TEMPLATES remove files --- Moose Development/Moose/Modules.lua | 1 - .../Moose/Utilities/Templates.lua | 612 ------------------ 2 files changed, 613 deletions(-) delete mode 100644 Moose Development/Moose/Utilities/Templates.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 0de9d2a9f..cd3b1e4ac 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -1,7 +1,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Enums.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Utils.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Profiler.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Templates.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/FiFo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' ) diff --git a/Moose Development/Moose/Utilities/Templates.lua b/Moose Development/Moose/Utilities/Templates.lua deleted file mode 100644 index 52a648a9a..000000000 --- a/Moose Development/Moose/Utilities/Templates.lua +++ /dev/null @@ -1,612 +0,0 @@ ---- **Utilities** - Templates. --- --- DCS unit templates --- --- @module Utilities.Templates --- @image MOOSE.JPG - ---- TEMPLATE class. --- @type TEMPLATE --- @field #string ClassName Name of the class. - ---- *Templates* --- --- === --- --- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg) --- --- Get DCS templates from thin air. --- --- # Ground Units --- --- Ground units. --- --- # Naval Units --- --- Ships are not implemented yet. --- --- # Aircraft --- --- ## Airplanes --- --- Airplanes are not implemented yet. --- --- ## Helicopters --- --- Helicopters are not implemented yet. --- --- @field #TEMPLATE -TEMPLATE = { - ClassName = "TEMPLATE", - Ground = {}, - Naval = {}, - Airplane = {}, - Helicopter = {}, -} - ---- Ground unit type names. --- @type TEMPLATE.TypeGround --- @param #string InfantryAK -TEMPLATE.TypeGround={ - InfantryAK="Infantry AK", - ParatrooperAKS74="Paratrooper AKS-74", - ParatrooperRPG16="Paratrooper RPG-16", - SoldierWWIIUS="soldier_wwii_us", - InfantryM248="Infantry M249", - SoldierM4="Soldier M4", -} - ---- Naval unit type names. --- @type TEMPLATE.TypeNaval --- @param #string Ticonderoga -TEMPLATE.TypeNaval={ - Ticonderoga="TICONDEROG", -} - ---- Rotary wing unit type names. --- @type TEMPLATE.TypeAirplane --- @param #string A10C -TEMPLATE.TypeAirplane={ - A10C="A-10C", -} - ---- Rotary wing unit type names. --- @type TEMPLATE.TypeHelicopter --- @param #string AH1W -TEMPLATE.TypeHelicopter={ - AH1W="AH-1W", -} - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Ground Template -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Get template for ground units. --- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. --- @param #string GroupName Name of the spawned group. **Must be unique!** --- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. --- @param DCS#Vec3 Vec3 Position of the group and the first unit. --- @param #number Nunits Number of units. Default 1. --- @param #number Radius Spawn radius for additonal units in meters. Default 50 m. --- @return #table Template Template table. -function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) - - -- Defaults. - TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4 - GroupName=GroupName or "Ground-1" - CountryID=CountryID or country.id.USA - Vec3=Vec3 or {x=0, y=0, z=0} - Nunits=Nunits or 1 - Radius=Radius or 50 - - - -- Get generic template. - local template=UTILS.DeepCopy(TEMPLATE.GenericGround) - - -- Set group name. - template.name=GroupName - - -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. - template.CountryID=CountryID - template.CoalitionID=coalition.getCountryCoalition(template.CountryID) - template.CategoryID=Unit.Category.GROUND_UNIT - - -- Set first unit. - template.units[1].type=TypeName - template.units[1].name=GroupName.."-1" - - if Vec3 then - TEMPLATE.SetPositionFromVec3(template, Vec3) - end - - TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) - - return template -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Naval Template -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Get template for ground units. --- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. --- @param #string GroupName Name of the spawned group. **Must be unique!** --- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. --- @param DCS#Vec3 Vec3 Position of the group and the first unit. --- @param #number Nunits Number of units. Default 1. --- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. --- @return #table Template Template table. -function TEMPLATE.GetNaval(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) - - -- Defaults. - TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga - GroupName=GroupName or "Naval-1" - CountryID=CountryID or country.id.USA - Vec3=Vec3 or {x=0, y=0, z=0} - Nunits=Nunits or 1 - Radius=Radius or 500 - - - -- Get generic template. - local template=UTILS.DeepCopy(TEMPLATE.GenericNaval) - - -- Set group name. - template.name=GroupName - - -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. - template.CountryID=CountryID - template.CoalitionID=coalition.getCountryCoalition(template.CountryID) - template.CategoryID=Unit.Category.SHIP - - -- Set first unit. - template.units[1].type=TypeName - template.units[1].name=GroupName.."-1" - - if Vec3 then - TEMPLATE.SetPositionFromVec3(template, Vec3) - end - - TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) - - return template -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Aircraft Template -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Get template for fixed wing units. --- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. --- @param #string GroupName Name of the spawned group. **Must be unique!** --- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. --- @param DCS#Vec3 Vec3 Position of the group and the first unit. --- @param #number Nunits Number of units. Default 1. --- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. --- @return #table Template Template table. -function TEMPLATE.GetAirplane(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) - - -- Defaults. - TypeName=TypeName or TEMPLATE.TypeAirplane.A10C - GroupName=GroupName or "Airplane-1" - CountryID=CountryID or country.id.USA - Vec3=Vec3 or {x=0, y=1000, z=0} - Nunits=Nunits or 1 - Radius=Radius or 100 - - local template=TEMPLATE._GetAircraft(true, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) - - return template -end - ---- Get template for fixed wing units. --- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. --- @param #string GroupName Name of the spawned group. **Must be unique!** --- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. --- @param DCS#Vec3 Vec3 Position of the group and the first unit. --- @param #number Nunits Number of units. Default 1. --- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. --- @return #table Template Template table. -function TEMPLATE.GetHelicopter(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) - - -- Defaults. - TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W - GroupName=GroupName or "Helicopter-1" - CountryID=CountryID or country.id.USA - Vec3=Vec3 or {x=0, y=500, z=0} - Nunits=Nunits or 1 - Radius=Radius or 100 - - -- Limit unis to 4. - Nunits=math.min(Nunits, 4) - - local template=TEMPLATE._GetAircraft(false, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) - - return template -end - - ---- Get template for aircraft units. --- @param #boolean Airplane If true, this is a fixed wing. Else, rotary wing. --- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. --- @param #string GroupName Name of the spawned group. **Must be unique!** --- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. --- @param DCS#Vec3 Vec3 Position of the group and the first unit. --- @param #number Nunits Number of units. Default 1. --- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. --- @return #table Template Template table. -function TEMPLATE._GetAircraft(Airplane, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) - - -- Defaults. - TypeName=TypeName - GroupName=GroupName or "Aircraft-1" - CountryID=CountryID or country.id.USA - Vec3=Vec3 or {x=0, y=0, z=0} - Nunits=Nunits or 1 - Radius=Radius or 100 - - -- Get generic template. - local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft) - - -- Set group name. - template.name=GroupName - - -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. - template.CountryID=CountryID - template.CoalitionID=coalition.getCountryCoalition(template.CountryID) - if Airplane then - template.CategoryID=Unit.Category.AIRPLANE - else - template.CategoryID=Unit.Category.HELICOPTER - end - - -- Set first unit. - template.units[1].type=TypeName - template.units[1].name=GroupName.."-1" - - -- Set position. - if Vec3 then - TEMPLATE.SetPositionFromVec3(template, Vec3) - end - - -- Set number of units. - TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) - - return template -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Misc Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Set the position of the template. --- @param #table Template The template to be modified. --- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group. -function TEMPLATE.SetPositionFromVec2(Template, Vec2) - - Template.x=Vec2.x - Template.y=Vec2.y - - for _,unit in pairs(Template.units) do - unit.x=Vec2.x - unit.y=Vec2.y - end - - Template.route.points[1].x=Vec2.x - Template.route.points[1].y=Vec2.y - Template.route.points[1].alt=0 --TODO: Use land height. - -end - ---- Set the position of the template. --- @param #table Template The template to be modified. --- @param DCS#Vec3 Vec3 Position vector of the group. -function TEMPLATE.SetPositionFromVec3(Template, Vec3) - - local Vec2={x=Vec3.x, y=Vec3.z} - - TEMPLATE.SetPositionFromVec2(Template, Vec2) - -end - ---- Set the position of the template. --- @param #table Template The template to be modified. --- @param #number N Total number of units in the group. --- @param Core.Point#COORDINATE Coordinate Position of the first unit. --- @param #number Radius Radius in meters to randomly place the additional units. -function TEMPLATE.SetUnits(Template, N, Coordinate, Radius) - - local units=Template.units - - local unit1=units[1] - - local Vec3=Coordinate:GetVec3() - - unit1.x=Vec3.x - unit1.y=Vec3.z - unit1.alt=Vec3.y - - for i=2,N do - units[i]=UTILS.DeepCopy(unit1) - end - - for i=1,N do - local unit=units[i] - unit.name=string.format("%s-%d", Template.name, i) - if i>1 then - local vec2=Coordinate:GetRandomCoordinateInRadius(Radius, 5):GetVec2() - unit.x=vec2.x - unit.y=vec2.y - unit.alt=unit1.alt - end - end - -end - ---- Set the position of the template. --- @param #table Template The template to be modified. --- @param Wrapper.Airbase#AIRBASE AirBase The airbase where the aircraft are spawned. --- @param #table ParkingSpots List of parking spot IDs. Every unit needs one! --- @param #boolean EngineOn If true, aircraft are spawned hot. -function TEMPLATE.SetAirbase(Template, AirBase, ParkingSpots, EngineOn) - - -- Airbase ID. - local AirbaseID=AirBase:GetID() - - -- Spawn point. - local point=Template.route.points[1] - - -- Set ID. - if AirBase:IsAirdrome() then - point.airdromeId=AirbaseID - else - point.helipadId=AirbaseID - point.linkUnit=AirbaseID - end - - if EngineOn then - point.action=COORDINATE.WaypointAction.FromParkingAreaHot - point.type=COORDINATE.WaypointType.TakeOffParkingHot - else - point.action=COORDINATE.WaypointAction.FromParkingArea - point.type=COORDINATE.WaypointType.TakeOffParking - end - - for i,unit in ipairs(Template.units) do - unit.parking_id=ParkingSpots[i] - end - -end - ---- Add a waypoint. --- @param #table Template The template to be modified. --- @param #table Waypoint Waypoint table. -function TEMPLATE.AddWaypoint(Template, Waypoint) - - table.insert(Template.route.points, Waypoint) - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Generic Ground Template -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -TEMPLATE.GenericGround= -{ - ["visible"] = false, - ["tasks"] = {}, -- end of ["tasks"] - ["uncontrollable"] = false, - ["task"] = "Ground Nothing", - ["route"] = - { - ["spans"] = {}, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 0, - ["x"] = 0, - ["ETA_locked"] = true, - ["speed"] = 0, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = nil, - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["skill"] = "Average", - ["type"] = "Infantry AK", - ["unitId"] = nil, - ["y"] = 0, - ["x"] = 0, - ["name"] = "Infantry AK-47 Rus", - ["heading"] = 0, - ["playerCanDrive"] = false, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 0, - ["x"] = 0, - ["name"] = "Infantry AK-47 Rus", - ["start_time"] = 0, -} - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Generic Ship Template -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -TEMPLATE.GenericNaval= -{ - ["visible"] = false, - ["tasks"] = {}, -- end of ["tasks"] - ["uncontrollable"] = false, - ["route"] = - { - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 0, - ["x"] = 0, - ["ETA_locked"] = true, - ["speed"] = 0, - ["action"] = "Turning Point", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = nil, - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["skill"] = "Average", - ["type"] = "TICONDEROG", - ["unitId"] = nil, - ["y"] = 0, - ["x"] = 0, - ["name"] = "Naval-1-1", - ["heading"] = 0, - ["modulation"] = 0, - ["frequency"] = 127500000, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 0, - ["x"] = 0, - ["name"] = "Naval-1", - ["start_time"] = 0, -} - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Generic Aircraft Template -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -TEMPLATE.GenericAircraft= -{ - ["groupId"] = nil, - ["name"] = "Rotary-1", - ["uncontrolled"] = false, - ["hidden"] = false, - ["task"] = "Nothing", - ["y"] = 0, - ["x"] = 0, - ["start_time"] = 0, - ["communication"] = true, - ["radioSet"] = false, - ["frequency"] = 127.5, - ["modulation"] = 0, - ["taskSelected"] = true, - ["tasks"] = {}, -- end of ["tasks"] - ["route"] = - { - ["points"] = - { - [1] = - { - ["y"] = 0, - ["x"] = 0, - ["alt"] = 1000, - ["alt_type"] = "BARO", - ["action"] = "Turning Point", - ["type"] = "Turning Point", - ["airdromeId"] = nil, - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = {}, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["ETA"] = 0, - ["ETA_locked"] = true, - ["speed"] = 100, - ["speed_locked"] = true, - ["formation_template"] = "", - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["units"] = - { - [1] = - { - ["name"] = "Rotary-1-1", - ["unitId"] = nil, - ["type"] = "AH-1W", - ["onboard_num"] = "050", - ["livery_id"] = "USA X Black", - ["skill"] = "High", - ["ropeLength"] = 15, - ["speed"] = 0, - ["x"] = 0, - ["y"] = 0, - ["alt"] = 10, - ["alt_type"] = "BARO", - ["heading"] = 0, - ["psi"] = 0, - ["parking"] = nil, - ["parking_id"] = nil, - ["payload"] = - { - ["pylons"] = {}, -- end of ["pylons"] - ["fuel"] = "1250.0", - ["flare"] = 30, - ["chaff"] = 30, - ["gun"] = 100, - }, -- end of ["payload"] - ["callsign"] = - { - [1] = 2, - [2] = 1, - [3] = 1, - ["name"] = "Springfield11", - }, -- end of ["callsign"] - }, -- end of [1] - }, -- end of ["units"] -} -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - From 32f0bb33c3fcfe7a84004869adb3848e9b6e483d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 13 Mar 2025 10:50:15 +0100 Subject: [PATCH 117/158] #ZONE - Trigger added OnAfterZoneEmpty and OnAfterObjectDead --- Moose Development/Moose/Core/Zone.lua | 48 ++++++++++++++++++++++- Moose Development/Moose/Wrapper/Group.lua | 2 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 32ec18222..4e97f195b 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -605,10 +605,13 @@ function ZONE_BASE:Trigger(Objects) self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning") self:AddTransition("*","EnteredZone","*") self:AddTransition("*","LeftZone","*") + self:AddTransition("*","ZoneEmpty","*") + self:AddTransition("*","ObjectDead","*") self:AddTransition("*","TriggerRunCheck","*") self:AddTransition("*","TriggerStop","TriggerStopped") self:TriggerStart() self.checkobjects = Objects + self.ObjectsInZone = false if UTILS.IsInstanceOf(Objects,"SET_BASE") then self.objectset = Objects.Set else @@ -646,6 +649,22 @@ function ZONE_BASE:Trigger(Objects) -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable leaving the zone. + + --- On After "ObjectDead" event. An observed object has left the zone. + -- @function [parent=#ZONE_BASE] OnAfterObjectDead + -- @param #ZONE_BASE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable which died. Might be nil. + + --- On After "ZoneEmpty" event. All observed objects have left the zone or are dead. + -- @function [parent=#ZONE_BASE] OnAfterZoneEmpty + -- @param #ZONE_BASE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + end --- (Internal) Check the assigned objects for being in/out of the zone @@ -659,9 +678,13 @@ function ZONE_BASE:_TriggerCheck(fromstart) -- just earmark everyone in/out for _,_object in pairs(objectset) do local obj = _object -- Wrapper.Controllable#CONTROLLABLE - if not obj.TriggerInZone then obj.TriggerInZone = {} end + if not obj.TriggerInZone then + obj.TriggerInZone = {} + obj.TriggerZoneDeadNotification = false + end if obj and obj:IsAlive() and self:IsCoordinateInZone(obj:GetCoordinate()) then obj.TriggerInZone[self.ZoneName] = true + self.ObjectsInZone = true else obj.TriggerInZone[self.ZoneName] = false end @@ -669,6 +692,7 @@ function ZONE_BASE:_TriggerCheck(fromstart) end else -- Check for changes + local objcount = 0 for _,_object in pairs(objectset) do local obj = _object -- Wrapper.Controllable#CONTROLLABLE if obj and obj:IsAlive() then @@ -683,11 +707,20 @@ function ZONE_BASE:_TriggerCheck(fromstart) -- is obj in zone? local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) + if inzone and obj.TriggerInZone[self.ZoneName] then + -- just count + objcount = objcount + 1 + self.ObjectsInZone = true + obj.TriggerZoneDeadNotification = false + end if inzone and not obj.TriggerInZone[self.ZoneName] then -- wasn't in zone before --self:I("Newly entered") self:__EnteredZone(0.5,obj) obj.TriggerInZone[self.ZoneName] = true + objcount = objcount + 1 + self.ObjectsInZone = true + obj.TriggerZoneDeadNotification = false elseif (not inzone) and obj.TriggerInZone[self.ZoneName] then -- has left the zone --self:I("Newly left") @@ -696,8 +729,21 @@ function ZONE_BASE:_TriggerCheck(fromstart) else --self:I("Not left or not entered, or something went wrong!") end + else + -- object dead + if not obj.TriggerZoneDeadNotification == true then + obj.TriggerInZone = nil + self:__ObjectDead(0.5,obj) + obj.TriggerZoneDeadNotification = true + end end end + -- zone empty? + if objcount == 0 and self.ObjectsInZone == true then + -- zone was not but is now empty + self.ObjectsInZone = false + self:__ZoneEmpty(0.5) + end end return self end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 75ece35ee..9c183238d 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2816,7 +2816,7 @@ do -- Event Handling self:EventDispatcher():Reset( self ) - for UnitID, UnitData in pairs( self:GetUnits() ) do + for UnitID, UnitData in pairs( self:GetUnits() or {}) do UnitData:ResetEvents() end From 3c57928f4635bdf70df11e73022d449b22462bd8 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 13 Mar 2025 16:40:57 +0100 Subject: [PATCH 118/158] Update Airbase.lua - Fixed bug in counting the number of parking spots per terminal type --- Moose Development/Moose/Wrapper/Airbase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 21fd34fda..5bb9af837 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1638,7 +1638,7 @@ function AIRBASE:_InitParkingSpots() self.NparkingTotal=self.NparkingTotal+1 for _,terminalType in pairs(AIRBASE.TerminalType) do - if self._CheckTerminalType(terminalType, park.TerminalType) then + if self._CheckTerminalType(park.TerminalType, terminalType) then self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 end end From 6028c91f81052ad4cc256f0edbcf1268b337ce74 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 14 Mar 2025 10:40:49 +0100 Subject: [PATCH 119/158] #NET small fix if PlayerSlot remains nil --- Moose Development/Moose/Wrapper/Net.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua index 7316075fd..716954717 100644 --- a/Moose Development/Moose/Wrapper/Net.lua +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -207,10 +207,10 @@ function NET:_EventHandler(EventData) local PlayerID = self:GetPlayerIDByName(name) or "none" local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit) if not PlayerSide then PlayerSide = EventData.IniCoalition end - if not PlayerSlot then PlayerSlot = EventData.IniUnit:GetID() end + if not PlayerSlot then PlayerSlot = EventData.IniUnit:GetID() or -1 end local TNow = timer.getTime() - self:T(self.lid.."Event for: "..name.." | UCID: "..ucid .. " | ID/SIDE/SLOT "..PlayerID.."/"..PlayerSide.."/"..PlayerSlot) + --self:T(self.lid.."Event for: "..name.." | UCID: "..ucid .. " | ID/SIDE/SLOT "..PlayerID.."/"..PlayerSide.."/"..PlayerSlot) -- Joining if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then From c9a09c2fc9ab8cd79ddd5a462c451b2002c3a5e7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 15 Mar 2025 10:56:13 +0100 Subject: [PATCH 120/158] #SOUND/MSRS - small fix to delete the "--ssml" tag if google provider is used in conjunction with a file based sound output. --- Moose Development/Moose/Sound/SRS.lua | 144 ++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 3726f309f..c2e9cf152 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -267,6 +267,135 @@ MSRS.version="0.3.3" --- Voices -- @type MSRS.Voices MSRS.Voices = { + Amazon = { + Generative = { + en_AU = { + Olivia = "Olivia", + }, + en_GB = { + Amy = "Amy", + }, + en_US = { + Danielle = "Danielle", + Joanna = "Joanna", + Ruth = "Ruth", + Stephen = "Stephen", + }, + fr_FR = { + ["Léa"] = "Léa", + ["Rémi"] = "Rémi", + }, + de_DE = { + Vicki = "Vicki", + Daniel = "Daniel", + }, + it_IT = { + Bianca = "Bianca", + Adriano = "Adriano", + }, + es_ES = { + Lucia = "Lucia", + Sergio = "Sergio", + }, + }, + LongForm = { + en_US = { + Danielle = "Danielle", + Gregory = "Gregory", + Ivy = "Ivy", + Ruth = "Ruth", + Patrick = "Patrick", + }, + es_ES = { + Alba = "Alba", + ["Raúl"] = "Raúl", + }, + }, + Neural = { + en_AU = { + Olivia = "Olivia", + }, + en_GB = { + Amy = "Amy", + Emma = "Emma", + Brian = "Brian", + Arthur = "Arthur", + }, + en_US = { + Danielle = "Danielle", + Gregory = "Gregory", + Ivy = "Ivy", + Joanna = "Joanna", + Kendra = "Kendra", + Kimberly = "Kimberly", + Salli = "Salli", + Joey = "Joey", + Kevin = "Kevin", + Ruth = "Ruth", + Stephen = "Stephen", + }, + fr_FR = { + ["Léa"] = "Léa", + ["Rémi"] = "Rémi", + }, + de_DE = { + Vicki = "Vicki", + Daniel = "Daniel", + }, + it_IT = { + Bianca = "Bianca", + Adriano = "Adriano", + }, + es_ES = { + Lucia = "Lucia", + Sergio = "Sergio", + }, + }, + Standard = { + en_AU = { + Nicole = "Nicole", + Russel = "Russel", + }, + en_GB = { + Amy = "Amy", + Emma = "Emma", + Brian = "Brian", + }, + en_IN = { + Aditi = "Aditi", + Raveena = "Raveena", + }, + en_US = { + Ivy = "Ivy", + Joanna = "Joanna", + Kendra = "Kendra", + Kimberly = "Kimberly", + Salli = "Salli", + Joey = "Joey", + Kevin = "Kevin", + }, + fr_FR = { + Celine = "Celine", + ["Léa"] = "Léa", + Mathieu = "Mathieu", + }, + de_DE = { + Marlene = "Marlene", + Vicki = "Vicki", + Hans = "Hans", + }, + it_IT = { + Carla = "Carla", + Bianca = "Bianca", + Giorgio = "Giorgio", + }, + es_ES = { + Conchita = "Conchita", + Lucia = "Lucia", + Enrique = "Enrique", + }, + }, + }, Microsoft = { -- working ones if not using gRPC and MS ["Hedda"] = "Microsoft Hedda Desktop", -- de-DE ["Hazel"] = "Microsoft Hazel Desktop", -- en-GB @@ -974,7 +1103,7 @@ end -- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default) -- - `MSRS.Provider.GOOGLE`: Google Cloud -- - `MSRS.Provider.AZURE`: Microsoft Azure (only with DCS-gRPC backend) --- - `MSRS.Provier.AMAZON`: Amazone Web Service (only with DCS-gRPC backend) +-- - `MSRS.Provier.AMAZON`: Amazon Web Service (only with DCS-gRPC backend) -- -- Note that all providers except Microsoft Windows need as additonal information the credentials of your account. -- @@ -1184,7 +1313,8 @@ function MSRS:PlaySoundFile(Soundfile, Delay) -- Append file. command=command..' --file="'..tostring(soundfile)..'"' - + command=string.gsub(command,"--ssml","-h") + -- Execute command. self:_ExecCommand(command) @@ -1442,7 +1572,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp elseif self.provider==MSRS.Provider.WINDOWS then -- Nothing to do. else - self:E("ERROR: SRS only supports WINWOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as ") + self:E("ERROR: SRS only supports WINDOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as AWS and Azure.") end if not UTILS.FileExists(fullPath) then @@ -1477,7 +1607,7 @@ function MSRS:_ExecCommand(command) if self.UsePowerShell == true then filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".ps1" batContent = command .. "\'" - self:I({batContent=batContent}) + self:T({batContent=batContent}) end local script=io.open(filename, "w+") @@ -1660,7 +1790,7 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab ssml=string.format("%s", gender, language, Text) end end - + for _,freq in pairs(Frequencies) do self:T("Calling GRPC.tts with the following parameter:") self:T({ssml=ssml, freq=freq, options=options}) @@ -1986,7 +2116,7 @@ end -- @param Core.Point#COORDINATE coordinate Coordinate to be used -- @return #MSRSQUEUE.Transmission Radio transmission table. function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate) - + self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label}) if self.TransmitOnlyWithPlayers then if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then return self @@ -2026,7 +2156,7 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr transmission.volume = volume or msrs.volume transmission.label = label or msrs.Label transmission.coordinate = coordinate or msrs.coordinate - + -- Add transmission to queue. self:AddTransmission(transmission) From 9ac4f136aade6a49e3de2d5289f1bdcfc2fd2544 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 15 Mar 2025 16:29:24 +0100 Subject: [PATCH 121/158] #MANTIS - extend scaling for short/point systems to make them more reactive --- Moose Development/Moose/Functional/Mantis.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index fd6dea106..b5e809816 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -355,6 +355,14 @@ MANTIS.SamType = { POINT = "Point", } +--- SAM Radiusscale +-- @type MANTIS.radiusscale +MANTIS.radiusscale = {} +MANTIS.radiusscale[MANTIS.SamType.LONG] = 1.1 +MANTIS.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 +MANTIS.radiusscale[MANTIS.SamType.SHORT] = 1.75 +MANTIS.radiusscale[MANTIS.SamType.POINT] = 3 + --- SAM data -- @type MANTIS.SamData -- @field #number Range Max firing range in km @@ -584,11 +592,6 @@ do self.SuppressedGroups = {} -- 0.8 additions self.automode = true - self.radiusscale = {} - self.radiusscale[MANTIS.SamType.LONG] = 1.1 - self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 - self.radiusscale[MANTIS.SamType.SHORT] = 1.3 - self.radiusscale[MANTIS.SamType.POINT] = 1.4 --self.SAMCheckRanges = {} self.usezones = false self.AcceptZones = {} @@ -685,7 +688,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.25" + self.version="0.9.26" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- From 23aeef7a202f23d761f09e2b489231cd3536fb62 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 15 Mar 2025 22:49:43 +0100 Subject: [PATCH 122/158] Airbase - Airbase changes for helipads that are also airdromes - Improved SpawnAtAirbase function --- Moose Development/Moose/Core/Spawn.lua | 120 ++++++-------------- Moose Development/Moose/Wrapper/Airbase.lua | 9 +- 2 files changed, 38 insertions(+), 91 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 33eb2e804..37e8a1962 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2033,12 +2033,10 @@ end -- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) -- -function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- R2.2, R2.4 - --self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) +function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - --self:T2( PointVec3 ) -- Set take off type. Default is hot. Takeoff = Takeoff or SPAWN.Takeoff.Hot @@ -2048,39 +2046,24 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT EmergencyAirSpawn = true end - --self:F( { SpawnIndex = self.SpawnIndex } ) - if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then -- Get group template. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - --self:F( { SpawnTemplate = SpawnTemplate } ) - if SpawnTemplate then - -- Check if the aircraft with the specified SpawnIndex is already spawned. - -- If yes, ensure that the aircraft is spawned at the same aircraft spot. - - local GroupAlive = self:GetGroupFromIndex( self.SpawnIndex ) - - --self:F( { GroupAlive = GroupAlive } ) - - -- Debug output - --self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) - -- Template group, unit and its attributes. - local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) - local TemplateUnit = TemplateGroup:GetUnit( 1 ) + local group = GROUP:FindByName( self.SpawnTemplatePrefix ) + local unit = group:GetUnit( 1 ) -- General category of spawned group. - local group = TemplateGroup local istransport = group:HasAttribute( "Transports" ) and group:HasAttribute( "Planes" ) local isawacs = group:HasAttribute( "AWACS" ) local isfighter = group:HasAttribute( "Fighters" ) or group:HasAttribute( "Interceptors" ) or group:HasAttribute( "Multirole fighters" ) or (group:HasAttribute( "Bombers" ) and not group:HasAttribute( "Strategic bombers" )) local isbomber = group:HasAttribute( "Strategic bombers" ) local istanker = group:HasAttribute( "Tankers" ) - local ishelo = TemplateUnit:HasAttribute( "Helicopters" ) + local ishelo = unit:HasAttribute( "Helicopters" ) -- Number of units in the group. With grouping this can actually differ from the template group size! local nunits = #SpawnTemplate.units @@ -2098,40 +2081,30 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() --self:F( { AirbaseCategory = AirbaseCategory } ) - -- Set airdromeId. + -- Set airdrome ID. For helipads and ships we need to add the helipad ID and linked unit. + SpawnPoint.airdromeId = AirbaseID if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID SpawnPoint.helipadId = AirbaseID elseif AirbaseCategory == Airbase.Category.HELIPAD then SpawnPoint.linkUnit = AirbaseID SpawnPoint.helipadId = AirbaseID - elseif AirbaseCategory == Airbase.Category.AIRDROME then - SpawnPoint.airdromeId = AirbaseID end -- Set waypoint type/action. - SpawnPoint.alt = 0 - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action -- Check if we spawn on ground. local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) - --self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) -- Check where we actually spawn if we spawn on ground. - local spawnonship = false - local spawnonfarp = false - local spawnonrunway = false - local spawnonairport = false - if spawnonground then - if AirbaseCategory == Airbase.Category.SHIP then - spawnonship = true - elseif AirbaseCategory == Airbase.Category.HELIPAD then - spawnonfarp = true - elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnonairport = true - end - spawnonrunway = Takeoff == SPAWN.Takeoff.Runway + local autoparking=false + if SpawnAirbase.isAirdrome then + autoparking=false + else + autoparking=true end -- Array with parking spots coordinates. @@ -2147,8 +2120,8 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Set terminal type. local termtype = TerminalType - if spawnonrunway then - if spawnonship then + if Takeoff==SPAWN.Takeoff.Runway then + if SpawnAirbase.isShip then -- Looks like there are no runway spawn spots on the stennis! if ishelo then termtype = AIRBASE.TerminalType.HelicopterUsable @@ -2168,9 +2141,8 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local verysafe = false -- Number of free parking spots at the airbase. - if spawnonship or spawnonfarp or spawnonrunway then + if autoparking then -- These places work procedural and have some kind of build in queue ==> Less effort. - --self:T2( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) elseif Parkingdata~=nil then @@ -2182,18 +2154,18 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Not enough helo ports. Let's try also other terminal types. --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- No terminal type specified. We try all spots except shelters. --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else @@ -2202,44 +2174,33 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if isbomber or istransport or istanker or isawacs then -- First we fill the potentially bigger spots. --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Now we try the smaller ones. --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else --self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- Terminal type explicitly given. --self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end end end - -- Debug: Get parking data. - --[[ - local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) - --self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) - for _,_spot in pairs(parkingdata) do - --self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", - SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) - end - --self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) - ]] - -- Set this to true if not enough spots are available for emergency air start. local _notenough = false -- Need to differentiate some cases again. - if spawnonship or spawnonfarp or spawnonrunway then + if autoparking then -- On free spot required in these cases. if nfree >= 1 then @@ -2257,7 +2218,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT _notenough = true end - elseif spawnonairport then + else if nfree >= nunits then @@ -2279,13 +2240,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Not enough parking spots at the airport ==> Spawn in air. - spawnonground = false - spawnonship = false - spawnonfarp = false - spawnonrunway = false + autoparking=false -- Set waypoint type/action to turning point. - SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point + SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point SpawnPoint.action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point -- Adjust altitude to be 500-1000 m above the airbase. @@ -2327,7 +2285,6 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT SpawnTemplate.parked = true for UnitID = 1, nunits do - --self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] @@ -2343,9 +2300,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if spawnonground then -- Ships and FARPS seem to have a build in queue. - if spawnonship or spawnonfarp or spawnonrunway then - - --self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + if autoparking then -- Spawn on ship. We take only the position of the ship. SpawnTemplate.units[UnitID].x = PointVec3.x -- TX @@ -2354,20 +2309,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT else - --self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) - -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - -- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end else - --self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) - -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. SpawnTemplate.units[UnitID].x = TX SpawnTemplate.units[UnitID].y = TY @@ -2381,11 +2331,6 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if parkingindex[UnitID] then UnitTemplate.parking = parkingindex[UnitID] end - - -- Debug output. - --self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) - --self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) - --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -2405,14 +2350,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- When spawned in the air, we need to generate a Takeoff Event. if Takeoff == GROUP.Takeoff.Air then for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do - SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) + --SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) --No need to create a new SCHEDULER instance every time! + self:ScheduleOnce(5, BASE.CreateEventTakeoff, {GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject()}) end end -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) - end + --if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then + -- SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) + --end return GroupSpawned end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 5bb9af837..27405dd00 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1038,12 +1038,14 @@ end self:_InitParkingSpots() -- Some heliports identify as airdromes in the airbase category. This is buggy in the descriptors category but also in the getCategory() and getCategoryEx() functions. + -- Well, thinking about it, this is actually not that "buggy" since these are really helicopter airdromes, which do not have an automatic parking spot routine. + -- I am still changing the category but marking it as airdrome and heliport at the same time via isAirdrome=true and isHelipad=true (important in SPAWN.SpawnAtAirbase). + -- The main reason for changing the category is to be able to filter airdromes from helipads, e.g. in SET_AIRBASE. if self.category==Airbase.Category.AIRDROME and (Nrunways==0 or self.NparkingTotal==self.NparkingTerminal[AIRBASE.TerminalType.HelicopterOnly]) then - self:E(string.format("WARNING: %s identifies as airdrome (category=0) but has no runways or just helo parking ==> will change to helipad (category=1)", self.AirbaseName)) + --self:E(string.format("WARNING: %s identifies as airdrome (category=0) but has no runways or just helo parking ==> will change to helipad (category=1)", self.AirbaseName)) self.category=Airbase.Category.HELIPAD - self.isAirdrome=false + self.isAirdrome=true self.isHelipad=true - --self:GetCoordinate():MarkToAll("Helipad not airdrome") end -- Get 2D position vector. @@ -1052,7 +1054,6 @@ end -- Init coordinate. self:GetCoordinate() - -- Storage. self.storage=_DATABASE:AddStorage(AirbaseName) From df86c3f2a1fcbf529f3490e0282cc77249279067 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Mar 2025 11:02:55 +0100 Subject: [PATCH 123/158] #AUTOLASE - added a function to allow to set the monitoring report frequency --- .../Moose/Functional/Autolase.lua | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 88cb6c9e6..3b074ce8e 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -74,7 +74,7 @@ -- @image Designation.JPG -- -- Date: 24 Oct 2021 --- Last Update: Feb 2025 +-- Last Update: Mar 2025 -- --- Class AUTOLASE -- @type AUTOLASE @@ -92,6 +92,7 @@ -- @field #number RoundingPrecision -- @field #table smokeoffset -- @field #boolean increasegroundawareness +-- @field #number MonitorFrequency -- @extends Ops.Intel#INTEL --- @@ -105,6 +106,7 @@ AUTOLASE = { smokemenu = true, RoundingPrecision = 0, increasegroundawareness = true, + MonitorFrequency = 30, } --- Laser spot info @@ -123,7 +125,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.29" +AUTOLASE.version = "0.1.30" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -215,6 +217,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.threatmenu = true self.RoundingPrecision = 0 self.increasegroundawareness = true + self.MonitorFrequency = 30 self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) @@ -319,11 +322,20 @@ end -- Helper Functions ------------------------------------------------------------------- +--- [User] When using Monitor, set the frequency here in which the report will appear +-- @param #AUTOLASE self +-- @param #number Seconds Run the report loop every number of seconds defined here. +-- @return #AUTOLASE self +function AUTOLASE:SetMonitorFrequency(Seconds) + self.MonitorFrequency = Seconds or 30 + return self +end + --- [User] Set a table of possible laser codes. --- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 } . +-- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 }. -- @param #AUTOLASE self -- @param #list<#number> LaserCodes --- @return #AUTOLASE +-- @return #AUTOLASE self function AUTOLASE:SetLaserCodes( LaserCodes ) self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes } return self @@ -1203,7 +1215,7 @@ function AUTOLASE:onafterMonitor(From, Event, To) end end - self:__Monitor(-30) + self:__Monitor(self.MonitorFrequency or 30) return self end From 618a8744a22419bd6f71de8216b108b8b24ba50b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Mar 2025 13:06:53 +0100 Subject: [PATCH 124/158] #MANTIS - improve point defense behaviour if not SAM to defend is around --- Moose Development/Moose/Functional/Mantis.lua | 4 ++-- Moose Development/Moose/Functional/Shorad.lua | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index b5e809816..6f2142436 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -600,7 +600,7 @@ do self.maxlongrange = 1 self.maxmidrange = 2 self.maxshortrange = 2 - self.maxpointdefrange =6 + self.maxpointdefrange = 6 self.maxclassic = 6 self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP @@ -2188,7 +2188,7 @@ do local Shorad = self.Shorad local radius = self.checkradius local ontime = self.ShoradTime - Shorad:WakeUpShorad(Name, radius, ontime) + Shorad:WakeUpShorad(Name, radius, ontime, nil, true) self:__ShoradActivated(1,Name, radius, ontime) end return self diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 68ffe9c56..01b90262e 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -472,6 +472,7 @@ do -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active -- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC + -- @param #boolean ShotAt If true, function is called after a shot -- @return #SHORAD self -- @usage Use this function to integrate with other systems, example -- @@ -481,7 +482,7 @@ do -- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() - function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat) + function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt) self:T(self.lid .. " WakeUpShorad") self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) local targetcat = TargetCat or Object.Category.UNIT @@ -526,7 +527,7 @@ do local groupname = _group:GetName() - if groupname == TargetGroup then + if groupname == TargetGroup and ShotAt==true then -- Shot at a SHORAD group if self.UseEmOnOff then _group:EnableEmission(false) @@ -628,7 +629,7 @@ do _targetgroupname = tgtgrp:GetName() -- group name _targetskill = tgtgrp:GetUnit(1):GetSkill() self:T("*** Found Target = ".. _targetgroupname) - self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT) + self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT,true) end end end @@ -757,7 +758,7 @@ do -- if being shot at, find closest SHORADs to activate if shotatsams or shotatus then self:T({shotatsams=shotatsams,shotatus=shotatus}) - self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat, true) end end end From 5ca3e3b2b8fc959650459e953af9d62f22ddb96a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Mar 2025 13:58:58 +0100 Subject: [PATCH 125/158] #SHORAD tweak --- Moose Development/Moose/Core/Event.lua | 2 +- Moose Development/Moose/Functional/Shorad.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index fe620c26a..f22185616 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1375,7 +1375,7 @@ function EVENT:onEvent( Event ) Event.IniDCSUnitName = Event.IniDCSUnit.getName and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000) Event.IniUnitName = Event.IniDCSUnitName Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator ) - Event.IniCategory = Event.IniDCSUnit:getDesc().category + Event.IniCategory = Event.IniDCSUnit.getDesc and Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" elseif Event.IniObjectCategory == Object.Category.BASE then diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 01b90262e..b209f3acd 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -543,7 +543,7 @@ do self:__ShootAndScoot(1,_group) end - elseif _group:IsAnyInZone(targetzone) then + elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then -- shot at a group we protect local text = string.format("Waking up SHORAD %s", _group:GetName()) self:T(text) From be8405b72b83edcaf60eeb99dc97ef5ccf22495f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 16 Mar 2025 16:57:11 +0100 Subject: [PATCH 126/158] #MANTIS - fix for data table --- Moose Development/Moose/Functional/Mantis.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 6f2142436..db49d8bc9 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Feb 2025 +-- Last Update: Mar 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -399,7 +399,6 @@ MANTIS.SamData = { ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, - ["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="Igla", Point="true" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, } @@ -688,7 +687,7 @@ do -- TODO Version -- @field #string version - self.version="0.9.26" + self.version="0.9.27" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- From a915452e6e98ab23efa8664c03b4d0fe0ccde970 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 21 Mar 2025 09:22:08 +0100 Subject: [PATCH 127/158] #COORDINATE - use magnetic for BRAA --- Moose Development/Moose/Core/Point.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 7f2b3d558..9afc49b7a 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1221,7 +1221,7 @@ do -- COORDINATE local s = string.format( '%03d°', AngleDegrees ) if MagVar then - local variation = UTILS.GetMagneticDeclination() or 0 + local variation = self:GetMagneticDeclination() or 0 local AngleMagnetic = AngleDegrees - variation if AngleMagnetic < 0 then AngleMagnetic = 360-AngleMagnetic end @@ -2959,6 +2959,8 @@ do -- COORDINATE local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 ) + local magnetic = self:GetMagneticDeclination() or 0 + bearing = bearing - magnetic local rangeMetres = self:Get2DDistance(currentCoord) local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) From d375e0ce29b8b3e9d08340eca30bb2fb3e202fcf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Mar 2025 10:42:36 +0100 Subject: [PATCH 128/158] #AUTOLASE - small fix for interval --- Moose Development/Moose/Functional/Autolase.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 3b074ce8e..9fed56cfc 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -125,7 +125,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.30" +AUTOLASE.version = "0.1.31" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -1215,7 +1215,8 @@ function AUTOLASE:onafterMonitor(From, Event, To) end end - self:__Monitor(self.MonitorFrequency or 30) + local nextloop = -self.MonitorFrequency or -30 + self:__Monitor(nextloop) return self end From 792aa73832fd42310307b25131f35d6fbb201b86 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 22 Mar 2025 14:58:24 +0100 Subject: [PATCH 129/158] #AIBRASE - Afghanistan new Airbases --- Moose Development/Moose/Wrapper/Airbase.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 27405dd00..3f04a8a04 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -823,38 +823,57 @@ AIRBASE.Kola = { --- Airbases of the Afghanistan map -- -- * AIRBASE.Afghanistan.Bost +-- * AIRBASE.Afghanistan.Bagram +-- * AIRBASE.Afghanistan.Bamyan -- * AIRBASE.Afghanistan.Camp_Bastion -- * AIRBASE.Afghanistan.Camp_Bastion_Heliport -- * AIRBASE.Afghanistan.Chaghcharan -- * AIRBASE.Afghanistan.Dwyer -- * AIRBASE.Afghanistan.Farah -- * AIRBASE.Afghanistan.Herat +-- * AIRBASE.Afghanistan.Gardez +-- * AIRBASE.Afghanistan.Ghazni_Heliport +-- * AIRBASE.Afghanistan.Jalalabad +-- * AIRBASE.Afghanistan.Kabul -- * AIRBASE.Afghanistan.Kandahar -- * AIRBASE.Afghanistan.Kandahar_Heliport +-- * AIRBASE.Afghanistan.Khost +-- * AIRBASE.Afghanistan.Khost_Heliport -- * AIRBASE.Afghanistan.Maymana_Zahiraddin_Faryabi -- * AIRBASE.Afghanistan.Nimroz -- * AIRBASE.Afghanistan.Qala_i_Naw -- * AIRBASE.Afghanistan.Shindand -- * AIRBASE.Afghanistan.Shindand_Heliport -- * AIRBASE.Afghanistan.Tarinkot +-- * AIRBASE.Afghanistan.Urgoon_Heliport -- -- @field Afghanistan AIRBASE.Afghanistan = { + ["Bagram"] = "Bagram", + ["Bamyan"] = "Bamyan", ["Bost"] = "Bost", ["Camp_Bastion"] = "Camp Bastion", ["Camp_Bastion_Heliport"] = "Camp Bastion Heliport", ["Chaghcharan"] = "Chaghcharan", ["Dwyer"] = "Dwyer", ["Farah"] = "Farah", + ["Gardez"] = "Gardez", + ["Ghazni_Heliport"] = "Ghazni Heliport", ["Herat"] = "Herat", + ["Jalalabad"] = "Jalalabad", + ["Kabul"] = "Kabul", ["Kandahar"] = "Kandahar", ["Kandahar_Heliport"] = "Kandahar Heliport", + ["Khost"] = "Khost", + ["Khost_Heliport"] = "Khost Heliport", ["Maymana_Zahiraddin_Faryabi"] = "Maymana Zahiraddin Faryabi", ["Nimroz"] = "Nimroz", ["Qala_i_Naw"] = "Qala i Naw", + ["Sharana"] = "Sharana", ["Shindand"] = "Shindand", ["Shindand_Heliport"] = "Shindand Heliport", ["Tarinkot"] = "Tarinkot", + ["Urgoon_Heliport"] = "Urgoon Heliport", } --- Airbases of the Iraq map From 4955fe4d9260ad3fba0e0688a0be4f562f872614 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 23 Mar 2025 14:11:21 +0100 Subject: [PATCH 130/158] Update Spawn.lua - Fix for problem that helos are not spawned on ships but at origin of the map --- Moose Development/Moose/Core/Spawn.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 37e8a1962..de81f9a46 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2082,13 +2082,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT --self:F( { AirbaseCategory = AirbaseCategory } ) -- Set airdrome ID. For helipads and ships we need to add the helipad ID and linked unit. - SpawnPoint.airdromeId = AirbaseID + -- Note, it is important not to set the airdrome ID for at least ships, because spawn will happen at origin of the map if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID SpawnPoint.helipadId = AirbaseID elseif AirbaseCategory == Airbase.Category.HELIPAD then SpawnPoint.linkUnit = AirbaseID SpawnPoint.helipadId = AirbaseID + else + SpawnPoint.airdromeId = AirbaseID end -- Set waypoint type/action. From 47c9e1ba1fd6c820dc3cf718a3a7b9b014518447 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 23 Mar 2025 22:45:15 +0100 Subject: [PATCH 131/158] Update Legion.lua - Dead assets are removed from cohort & legion --- Moose Development/Moose/Ops/Legion.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 6cc2ccb8c..075866209 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -445,6 +445,21 @@ function LEGION:DelCohort(Cohort) return self end +--- Remove specific asset from legion. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. +-- @return #LEGION self +function LEGION:DelAsset(Asset) + + if Asset.cohort then + Asset.cohort:DelAsset(Asset) + else + self:E(self.lid..string.format("ERROR: Asset has not cohort attached. Cannot remove it from legion!")) + end + + return self +end + --- Relocate a cohort to another legion. -- Assets in stock are spawned and routed to the new legion. @@ -1643,6 +1658,9 @@ function LEGION:onafterAssetDead(From, Event, To, asset, request) if self.commander and self.commander.chief then self.commander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) end + + -- Remove asset from cohort and legion. + self:DelAsset(asset) -- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function -- Remove asset from squadron same From 00d14c7c0a5783e773aeea3e9e43ea0e3311e5e8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 27 Mar 2025 11:09:01 +0100 Subject: [PATCH 132/158] #PLAYERRECCE - small tweak for visual distance setting --- Moose Development/Moose/Ops/PlayerRecce.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index a88685565..e178b58f0 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -838,7 +838,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) local minview = 0 local typename = unit:GetTypeName() local playername = unit:GetPlayerName() - local maxview = self.MaxViewDistance[typename] or 5000 + local maxview = self.MaxViewDistance[typename] or 8000 local heading,nod,maxview,angle = 0,30,8000,10 local camon = false local name = unit:GetName() @@ -846,24 +846,25 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit) angle=10 -- Model nod and actual TV view don't compute - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 elseif string.find(typename,"Ka-50") and camera then heading = unit:GetHeading() nod,maxview,camon = 10,1000,true angle = 10 - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 elseif string.find(typename,"OH58") and camera then --heading = unit:GetHeading() nod,maxview,camon = 0,8000,true heading,nod,maxview,camon = self:_GetKiowaMMSSight(unit) angle = 8 if maxview == 0 then - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 end else -- visual heading = unit:GetHeading() - nod,maxview,camon = 10,1000,true + nod,maxview,camon = 10,3000,true + maxview = self.MaxViewDistance[typename] or 3000 angle = 45 end if laser then From 659615114aca1e5ac0f02b9021f5bf9d041beb76 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 28 Mar 2025 15:04:42 +0100 Subject: [PATCH 133/158] xx --- Moose Development/Moose/AI/AI_BAI.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index 9877ac51b..ec338e30b 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -174,8 +174,7 @@ function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. - -- @param #string To The To State string. - + -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Engage. From 0a38700edbe50c10fce8c234c33cf13c2e19870b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 28 Mar 2025 15:05:30 +0100 Subject: [PATCH 134/158] #AI_Patrol - small enhancement --- Moose Development/Moose/AI/AI_Patrol.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index d5ce61d72..e6516736a 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -846,7 +846,6 @@ function AI_PATROL_ZONE:onafterStatus() OldAIControllable:SetTask( TimedOrbitTask, 10 ) RTB = true - else end -- TODO: Check GROUP damage function. @@ -856,6 +855,16 @@ function AI_PATROL_ZONE:onafterStatus() RTB = true end + if self:IsInstanceOf("AI_CAS") or self:IsInstanceOf("AI_BAI") then + local atotal,shells,rockets,bombs,missiles = self.Controllable:GetAmmunition() + local arelevant = rockets+bombs + if arelevant == 0 or missiles == 0 then + RTB = true + self:T({total=atotal,shells=shells,rockets=rockets,bombs=bombs,missiles=missiles}) + self:T( self.Controllable:GetName() .. " is out of ammo, RTB!" ) + end + end + if RTB == true then self:RTB() else From dc83af4d0205219d9c2f4640cde60ae386e25cdf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 30 Mar 2025 16:45:46 +0200 Subject: [PATCH 135/158] #SHORAD - fix typo for maxscootdist #CTLD - slight refactoring of fixed wing support. * Added `CTLD:AddAllowedFixedWingType(typename)` to add new types * Renamed enabler flags into `enableFixedWing, FixedMinAngels, FixedMaxAngels, FixedMaxSpeed` * Base support for Mosquito added --- Moose Development/Moose/Functional/Escort.lua | 173 ------------------ Moose Development/Moose/Functional/Shorad.lua | 2 +- Moose Development/Moose/Ops/CTLD.lua | 122 +++++++----- 3 files changed, 76 insertions(+), 221 deletions(-) diff --git a/Moose Development/Moose/Functional/Escort.lua b/Moose Development/Moose/Functional/Escort.lua index a1997334e..ecf53a382 100644 --- a/Moose Development/Moose/Functional/Escort.lua +++ b/Moose Development/Moose/Functional/Escort.lua @@ -1154,8 +1154,6 @@ function ESCORT:_ReportTargetsScheduler() if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - if true then - local EscortGroupName = self.EscortGroup:GetName() self.EscortMenuAttackNearbyTargets:RemoveSubMenus() @@ -1226,177 +1224,6 @@ function ESCORT:_ReportTargetsScheduler() end return true - else --- local EscortGroupName = self.EscortGroup:GetName() --- local EscortTargets = self.EscortGroup:GetDetectedTargets() --- --- local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets --- --- local EscortTargetMessages = "" --- for EscortTargetID, EscortTarget in pairs( EscortTargets ) do --- local EscortObject = EscortTarget.object --- self:T( EscortObject ) --- if EscortObject and EscortObject:isExist() and EscortObject.id_ < 50000000 then --- --- local EscortTargetUnit = UNIT:Find( EscortObject ) --- local EscortTargetUnitName = EscortTargetUnit:GetName() --- --- --- --- -- local EscortTargetIsDetected, --- -- EscortTargetIsVisible, --- -- EscortTargetLastTime, --- -- EscortTargetKnowType, --- -- EscortTargetKnowDistance, --- -- EscortTargetLastPos, --- -- EscortTargetLastVelocity --- -- = self.EscortGroup:IsTargetDetected( EscortObject ) --- -- --- -- self:T( { EscortTargetIsDetected, --- -- EscortTargetIsVisible, --- -- EscortTargetLastTime, --- -- EscortTargetKnowType, --- -- EscortTargetKnowDistance, --- -- EscortTargetLastPos, --- -- EscortTargetLastVelocity } ) --- --- --- local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3() --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + --- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + --- ( EscortTargetUnitVec3.z - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- --- self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } ) --- --- if Distance <= 15 then --- --- if not ClientEscortTargets[EscortTargetUnitName] then --- ClientEscortTargets[EscortTargetUnitName] = {} --- end --- ClientEscortTargets[EscortTargetUnitName].AttackUnit = EscortTargetUnit --- ClientEscortTargets[EscortTargetUnitName].visible = EscortTarget.visible --- ClientEscortTargets[EscortTargetUnitName].type = EscortTarget.type --- ClientEscortTargets[EscortTargetUnitName].distance = EscortTarget.distance --- else --- if ClientEscortTargets[EscortTargetUnitName] then --- ClientEscortTargets[EscortTargetUnitName] = nil --- end --- end --- end --- end --- --- self:T( { "Sorting Targets Table:", ClientEscortTargets } ) --- table.sort( ClientEscortTargets, function( a, b ) return a.Distance < b.Distance end ) --- self:T( { "Sorted Targets Table:", ClientEscortTargets } ) --- --- -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. --- self.EscortMenuAttackNearbyTargets:RemoveSubMenus() --- --- if self.EscortMenuTargetAssistance then --- self.EscortMenuTargetAssistance:RemoveSubMenus() --- end --- --- --for MenuIndex = 1, #self.EscortMenuAttackTargets do --- -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } ) --- -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove() --- --end --- --- --- if ClientEscortTargets then --- for ClientEscortTargetUnitName, ClientEscortTargetData in pairs( ClientEscortTargets ) do --- --- for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do --- --- if ClientEscortTargetData and ClientEscortTargetData.AttackUnit:IsAlive() then --- --- local EscortTargetMessage = "" --- local EscortTargetCategoryName = ClientEscortTargetData.AttackUnit:GetCategoryName() --- local EscortTargetCategoryType = ClientEscortTargetData.AttackUnit:GetTypeName() --- if ClientEscortTargetData.type then --- EscortTargetMessage = EscortTargetMessage .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at " --- else --- EscortTargetMessage = EscortTargetMessage .. "Unknown target at " --- end --- --- local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3() --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + --- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + --- ( EscortTargetUnitVec3.z - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- --- self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } ) --- if ClientEscortTargetData.visible == false then --- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " estimated km" --- else --- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km" --- end --- --- if ClientEscortTargetData.visible then --- EscortTargetMessage = EscortTargetMessage .. ", visual" --- end --- --- if ClientEscortGroupName == EscortGroupName then --- --- MENU_GROUP_COMMAND:New( self.EscortClient, --- EscortTargetMessage, --- self.EscortMenuAttackNearbyTargets, --- ESCORT._AttackTarget, --- { ParamSelf = self, --- ParamUnit = ClientEscortTargetData.AttackUnit --- } --- ) --- EscortTargetMessages = EscortTargetMessages .. "\n - " .. EscortTargetMessage --- else --- if self.EscortMenuTargetAssistance then --- local MenuTargetAssistance = MENU_GROUP:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance ) --- MENU_GROUP_COMMAND:New( self.EscortClient, --- EscortTargetMessage, --- MenuTargetAssistance, --- ESCORT._AssistTarget, --- self, --- EscortGroupData.EscortGroup, --- ClientEscortTargetData.AttackUnit --- ) --- end --- end --- else --- ClientEscortTargetData = nil --- end --- end --- end --- --- if EscortTargetMessages ~= "" and self.ReportTargets == true then --- self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient ) --- else --- self.EscortGroup:MessageToClient( "No targets detected!", 20, self.EscortClient ) --- end --- end --- --- if self.EscortMenuResumeMission then --- self.EscortMenuResumeMission:RemoveSubMenus() --- --- -- if self.EscortMenuResumeWayPoints then --- -- for MenuIndex = 1, #self.EscortMenuResumeWayPoints do --- -- self:T( { "Remove Menu:", self.EscortMenuResumeWayPoints[MenuIndex] } ) --- -- self.EscortMenuResumeWayPoints[MenuIndex] = self.EscortMenuResumeWayPoints[MenuIndex]:Remove() --- -- end --- -- end --- --- local TaskPoints = self:RegisterRoute() --- for WayPointID, WayPoint in pairs( TaskPoints ) do --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + --- ( WayPoint.y - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- MENU_GROUP_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } ) --- end --- end --- --- return true - end end return false diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index b209f3acd..526ff062f 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -113,7 +113,7 @@ SHORAD = { SkateNumber = 3, SkateZones = nil, minscootdist = 100, - minscootdist = 3000, + maxscootdist = 3000, scootrandomcoord = false, } diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 7f195ac60..6caede880 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update Feb 2025 +-- Last Update April 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -923,7 +923,6 @@ do -- ["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats -- ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats -- ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, --- ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, -- ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, -- ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, -- ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 8000}, @@ -1087,11 +1086,11 @@ do -- -- ## 4.7 List Inventory -- --- Lists invetory of available units to drop or build. +-- Lists inventory of available units to drop or build. -- --- ## 5. Support for Hercules mod by Anubis +-- ## 5. Support for fixed wings -- --- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same way and for the same objects as +-- Basic support for the Hercules mod By Anubis has been build into CTLD, as well as Bronco and Mosquito - that is you can load/drop/build the same way and for the same objects as -- the helicopters (main method). -- To cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu (F8), you need to use @{#CTLD_HERCULES.New}() and link -- this object to your CTLD setup (alternative method). In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod @@ -1103,13 +1102,13 @@ do -- -- Enable these options for Hercules support: -- --- my_ctld.enableHercules = true --- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft --- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft --- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn +-- my_ctld.enableFixedWing = true +-- my_ctld.FixedMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft +-- my_ctld.FixedMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- my_ctld.FixedMaxSpeed = 77 -- 77mps or 270kph or 150kn -- --- Hint: you can **only** airdrop from the Hercules if you are "in parameters", i.e. at or below `HercMaxSpeed` and in the AGL bracket between --- `HercMinAngels` and `HercMaxAngels`! +-- Hint: you can **only** airdrop from the Hercules if you are "in parameters", i.e. at or below `FixedMaxSpeed` and in the AGLFixedMinAngelseen +-- `FixedMinAngels` and `FixedMaxAngels`! -- -- Also, the following options need to be set to `true`: -- @@ -1117,9 +1116,9 @@ do -- -- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method, use either the above OR this method, NOT both!) -- --- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: +-- Taking another approach, integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: -- --- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew +-- my_ctld.enableFixedWing = false -- avoid dual loading via CTLD F10 and F8 ground crew -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- -- You also need: @@ -1384,11 +1383,20 @@ CTLD.UnitTypeCapabilities = { ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 10800}, + ["MosquitoFBMkVI"] = {type="MosquitoFBMkVI", crates= true, troops=false, cratelimit = 2, trooplimit = 0, length = 13, cargoweightlimit = 1800}, +} + +--- Allowed Fixed Wing Types +-- @type CTLD.FixedWingTypes +CTLD.FixedWingTypes = { + ["Hercules"] = "Hercules", + ["Bronco"] = "Bronco", + ["Mosquito"] = "Mosquito", } --- CTLD class version. -- @field #string version -CTLD.version="1.1.30" +CTLD.version="1.1.31" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1528,10 +1536,11 @@ function CTLD:New(Coalition, Prefixes, Alias) self.troopdropzoneradius = 100 -- added support Hercules Mod - self.enableHercules = false - self.HercMinAngels = 165 -- for troop/cargo drop via chute - self.HercMaxAngels = 2000 -- for troop/cargo drop via chute - self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps + self.enableHercules = false -- deprecated + self.enableFixedWing = false + self.FixedMinAngels = 165 -- for troop/cargo drop via chute + self.FixedMaxAngels = 2000 -- for troop/cargo drop via chute + self.FixedMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps -- message suppression self.suppressmessages = false @@ -2015,7 +2024,7 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end -- Herc support - if self:IsHercules(_unit) and self.enableHercules then + if self:IsFixedWing(_unit) and self.enableFixedWing then local unitname = event.IniUnitName or "none" self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() @@ -2183,6 +2192,22 @@ function CTLD:_FindCratesCargoObject(Name) return nil end +--- (User) Add a new fixed wing type to the list of allowed types. +-- @param #CTLD self +-- @param #string typename The typename to add. Can be handed as Wrapper.Unit#UNIT object. Do NOT forget to `myctld:SetUnitCapabilities()` for this type! +-- @return #CTLD self +function CTLD:AddAllowedFixedWingType(typename) + if type(typename) == "string" then + self.FixedWingTypes[typename] = typename + elseif typename and typename.ClassName and typename:IsInstanceOf("UNIT") then + local TypeName = typename:GetTypeName() or "none" + self.FixedWingTypes[TypeName] = TypeName + else + self:E(self.lid.."No valid typename or no UNIT handed!") + end + return self +end + --- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot! -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object @@ -2715,7 +2740,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) return self end -- spawn crates in front of helicopter - local IsHerc = self:IsHercules(Unit) -- Herc, Bronco and Hook load from behind + local IsHerc = self:IsFixedWing(Unit) -- Herc, Bronco and Hook load from behind local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number @@ -3088,7 +3113,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype --self:I(self.lid .. " Unit can carry: " .. tostring(cando)) --- Testing local distance = self:_GetDistance(location,staticpos) - --self:I(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) + self:T(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) if distance <= finddist and (weight <= maxloadable or _ignoreweight) and restricted == false and cando == true then index = index + 1 table.insert(found, staticid, cargo) @@ -3495,16 +3520,18 @@ function CTLD:_ListInventory(Group, Unit) return self end ---- (Internal) Function to check if a unit is a Hercules C-130 or a Bronco. +--- (Internal) Function to check if a unit is an allowed fixed wing. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome -function CTLD:IsHercules(Unit) - if Unit:GetTypeName() == "Hercules" or string.find(Unit:GetTypeName(),"Bronco") then - return true - else - return false +function CTLD:IsFixedWing(Unit) + local typename = Unit:GetTypeName() or "none" + for _,_name in pairs(self.FixedWingTypes or {}) do + if typename == _name or string.find(typename,_name,1,true) then + return true + end end + return false end --- (Internal) Function to check if a unit is a CH-47 @@ -3570,7 +3597,7 @@ function CTLD:_UnloadTroops(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) + local IsHerc = self:IsFixedWing(Unit) local IsHook = self:IsHook(Unit) if IsHerc and (not IsHook) then -- no hover but airdrop here @@ -3718,7 +3745,7 @@ function CTLD:_UnloadCrates(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) + local IsHerc = self:IsFixedWing(Unit) local IsHook = self:IsHook(Unit) if IsHerc and (not IsHook) then -- no hover but airdrop here @@ -3784,7 +3811,7 @@ end function CTLD:_BuildCrates(Group, Unit,Engineering) self:T(self.lid .. " _BuildCrates") -- avoid users trying to build from flying Hercs - if self:IsHercules(Unit) and self.enableHercules and not Engineering then + if self:IsFixedWing(Unit) and self.enableFixedWing and not Engineering then local speed = Unit:GetVelocityKMH() if speed > 1 then self:_SendMessage("You need to land / stop to build something, Pilot!", 10, false, Group) @@ -4150,7 +4177,7 @@ function CTLD:_RefreshF10Menus() local firstUnit = groupObj:GetFirstUnitAlive() if firstUnit then if firstUnit:IsPlayer() then - if firstUnit:IsHelicopter() or (self.enableHercules and self:IsHercules(firstUnit)) then + if firstUnit:IsHelicopter() or (self.enableFixedWing and self:IsFixedWing(firstUnit)) then local _unit = firstUnit:GetName() _UnitList[_unit] = _unit end @@ -4362,7 +4389,7 @@ function CTLD:_RefreshF10Menus() MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() - if self:IsHercules(_unit) then + if self:IsFixedWing(_unit) then MENU_GROUP_COMMAND:New(_group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() else MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() @@ -4597,7 +4624,7 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) -- Check hover/airdrop/landed logic local grounded = not self:IsUnitInAir(Unit) local hoverunload = self:IsCorrectHover(Unit) - local isHerc = self:IsHercules(Unit) + local isHerc = self:IsFixedWing(Unit) local isHook = self:IsHook(Unit) if isHerc and not isHook then hoverunload = self:IsCorrectFlightParameters(Unit) @@ -4758,7 +4785,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) end local hoverunload = self:IsCorrectHover(Unit) - local isHerc = self:IsHercules(Unit) + local isHerc = self:IsFixedWing(Unit) local isHook = self:IsHook(Unit) if isHerc and not isHook then hoverunload = self:IsCorrectFlightParameters(Unit) @@ -4900,7 +4927,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) self.Loaded_Cargo[unitName].Cratesloaded = cratesLoaded self:_RefreshDropTroopsMenu(Group, Unit) else - local isHerc = self:IsHercules(Unit) + local isHerc = self:IsFixedWing(Unit) if isHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else @@ -5802,9 +5829,9 @@ end end local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground - local minh = self.HercMinAngels-- 1500m - local maxh = self.HercMaxAngels -- 5000m - local maxspeed = self.HercMaxSpeed -- 77 mps + local minh = self.FixedMinAngels-- 1500m + local maxh = self.FixedMaxAngels -- 5000m + local maxspeed = self.FixedMaxSpeed -- 77 mps -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 local knspeed = kmspeed / 1.86 @@ -5847,12 +5874,12 @@ end if not inhover then htxt = "false" end local text = "" if _SETTINGS:IsImperial() then - local minheight = UTILS.MetersToFeet(self.HercMinAngels) - local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + local minheight = UTILS.MetersToFeet(self.FixedMinAngels) + local maxheight = UTILS.MetersToFeet(self.FixedMaxAngels) text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) else - local minheight = self.HercMinAngels - local maxheight = self.HercMaxAngels + local minheight = self.FixedMinAngels + local maxheight = self.FixedMaxAngels text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) @@ -5865,7 +5892,7 @@ end -- @return #boolean Outcome function CTLD:CanHoverLoad(Unit) self:T(self.lid .. " CanHoverLoad") - if self:IsHercules(Unit) then return false end + if self:IsFixedWing(Unit) then return false end local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) if not outcome then outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) --and self:IsCorrectHover(Unit) @@ -5880,7 +5907,7 @@ end function CTLD:IsUnitInAir(Unit) -- get speed and height local minheight = self.minimumHoverHeight - if self.enableHercules and self:IsHercules(Unit) then + if self.enableFixedWing and self:IsFixedWing(Unit) then minheight = 5.1 -- herc is 5m AGL on the ground end local uheight = Unit:GetHeight() @@ -6727,11 +6754,12 @@ end function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) self:I(self.lid .. "Started ("..self.version..")") + if self.enableHercules then self.enableFixedWing = true end if self.UserSetGroup then self.PilotGroups = self.UserSetGroup - elseif self.useprefix or self.enableHercules then + elseif self.useprefix or self.enableFixedWing then local prefix = self.prefixes - if self.enableHercules then + if self.enableFixedWing then self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() @@ -7533,7 +7561,7 @@ CTLD_HERCULES.Types = { -- @usage -- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: -- --- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew +-- my_ctld.enableFixedWing = false -- avoid dual loading via CTLD F10 and F8 ground crew -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- -- You also need: From b66e91b11ff8d20ccae79f7a75c9099da9bed369 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:31:23 +0200 Subject: [PATCH 136/158] Update DynamicCargo.lua #DYNAMICCARGO small fixes --- Moose Development/Moose/Wrapper/DynamicCargo.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 3ff7ddc53..75d1105dc 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -124,7 +124,7 @@ DYNAMICCARGO.AircraftDimensions = { --- DYNAMICCARGO class version. -- @field #string version -DYNAMICCARGO.version="0.0.5" +DYNAMICCARGO.version="0.0.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -227,7 +227,7 @@ end -- @param #DYNAMICCARGO self -- @return #boolean Outcome function DYNAMICCARGO:IsUnloaded() - if self.CargoState and self.CargoState == DYNAMICCARGO.State.REMOVED then + if self.CargoState and self.CargoState == DYNAMICCARGO.State.UNLOADED then return true else return false @@ -238,7 +238,7 @@ end -- @param #DYNAMICCARGO self -- @return #boolean Outcome function DYNAMICCARGO:IsRemoved() - if self.CargoState and self.CargoState == DYNAMICCARGO.State.UNLOADED then + if self.CargoState and self.CargoState == DYNAMICCARGO.State.REMOVED then return true else return false From 82e1dcfc047ed46e302501ed798fed383b2e0a4b Mon Sep 17 00:00:00 2001 From: shaji Date: Mon, 31 Mar 2025 13:11:56 +0200 Subject: [PATCH 137/158] [Fixed] Parameter #1 (unit name) is incorrect --- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 352e30045..d268151a5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -9045,7 +9045,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo)) -- For airborne units, we set the weight in game. - if self.isFlightgroup then + if self.isFlightgroup and element.name then trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo end From 222722225e0dbb4ff8978e7fb0565794e8f9c2c7 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 31 Mar 2025 14:04:07 +0200 Subject: [PATCH 138/158] Update OpsGroup.lua Better make this a unit and alive check --- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index d268151a5..268dfd008 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -9045,7 +9045,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo)) -- For airborne units, we set the weight in game. - if self.isFlightgroup and element.name then + if self.isFlightgroup and element.unit and element.unit:IsAlive() then -- #2272 trying to deduct cargo weight from possibly dead units trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo end From 76dc0d690a2283cadb43b8e3acce4b9517b756e0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 1 Apr 2025 13:17:48 +0200 Subject: [PATCH 139/158] #POINT - Removal of References to legacy POINT_VEC2/3 classes --- Moose Development/Moose/AI/AI_Air.lua | 12 +- Moose Development/Moose/AI/AI_Air_Engage.lua | 8 +- Moose Development/Moose/AI/AI_Air_Patrol.lua | 6 +- Moose Development/Moose/AI/AI_BAI.lua | 12 +- Moose Development/Moose/AI/AI_Balancer.lua | 9 +- Moose Development/Moose/AI/AI_CAP.lua | 12 +- Moose Development/Moose/AI/AI_CAS.lua | 12 +- .../Moose/AI/AI_Cargo_Airplane.lua | 2 +- .../Moose/AI/AI_Cargo_Helicopter.lua | 22 +- Moose Development/Moose/AI/AI_Formation.lua | 6 +- Moose Development/Moose/AI/AI_Patrol.lua | 24 +- Moose Development/Moose/Cargo/Cargo.lua | 14 +- Moose Development/Moose/Cargo/CargoCrate.lua | 2 +- Moose Development/Moose/Cargo/CargoGroup.lua | 7 +- Moose Development/Moose/Cargo/CargoUnit.lua | 8 +- Moose Development/Moose/Core/Point.lua | 439 ++++------- Moose Development/Moose/Core/Set.lua | 50 +- Moose Development/Moose/Core/Spawn.lua | 10 +- Moose Development/Moose/Core/SpawnStatic.lua | 709 +++--------------- Moose Development/Moose/Core/Zone.lua | 86 +-- .../Moose/Core/Zone_Detection.lua | 8 +- Moose Development/Moose/Ops/CTLD.lua | 8 +- Moose Development/Moose/Wrapper/Group.lua | 10 +- .../Moose/Wrapper/Positionable.lua | 18 +- docs/archive/classes-core.md | 2 +- 25 files changed, 454 insertions(+), 1042 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 5d2feda5e..d3d11cf16 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -657,8 +657,8 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) --- Create a route point of type air. local FromRTBRoutePoint = FromCoord:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, RTBSpeed, true ) @@ -666,8 +666,8 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) --- Create a route point of type air. local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, RTBSpeed, true ) @@ -761,10 +761,10 @@ function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) --- Create a route point of type air. - local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true) + local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToRefuelSpeed, true) --- Create a route point of type air. NOT used! - local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true) + local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToRefuelSpeed, true) self:F( { ToRefuelSpeed = ToRefuelSpeed } ) diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index 70898d2ba..ff3327421 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -453,7 +453,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac --- Calculate the target route point. - local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) + local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) EngageRoute[#EngageRoute+1] = FromWP @@ -462,7 +462,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ) - local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) + local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) EngageRoute[#EngageRoute+1] = ToWP @@ -536,7 +536,7 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU local EngageRoute = {} local AttackTasks = {} - local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) + local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) EngageRoute[#EngageRoute+1] = FromWP self:SetTargetDistance( TargetCoord ) -- For RTB status check @@ -544,7 +544,7 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ) - local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) + local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) EngageRoute[#EngageRoute+1] = ToWP -- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task! diff --git a/Moose Development/Moose/AI/AI_Air_Patrol.lua b/Moose Development/Moose/AI/AI_Air_Patrol.lua index 3185f987e..5b0abe72e 100644 --- a/Moose Development/Moose/AI/AI_Air_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Air_Patrol.lua @@ -309,7 +309,7 @@ function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) local speedkmh=ToTargetSpeed - local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true) + local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true) PatrolRoute[#PatrolRoute+1] = FromWP if self.racetrack then @@ -359,9 +359,9 @@ function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) else --- Create a route point of type air. - local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true) + local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true) PatrolRoute[#PatrolRoute+1] = ToWP - + local Tasks = {} Tasks[#Tasks+1] = AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self) PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks ) diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index ec338e30b..6b2a1dfe3 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -521,12 +521,12 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -577,13 +577,13 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, self:T2( ToTargetVec2 ) --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) + local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) --- Create a route point of type air. local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua index b64e27057..34afc35d0 100644 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ b/Moose Development/Moose/AI/AI_Balancer.lua @@ -220,16 +220,9 @@ function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup ) AIGroup:MessageToRed( "Returning to home base ...", 30 ) else -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) + local PointVec2 = COORDINATE:New(AIGroup:GetVec2().x, 0, AIGroup:GetVec2().y) local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) self:T( ClosestAirbase.AirbaseName ) - --[[ - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - ]] AIGroup:RouteRTB(ClosestAirbase) end diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index d59931452..dc0a843a8 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -423,12 +423,12 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToEngageZoneSpeed, true ) @@ -445,13 +445,13 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) + local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) --- Create a route point of type air. local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true ) diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index c6d2c11f4..832cc6bd5 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -466,12 +466,12 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -508,13 +508,13 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, self:T2( ToTargetVec2 ) --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) + local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) --- Create a route point of type air. local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua index 9dad75f25..79d1b6307 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua @@ -440,7 +440,7 @@ function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled -- To point. local AirbasePointVec2 = Airbase:GetPointVec2() - local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase) + local ToWaypoint = AirbasePointVec2:WaypointAir(COORDINATE.WaypointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase) --ToWaypoint["airdromeId"] = Airbase:GetID() --ToWaypoint["speed_locked"] = true diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 6edfc3894..b91c0db38 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -367,8 +367,8 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina -- local CoordinateFrom = Helicopter:GetCoordinate() -- local WaypointFrom = CoordinateFrom:WaypointAir( -- "RADIO", --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, +-- COORDINATE.WaypointType.TurningPoint, +-- COORDINATE.WaypointAction.TurningPoint, -- Speed, -- true -- ) @@ -380,8 +380,8 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina local WaypointTo = CoordinateTo:WaypointAir( "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, 50, true ) @@ -427,7 +427,7 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina local landheight = CoordinateTo:GetLandHeight() -- get target height CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, 50, true) Route[#Route+1] = WaypointTo local Tasks = {} @@ -496,14 +496,14 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin local CoordinateFrom = Helicopter:GetCoordinate() --- Create a route point of type air. - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) + local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true) --- Create a route point of type air. local CoordinateTo = Coordinate local landheight = CoordinateTo:GetLandHeight() -- get target height CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint,_speed, true) Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointTo @@ -563,7 +563,7 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin --- Create a route point of type air. local CoordinateFrom = Helicopter:GetCoordinate() - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) + local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true) Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointFrom @@ -573,7 +573,7 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin local landheight = CoordinateTo:GetLandHeight() -- get target height CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true) Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo @@ -631,7 +631,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Create a route point of type air. local CoordinateFrom = Helicopter:GetCoordinate() - local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true) + local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true) Route[#Route+1] = WaypointFrom --- Create a route point of type air. @@ -639,7 +639,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat local landheight = CoordinateTo:GetLandHeight() -- get target height CoordinateTo.y = landheight + Height -- flight height should be 50m above ground - local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true) Route[#Route+1] = WaypointTo diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index d7d2d1966..e42bd3a8f 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -725,7 +725,7 @@ function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, X for FollowID, FollowGroup in pairs( FollowSet ) do - local PointVec3 = POINT_VEC3:New() + local PointVec3 = COORDINATE:New() PointVec3:SetX( XStart + i * XSpace ) PointVec3:SetY( YStart + i * YSpace ) PointVec3:SetZ( ZStart + i * ZSpace ) @@ -877,7 +877,7 @@ function AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , for FollowID, FollowGroup in pairs( FollowSet ) do - local PointVec3 = POINT_VEC3:New() + local PointVec3 = COORDINATE:New() local Side = ( i % 2 == 0 ) and 1 or -1 local Row = i / 2 + 1 @@ -936,7 +936,7 @@ function AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XS for FollowID, FollowGroup in pairs( FollowSet ) do - local PointVec3 = POINT_VEC3:New() + local PointVec3 = COORDINATE:New() local ZIndex = i % ZLevels local XIndex = math.floor( i / ZLevels ) diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index e6516736a..408e3b1a6 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -751,12 +751,12 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) if not CurrentVec2 then return end --Done: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TakeOffParking, - POINT_VEC3.RoutePointAction.FromParkingArea, + COORDINATE.WaypointType.TakeOffParking, + COORDINATE.WaypointAction.FromParkingArea, ToPatrolZoneSpeed, true ) @@ -767,12 +767,12 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) if not CurrentVec2 then return end --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToPatrolZoneSpeed, true ) @@ -792,13 +792,13 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) + local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) --- Create a route point of type air. local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true ) @@ -890,12 +890,12 @@ function AI_PATROL_ZONE:onafterRTB() --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). --local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToPatrolZoneSpeed, true ) diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index 8b7d6040e..413beb514 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -275,14 +275,14 @@ -- The cargo must be in the **Loaded** state. -- @function [parent=#CARGO] UnBoard -- @param #CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. +-- @param Core.Point#COORDINATE ToPointVec2 (optional) @{Core.Point#COORDINATE) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. --- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. -- The cargo must be in the **Loaded** state. -- @function [parent=#CARGO] __UnBoard -- @param #CARGO self -- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. +-- @param Core.Point#COORDINATE ToPointVec2 (optional) @{Core.Point#COORDINATE) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. -- Load @@ -307,14 +307,14 @@ -- The cargo must be in the **Loaded** state. -- @function [parent=#CARGO] UnLoad -- @param #CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. +-- @param Core.Point#COORDINATE ToPointVec2 (optional) @{Core.Point#COORDINATE) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. --- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. -- The cargo must be in the **Loaded** state. -- @function [parent=#CARGO] __UnLoad -- @param #CARGO self -- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. +-- @param Core.Point#COORDINATE ToPointVec2 (optional) @{Core.Point#COORDINATE) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. -- State Transition Functions @@ -467,7 +467,7 @@ do -- CARGO self.Type = Type self.Name = Name self.Weight = Weight or 0 - self.CargoObject = nil + self.CargoObject = nil -- Wrapper.Group#GROUP self.CargoCarrier = nil -- Wrapper.Client#CLIENT self.Representable = false self.Slingloadable = false @@ -897,7 +897,7 @@ do -- CARGO --- Get the current PointVec2 of the cargo. -- @param #CARGO self - -- @return Core.Point#POINT_VEC2 + -- @return Core.Point#COORDINATE function CARGO:GetPointVec2() return self.CargoObject:GetPointVec2() end @@ -1094,7 +1094,7 @@ do -- CARGO_REPRESENTABLE --- Route a cargo unit to a PointVec2. -- @param #CARGO_REPRESENTABLE self - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number Speed -- @return #CARGO_REPRESENTABLE function CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) diff --git a/Moose Development/Moose/Cargo/CargoCrate.lua b/Moose Development/Moose/Cargo/CargoCrate.lua index c0fbdd631..2dc94cd5a 100644 --- a/Moose Development/Moose/Cargo/CargoCrate.lua +++ b/Moose Development/Moose/Cargo/CargoCrate.lua @@ -114,7 +114,7 @@ do -- CARGO_CRATE -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 + -- @param Core.Point#COORDINATE function CARGO_CRATE:onenterUnLoaded( From, Event, To, ToPointVec2 ) --self:T( { ToPointVec2, From, Event, To } ) diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index ca6a96a69..abcc89ea8 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -22,6 +22,7 @@ do -- CARGO_GROUP --- @type CARGO_GROUP -- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects. -- @field #string GroupName The name of the CargoGroup. + -- @field Wrapper.Group#GROUÜ CargoCarrier The carrier group. -- @extends Cargo.Cargo#CARGO_REPORTABLE --- Defines a cargo that is represented by a @{Wrapper.Group} object within the simulator. @@ -410,7 +411,7 @@ do -- CARGO_GROUP -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onafterUnBoard( From, Event, To, ToPointVec2, NearRadius, ... ) self:T( {From, Event, To, ToPointVec2, NearRadius } ) @@ -453,7 +454,7 @@ do -- CARGO_GROUP -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) --self:T( { From, Event, To, ToPointVec2, NearRadius } ) @@ -491,7 +492,7 @@ do -- CARGO_GROUP -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... ) --self:T( { From, Event, To, ToPointVec2 } ) diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua index a1d86dd49..a76469870 100644 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -72,7 +72,7 @@ do -- CARGO_UNIT -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius (optional) Defaut 25 m. function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:T( { From, Event, To, ToPointVec2, NearRadius } ) @@ -145,7 +145,7 @@ do -- CARGO_UNIT -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius (optional) Defaut 100 m. function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:T( { From, Event, To, ToPointVec2, NearRadius } ) @@ -171,7 +171,7 @@ do -- CARGO_UNIT -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius (optional) Defaut 100 m. function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:T( { From, Event, To, ToPointVec2, NearRadius } ) @@ -197,7 +197,7 @@ do -- CARGO_UNIT -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 + -- @param Core.Point#COORDINATE function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 ) self:T( { ToPointVec2, From, Event, To } ) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 9afc49b7a..31b286aa3 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1157,6 +1157,151 @@ do -- COORDINATE return vec3 end + --- Return the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The x coordinate. + function COORDINATE:GetX() + return self.x + end + + --- Return the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The y coordinate. + function COORDINATE:GetY() + return self.y + end + + --- Return the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The z coordinate. + function COORDINATE:GetZ() + return self.z + end + + --- Set the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number x The x coordinate. + -- @return #COORDINATE + function COORDINATE:SetX( x ) + self.x = x + return self + end + + --- Set the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number y The y coordinate. + -- @return #COORDINATE + function COORDINATE:SetY( y ) + self.y = y + return self + end + + --- Set the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number z The z coordinate. + -- @return #COORDINATE + function COORDINATE:SetZ( z ) + self.z = z + return self + end + + --- Add to the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number x The x coordinate value to add to the current x coordinate. + -- @return #COORDINATE + function COORDINATE:AddX( x ) + self.x = self.x + x + return self + end + + + --- Return Return the Lat(itude) coordinate of the COORDINATE (ie: (parent)COORDINATE.x). + -- @param #COORDINATE self + -- @return #number The x coordinate. + function COORDINATE:GetLat() + return self.x + end + + --- Set the Lat(itude) coordinate of the COORDINATE (ie: COORDINATE.x). + -- @param #COORDINATE self + -- @param #number x The x coordinate. + -- @return #COORDINATE + function COORDINATE:SetLat( x ) + self.x = x + return self + end + + --- Return the Lon(gitude) coordinate of the COORDINATE (ie: (parent)COORDINATE.z). + -- @param #COORDINATE self + -- @return #number The y coordinate. + function COORDINATE:GetLon() + return self.z + end + + --- Set the Lon(gitude) coordinate of the COORDINATE (ie: COORDINATE.z). + -- @param #COORDINATE self + -- @param #number y The y coordinate. + -- @return #COORDINATE + function COORDINATE:SetLon( z ) + self.z = z + return self + end + + --- Return the altitude (height) of the land at the COORDINATE. + -- @param #COORDINATE self + -- @return #number The land altitude. + function COORDINATE:GetAlt() + return self.y ~= 0 or land.getHeight( { x = self.x, y = self.z } ) + end + + --- Set the altitude of the COORDINATE. + -- @param #COORDINATE self + -- @param #number Altitude The land altitude. If nothing (nil) is given, then the current land altitude is set. + -- @return #COORDINATE + function COORDINATE:SetAlt( Altitude ) + self.y = Altitude or land.getHeight( { x = self.x, y = self.z } ) + return self + end + + --- Add to the current land height an altitude. + -- @param #COORDINATE self + -- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. + -- @return #COORDINATE + function COORDINATE:AddAlt( Altitude ) + self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 + return self + end + + + --- Return a random COORDINATE within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. + -- @param #COORDINATE self + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius + -- @return #COORDINATE + function COORDINATE:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) + self:F2( { OuterRadius, InnerRadius } ) + + return COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) + end + + --- Add to the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number y The y coordinate value to add to the current y coordinate. + -- @return #COORDINATE + function COORDINATE:AddY( y ) + self.y = self.y + y + return self + end + + --- Add to the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number z The z coordinate value to add to the current z coordinate. + -- @return #COORDINATE + function COORDINATE:AddZ( z ) + self.z = self.z +z + return self + end + --- Returns a text documenting the wind direction (from) and strength according the measurement system @{Core.Settings}. -- The text will reflect the wind like this: @@ -3474,9 +3619,18 @@ do -- COORDINATE return flat, elev end + --- Return a random COORDINATE within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. + -- @param #COORDINATE self + -- @param DCS#Distance OuterRadius + -- @param DCS#Distance InnerRadius + -- @return #COORDINATE + function COORDINATE:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) + return COORDINATE:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) + end + end -do -- POINT_VEC3 +do --- The POINT_VEC3 class -- @type POINT_VEC3 @@ -3493,6 +3647,8 @@ do -- POINT_VEC3 --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- + -- **DEPRECATED - PLEASE USE COORDINATE!** + -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. -- In order to keep the credibility of the the author, -- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors, @@ -3580,130 +3736,19 @@ do -- POINT_VEC3 return self end - --- Create a new POINT_VEC3 object from Vec2 coordinates. - -- @param #POINT_VEC3 self - -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @param DCS#Distance LandHeightAdd (optional) Add a landheight. - -- @return Core.Point#POINT_VEC3 self - function POINT_VEC3:NewFromVec2( Vec2, LandHeightAdd ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) ) -- Core.Point#POINT_VEC3 - self:F2( self ) - - return self - end - - - --- Create a new POINT_VEC3 object from Vec3 coordinates. - -- @param #POINT_VEC3 self - -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return Core.Point#POINT_VEC3 self - function POINT_VEC3:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec3( Vec3 ) ) -- Core.Point#POINT_VEC3 - self:F2( self ) - - return self - end - - - - --- Return the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The x coordinate. - function POINT_VEC3:GetX() - return self.x - end - - --- Return the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The y coordinate. - function POINT_VEC3:GetY() - return self.y - end - - --- Return the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The z coordinate. - function POINT_VEC3:GetZ() - return self.z - end - - --- Set the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetX( x ) - self.x = x - return self - end - - --- Set the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetY( y ) - self.y = y - return self - end - - --- Set the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number z The z coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetZ( z ) - self.z = z - return self - end - - --- Add to the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number x The x coordinate value to add to the current x coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddX( x ) - self.x = self.x + x - return self - end - - --- Add to the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number y The y coordinate value to add to the current y coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddY( y ) - self.y = self.y + y - return self - end - - --- Add to the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number z The z coordinate value to add to the current z coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddZ( z ) - self.z = self.z +z - return self - end - - --- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param DCS#Distance OuterRadius - -- @param DCS#Distance InnerRadius - -- @return #POINT_VEC3 - function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) - - return POINT_VEC3:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) - end - end -do -- POINT_VEC2 +do - -- @type POINT_VEC2 + --- @type POINT_VEC2 -- @field DCS#Distance x The x coordinate in meters. -- @field DCS#Distance y the y coordinate in meters. -- @extends Core.Point#COORDINATE --- Defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. -- + -- **DEPRECATED - PLEASE USE COORDINATE!** + -- -- ## POINT_VEC2 constructor -- -- A new POINT_VEC2 instance can be created with: @@ -3751,166 +3796,4 @@ do -- POINT_VEC2 return self end - --- Create a new POINT_VEC2 object from Vec2 coordinates. - -- @param #POINT_VEC2 self - -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @return Core.Point#POINT_VEC2 self - function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) - - local LandHeight = land.getHeight( Vec2 ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - local self = BASE:Inherit( self, COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) ) -- #POINT_VEC2 - self:F2( self ) - - return self - end - - --- Create a new POINT_VEC2 object from Vec3 coordinates. - -- @param #POINT_VEC2 self - -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return Core.Point#POINT_VEC2 self - function POINT_VEC2:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec3( Vec3 ) ) -- #POINT_VEC2 - self:F2( self ) - - return self - end - - --- Return the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The x coordinate. - function POINT_VEC2:GetX() - return self.x - end - - --- Return the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The y coordinate. - function POINT_VEC2:GetY() - return self.z - end - - --- Set the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetX( x ) - self.x = x - return self - end - - --- Set the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetY( y ) - self.z = y - return self - end - - --- Return Return the Lat(itude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.x). - -- @param #POINT_VEC2 self - -- @return #number The x coordinate. - function POINT_VEC2:GetLat() - return self.x - end - - --- Set the Lat(itude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.x). - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetLat( x ) - self.x = x - return self - end - - --- Return the Lon(gitude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.z). - -- @param #POINT_VEC2 self - -- @return #number The y coordinate. - function POINT_VEC2:GetLon() - return self.z - end - - --- Set the Lon(gitude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.z). - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetLon( z ) - self.z = z - return self - end - - --- Return the altitude (height) of the land at the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The land altitude. - function POINT_VEC2:GetAlt() - return self.y ~= 0 or land.getHeight( { x = self.x, y = self.z } ) - end - - --- Set the altitude of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number Altitude The land altitude. If nothing (nil) is given, then the current land altitude is set. - -- @return #POINT_VEC2 - function POINT_VEC2:SetAlt( Altitude ) - self.y = Altitude or land.getHeight( { x = self.x, y = self.z } ) - return self - end - - --- Add to the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:AddX( x ) - self.x = self.x + x - return self - end - - --- Add to the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:AddY( y ) - self.z = self.z + y - return self - end - - --- Add to the current land height an altitude. - -- @param #POINT_VEC2 self - -- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. - -- @return #POINT_VEC2 - function POINT_VEC2:AddAlt( Altitude ) - self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 - return self - end - - - --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param DCS#Distance OuterRadius - -- @param DCS#Distance InnerRadius - -- @return #POINT_VEC2 - function POINT_VEC2:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { OuterRadius, InnerRadius } ) - - return POINT_VEC2:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) - end - - -- TODO: Check this to replace - --- Calculate the distance from a reference @{#POINT_VEC2}. - -- @param #POINT_VEC2 self - -- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. - -- @return DCS#Distance The distance from the reference @{#POINT_VEC2} in meters. - function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) - self:F2( PointVec2Reference ) - - local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5 - - self:T2( Distance ) - return Distance - end - end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 3f4fa2e71..0b54f06d0 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -629,14 +629,14 @@ do -- SET_BASE return self end - --- Iterate the SET_BASE while identifying the nearest object in the set from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_BASE while identifying the nearest object in the set from a @{Core.Point#COORDINATE}. -- @param #SET_BASE self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#COORDINATE} or @{Core.Point#POINT_VEC2} object (but **not** a simple DCS#Vec2!) from where to evaluate the closest object in the set. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object (but **not** a simple DCS#Vec2!) from where to evaluate the closest object in the set. -- @return Core.Base#BASE The closest object. -- @usage -- myset:FindNearestObjectFromPointVec2( ZONE:New("Test Zone"):GetCoordinate() ) - function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) - --self:F2( PointVec2 ) + function SET_BASE:FindNearestObjectFromPointVec2( Coordinate ) + --self:F2( Coordinate ) local NearestObject = nil local ClosestDistance = nil @@ -644,9 +644,9 @@ do -- SET_BASE for ObjectID, ObjectData in pairs( self.Set ) do if NearestObject == nil then NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + ClosestDistance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) else - local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + local Distance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) if Distance < ClosestDistance then NearestObject = ObjectData ClosestDistance = Distance @@ -1242,12 +1242,12 @@ do return GroupFound end - --- Iterate the SET_GROUP while identifying the nearest object from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_GROUP while identifying the nearest object from a @{Core.Point#COORDINATE}. -- @param #SET_GROUP self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest object in the set. -- @return Wrapper.Group#GROUP The closest group. - function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) - --self:F2( PointVec2 ) + function SET_GROUP:FindNearestGroupFromPointVec2( Coordinate ) + --self:F2( Coordinate ) local NearestGroup = nil -- Wrapper.Group#GROUP local ClosestDistance = nil @@ -1257,9 +1257,9 @@ do for ObjectID, ObjectData in pairs( Set ) do if NearestGroup == nil then NearestGroup = ObjectData - ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + ClosestDistance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) else - local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + local Distance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) if Distance < ClosestDistance then NearestGroup = ObjectData ClosestDistance = Distance @@ -5670,14 +5670,14 @@ do -- SET_AIRBASE return self end - --- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#COORDINATE}. -- @param #SET_AIRBASE self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. -- @return Wrapper.Airbase#AIRBASE The closest @{Wrapper.Airbase#AIRBASE}. - function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) - --self:F2( PointVec2 ) + function SET_AIRBASE:FindNearestAirbaseFromPointVec2( Coordinate ) + --self:F2( Coordinate ) - local NearestAirbase = self:FindNearestObjectFromPointVec2( PointVec2 ) + local NearestAirbase = self:FindNearestObjectFromPointVec2( Coordinate ) return NearestAirbase end @@ -6007,17 +6007,19 @@ do -- SET_CARGO return self end - --- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#POINT_VEC2}. + --- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#COORDINATE}. -- @param #SET_CARGO self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Cargo.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. - function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) -- R2.1 - --self:F2( PointVec2 ) + function SET_CARGO:FindNearestCargoFromPointVec2( Coordinate ) -- R2.1 + --self:F2( Coordinate ) - local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) + local NearestCargo = self:FindNearestObjectFromPointVec2( Coordinate ) return NearestCargo end - + + --- + -- @param #SET_CARGO self function SET_CARGO:FirstCargoWithState( State ) local FirstCargo = nil @@ -6032,6 +6034,8 @@ do -- SET_CARGO return FirstCargo end + --- + -- @param #SET_CARGO self function SET_CARGO:FirstCargoWithStateAndNotDeployed( State ) local FirstCargo = nil diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index de81f9a46..c81aea59d 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1631,7 +1631,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if SpawnTemplate then - local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) + local PointVec3 = COORDINATE:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) --self:T2( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) -- If RandomizePosition, then Randomize the formation in the zone band, keeping the template. @@ -2830,7 +2830,7 @@ end function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) --self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) + local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) --self:T2( PointVec3 ) if SpawnIndex then @@ -2906,7 +2906,7 @@ end -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group. +-- @param Core.Point#COORDINATE PointVec3 The COORDINATE coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage @@ -2954,12 +2954,12 @@ function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex ) return self:SpawnFromVec3( { x = Vec2.x, y = Height, z = Vec2.y }, SpawnIndex ) -- y can be nil. In this case, spawn on the ground for vehicles, and in the template altitude for air. end ---- Will spawn a group from a POINT_VEC2 in 3D space. +--- Will spawn a group from a COORDINATE in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 coordinates where to spawn the group. +-- @param Core.Point#COORDINATE PointVec2 The coordinates where to spawn the group. -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index d68a1426d..9feb2eb90 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -1,599 +1,130 @@ ---- **Core** - Spawn statics. --- --- === --- --- ## Features: --- --- * Spawn new statics from a static already defined in the mission editor. --- * Spawn new statics from a given template. --- * Spawn new statics from a given type. --- * Spawn with a custom heading and location. --- * Spawn within a zone. --- * Spawn statics linked to units, .e.g on aircraft carriers. --- --- === --- --- # Demo Missions --- --- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/SpawnStatic) --- --- --- === --- --- # YouTube Channel --- --- ## No videos yet! --- --- === --- --- ### Author: **FlightControl** --- ### Contributions: **funkyfranky** --- --- === --- --- @module Core.SpawnStatic --- @image Core_Spawnstatic.JPG ---- @type SPAWNSTATIC --- @field #string SpawnTemplatePrefix Name of the template group. --- @field #number CountryID Country ID. --- @field #number CoalitionID Coalition ID. --- @field #number CategoryID Category ID. --- @field #number SpawnIndex Running number increased with each new Spawn. --- @field Wrapper.Unit#UNIT InitLinkUnit The unit the static is linked to. --- @field #number InitOffsetX Link offset X coordinate. --- @field #number InitOffsetY Link offset Y coordinate. --- @field #number InitOffsetAngle Link offset angle in degrees. --- @field #number InitStaticHeading Heading of the static. --- @field #string InitStaticLivery Livery for aircraft. --- @field #string InitStaticShape Shape of the static. --- @field #string InitStaticType Type of the static. --- @field #string InitStaticCategory Categrory of the static. --- @field #string InitStaticName Name of the static. --- @field Core.Point#COORDINATE InitStaticCoordinate Coordinate where to spawn the static. --- @field #boolean InitStaticDead Set static to be dead if true. --- @field #boolean InitStaticCargo If true, static can act as cargo. --- @field #number InitStaticCargoMass Mass of cargo in kg. --- @extends Core.Base#BASE +--[[ +local CA_SET=SET_CLIENT:New():HandleCASlots():FilterCoalitions("blue"):FilterStart() - ---- Allows to spawn dynamically new @{Wrapper.Static}s into your mission. --- --- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), --- and "copy" these properties to create a new static object and place it at the desired coordinate. --- --- New spawned @{Wrapper.Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method. --- By default, spawned @{Wrapper.Static}s will follow a naming convention at run-time: --- --- * Spawned @{Wrapper.Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**. --- --- # SPAWNSTATIC Constructors --- --- Firstly, we need to create a SPAWNSTATIC object that will be used to spawn new statics into the mission. There are three ways to do this. --- --- ## Use another Static --- --- A new SPAWNSTATIC object can be created using another static by the @{#SPAWNSTATIC.NewFromStatic}() function. All parameters such as position, heading, country will be initialized --- from the static. --- --- ## From a Template --- --- A SPAWNSTATIC object can also be created from a template table using the @{#SPAWNSTATIC.NewFromTemplate}(SpawnTemplate, CountryID) function. All parameters are taken from the template. --- --- ## From a Type --- --- A very basic method is to create a SPAWNSTATIC object by just giving the type of the static. All parameters must be initialized from the InitXYZ functions described below. Otherwise default values --- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map. --- --- # Setting Parameters --- --- Parameters such as the spawn position, heading, country etc. can be set via :Init*XYZ* functions. Note that these functions must be given before the actual spawn command! --- --- * @{#SPAWNSTATIC.InitCoordinate}(Coordinate) Sets the coordinate where the static is spawned. Statics are always spawnd on the ground. --- * @{#SPAWNSTATIC.InitHeading}(Heading) sets the orientation of the static. --- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this. --- * @{#SPAWNSTATIC.InitType}(StaticType) sets the type of the static. --- * @{#SPAWNSTATIC.InitShape}(StaticType) sets the shape of the static. Not all statics have this parameter. --- * @{#SPAWNSTATIC.InitNamePrefix}(NamePrefix) sets the name prefix of the spawned statics. --- * @{#SPAWNSTATIC.InitCountry}(CountryID) sets the country and therefore the coalition of the spawned statics. --- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier. --- --- # Spawning the Statics --- --- Once the SPAWNSTATIC object is created and parameters are initialized, the spawn command can be given. There are different methods where some can be used to directly set parameters --- such as position and heading. --- --- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**! --- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**! --- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**! --- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Core.Zone}. Optionally, heading and name can be given. The name **must be unique**! --- --- @field #SPAWNSTATIC SPAWNSTATIC --- -SPAWNSTATIC = { - ClassName = "SPAWNSTATIC", - SpawnIndex = 0, -} - ---- Static template table data. --- @type SPAWNSTATIC.TemplateData --- @field #string name Name of the static. --- @field #string type Type of the static. --- @field #string category Category of the static. --- @field #number x X-coordinate of the static. --- @field #number y Y-coordinate of teh static. --- @field #number heading Heading in rad. --- @field #boolean dead Static is dead if true. --- @field #string livery_id Livery name. --- @field #number unitId Unit ID. --- @field #number groupId Group ID. --- @field #table offsets Offset parameters when linked to a unit. --- @field #number mass Cargo mass in kg. --- @field #boolean canCargo Static can be a cargo. - ---- Creates the main object to spawn a @{Wrapper.Static} defined in the mission editor (ME). --- @param #SPAWNSTATIC self --- @param #string SpawnTemplateName Name of the static object in the ME. Each new static will have the name starting with this prefix. --- @param DCS#country.id SpawnCountryID (Optional) The ID of the country. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) - - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - - local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) - - if TemplateStatic then - self.SpawnTemplatePrefix = SpawnTemplateName - self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) - self.CountryID = SpawnCountryID or CountryID - self.CategoryID = CategoryID - self.CoalitionID = CoalitionID - self.SpawnIndex = 0 - else - error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) +function CA_SET:OnAfterAdded(From,Event,To,ObjectName,Object) + MESSAGE:New("Player joined CA Slot: "..ObjectName,10,"CA"):ToAll() + local client = Object -- Wrapper.Client#CLIENT + local group = client:GetGroup() + if group then + MENU_GROUP:New(group,"Test CA") end - - self:SetEventPriority( 5 ) - - return self end ---- Creates the main object to spawn a @{Wrapper.Static} given a template table. --- @param #SPAWNSTATIC self --- @param #table SpawnTemplate Template used for spawning. --- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID) - - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - - self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate) - self.SpawnTemplatePrefix = SpawnTemplate.name - self.CountryID = CountryID or country.id.USA - - return self -end - ---- Creates the main object to spawn a @{Wrapper.Static} from a given type. --- NOTE that you have to init many other parameters as spawn coordinate etc. --- @param #SPAWNSTATIC self --- @param #string StaticType Type of the static. --- @param #string StaticCategory Category of the static, e.g. "Planes". --- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID) - - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - - self.InitStaticType=StaticType - self.InitStaticCategory=StaticCategory - self.CountryID=CountryID or country.id.USA - self.SpawnTemplatePrefix=self.InitStaticType - self.TemplateStaticUnit = {} - - self.InitStaticCoordinate=COORDINATE:New(0, 0, 0) - self.InitStaticHeading=0 - - return self -end - ---- (Internal/Cargo) Init the resource table for STATIC object that should be spawned containing storage objects. --- NOTE that you have to init many other parameters as the resources. --- @param #SPAWNSTATIC self --- @param #number CombinedWeight The weight this cargo object should have (some have fixed weights!), defaults to 1kg. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:_InitResourceTable(CombinedWeight) - if not self.TemplateStaticUnit.resourcePayload then - self.TemplateStaticUnit.resourcePayload = { - ["weapons"] = {}, - ["aircrafts"] = {}, - ["gasoline"] = 0, - ["diesel"] = 0, - ["methanol_mixture"] = 0, - ["jet_fuel"] = 0, - } - end - self:InitCargo(true) - self:InitCargoMass(CombinedWeight or 1) - return self -end - ---- (User/Cargo) Add to resource table for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. --- @param #SPAWNSTATIC self --- @param #string Type Type of cargo. Known types are: STORAGE.Type.WEAPONS, STORAGE.Type.LIQUIDS, STORAGE.Type.AIRCRAFT. Liquids are fuel. --- @param #string Name Name of the cargo type. Liquids can be STORAGE.LiquidName.JETFUEL, STORAGE.LiquidName.GASOLINE, STORAGE.LiquidName.MW50 and STORAGE.LiquidName.DIESEL. The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. Aircraft go by their typename. --- @param #number Amount of tons (liquids) or number (everything else) to add. --- @param #number CombinedWeight Combined weight to be set to this static cargo object. NOTE - some static cargo objects have fixed weights! --- @return #SPAWNSTATIC self -function SPAWNSTATIC:AddCargoResource(Type,Name,Amount,CombinedWeight) - if not self.TemplateStaticUnit.resourcePayload then - self:_InitResourceTable(CombinedWeight) - end - if Type == STORAGE.Type.LIQUIDS and type(Name) == "string" then - self.TemplateStaticUnit.resourcePayload[Name] = Amount - else - self.TemplateStaticUnit.resourcePayload[Type] = { - [Name] = { - ["amount"] = Amount, - } - } - end - UTILS.PrintTableToLog(self.TemplateStaticUnit) - return self -end - ---- (User/Cargo) Resets resource table to zero for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. --- Handy if you spawn from cargo statics which have resources already set. --- @param #SPAWNSTATIC self --- @return #SPAWNSTATIC self -function SPAWNSTATIC:ResetCargoResources() - self.TemplateStaticUnit.resourcePayload = nil - self:_InitResourceTable() - return self -end - ---- Initialize heading of the spawned static. --- @param #SPAWNSTATIC self --- @param Core.Point#COORDINATE Coordinate Position where the static is spawned. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitCoordinate(Coordinate) - self.InitStaticCoordinate=Coordinate - return self -end - ---- Initialize heading of the spawned static. --- @param #SPAWNSTATIC self --- @param #number Heading The heading in degrees. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitHeading(Heading) - self.InitStaticHeading=Heading - return self -end - ---- Initialize livery of the spawned static. --- @param #SPAWNSTATIC self --- @param #string LiveryName Name of the livery to use. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitLivery(LiveryName) - self.InitStaticLivery=LiveryName - return self -end - ---- Initialize type of the spawned static. --- @param #SPAWNSTATIC self --- @param #string StaticType Type of the static, e.g. "FA-18C_hornet". --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitType(StaticType) - self.InitStaticType=StaticType - return self -end - ---- Initialize shape of the spawned static. Required by some but not all statics. --- @param #SPAWNSTATIC self --- @param #string StaticShape Shape of the static, e.g. "carrier_tech_USA". --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitShape(StaticShape) - self.InitStaticShape=StaticShape - return self -end - ---- Initialize parameters for spawning FARPs. --- @param #SPAWNSTATIC self --- @param #number CallsignID Callsign ID. Default 1 (="London"). --- @param #number Frequency Frequency in MHz. Default 127.5 MHz. --- @param #number Modulation Modulation 0=AM, 1=FM. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation) - self.InitFarp=true - self.InitFarpCallsignID=CallsignID or 1 - self.InitFarpFreq=Frequency or 127.5 - self.InitFarpModu=Modulation or 0 - return self -end - ---- Initialize cargo mass. --- @param #SPAWNSTATIC self --- @param #number Mass Mass of the cargo in kg. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitCargoMass(Mass) - self.InitStaticCargoMass=Mass - return self -end - ---- Initialize as cargo. --- @param #SPAWNSTATIC self --- @param #boolean IsCargo If true, this static can act as cargo. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitCargo(IsCargo) - self.InitStaticCargo=IsCargo - return self -end - ---- Initialize as dead. --- @param #SPAWNSTATIC self --- @param #boolean IsDead If true, this static is dead. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitDead(IsDead) - self.InitStaticDead=IsDead - return self -end - ---- Initialize country of the spawned static. This determines the category. --- @param #SPAWNSTATIC self --- @param #string CountryID The country ID, e.g. country.id.USA. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitCountry(CountryID) - self.CountryID=CountryID - return self -end - ---- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc. --- @param #SPAWNSTATIC self --- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitNamePrefix(NamePrefix) - self.SpawnTemplatePrefix=NamePrefix - return self -end - ---- Init link to a unit. --- @param #SPAWNSTATIC self --- @param Wrapper.Unit#UNIT Unit The unit to which the static is linked. --- @param #number OffsetX Offset in X. --- @param #number OffsetY Offset in Y. --- @param #number OffsetAngle Offset angle in degrees. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle) - - self.InitLinkUnit=Unit - self.InitOffsetX=OffsetX or 0 - self.InitOffsetY=OffsetY or 0 - self.InitOffsetAngle=OffsetAngle or 0 - - return self -end - ---- Allows to place a CallFunction hook when a new static spawns. --- The provided method will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned. --- @param #SPAWNSTATIC self --- @param #function SpawnCallBackFunction The function to be called when a group spawns. --- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. --- @return #SPAWNSTATIC self -function SPAWNSTATIC:OnSpawnStatic( SpawnCallBackFunction, ... ) - self:F( "OnSpawnStatic" ) - - self.SpawnFunctionHook = SpawnCallBackFunction - self.SpawnFunctionArguments = {} - if arg then - self.SpawnFunctionArguments = arg - end - - return self -end - ---- Spawn a new STATIC object. --- @param #SPAWNSTATIC self --- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template. --- @param #string NewName (Optional) The name of the new static. --- @return Wrapper.Static#STATIC The static spawned. -function SPAWNSTATIC:Spawn(Heading, NewName) - - if Heading then - self.InitStaticHeading=Heading - end - - if NewName then - self.InitStaticName=NewName - end - - return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID) - -end - ---- Creates a new @{Wrapper.Static} from a POINT_VEC2. --- @param #SPAWNSTATIC self --- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static. --- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. --- @param #string NewName (Optional) The name of the new static. --- @return Wrapper.Static#STATIC The static spawned. -function SPAWNSTATIC:SpawnFromPointVec2(PointVec2, Heading, NewName) - - local vec2={x=PointVec2:GetX(), y=PointVec2:GetY()} - - local Coordinate=COORDINATE:NewFromVec2(vec2) - - return self:SpawnFromCoordinate(Coordinate, Heading, NewName) -end - - ---- Creates a new @{Wrapper.Static} from a COORDINATE. --- @param #SPAWNSTATIC self --- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static. --- @param #number Heading (Optional) Heading The heading of the static in degrees. Default is 0 degrees. --- @param #string NewName (Optional) The name of the new static. --- @return Wrapper.Static#STATIC The spawned STATIC object. -function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName) - - -- Set up coordinate. - self.InitStaticCoordinate=Coordinate - - if Heading then - self.InitStaticHeading=Heading - end - - if NewName then - self.InitStaticName=NewName - end - - return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID) -end - - ---- Creates a new @{Wrapper.Static} from a @{Core.Zone}. --- @param #SPAWNSTATIC self --- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static. --- @param #number Heading (Optional)The heading of the static in degrees. Default is the heading of the template. --- @param #string NewName (Optional) The name of the new static. --- @return Wrapper.Static#STATIC The static spawned. -function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName) - - -- Spawn the new static at the center of the zone. - local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName ) - - return Static -end - ---- Spawns a new static using a given template. Additionally, the country ID needs to be specified, which also determines the coalition of the spawned static. --- @param #SPAWNSTATIC self --- @param #SPAWNSTATIC.TemplateData Template Spawn unit template. --- @param #number CountryID The country ID. --- @return Wrapper.Static#STATIC The static spawned. -function SPAWNSTATIC:_SpawnStatic(Template, CountryID) - - Template=Template or {} - - local CountryID=CountryID or self.CountryID - - if self.InitStaticType then - Template.type=self.InitStaticType - end - - if self.InitStaticCategory then - Template.category=self.InitStaticCategory - end - - if self.InitStaticCoordinate then - Template.x = self.InitStaticCoordinate.x - Template.y = self.InitStaticCoordinate.z - Template.alt = self.InitStaticCoordinate.y - end - - if self.InitStaticHeading then - Template.heading = math.rad(self.InitStaticHeading) - end - - if self.InitStaticShape then - Template.shape_name=self.InitStaticShape - end - - if self.InitStaticLivery then - Template.livery_id=self.InitStaticLivery - end - - if self.InitStaticDead~=nil then - Template.dead=self.InitStaticDead - end - - if self.InitStaticCargo~=nil then - Template.canCargo=self.InitStaticCargo - end - - if self.InitStaticCargoMass~=nil then - Template.mass=self.InitStaticCargoMass - end - - if self.InitLinkUnit then - Template.linkUnit=self.InitLinkUnit:GetID() - Template.linkOffset=true - Template.offsets={} - Template.offsets.y=self.InitOffsetY - Template.offsets.x=self.InitOffsetX - Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0 - end - - if self.InitFarp then - Template.heliport_callsign_id = self.InitFarpCallsignID - Template.heliport_frequency = self.InitFarpFreq - Template.heliport_modulation = self.InitFarpModu - Template.unitId=nil - end - - -- Increase spawn index counter. - self.SpawnIndex = self.SpawnIndex + 1 - - -- Name of the spawned static. - Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) - - -- Add static to the game. - local Static=nil --DCS#StaticObject - - if self.InitFarp then - - local TemplateGroup={} - TemplateGroup.units={} - TemplateGroup.units[1]=Template - - TemplateGroup.visible=true - TemplateGroup.hidden=false - TemplateGroup.x=Template.x - TemplateGroup.y=Template.y - TemplateGroup.name=Template.name - - self:T("Spawning FARP") - self:T({Template=Template}) - self:T({TemplateGroup=TemplateGroup}) - - -- ED's dirty way to spawn FARPS. - Static=coalition.addGroup(CountryID, -1, TemplateGroup) - - -- Currently DCS 2.8 does not trigger birth events if FARPS are spawned! - -- We create such an event. The airbase is registered in Core.Event - local Event = { - id = EVENTS.Birth, - time = timer.getTime(), - initiator = Static - } - -- Create BIRTH event. - world.onEvent(Event) - - else - self:T("Spawning Static") - self:T2({Template=Template}) - Static=coalition.addStaticObject(CountryID, Template) - - if Static then - self:T(string.format("Succesfully spawned static object \"%s\" ID=%d", Static:getName(), Static:getID())) - --[[ - local static=StaticObject.getByName(Static:getName()) - if static then - env.info(string.format("FF got static from StaticObject.getByName")) - else - env.error(string.format("FF error did NOT get static from StaticObject.getByName")) - end ]] - else - self:E(string.format("ERROR: DCS static object \"%s\" is nil!", tostring(Template.name))) - end + local e = {} + function e:onEvent(event) + local m = {} + m[#m+1] = "Event ID: " + m[#m+1] = event.id + if event.initiator then + m[#m+1] = "\nInitiator : " + m[#m+1] = event.initiator:getName() + end + if event.weapon then + m[#m+1] = "\nWeapon : " + m[#m+1] = event.weapon :getTypeName() + end + if event.target then + m[#m+1] = "\nTarget : " + m[#m+1] = event.target :getName() + end + env.info(table.concat(m)) end + world.addEventHandler(e) - -- Add and register the new static. - local mystatic=_DATABASE:AddStatic(Template.name) + local recce = PLAYERRECCE:New(Name,Coalition,PlayerSet) - -- If there is a SpawnFunction hook defined, call it. - if self.SpawnFunctionHook then - -- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. - self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments)) - end + + US_Patrol_Plane = SPAWN + :New("Bird Dog") + :InitLimit(1,4) + :OnSpawnGroup(function ( SpawnedGroup ) + -- Setup AI Patrol + PatrolZone = ZONE:New("Conflict Zone Alpha") + EngageZone = ZONE:New("Conflict Zone Alpha") + EngageZone:Draw() + AICaszone = AI_CAS_ZONE:New(PatrolZone, 100, 1000, 100, 100, EngageZone, "RADIO") + AICaszone:SetControllable(SpawnedGroup) + --AICaszone:SetEngageRange(2000) + AICaszone:__Start(1) + end + ) + :SpawnScheduled(30, 0) + +--]] - return mystatic +local grp = GROUP:FindByName("IR Blinker") +grp:NewIRMarker(true,90) + +function DestGroup() + if grp and grp:IsAlive() then + grp:Destroy() + end +end + +function DisableMarker() + if grp and grp:IsAlive() then + grp:DisableIRMarker() + end +end + +function EnableMarker() + if grp and grp:IsAlive() then + grp:EnableIRMarker() + end +end + +function RespGroup() + if grp and not grp:IsAlive() then + grp:Respawn() + end +end + +local mymsrs = MSRS:New(nil,243,0) +local jammersound=SOUNDFILE:New("beacon.ogg", "C:\\Users\\post\\Saved Games\\DCS\\Missions\\", 2, true) +function Play() + mymsrs:PlaySoundFile(jammersound) +end + +local topmenu = MENU_COALITION:New(coalition.side.BLUE,"IR Marker Test") +local startmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Enable IR",topmenu,EnableMarker) +local stopmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Disable IR",topmenu,DisableMarker) +local destmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Destroy Group",topmenu,DestGroup) +local respmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Respawn Group",topmenu,RespGroup) +local respmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Play Sound",topmenu,Play) + +local testzone = ZONE:New("Testzone") +testzone:Trigger(grp) + +function testzone:OnAfterObjectDead(From,Event,To,Controllable) + MESSAGE:New("Object Dead",15,"Test"):ToAll():ToLog() +end + +function testzone:OnAfterZoneEmpty(From,Event,To) + MESSAGE:New("Zone Empty",15,"Test"):ToAll():ToLog() +end + +local BlueBorder = ZONE:New("Blue Border") +local RedBorder = ZONE:New("Red Border") +local Conflict = ZONE:New("Conflict") + +BlueBorder:DrawZone(-1,{0,0,1},1,{0,0,1},.2,1,true) +RedBorder:DrawZone(-1,{1,0,0},1,{1,0,0},.2,1,true) +Conflict:DrawZone(-1,{1,254/255,1/33},1,{1,254/255,1/33},.2,1,true) + +BASE:TraceOn() +BASE:TraceClass("SHORAD") + +local mymantis = MANTIS:New("Red Defense","Red SAM","Red EWR",hq,"red",true,awacs,true) +mymantis:AddZones({RedBorder},{BlueBorder},{Conflict}) +mymantis.verbose = true +mymantis.debug = true +mymantis:Start() + +local myctld = CTLD:New() + +function myctld:OnAfterCratesDropped(From,Event,To,Group,Unit,Cargotable) + if Unit and string.find(Unit:GetTypeName(),"Mosquito",1,true) then + myctld:_BuildCrates(Group,Unit,true) + end end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 4e97f195b..0ad088d6d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -213,7 +213,7 @@ end --- Returns if a PointVec3 is within the zone. -- @param #ZONE_BASE self --- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 to test. +-- @param Core.Point#COORDINATE PointVec3 The PointVec3 to test. -- @return #boolean true if the PointVec3 is within the zone. function ZONE_BASE:IsPointVec3InZone( PointVec3 ) local InZone = self:IsPointVec2InZone( PointVec3 ) @@ -227,16 +227,16 @@ function ZONE_BASE:GetVec2() return nil end ---- Returns a @{Core.Point#POINT_VEC2} of the zone. +--- Returns a @{Core.Point#COORDINATE} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. +-- @return Core.Point#COORDINATE The COORDINATE of the zone. function ZONE_BASE:GetPointVec2() --self:F2( self.ZoneName ) local Vec2 = self:GetVec2() - local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) + local PointVec2 = COORDINATE:NewFromVec2( Vec2 ) --self:T2( { PointVec2 } ) @@ -261,16 +261,16 @@ function ZONE_BASE:GetVec3( Height ) return Vec3 end ---- Returns a @{Core.Point#POINT_VEC3} of the zone. +--- Returns a @{Core.Point#COORDINATE} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. +-- @return Core.Point#COORDINATE The PointVec3 of the zone. function ZONE_BASE:GetPointVec3( Height ) --self:F2( self.ZoneName ) local Vec3 = self:GetVec3( Height ) - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) + local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) --self:T2( { PointVec3 } ) @@ -330,16 +330,16 @@ function ZONE_BASE:GetRandomVec2() return nil end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_BASE:GetRandomPointVec2() return nil end ---- Define a random @{Core.Point#POINT_VEC3} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_BASE:GetRandomPointVec3() return nil end @@ -814,8 +814,8 @@ end -- Various functions exist to find random points within the zone. -- -- * @{#ZONE_RADIUS.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. +-- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#COORDINATE} object representing a random 2D point in the zone. +-- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#COORDINATE} object representing a random 3D point in the zone. Note that the height of the point is at landheight. -- -- ## Draw zone -- @@ -1010,7 +1010,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Smoke( SmokeColor ) end return self @@ -1040,7 +1040,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Flare( FlareColor, Azimuth ) end return self @@ -1561,15 +1561,15 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) return point end ---- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2( inner, outer ) ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2( inner, outer ) ) --self:T3( { PointVec2 } ) @@ -1592,15 +1592,15 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) end ---- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC3 The @{Core.Point#POINT_VEC3} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) --self:F( self.ZoneName, inner, outer ) - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2( inner, outer ) ) + local PointVec3 = COORDINATE:NewFromVec2( self:GetRandomVec2( inner, outer ) ) --self:T3( { PointVec3 } ) @@ -2036,15 +2036,15 @@ function ZONE_GROUP:GetRandomVec2() return Point end ---- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_GROUP self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_GROUP:GetRandomPointVec2( inner, outer ) --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) --self:T3( { PointVec2 } ) @@ -2192,8 +2192,8 @@ end -- Various functions exist to find random points within the zone. -- -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#COORDINATE} object representing a random 2D point within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#COORDINATE} object representing a random 3D point at landheight within the zone. -- -- ## Draw zone -- @@ -2769,7 +2769,7 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) - POINT_VEC2:New( PointX, PointY ):Smoke( SmokeColor ) + COORDINATE:New( PointX, 0, PointY ):Smoke( SmokeColor ) end j = i i = i + 1 @@ -2804,7 +2804,7 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) - POINT_VEC2:New( PointX, PointY, AddHeight ):Flare(FlareColor, Azimuth) + COORDINATE:New( PointX, AddHeight, PointY ):Flare(FlareColor, Azimuth) end j = i i = i + 1 @@ -2880,26 +2880,26 @@ function ZONE_POLYGON_BASE:GetRandomVec2() end end ---- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Return a @{Core.Point#COORDINATE} object representing a random 2D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_POLYGON_BASE self --- @return @{Core.Point#POINT_VEC2} +-- @return @{Core.Point#COORDINATE} function ZONE_POLYGON_BASE:GetRandomPointVec2() --self:F2() - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) --self:T2( PointVec2 ) return PointVec2 end ---- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Return a @{Core.Point#COORDINATE} object representing a random 3D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_POLYGON_BASE self --- @return @{Core.Point#POINT_VEC3} +-- @return @{Core.Point#COORDINATE} function ZONE_POLYGON_BASE:GetRandomPointVec3() --self:F2() - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) + local PointVec3 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) --self:T2( PointVec3 ) @@ -3931,18 +3931,18 @@ function ZONE_OVAL:GetRandomVec2() return {x=rx, y=ry} end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_OVAL self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_OVAL:GetRandomPointVec2() - return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) + return COORDINATE:NewFromVec2(self:GetRandomVec2()) end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_OVAL self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_OVAL:GetRandomPointVec3() - return POINT_VEC3:NewFromVec3(self:GetRandomVec2()) + return COORDINATE:NewFromVec3(self:GetRandomVec2()) end --- Draw the zone on the F10 map. @@ -4082,15 +4082,15 @@ do -- ZONE_AIRBASE return ZoneVec2 end - --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. + --- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_AIRBASE self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. - -- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. + -- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_AIRBASE:GetRandomPointVec2( inner, outer ) --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) --self:T3( { PointVec2 } ) diff --git a/Moose Development/Moose/Core/Zone_Detection.lua b/Moose Development/Moose/Core/Zone_Detection.lua index bb5424a37..8c05919c9 100644 --- a/Moose Development/Moose/Core/Zone_Detection.lua +++ b/Moose Development/Moose/Core/Zone_Detection.lua @@ -2,7 +2,8 @@ -- @module Core.Zone_Detection -- @image MOOSE.JPG ---- @type ZONE_DETECTION +--- +-- @type ZONE_DETECTION -- @field DCS#Vec2 Vec2 The current location of the zone. -- @field DCS#Distance Radius The radius of the zone. -- @extends #ZONE_BASE @@ -106,7 +107,7 @@ function ZONE_DETECTION:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor ) + COORDINATE:New( Point.x, AddHeight, Point.y):Smoke( SmokeColor ) end return self @@ -137,7 +138,7 @@ function ZONE_DETECTION:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Flare( FlareColor, Azimuth ) end return self @@ -201,4 +202,3 @@ function ZONE_DETECTION:IsVec3InZone( Vec3 ) return InZone end - diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6caede880..f0d4b1997 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -7699,7 +7699,7 @@ end --- [Internal] Function to spawn a soldier group of 10 units -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #number Cargo_Country @@ -7722,7 +7722,7 @@ end --- [Internal] Function to spawn a group -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #number Cargo_Country @@ -7746,7 +7746,7 @@ end --- [Internal] Function to spawn static cargo -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #boolean dead @@ -7768,7 +7768,7 @@ end --- [Internal] Function to spawn cargo by type at position -- @param #CTLD_HERCULES self -- @param #string Cargo_Type_name --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @return #CTLD_HERCULES self function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name, _pos) local theCargo = self.CTLD:_FindCratesCargoObject(_name) -- #CTLD_CARGO diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 9c183238d..db102dc8f 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1179,9 +1179,9 @@ function GROUP:GetAverageVec3() end end ---- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission. +--- Returns a COORDINATE object indicating the point in 2D of the first UNIT of the GROUP within the mission. -- @param #GROUP self --- @return Core.Point#POINT_VEC2 The 2D point vector of the first DCS Unit of the GROUP. +-- @return Core.Point#COORDINATE The 3D point vector of the first DCS Unit of the GROUP. -- @return #nil The first UNIT is not existing or alive. function GROUP:GetPointVec2() --self:F2(self.GroupName) @@ -1194,7 +1194,7 @@ function GROUP:GetPointVec2() return FirstUnitPointVec2 end - BASE:E( { "Cannot GetPointVec2", Group = self, Alive = self:IsAlive() } ) + BASE:E( { "Cannot get COORDINATE", Group = self, Alive = self:IsAlive() } ) return nil end @@ -2093,7 +2093,7 @@ function GROUP:Respawn( Template, Reset ) GroupUnitVec3 = Zone:GetRandomVec3() else if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then - GroupUnitVec3 = POINT_VEC3:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) + GroupUnitVec3 = COORDINATE:NewFromVec3(From):GetRandomVec3InRadius(self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner) else GroupUnitVec3 = Zone:GetVec3() end @@ -2144,7 +2144,7 @@ function GROUP:Respawn( Template, Reset ) GroupUnitVec3 = Zone:GetRandomVec3() else if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then - GroupUnitVec3 = POINT_VEC3:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) + GroupUnitVec3 = COORDINATE:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) else GroupUnitVec3 = Zone:GetVec3() end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index df5fdcb37..41857a215 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -16,7 +16,7 @@ --- @type POSITIONABLE -- @field Core.Point#COORDINATE coordinate Coordinate object. --- @field Core.Point#POINT_VEC3 pointvec3 Point Vec3 object. +-- @field Core.Point#COORDINATE pointvec3 Point Vec3 object. -- @extends Wrapper.Identifiable#IDENTIFIABLE @@ -284,9 +284,9 @@ function POSITIONABLE:GetVec2() return nil end ---- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. +--- Returns a COORDINATE object indicating the point in 2D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self --- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. +-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec2() self:F2( self.PositionableName ) @@ -296,20 +296,20 @@ function POSITIONABLE:GetPointVec2() if DCSPositionable then local PositionableVec3 = DCSPositionable:getPosition().p - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) + local PositionablePointVec2 = COORDINATE:NewFromVec3( PositionableVec3 ) -- self:F( PositionablePointVec2 ) return PositionablePointVec2 end - self:E( { "Cannot GetPointVec2", Positionable = self, Alive = self:IsAlive() } ) + self:E( { "Cannot Coordinate", Positionable = self, Alive = self:IsAlive() } ) return nil end ---- Returns a POINT_VEC3 object indicating the point in 3D of the POSITIONABLE within the mission. +--- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self --- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE. +-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec3() @@ -329,8 +329,8 @@ function POSITIONABLE:GetPointVec3() else - -- Create a new POINT_VEC3 object. - self.pointvec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) + -- Create a new COORDINATE object. + self.pointvec3 = COORDINATE:NewFromVec3( PositionableVec3 ) end diff --git a/docs/archive/classes-core.md b/docs/archive/classes-core.md index 2bf02a4fb..0fe685d15 100644 --- a/docs/archive/classes-core.md +++ b/docs/archive/classes-core.md @@ -169,7 +169,7 @@ Defines an extensive API to manage 3D points in the DCS World 3D simulation spac **Features:** * Provides a COORDINATE class, which allows to manage points in 3D space and perform various operations on it. - * Provides a POINT_VEC2 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a + * Provides a COORDINATE class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a Lat/Lon and Altitude perspective. * Provides a POINT_VEC3 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a X, Z and Y vector perspective. From 6c00b0c7ebe6cdf97ab907075262adc9931f84c9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 1 Apr 2025 14:13:14 +0200 Subject: [PATCH 140/158] #POINT - some catches for POINT_VEC2 behaviour --- Moose Development/Moose/Core/Point.lua | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 31b286aa3..4a5d26e22 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1168,6 +1168,9 @@ do -- COORDINATE -- @param #COORDINATE self -- @return #number The y coordinate. function COORDINATE:GetY() + if self:IsInstanceOf("POINT_VEC2") then + return self.z + end return self.y end @@ -1192,7 +1195,11 @@ do -- COORDINATE -- @param #number y The y coordinate. -- @return #COORDINATE function COORDINATE:SetY( y ) - self.y = y + if self:IsInstanceOf("POINT_VEC2") then + self.z = y + else + self.y = y + end return self end @@ -1289,7 +1296,11 @@ do -- COORDINATE -- @param #number y The y coordinate value to add to the current y coordinate. -- @return #COORDINATE function COORDINATE:AddY( y ) - self.y = self.y + y + if self:IsInstanceOf("POINT_VEC2") then + return self:AddZ(y) + else + self.y = self.y + y + end return self end From 4074023ed3705dca6df618f82a00e9e10ab6de18 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 1 Apr 2025 15:27:23 +0200 Subject: [PATCH 141/158] #DYNAMICCARGO - enhance checks for unloading cargo from hovering Hooks --- .../Moose/Wrapper/DynamicCargo.lua | 84 +++++++++++++------ 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 75d1105dc..9b3d8dfd6 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -124,7 +124,7 @@ DYNAMICCARGO.AircraftDimensions = { --- DYNAMICCARGO class version. -- @field #string version -DYNAMICCARGO.version="0.0.6" +DYNAMICCARGO.version="0.0.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -183,7 +183,7 @@ end -- @param #DYNAMICCARGO self -- @return DCS static object function DYNAMICCARGO:GetDCSObject() - local DCSStatic = Unit.getByName( self.StaticName ) + local DCSStatic = StaticObject.getByName( self.StaticName ) or Unit.getByName( self.StaticName ) if DCSStatic then return DCSStatic end @@ -376,6 +376,33 @@ end -- Private Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- [Internal] _Get helo hovering intel +-- @param #DYNAMICCARGO self +-- @param Wrapper.Unit#UNIT Unit The Unit to test +-- @param #number ropelength Ropelength to test +-- @return #boolean Outcome +function DYNAMICCARGO:_HeloHovering(Unit,ropelength) + local DCSUnit = Unit:GetDCSObject() --DCS#Unit + local hovering = false + local Height = 0 + if DCSUnit then + local UnitInAir = DCSUnit:inAir() + local UnitCategory = DCSUnit:getDesc().category + if UnitInAir == true and UnitCategory == Unit.Category.HELICOPTER then + local VelocityVec3 = DCSUnit:getVelocity() + local Velocity = UTILS.VecNorm(VelocityVec3) + local Coordinate = DCSUnit:getPoint() + local LandHeight = land.getHeight({ x = Coordinate.x, y = Coordinate.z }) + Height = Coordinate.y - LandHeight + if Velocity < 1 and Height <= ropelength and Height > 6 then -- hover lower than ropelength but higher than the normal FARP height. + hovering = true + end + end + return hovering, Height + end + return false +end + --- [Internal] _Get Possible Player Helo Nearby -- @param #DYNAMICCARGO self -- @param Core.Point#COORDINATE pos @@ -393,30 +420,37 @@ function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) local name = helo:GetPlayerName() or _DATABASE:_FindPlayerNameByUnitName(helo:GetName()) or "None" self:T(self.lid.." Checking: "..name) local hpos = helo:GetCoordinate() - -- TODO Unloading via sling load? - --local inair = hpos.y-hpos:GetLandHeight() > 4.5 and true or false -- Standard FARP is 4.5m - local inair = helo:InAir() - self:T(self.lid.." InAir: AGL/InAir: "..hpos.y-hpos:GetLandHeight().."/"..tostring(inair)) + -- TODO Check unloading via sling load? local typename = helo:GetTypeName() - if hpos and typename and inair == false then - local dimensions = DYNAMICCARGO.AircraftDimensions[typename] - if dimensions then - local delta2D = hpos:Get2DDistance(pos) - local delta3D = hpos:Get3DDistance(pos) - if self.testing then - self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) - self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) - end - if loading~=true and delta2D > dimensions.length or delta2D > dimensions.width or delta3D > dimensions.ropelength then - success = true - Helo = helo - Playername = name - end - if loading == true and delta2D < dimensions.length or delta2D < dimensions.width or delta3D < dimensions.ropelength then - success = true - Helo = helo - Playername = name - end + local dimensions = DYNAMICCARGO.AircraftDimensions[typename] + local hovering, height = self:_HeloHovering(helo,dimensions.ropelength) + local helolanded = not helo:InAir() + self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering)) + if hpos and typename and dimensions then + local delta2D = hpos:Get2DDistance(pos) + local delta3D = hpos:Get3DDistance(pos) + if self.testing then + self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) + self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) + self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height)) + end + -- unloading from ground + if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then + success = true + Helo = helo + Playername = name + end + -- unloading from hover/rope + if loading~=true and (delta2D < dimensions.length or delta2D < dimensions.width) and hovering then + success = true + Helo = helo + Playername = name + end + -- loading + if loading == true and (delta2D < dimensions.length or delta2D < dimensions.width or delta3D < dimensions.ropelength) then + success = true + Helo = helo + Playername = name end end end From 4f3fd06cc987a74c5b42426edec9051bab739364 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 2 Apr 2025 09:42:02 +0200 Subject: [PATCH 142/158] Update SpawnStatic.lua Restored previous version as overwritten --- Moose Development/Moose/Core/SpawnStatic.lua | 709 +++++++++++++++---- 1 file changed, 589 insertions(+), 120 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 9feb2eb90..d68a1426d 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -1,130 +1,599 @@ +--- **Core** - Spawn statics. +-- +-- === +-- +-- ## Features: +-- +-- * Spawn new statics from a static already defined in the mission editor. +-- * Spawn new statics from a given template. +-- * Spawn new statics from a given type. +-- * Spawn with a custom heading and location. +-- * Spawn within a zone. +-- * Spawn statics linked to units, .e.g on aircraft carriers. +-- +-- === +-- +-- # Demo Missions +-- +-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/SpawnStatic) +-- +-- +-- === +-- +-- # YouTube Channel +-- +-- ## No videos yet! +-- +-- === +-- +-- ### Author: **FlightControl** +-- ### Contributions: **funkyfranky** +-- +-- === +-- +-- @module Core.SpawnStatic +-- @image Core_Spawnstatic.JPG ---[[ -local CA_SET=SET_CLIENT:New():HandleCASlots():FilterCoalitions("blue"):FilterStart() +--- @type SPAWNSTATIC +-- @field #string SpawnTemplatePrefix Name of the template group. +-- @field #number CountryID Country ID. +-- @field #number CoalitionID Coalition ID. +-- @field #number CategoryID Category ID. +-- @field #number SpawnIndex Running number increased with each new Spawn. +-- @field Wrapper.Unit#UNIT InitLinkUnit The unit the static is linked to. +-- @field #number InitOffsetX Link offset X coordinate. +-- @field #number InitOffsetY Link offset Y coordinate. +-- @field #number InitOffsetAngle Link offset angle in degrees. +-- @field #number InitStaticHeading Heading of the static. +-- @field #string InitStaticLivery Livery for aircraft. +-- @field #string InitStaticShape Shape of the static. +-- @field #string InitStaticType Type of the static. +-- @field #string InitStaticCategory Categrory of the static. +-- @field #string InitStaticName Name of the static. +-- @field Core.Point#COORDINATE InitStaticCoordinate Coordinate where to spawn the static. +-- @field #boolean InitStaticDead Set static to be dead if true. +-- @field #boolean InitStaticCargo If true, static can act as cargo. +-- @field #number InitStaticCargoMass Mass of cargo in kg. +-- @extends Core.Base#BASE -function CA_SET:OnAfterAdded(From,Event,To,ObjectName,Object) - MESSAGE:New("Player joined CA Slot: "..ObjectName,10,"CA"):ToAll() - local client = Object -- Wrapper.Client#CLIENT - local group = client:GetGroup() - if group then - MENU_GROUP:New(group,"Test CA") + +--- Allows to spawn dynamically new @{Wrapper.Static}s into your mission. +-- +-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), +-- and "copy" these properties to create a new static object and place it at the desired coordinate. +-- +-- New spawned @{Wrapper.Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method. +-- By default, spawned @{Wrapper.Static}s will follow a naming convention at run-time: +-- +-- * Spawned @{Wrapper.Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**. +-- +-- # SPAWNSTATIC Constructors +-- +-- Firstly, we need to create a SPAWNSTATIC object that will be used to spawn new statics into the mission. There are three ways to do this. +-- +-- ## Use another Static +-- +-- A new SPAWNSTATIC object can be created using another static by the @{#SPAWNSTATIC.NewFromStatic}() function. All parameters such as position, heading, country will be initialized +-- from the static. +-- +-- ## From a Template +-- +-- A SPAWNSTATIC object can also be created from a template table using the @{#SPAWNSTATIC.NewFromTemplate}(SpawnTemplate, CountryID) function. All parameters are taken from the template. +-- +-- ## From a Type +-- +-- A very basic method is to create a SPAWNSTATIC object by just giving the type of the static. All parameters must be initialized from the InitXYZ functions described below. Otherwise default values +-- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map. +-- +-- # Setting Parameters +-- +-- Parameters such as the spawn position, heading, country etc. can be set via :Init*XYZ* functions. Note that these functions must be given before the actual spawn command! +-- +-- * @{#SPAWNSTATIC.InitCoordinate}(Coordinate) Sets the coordinate where the static is spawned. Statics are always spawnd on the ground. +-- * @{#SPAWNSTATIC.InitHeading}(Heading) sets the orientation of the static. +-- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this. +-- * @{#SPAWNSTATIC.InitType}(StaticType) sets the type of the static. +-- * @{#SPAWNSTATIC.InitShape}(StaticType) sets the shape of the static. Not all statics have this parameter. +-- * @{#SPAWNSTATIC.InitNamePrefix}(NamePrefix) sets the name prefix of the spawned statics. +-- * @{#SPAWNSTATIC.InitCountry}(CountryID) sets the country and therefore the coalition of the spawned statics. +-- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier. +-- +-- # Spawning the Statics +-- +-- Once the SPAWNSTATIC object is created and parameters are initialized, the spawn command can be given. There are different methods where some can be used to directly set parameters +-- such as position and heading. +-- +-- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**! +-- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**! +-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**! +-- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Core.Zone}. Optionally, heading and name can be given. The name **must be unique**! +-- +-- @field #SPAWNSTATIC SPAWNSTATIC +-- +SPAWNSTATIC = { + ClassName = "SPAWNSTATIC", + SpawnIndex = 0, +} + +--- Static template table data. +-- @type SPAWNSTATIC.TemplateData +-- @field #string name Name of the static. +-- @field #string type Type of the static. +-- @field #string category Category of the static. +-- @field #number x X-coordinate of the static. +-- @field #number y Y-coordinate of teh static. +-- @field #number heading Heading in rad. +-- @field #boolean dead Static is dead if true. +-- @field #string livery_id Livery name. +-- @field #number unitId Unit ID. +-- @field #number groupId Group ID. +-- @field #table offsets Offset parameters when linked to a unit. +-- @field #number mass Cargo mass in kg. +-- @field #boolean canCargo Static can be a cargo. + +--- Creates the main object to spawn a @{Wrapper.Static} defined in the mission editor (ME). +-- @param #SPAWNSTATIC self +-- @param #string SpawnTemplateName Name of the static object in the ME. Each new static will have the name starting with this prefix. +-- @param DCS#country.id SpawnCountryID (Optional) The ID of the country. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) + + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC + + local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) + + if TemplateStatic then + self.SpawnTemplatePrefix = SpawnTemplateName + self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) + self.CountryID = SpawnCountryID or CountryID + self.CategoryID = CategoryID + self.CoalitionID = CoalitionID + self.SpawnIndex = 0 + else + error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) end + + self:SetEventPriority( 5 ) + + return self end - local e = {} - function e:onEvent(event) - local m = {} - m[#m+1] = "Event ID: " - m[#m+1] = event.id - if event.initiator then - m[#m+1] = "\nInitiator : " - m[#m+1] = event.initiator:getName() - end - if event.weapon then - m[#m+1] = "\nWeapon : " - m[#m+1] = event.weapon :getTypeName() - end - if event.target then - m[#m+1] = "\nTarget : " - m[#m+1] = event.target :getName() - end - env.info(table.concat(m)) +--- Creates the main object to spawn a @{Wrapper.Static} given a template table. +-- @param #SPAWNSTATIC self +-- @param #table SpawnTemplate Template used for spawning. +-- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID) + + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC + + self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate) + self.SpawnTemplatePrefix = SpawnTemplate.name + self.CountryID = CountryID or country.id.USA + + return self +end + +--- Creates the main object to spawn a @{Wrapper.Static} from a given type. +-- NOTE that you have to init many other parameters as spawn coordinate etc. +-- @param #SPAWNSTATIC self +-- @param #string StaticType Type of the static. +-- @param #string StaticCategory Category of the static, e.g. "Planes". +-- @param DCS#country.id CountryID The ID of the country. Default `country.id.USA`. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID) + + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC + + self.InitStaticType=StaticType + self.InitStaticCategory=StaticCategory + self.CountryID=CountryID or country.id.USA + self.SpawnTemplatePrefix=self.InitStaticType + self.TemplateStaticUnit = {} + + self.InitStaticCoordinate=COORDINATE:New(0, 0, 0) + self.InitStaticHeading=0 + + return self +end + +--- (Internal/Cargo) Init the resource table for STATIC object that should be spawned containing storage objects. +-- NOTE that you have to init many other parameters as the resources. +-- @param #SPAWNSTATIC self +-- @param #number CombinedWeight The weight this cargo object should have (some have fixed weights!), defaults to 1kg. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:_InitResourceTable(CombinedWeight) + if not self.TemplateStaticUnit.resourcePayload then + self.TemplateStaticUnit.resourcePayload = { + ["weapons"] = {}, + ["aircrafts"] = {}, + ["gasoline"] = 0, + ["diesel"] = 0, + ["methanol_mixture"] = 0, + ["jet_fuel"] = 0, + } + end + self:InitCargo(true) + self:InitCargoMass(CombinedWeight or 1) + return self +end + +--- (User/Cargo) Add to resource table for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. +-- @param #SPAWNSTATIC self +-- @param #string Type Type of cargo. Known types are: STORAGE.Type.WEAPONS, STORAGE.Type.LIQUIDS, STORAGE.Type.AIRCRAFT. Liquids are fuel. +-- @param #string Name Name of the cargo type. Liquids can be STORAGE.LiquidName.JETFUEL, STORAGE.LiquidName.GASOLINE, STORAGE.LiquidName.MW50 and STORAGE.LiquidName.DIESEL. The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. Aircraft go by their typename. +-- @param #number Amount of tons (liquids) or number (everything else) to add. +-- @param #number CombinedWeight Combined weight to be set to this static cargo object. NOTE - some static cargo objects have fixed weights! +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:AddCargoResource(Type,Name,Amount,CombinedWeight) + if not self.TemplateStaticUnit.resourcePayload then + self:_InitResourceTable(CombinedWeight) + end + if Type == STORAGE.Type.LIQUIDS and type(Name) == "string" then + self.TemplateStaticUnit.resourcePayload[Name] = Amount + else + self.TemplateStaticUnit.resourcePayload[Type] = { + [Name] = { + ["amount"] = Amount, + } + } + end + UTILS.PrintTableToLog(self.TemplateStaticUnit) + return self +end + +--- (User/Cargo) Resets resource table to zero for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. +-- Handy if you spawn from cargo statics which have resources already set. +-- @param #SPAWNSTATIC self +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:ResetCargoResources() + self.TemplateStaticUnit.resourcePayload = nil + self:_InitResourceTable() + return self +end + +--- Initialize heading of the spawned static. +-- @param #SPAWNSTATIC self +-- @param Core.Point#COORDINATE Coordinate Position where the static is spawned. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitCoordinate(Coordinate) + self.InitStaticCoordinate=Coordinate + return self +end + +--- Initialize heading of the spawned static. +-- @param #SPAWNSTATIC self +-- @param #number Heading The heading in degrees. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitHeading(Heading) + self.InitStaticHeading=Heading + return self +end + +--- Initialize livery of the spawned static. +-- @param #SPAWNSTATIC self +-- @param #string LiveryName Name of the livery to use. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitLivery(LiveryName) + self.InitStaticLivery=LiveryName + return self +end + +--- Initialize type of the spawned static. +-- @param #SPAWNSTATIC self +-- @param #string StaticType Type of the static, e.g. "FA-18C_hornet". +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitType(StaticType) + self.InitStaticType=StaticType + return self +end + +--- Initialize shape of the spawned static. Required by some but not all statics. +-- @param #SPAWNSTATIC self +-- @param #string StaticShape Shape of the static, e.g. "carrier_tech_USA". +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitShape(StaticShape) + self.InitStaticShape=StaticShape + return self +end + +--- Initialize parameters for spawning FARPs. +-- @param #SPAWNSTATIC self +-- @param #number CallsignID Callsign ID. Default 1 (="London"). +-- @param #number Frequency Frequency in MHz. Default 127.5 MHz. +-- @param #number Modulation Modulation 0=AM, 1=FM. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation) + self.InitFarp=true + self.InitFarpCallsignID=CallsignID or 1 + self.InitFarpFreq=Frequency or 127.5 + self.InitFarpModu=Modulation or 0 + return self +end + +--- Initialize cargo mass. +-- @param #SPAWNSTATIC self +-- @param #number Mass Mass of the cargo in kg. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitCargoMass(Mass) + self.InitStaticCargoMass=Mass + return self +end + +--- Initialize as cargo. +-- @param #SPAWNSTATIC self +-- @param #boolean IsCargo If true, this static can act as cargo. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitCargo(IsCargo) + self.InitStaticCargo=IsCargo + return self +end + +--- Initialize as dead. +-- @param #SPAWNSTATIC self +-- @param #boolean IsDead If true, this static is dead. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitDead(IsDead) + self.InitStaticDead=IsDead + return self +end + +--- Initialize country of the spawned static. This determines the category. +-- @param #SPAWNSTATIC self +-- @param #string CountryID The country ID, e.g. country.id.USA. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitCountry(CountryID) + self.CountryID=CountryID + return self +end + +--- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc. +-- @param #SPAWNSTATIC self +-- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitNamePrefix(NamePrefix) + self.SpawnTemplatePrefix=NamePrefix + return self +end + +--- Init link to a unit. +-- @param #SPAWNSTATIC self +-- @param Wrapper.Unit#UNIT Unit The unit to which the static is linked. +-- @param #number OffsetX Offset in X. +-- @param #number OffsetY Offset in Y. +-- @param #number OffsetAngle Offset angle in degrees. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle) + + self.InitLinkUnit=Unit + self.InitOffsetX=OffsetX or 0 + self.InitOffsetY=OffsetY or 0 + self.InitOffsetAngle=OffsetAngle or 0 + + return self +end + +--- Allows to place a CallFunction hook when a new static spawns. +-- The provided method will be called when a new group is spawned, including its given parameters. +-- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned. +-- @param #SPAWNSTATIC self +-- @param #function SpawnCallBackFunction The function to be called when a group spawns. +-- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:OnSpawnStatic( SpawnCallBackFunction, ... ) + self:F( "OnSpawnStatic" ) + + self.SpawnFunctionHook = SpawnCallBackFunction + self.SpawnFunctionArguments = {} + if arg then + self.SpawnFunctionArguments = arg + end + + return self +end + +--- Spawn a new STATIC object. +-- @param #SPAWNSTATIC self +-- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template. +-- @param #string NewName (Optional) The name of the new static. +-- @return Wrapper.Static#STATIC The static spawned. +function SPAWNSTATIC:Spawn(Heading, NewName) + + if Heading then + self.InitStaticHeading=Heading + end + + if NewName then + self.InitStaticName=NewName + end + + return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID) + +end + +--- Creates a new @{Wrapper.Static} from a POINT_VEC2. +-- @param #SPAWNSTATIC self +-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static. +-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. +-- @param #string NewName (Optional) The name of the new static. +-- @return Wrapper.Static#STATIC The static spawned. +function SPAWNSTATIC:SpawnFromPointVec2(PointVec2, Heading, NewName) + + local vec2={x=PointVec2:GetX(), y=PointVec2:GetY()} + + local Coordinate=COORDINATE:NewFromVec2(vec2) + + return self:SpawnFromCoordinate(Coordinate, Heading, NewName) +end + + +--- Creates a new @{Wrapper.Static} from a COORDINATE. +-- @param #SPAWNSTATIC self +-- @param Core.Point#COORDINATE Coordinate The 3D coordinate where to spawn the static. +-- @param #number Heading (Optional) Heading The heading of the static in degrees. Default is 0 degrees. +-- @param #string NewName (Optional) The name of the new static. +-- @return Wrapper.Static#STATIC The spawned STATIC object. +function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName) + + -- Set up coordinate. + self.InitStaticCoordinate=Coordinate + + if Heading then + self.InitStaticHeading=Heading + end + + if NewName then + self.InitStaticName=NewName + end + + return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID) +end + + +--- Creates a new @{Wrapper.Static} from a @{Core.Zone}. +-- @param #SPAWNSTATIC self +-- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static. +-- @param #number Heading (Optional)The heading of the static in degrees. Default is the heading of the template. +-- @param #string NewName (Optional) The name of the new static. +-- @return Wrapper.Static#STATIC The static spawned. +function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName) + + -- Spawn the new static at the center of the zone. + local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName ) + + return Static +end + +--- Spawns a new static using a given template. Additionally, the country ID needs to be specified, which also determines the coalition of the spawned static. +-- @param #SPAWNSTATIC self +-- @param #SPAWNSTATIC.TemplateData Template Spawn unit template. +-- @param #number CountryID The country ID. +-- @return Wrapper.Static#STATIC The static spawned. +function SPAWNSTATIC:_SpawnStatic(Template, CountryID) + + Template=Template or {} + + local CountryID=CountryID or self.CountryID + + if self.InitStaticType then + Template.type=self.InitStaticType + end + + if self.InitStaticCategory then + Template.category=self.InitStaticCategory + end + + if self.InitStaticCoordinate then + Template.x = self.InitStaticCoordinate.x + Template.y = self.InitStaticCoordinate.z + Template.alt = self.InitStaticCoordinate.y + end + + if self.InitStaticHeading then + Template.heading = math.rad(self.InitStaticHeading) + end + + if self.InitStaticShape then + Template.shape_name=self.InitStaticShape + end + + if self.InitStaticLivery then + Template.livery_id=self.InitStaticLivery + end + + if self.InitStaticDead~=nil then + Template.dead=self.InitStaticDead + end + + if self.InitStaticCargo~=nil then + Template.canCargo=self.InitStaticCargo + end + + if self.InitStaticCargoMass~=nil then + Template.mass=self.InitStaticCargoMass + end + + if self.InitLinkUnit then + Template.linkUnit=self.InitLinkUnit:GetID() + Template.linkOffset=true + Template.offsets={} + Template.offsets.y=self.InitOffsetY + Template.offsets.x=self.InitOffsetX + Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0 + end + + if self.InitFarp then + Template.heliport_callsign_id = self.InitFarpCallsignID + Template.heliport_frequency = self.InitFarpFreq + Template.heliport_modulation = self.InitFarpModu + Template.unitId=nil + end + + -- Increase spawn index counter. + self.SpawnIndex = self.SpawnIndex + 1 + + -- Name of the spawned static. + Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) + + -- Add static to the game. + local Static=nil --DCS#StaticObject + + if self.InitFarp then + + local TemplateGroup={} + TemplateGroup.units={} + TemplateGroup.units[1]=Template + + TemplateGroup.visible=true + TemplateGroup.hidden=false + TemplateGroup.x=Template.x + TemplateGroup.y=Template.y + TemplateGroup.name=Template.name + + self:T("Spawning FARP") + self:T({Template=Template}) + self:T({TemplateGroup=TemplateGroup}) + + -- ED's dirty way to spawn FARPS. + Static=coalition.addGroup(CountryID, -1, TemplateGroup) + + -- Currently DCS 2.8 does not trigger birth events if FARPS are spawned! + -- We create such an event. The airbase is registered in Core.Event + local Event = { + id = EVENTS.Birth, + time = timer.getTime(), + initiator = Static + } + -- Create BIRTH event. + world.onEvent(Event) + + else + self:T("Spawning Static") + self:T2({Template=Template}) + Static=coalition.addStaticObject(CountryID, Template) + + if Static then + self:T(string.format("Succesfully spawned static object \"%s\" ID=%d", Static:getName(), Static:getID())) + --[[ + local static=StaticObject.getByName(Static:getName()) + if static then + env.info(string.format("FF got static from StaticObject.getByName")) + else + env.error(string.format("FF error did NOT get static from StaticObject.getByName")) + end ]] + else + self:E(string.format("ERROR: DCS static object \"%s\" is nil!", tostring(Template.name))) + end end - world.addEventHandler(e) - local recce = PLAYERRECCE:New(Name,Coalition,PlayerSet) + -- Add and register the new static. + local mystatic=_DATABASE:AddStatic(Template.name) - - US_Patrol_Plane = SPAWN - :New("Bird Dog") - :InitLimit(1,4) - :OnSpawnGroup(function ( SpawnedGroup ) - -- Setup AI Patrol - PatrolZone = ZONE:New("Conflict Zone Alpha") - EngageZone = ZONE:New("Conflict Zone Alpha") - EngageZone:Draw() - AICaszone = AI_CAS_ZONE:New(PatrolZone, 100, 1000, 100, 100, EngageZone, "RADIO") - AICaszone:SetControllable(SpawnedGroup) - --AICaszone:SetEngageRange(2000) - AICaszone:__Start(1) - end - ) - :SpawnScheduled(30, 0) - ---]] - -local grp = GROUP:FindByName("IR Blinker") -grp:NewIRMarker(true,90) - -function DestGroup() - if grp and grp:IsAlive() then - grp:Destroy() - end -end - -function DisableMarker() - if grp and grp:IsAlive() then - grp:DisableIRMarker() - end -end - -function EnableMarker() - if grp and grp:IsAlive() then - grp:EnableIRMarker() - end -end - -function RespGroup() - if grp and not grp:IsAlive() then - grp:Respawn() - end -end - -local mymsrs = MSRS:New(nil,243,0) -local jammersound=SOUNDFILE:New("beacon.ogg", "C:\\Users\\post\\Saved Games\\DCS\\Missions\\", 2, true) -function Play() - mymsrs:PlaySoundFile(jammersound) -end - -local topmenu = MENU_COALITION:New(coalition.side.BLUE,"IR Marker Test") -local startmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Enable IR",topmenu,EnableMarker) -local stopmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Disable IR",topmenu,DisableMarker) -local destmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Destroy Group",topmenu,DestGroup) -local respmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Respawn Group",topmenu,RespGroup) -local respmenu = MENU_COALITION_COMMAND:New(coalition.side.BLUE,"Play Sound",topmenu,Play) - -local testzone = ZONE:New("Testzone") -testzone:Trigger(grp) - -function testzone:OnAfterObjectDead(From,Event,To,Controllable) - MESSAGE:New("Object Dead",15,"Test"):ToAll():ToLog() -end - -function testzone:OnAfterZoneEmpty(From,Event,To) - MESSAGE:New("Zone Empty",15,"Test"):ToAll():ToLog() -end - -local BlueBorder = ZONE:New("Blue Border") -local RedBorder = ZONE:New("Red Border") -local Conflict = ZONE:New("Conflict") - -BlueBorder:DrawZone(-1,{0,0,1},1,{0,0,1},.2,1,true) -RedBorder:DrawZone(-1,{1,0,0},1,{1,0,0},.2,1,true) -Conflict:DrawZone(-1,{1,254/255,1/33},1,{1,254/255,1/33},.2,1,true) - -BASE:TraceOn() -BASE:TraceClass("SHORAD") - -local mymantis = MANTIS:New("Red Defense","Red SAM","Red EWR",hq,"red",true,awacs,true) -mymantis:AddZones({RedBorder},{BlueBorder},{Conflict}) -mymantis.verbose = true -mymantis.debug = true -mymantis:Start() - -local myctld = CTLD:New() - -function myctld:OnAfterCratesDropped(From,Event,To,Group,Unit,Cargotable) - if Unit and string.find(Unit:GetTypeName(),"Mosquito",1,true) then - myctld:_BuildCrates(Group,Unit,true) + -- If there is a SpawnFunction hook defined, call it. + if self.SpawnFunctionHook then + -- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. + self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments)) end + + return mystatic end From 2109537f861b1391834c81f96b2c31f9eb239bba Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 2 Apr 2025 09:54:20 +0200 Subject: [PATCH 143/158] Update classes-core.md fix --- docs/archive/classes-core.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/archive/classes-core.md b/docs/archive/classes-core.md index 0fe685d15..b9869b59e 100644 --- a/docs/archive/classes-core.md +++ b/docs/archive/classes-core.md @@ -169,10 +169,6 @@ Defines an extensive API to manage 3D points in the DCS World 3D simulation spac **Features:** * Provides a COORDINATE class, which allows to manage points in 3D space and perform various operations on it. - * Provides a COORDINATE class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a - Lat/Lon and Altitude perspective. - * Provides a POINT_VEC3 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a - X, Z and Y vector perspective. **The coordinate system classes are essential to understand. Learn this!** From b0a192a76722082bd79921da9af389df655a4ba6 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:12:06 +0200 Subject: [PATCH 144/158] Update DynamicCargo.lua small fix --- Moose Development/Moose/Wrapper/DynamicCargo.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index 9b3d8dfd6..a31cfc40f 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -388,7 +388,7 @@ function DYNAMICCARGO:_HeloHovering(Unit,ropelength) if DCSUnit then local UnitInAir = DCSUnit:inAir() local UnitCategory = DCSUnit:getDesc().category - if UnitInAir == true and UnitCategory == Unit.Category.HELICOPTER then + if UnitInAir == true and UnitCategory == 1 then local VelocityVec3 = DCSUnit:getVelocity() local Velocity = UTILS.VecNorm(VelocityVec3) local Coordinate = DCSUnit:getPoint() From ddf33da78781af2acb2cd5f74c3c9b0530a41a17 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Apr 2025 09:29:19 +0200 Subject: [PATCH 145/158] #DYNAMICCARGO - Hover / Sling checks --- .../Moose/Wrapper/DynamicCargo.lua | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index a31cfc40f..db0d79834 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -2,17 +2,17 @@ -- -- ## Main Features: -- --- * Convenient access to DCS API functions +-- * Convenient access to Ground Crew created cargo items. -- -- === -- -- ## Example Missions: -- --- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Storage). +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/). -- -- === -- --- ### Author: **Applevangelist** +-- ### Author: **Applevangelist**; additional checks **Chesster** -- -- === -- @module Wrapper.DynamicCargo @@ -435,19 +435,19 @@ function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height)) end -- unloading from ground - if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then + if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then -- Theoretically the cargo could still be attached to the sling if landed next to the cargo. But once moved again it would go back into loaded state once lifted again. success = true Helo = helo Playername = name end -- unloading from hover/rope - if loading~=true and (delta2D < dimensions.length or delta2D < dimensions.width) and hovering then + if loading~=true and delta3D > dimensions.ropelength then success = true Helo = helo Playername = name end -- loading - if loading == true and (delta2D < dimensions.length or delta2D < dimensions.width or delta3D < dimensions.ropelength) then + if loading == true and ((delta2D < dimensions.length and delta2D < dimensions.width and helolanded) or (delta3D == dimensions.ropelength and helo:InAir())) then -- Loaded via ground or sling success = true Helo = helo Playername = name @@ -468,20 +468,22 @@ function DYNAMICCARGO:_UpdatePosition() self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z)) self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z)) end - if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then + if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then -- This checks if the cargo has moved more than 0.5m since last check. If so then the cargo is loaded --------------- -- LOAD Cargo --------------- - if self.CargoState == DYNAMICCARGO.State.NEW then - local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true) + if self.CargoState == DYNAMICCARGO.State.NEW or self.CargoState == DYNAMICCARGO.State.UNLOADED then + local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true) self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername)) self.CargoState = DYNAMICCARGO.State.LOADED self.Owner = playername - _DATABASE:CreateEventDynamicCargoLoaded(self) + _DATABASE:CreateEventDynamicCargoLoaded(self) + end --------------- -- UNLOAD Cargo - --------------- - elseif self.CargoState == DYNAMICCARGO.State.LOADED then + --------------- + -- If the cargo is stationary then we need to end this condition here to check whether it is unloaded or still onboard or still hooked if anyone can hover that precisly + elseif self.CargoState == DYNAMICCARGO.State.LOADED then -- TODO add checker if we are in flight somehow -- ensure not just the helo is moving local count = _DYNAMICCARGO_HELOS:CountAlive() @@ -493,26 +495,19 @@ function DYNAMICCARGO:_UpdatePosition() local isunloaded = true local client local playername = self.Owner - if count > 0 and (agl > 0 or self.testing) then - self:T(self.lid.." Possible alive helos: "..count or -1) - if agl ~= 0 or self.testing then - isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false) - end + if count > 0 then + self:T(self.lid.." Possible alive helos: "..count or -1) + isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false) if isunloaded then self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) self.CargoState = DYNAMICCARGO.State.UNLOADED self.Owner = playername _DATABASE:CreateEventDynamicCargoUnloaded(self) - end - elseif count > 0 and agl == 0 then - self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) - self.CargoState = DYNAMICCARGO.State.UNLOADED - self.Owner = playername - _DATABASE:CreateEventDynamicCargoUnloaded(self) + end end end self.LastPosition = pos - end + --end else --------------- -- REMOVED Cargo From 5c1e342a79ebd15997d9758e39840650069a191c Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 3 Apr 2025 09:33:49 +0200 Subject: [PATCH 146/158] Update classes-core.md --- docs/archive/classes-core.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/archive/classes-core.md b/docs/archive/classes-core.md index b9869b59e..6342620b8 100644 --- a/docs/archive/classes-core.md +++ b/docs/archive/classes-core.md @@ -395,3 +395,4 @@ Define zones within your mission of various forms, with various capabilities. [Wrapper.Static]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Wrapper.Static.html [Wrapper.Unit]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Wrapper.Unit.html [Core.Zone]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.Zone.html + From 5b107ce2da85362209bc88f41f6d1f66beb08eab Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Apr 2025 11:48:49 +0200 Subject: [PATCH 147/158] #CONTROLLABLE:CommandSmokeOnOff(OnOff, Delay) added --- .../Moose/Wrapper/Controllable.lua | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index c158eb0c6..b9f5a0d86 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -994,6 +994,65 @@ function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,Unit return self end +--- [AIR] Set smoke on or off. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #boolean OnOff Set to true for on and false for off. Defaults to true. +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeOnOff(OnOff, Delay) + local switch = (OnOff == nil) and true or OnOff + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = switch + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeOnOff,{self,switch},Delay) + else + self:SetCommand(command) + end + return self +end + +--- [AIR] Set smoke on. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeON(Delay) + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = true + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeON,{self},Delay) + else + self:SetCommand(command) + end + return self +end + +--- [AIR] Set smoke off. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeOFF(Delay) + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = false + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeOFF,{self},Delay) + else + self:SetCommand(command) + end + return self +end + --- Set EPLRS data link on/off. -- @param #CONTROLLABLE self -- @param #boolean SwitchOnOff If true (or nil) switch EPLRS on. If false switch off. From b7b6c1ea19b5fe0685a71aa1ac72f69c9c46f696 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 3 Apr 2025 14:21:58 +0200 Subject: [PATCH 148/158] #RADIOQUEUE - small tweak for a group when a unit dies. --- Moose Development/Moose/Sound/RadioQueue.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index a3740768e..b558f6be4 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -380,7 +380,8 @@ function RADIOQUEUE:Broadcast(transmission) self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName())) - if not self.senderinit then + --if not self.senderinit then + -- TODO Seems to be a DCS bug - if I explode ANY unit in a group the BC assignment gets lost -- Command to set the Frequency for the transmission. local commandFrequency={ @@ -394,7 +395,7 @@ function RADIOQUEUE:Broadcast(transmission) sender:SetCommand(commandFrequency) self.senderinit=true - end + --end -- Set subtitle only if duration>0 sec. local subtitle=nil From 30835991580bba76ad99370787b0edb139936511 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 6 Apr 2025 15:38:26 +0200 Subject: [PATCH 149/158] #CTLD - Allow CA Ground Transport --- Moose Development/Moose/Ops/CTLD.lua | 70 +++++++++++++++++---- Moose Development/Moose/Utilities/Utils.lua | 8 ++- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f0d4b1997..2510aa35e 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1242,7 +1242,21 @@ do -- end -- end -- +-- ## 8. Transport crates and troops with CA (Combined Arms) trucks -- +-- You can optionally also allow to CTLD with CA trucks and other vehicles: +-- +-- -- Create a SET_CLIENT to capture CA vehicles steered by players +-- local truckers = SET_CLIENT:New():HandleCASlots():FilterCoalitions("blue"):FilterPrefixes("Truck"):FilterStart() +-- -- Allow CA transport +-- my_ctld:AllowCATransport(true,truckers) +-- -- Set truck capability by typename +-- my_ctld:SetUnitCapabilities("M 818", true, true, 2, 12, 9, 4500) +-- -- Alternatively set truck capability with a UNIT object +-- local GazTruck = UNIT:FindByName("GazTruck-1-1") +-- my_ctld:SetUnitCapabilities(GazTruck, true, true, 2, 12, 9, 4500) +-- +-- -- @field #CTLD CTLD = { ClassName = "CTLD", @@ -1277,6 +1291,7 @@ CTLD = { UserSetGroup = nil, LoadedGroupsTable = {}, keeploadtable = true, + allowCATransport = false, } ------------------------------ @@ -1384,6 +1399,7 @@ CTLD.UnitTypeCapabilities = { ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 10800}, ["MosquitoFBMkVI"] = {type="MosquitoFBMkVI", crates= true, troops=false, cratelimit = 2, trooplimit = 0, length = 13, cargoweightlimit = 1800}, + ["M 818"] = {type="M 818", crates= true, troops=true, cratelimit = 4, trooplimit = 12, length = 9, cargoweightlimit = 4500}, } --- Allowed Fixed Wing Types @@ -1396,7 +1412,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.31" +CTLD.version="1.1.32" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1606,6 +1622,10 @@ function CTLD:New(Coalition, Prefixes, Alias) math.random() end + -- CA Transport + self.allowCATransport = false -- #boolean + self.CATransportSet = nil -- Core.Set#SET_CLIENT + self:_GenerateVHFrequencies() self:_GenerateUHFrequencies() self:_GenerateFMFrequencies() @@ -1949,6 +1969,16 @@ function CTLD:_GetUnitCapabilities(Unit) return capabilities end +--- (User) Function to allow transport via Combined Arms Trucks. +-- @param #CTLD self +-- @param #boolean OnOff Switch on (true) or off (false). +-- @param Core.Set#SET_CLIENT ClientSet The CA handling client set for ground transport. +-- @return #CTLD self +function CTLD:AllowCATransport(OnOff,ClientSet) + self.allowCATransport = OnOff -- #boolean + self.CATransportSet = ClientSet -- Core.Set#SET_CLIENT + return self +end --- (Internal) Function to generate valid UHF Frequencies -- @param #CTLD self @@ -2742,6 +2772,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) -- spawn crates in front of helicopter local IsHerc = self:IsFixedWing(Unit) -- Herc, Bronco and Hook load from behind local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind + local IsTruck = Unit:IsGround() local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number @@ -2763,7 +2794,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) local rheading = 0 local angleOffNose = 0 local addon = 0 - if IsHerc or IsHook then + if IsHerc or IsHook or IsTruck then -- spawn behind the Herc addon = 180 end @@ -3093,24 +3124,24 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype if not _ignoreweight then maxloadable = self:_GetMaxLoadableMass(_unit) end - self:T2(self.lid .. " Max loadable mass: " .. maxloadable) + self:T(self.lid .. " Max loadable mass: " .. maxloadable) for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates local weight = cargo:GetMass() -- weight in kgs of this cargo local staticid = cargo:GetID() - self:T2(self.lid .. " Found cargo mass: " .. weight) + self:T(self.lid .. " Found cargo mass: " .. weight) if static and static:IsAlive() then --or cargoalive) then local restricthooktononstatics = self.enableChinookGCLoading and IsHook - --self:I(self.lid .. " restricthooktononstatics: " .. tostring(restricthooktononstatics)) + self:T(self.lid .. " restricthooktononstatics: " .. tostring(restricthooktononstatics)) local cargoisstatic = cargo:GetType() == CTLD_CARGO.Enum.STATIC and true or false - --self:I(self.lid .. " Cargo is static: " .. tostring(cargoisstatic)) + self:T(self.lid .. " Cargo is static: " .. tostring(cargoisstatic)) local restricted = cargoisstatic and restricthooktononstatics - --self:I(self.lid .. " Loading restricted: " .. tostring(restricted)) + self:T(self.lid .. " Loading restricted: " .. tostring(restricted)) local staticpos = static:GetCoordinate() --or dcsunitpos local cando = cargo:UnitCanCarry(_unit) if ignoretype == true then cando = true end - --self:I(self.lid .. " Unit can carry: " .. tostring(cando)) + self:T(self.lid .. " Unit can carry: " .. tostring(cando)) --- Testing local distance = self:_GetDistance(location,staticpos) self:T(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) @@ -4184,6 +4215,19 @@ function CTLD:_RefreshF10Menus() end end end + + -- 3) CA Units + if self.allowCATransport and self.CATransportSet then + for _,_clientobj in pairs(self.CATransportSet.Set) do + local client = _clientobj -- Wrapper.Client#CLIENT + if client:IsGround() then + local cname = client:GetName() + --self:I(self.lid.."Adding: "..cname) + _UnitList[cname] = cname + end + end + end + self.CtldUnits = _UnitList -- subcats? @@ -4212,10 +4256,15 @@ function CTLD:_RefreshF10Menus() local menus = {} for _, _unitName in pairs(self.CtldUnits) do if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then + --self:I(self.lid.."Menu not done yet") local _unit = UNIT:FindByName(_unitName) + if not _unit and self.allowCATransport then + _unit = CLIENT:FindByName(_unitName) + end if _unit and _unit:IsAlive() then local _group = _unit:GetGroup() if _group then + --self:I(self.lid.."Unit and Group exist") local capabilities = self:_GetUnitCapabilities(_unit) local cantroops = capabilities.troops local cancrates = capabilities.crates @@ -5730,9 +5779,8 @@ end local unit = nil if type(Unittype) == "string" then unittype = Unittype - elseif type(Unittype) == "table" then - unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT - unittype = unit:GetTypeName() + elseif type(Unittype) == "table" and Unittype.ClassName and Unittype:IsInstanceOf("UNIT") then + unittype = Unittype:GetTypeName() else return self end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 626580dff..9e0e60514 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2329,8 +2329,12 @@ function UTILS.IsLoadingDoorOpen( unit_name ) BASE:T(unit_name .. " rear cargo door is open") return true end - - return false + + -- ground + local UnitDescriptor = unit:getDesc() + local IsGroundResult = (UnitDescriptor.category == Unit.Category.GROUND_UNIT) + + return IsGroundResult end -- nil From e50e572c78c2ac933d5ead860a46599231161231 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 6 Apr 2025 15:43:09 +0200 Subject: [PATCH 150/158] Update classes-core.md --- docs/archive/classes-core.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/archive/classes-core.md b/docs/archive/classes-core.md index 6342620b8..b9869b59e 100644 --- a/docs/archive/classes-core.md +++ b/docs/archive/classes-core.md @@ -395,4 +395,3 @@ Define zones within your mission of various forms, with various capabilities. [Wrapper.Static]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Wrapper.Static.html [Wrapper.Unit]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Wrapper.Unit.html [Core.Zone]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.Zone.html - From d09f0b1f6f3f5a8c73bafd54d5bb5d6b9230dece Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 6 Apr 2025 15:47:52 +0200 Subject: [PATCH 151/158] Update classes-core.md --- docs/archive/classes-core.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/archive/classes-core.md b/docs/archive/classes-core.md index 6342620b8..b9869b59e 100644 --- a/docs/archive/classes-core.md +++ b/docs/archive/classes-core.md @@ -395,4 +395,3 @@ Define zones within your mission of various forms, with various capabilities. [Wrapper.Static]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Wrapper.Static.html [Wrapper.Unit]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Wrapper.Unit.html [Core.Zone]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.Zone.html - From 0405af2bdebd6140b8e92e9403b4b043490e1cd0 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:04:07 +0200 Subject: [PATCH 152/158] Update docs-header.py --- Moose Development/docs-header.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/docs-header.py b/Moose Development/docs-header.py index 72dfc6f28..0b27a91f3 100644 --- a/Moose Development/docs-header.py +++ b/Moose Development/docs-header.py @@ -15,7 +15,7 @@ with open( os.path.dirname(__file__) + '/docs-header.html', 'r') as file: # that directory files = Path(directory).glob('*.html') for file in files: - # print(file) + print(file) with open(file, 'r') as fileread: filedata = fileread.read() # Replace the target string From ffccc31e38c1ddc3abff56970d0155652b631147 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:12:13 +0200 Subject: [PATCH 153/158] Update CargoGroup.lua --- Moose Development/Moose/Cargo/CargoGroup.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index abcc89ea8..c2d9b26a2 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -772,3 +772,4 @@ do -- CARGO_GROUP end -- CARGO_GROUP + From d707a4775c5846aa0fcae288353d7ef5c61b9878 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 6 Apr 2025 16:19:06 +0200 Subject: [PATCH 154/158] xx --- Moose Development/Moose/Core/SpawnStatic.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index d68a1426d..f603450d7 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -105,7 +105,7 @@ -- -- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**! -- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**! --- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**! +-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a COORDINATE coordinate. Optionally, heading and name can be given. The name **must be unique**! -- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Core.Zone}. Optionally, heading and name can be given. The name **must be unique**! -- -- @field #SPAWNSTATIC SPAWNSTATIC @@ -411,9 +411,9 @@ function SPAWNSTATIC:Spawn(Heading, NewName) end ---- Creates a new @{Wrapper.Static} from a POINT_VEC2. +--- Creates a new @{Wrapper.Static} from a COORDINATE. -- @param #SPAWNSTATIC self --- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static. +-- @param Core.Point#COORDINATE PointVec2 The 2D coordinate where to spawn the static. -- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. -- @param #string NewName (Optional) The name of the new static. -- @return Wrapper.Static#STATIC The static spawned. From 6ac452ff15db75c3ac3eaf7ecabc539f68d7006b Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:28:19 +0200 Subject: [PATCH 155/158] Update docs-header.py --- Moose Development/docs-header.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/docs-header.py b/Moose Development/docs-header.py index 0b27a91f3..a3f1cc29c 100644 --- a/Moose Development/docs-header.py +++ b/Moose Development/docs-header.py @@ -1,6 +1,7 @@ # import required module from pathlib import Path import os +import codecs # assign directory directory = '.' @@ -15,8 +16,9 @@ with open( os.path.dirname(__file__) + '/docs-header.html', 'r') as file: # that directory files = Path(directory).glob('*.html') for file in files: - print(file) - with open(file, 'r') as fileread: + #print(file) + #with open(file, 'r') as fileread: + with codecs.open(file, 'r', encoding='utf-8', errors='ignore') as fileread: filedata = fileread.read() # Replace the target string filedata = filedata.replace( '', newhead ) From 1856754614d9c3d2fc2d7737d4ad020e89b67b0d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 7 Apr 2025 10:40:26 +0200 Subject: [PATCH 156/158] #Smaller Changes --- Moose Development/Moose/Core/Set.lua | 58 ++++++++++++++++++++++++++++ Moose Development/Moose/Ops/CTLD.lua | 6 +-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 0b54f06d0..c66b3cf57 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -2519,6 +2519,35 @@ do -- SET_UNIT ) return self end + + --- Builds a set of units which belong to groups with certain **group names**. + -- @param #SET_UNIT self + -- @param #string Prefixes The (partial) group names to look for. Can be a single string or a table of strings. + -- @return #SET_UNIT self + function SET_UNIT:FilterGroupPrefixes(Prefixes) + if type(Prefixes) == "string" then + Prefixes = {Prefixes} + end + self:FilterFunction( + function(unit,prefixes) + local outcome = false + if unit then + local grp = unit:GetGroup() + local gname = grp ~= nil and grp:GetName() or "none" + for _,_fix in pairs(prefixes or {}) do + if string.find(gname,_fix) then + outcome = true + break + end + end + else + return false + end + return outcome + end, Prefixes + ) + return self + end --- Builds a set of units having a radar of give types. -- All the units having a radar of a given type will be included within the set. @@ -4434,6 +4463,35 @@ do -- SET_CLIENT end return self end + + --- Builds a set of clients which belong to groups with certain **group names**. + -- @param #SET_CLIENT self + -- @param #string Prefixes The (partial) group names to look for. Can be anywhere in the group name. Can be a single string or a table of strings. + -- @return #SET_CLIENT self + function SET_CLIENT:FilterGroupPrefixes(Prefixes) + if type(Prefixes) == "string" then + Prefixes = {Prefixes} + end + self:FilterFunction( + function(unit,prefixes) + local outcome = false + if unit then + local grp = unit:GetGroup() + local gname = grp ~= nil and grp:GetName() or "none" + for _,_fix in pairs(prefixes or {}) do + if string.find(gname,_fix) then + outcome = true + break + end + end + else + return false + end + return outcome + end, Prefixes + ) + return self + end --- Builds a set of clients that are only active. -- Only the clients that are active will be included within the set. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 2510aa35e..565c9bf1a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4222,7 +4222,7 @@ function CTLD:_RefreshF10Menus() local client = _clientobj -- Wrapper.Client#CLIENT if client:IsGround() then local cname = client:GetName() - --self:I(self.lid.."Adding: "..cname) + self:T(self.lid.."Adding: "..cname) _UnitList[cname] = cname end end @@ -4256,7 +4256,7 @@ function CTLD:_RefreshF10Menus() local menus = {} for _, _unitName in pairs(self.CtldUnits) do if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then - --self:I(self.lid.."Menu not done yet") + self:T(self.lid.."Menu not done yet for ".._unitName) local _unit = UNIT:FindByName(_unitName) if not _unit and self.allowCATransport then _unit = CLIENT:FindByName(_unitName) @@ -4264,7 +4264,7 @@ function CTLD:_RefreshF10Menus() if _unit and _unit:IsAlive() then local _group = _unit:GetGroup() if _group then - --self:I(self.lid.."Unit and Group exist") + self:T(self.lid.."Unit and Group exist") local capabilities = self:_GetUnitCapabilities(_unit) local cantroops = capabilities.troops local cancrates = capabilities.crates From 1a156e7e1204b90f35330dcde53491a89979d678 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 7 Apr 2025 11:57:15 +0200 Subject: [PATCH 157/158] #CTLD - make menu build for CA a bit faster --- Moose Development/Moose/Ops/CTLD.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 565c9bf1a..cd0bc8403 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1412,7 +1412,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.1.32" +CTLD.version="1.2.33" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2059,6 +2059,12 @@ function CTLD:_EventHandler(EventData) self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end + -- CA support + if _unit:IsGround() and self.allowCATransport then + local unitname = event.IniUnitName or "none" + self.Loaded_Cargo[unitname] = nil + self:_RefreshF10Menus() + end return elseif event.id == EVENTS.Land or event.id == EVENTS.Takeoff then local unitname = event.IniUnitName @@ -4141,6 +4147,7 @@ function CTLD:_MoveGroupToZone(Group) local groupcoord = Group:GetCoordinate() -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) + self:T({canmove=outcome, name=name, zone=zone, dist=distance,max=self.movetroopsdistance}) if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then -- yes, we can ;) local groupname = Group:GetName() @@ -5240,6 +5247,8 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) table = self.dropOffZones elseif ZoneType == CTLD.CargoZoneType.SHIP then table = self.shipZones + elseif ZoneType == CTLD.CargoZoneType.BEACON then + table = self.droppedBeacons else table = self.wpZones end @@ -5672,7 +5681,8 @@ function CTLD:IsUnitInZone(Unit,Zonetype) end local distance = self:_GetDistance(zonecoord,unitcoord) self:T("Distance Zone: "..distance) - if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and maxdist > distance then + self:T("Zone Active: "..tostring(active)) + if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and distance < maxdist then outcome = true maxdist = distance zoneret = zone From 49c11073e61f7db68ed8abba195b7eb1dc87fda0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 9 Apr 2025 08:15:50 +0200 Subject: [PATCH 158/158] #MANTIS Mod data updates --- Moose Development/Moose/Functional/Mantis.lua | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index db49d8bc9..0ebe76f4a 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -437,16 +437,16 @@ MANTIS.SamDataSMA = { -- units from SMA Mod (Sweedish Military Assets) -- https://forum.dcs.world/topic/295202-swedish-military-assets-for-dcs-by-currenthill/ -- group name MUST contain SMA to ID launcher type correctly! - ["RBS98M SMA"] = { Range=20, Blindspot=0, Height=8, Type="Short", Radar="RBS-98" }, - ["RBS70 SMA"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-70" }, - ["RBS70M SMA"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="BV410_RBS70" }, - ["RBS90 SMA"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-90" }, - ["RBS90M SMA"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="BV410_RBS90" }, - ["RBS103A SMA"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103A" }, - ["RBS103B SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" }, - ["RBS103AM SMA"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, - ["RBS103BM SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" }, - ["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Point", Radar="LvKv9040",Point="true" }, + ["RBS98M SMA"] = { Range=20, Blindspot=0.2, Height=8, Type="Short", Radar="RBS-98" }, + ["RBS70 SMA"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="RBS-70" }, + ["RBS70M SMA"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="BV410_RBS70" }, + ["RBS90 SMA"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="RBS-90" }, + ["RBS90M SMA"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="BV410_RBS90" }, + ["RBS103A SMA"] = { Range=160, Blindspot=1, Height=36, Type="Long", Radar="LvS-103_Lavett103_Rb103A" }, + ["RBS103B SMA"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103B" }, + ["RBS103AM SMA"] = { Range=160, Blindspot=1, Height=36, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, + ["RBS103BM SMA"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103B" }, + ["Lvkv9040M SMA"] = { Range=2, Blindspot=0.1, Height=1.2, Type="Point", Radar="LvKv9040",Point="true" }, } --- SAM data CH @@ -461,47 +461,49 @@ MANTIS.SamDataCH = { -- units from CH (Military Assets by Currenthill) -- https://www.currenthill.com/ -- group name MUST contain CHM to ID launcher type correctly! - ["2S38 CHM"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" }, + ["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" }, ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, - ["PGL-625 CHM"] = { Range=10, Blindspot=0.5, Height=5, Type="Short", Radar="PGL_625" }, - ["HQ-17A CHM"] = { Range=20, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, - ["M903PAC2 CHM"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, - ["M903PAC3 CHM"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, + ["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" }, ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, - ["PGZ-09 CHM"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" }, - ["S350-9M100 CHM"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, + ["PGZ-09 CHM"] = { Range=4, Blindspot=0.5, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" }, + ["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, ["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" }, - ["LAV-AD CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_LAVAD" }, + ["LAV-AD CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_LAVAD" }, ["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" }, - ["PGZ-95 CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" }, - ["LD-3000 CHM"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="CH_LD3000_stationary", Point="true" }, - ["LD-3000M CHM"] = { Range=3, Blindspot=0, Height=3, Type="Point", Radar="CH_LD3000", Point="true" }, - ["FlaRakRad CHM"] = { Range=8, Blindspot=1.5, Height=6, Type="Short", Radar="HQ17A" }, + ["PGZ-95 CHM"] = { Range=2.5, Blindspot=0.5, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" }, + ["LD-3000 CHM"] = { Range=2.5, Blindspot=0.1, Height=3, Type="Point", Radar="CH_LD3000_stationary", Point="true" }, + ["LD-3000M CHM"] = { Range=2.5, Blindspot=0.1, Height=3, Type="Point", Radar="CH_LD3000", Point="true" }, + ["FlaRakRad CHM"] = { Range=8, Blindspot=1.5, Height=6, Type="Short", Radar="CH_FlaRakRad" }, ["IRIS-T SLM CHM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, - ["M903PAC2KAT1 CHM"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="CH_MIM104_M903_PAC2_KAT1" }, - ["Skynex CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" }, - ["Skyshield CHM"] = { Range=3.5, Blindspot=0, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" }, - ["WieselOzelot CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" }, + ["M903PAC2KAT1 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="CH_MIM104_M903_PAC2_KAT1" }, + ["Skynex CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" }, + ["Skyshield CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" }, + ["WieselOzelot CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" }, ["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" }, ["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" }, ["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" }, ["Stormer CHM"] = { Range=7.5, Blindspot=0.3, Height=7, Type="Short", Radar="CH_StormerHVM" }, ["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" }, - ["USInfantryFIM92K CHM"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_USInfantry_FIM92" }, - ["RBS98M CHM"] = { Range=20, Blindspot=0, Height=8, Type="Short", Radar="RBS-98" }, - ["RBS70 CHM"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-70" }, - ["RBS90 CHM"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-90" }, - ["RBS103A CHM"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103A" }, - ["RBS103B CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" }, - ["RBS103AM CHM"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, - ["RBS103BM CHM"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" }, - ["Lvkv9040M CHM"] = { Range=4, Blindspot=0, Height=2.5, Type="Point", Radar="LvKv9040", Point="true" }, + ["USInfantryFIM92K CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_USInfantry_FIM92" }, + ["RBS98M CHM"] = { Range=20, Blindspot=0.2, Height=8, Type="Short", Radar="RBS-98" }, + ["RBS70 CHM"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="RBS-70" }, + ["RBS70M CHM"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="BV410_RBS70" }, + ["RBS90 CHM"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="RBS-90" }, + ["RBS90M CHM"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="BV410_RBS90" }, + ["RBS103A CHM"] = { Range=160, Blindspot=1, Height=36, Type="Long", Radar="LvS-103_Lavett103_Rb103A" }, + ["RBS103B CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103B" }, + ["RBS103AM CHM"] = { Range=160, Blindspot=1, Height=36, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, + ["RBS103BM CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103B" }, + ["Lvkv9040M CHM"] = { Range=2, Blindspot=0.1, Height=1.2, Type="Point", Radar="LvKv9040",Point="true" }, } -----------------------------------------------------------------------