diff --git a/Moose Development/Debugger/debugger.lua b/Moose Development/Debugger/debugger.lua index a5f606f74..c8150ad13 100644 --- a/Moose Development/Debugger/debugger.lua +++ b/Moose Development/Debugger/debugger.lua @@ -1953,7 +1953,7 @@ local function refct_from_id(id) -- refct = refct_from_id(CTypeID) unsigned = refct.unsigned, size = bit.band(bit.rshift(ctype.info, 16), 127), } - refct.bool, refct.const, refct.volatile, refct.unsigned = nil + refct.bool, refct.const, refct.volatile, refct.unsigned = nil, nil, nil, nil end if CT[4] then -- Merge sibling attributes onto this type. diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index d86c20bbe..08c4a7273 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -17,7 +17,9 @@ --- The AI_A2A_CAP class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group} -- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- +-- +-- ![Banner Image](..\Images\deprecated.png) +-- -- ![Process](..\Presentations\AI_CAP\Dia3.JPG) -- -- The AI_A2A_CAP is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_CAP process can be started using the **Start** event. diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index bbfaa868b..8e8e3df05 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -32,7 +32,9 @@ -- [DCS WORLD - MOOSE - A2A GCICAP - Build an automatic A2A Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) -- -- === --- +-- +-- ![Banner Image](..\Images\deprecated.png) +-- -- # QUICK START GUIDE -- -- There are basically two classes available to model an A2A defense system. diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua index 8f85f3cd2..3d56e0227 100644 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua @@ -19,6 +19,8 @@ --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- The AI_A2A_GCI is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_GCI process can be started using the **Start** event. -- -- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 71b392db1..6c7ce68cc 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -15,6 +15,8 @@ --- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) -- -- The AI_A2A_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2A_PATROL process can be started using the **Start** event. diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua index 7a6a80fca..8a39ad0d2 100644 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ b/Moose Development/Moose/AI/AI_A2G_BAI.lua @@ -15,7 +15,9 @@ -- @extends AI.AI_A2A_Engage#AI_A2A_Engage -- TODO: Documentation. This class does not exist, unable to determine what it extends. --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- +-- +-- ![Banner Image](..\Images\deprecated.png) +-- -- # Developer Note -- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE diff --git a/Moose Development/Moose/AI/AI_A2G_CAS.lua b/Moose Development/Moose/AI/AI_A2G_CAS.lua index 16c9cb976..d4c0fe417 100644 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ b/Moose Development/Moose/AI/AI_A2G_CAS.lua @@ -18,6 +18,8 @@ -- -- # Developer Note -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE -- Therefore, this class is considered to be deprecated -- diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 0396e4e9f..5aff7ad4f 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -36,6 +36,8 @@ -- -- # QUICK START GUIDE -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- The following class is available to model an A2G defense system. -- -- AI_A2G_DISPATCHER is the main A2G defense class that models the A2G defense system. diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua index 17f9f86f5..4ee600fcf 100644 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ b/Moose Development/Moose/AI/AI_A2G_SEAD.lua @@ -19,6 +19,8 @@ --- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- The AI_A2G_SEAD is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_SEAD process can be started using the **Start** event. -- -- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index d3d11cf16..1b0bf8cac 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -15,6 +15,7 @@ --- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}. -- +-- ![Banner Image](..\Images\deprecated.png) -- -- # 1) AI_AIR constructor -- diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua index 9e5939aa0..ad309f0c2 100644 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua @@ -36,6 +36,8 @@ -- -- # QUICK START GUIDE -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- The following class is available to model an AIR defense system. -- -- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system. diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index ff3327421..772f10b2e 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -13,12 +13,14 @@ --- @type AI_AIR_ENGAGE +--- @type AI_AIR_ENGAGE -- @extends AI.AI_AIR#AI_AIR --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event. -- -- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. diff --git a/Moose Development/Moose/AI/AI_Air_Patrol.lua b/Moose Development/Moose/AI/AI_Air_Patrol.lua index 2b4e1a937..b389d10ff 100644 --- a/Moose Development/Moose/AI/AI_Air_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Air_Patrol.lua @@ -15,6 +15,8 @@ --- The AI_AIR_PATROL class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} -- and automatically engage any airborne enemies that are within a certain range or within a certain zone. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- ![Process](..\Presentations\AI_CAP\Dia3.JPG) -- -- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL process can be started using the **Start** event. diff --git a/Moose Development/Moose/AI/AI_Air_Squadron.lua b/Moose Development/Moose/AI/AI_Air_Squadron.lua index 6651a92a5..7356c1baa 100644 --- a/Moose Development/Moose/AI/AI_Air_Squadron.lua +++ b/Moose Development/Moose/AI/AI_Air_Squadron.lua @@ -13,7 +13,7 @@ --- @type AI_AIR_SQUADRON +--- @type AI_AIR_SQUADRON -- @extends Core.Base#BASE @@ -21,6 +21,8 @@ -- -- # Developer Note -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE -- Therefore, this class is considered to be deprecated -- diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index 237bb9ab0..46c5c086a 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -38,6 +38,8 @@ --- Implements the core functions to provide BattleGround Air Interdiction in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- The AI_BAI_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone. -- -- ![HoldAndEngage](..\Presentations\AI_BAI\Dia3.JPG) diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua index 827a17764..f11a12c18 100644 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ b/Moose Development/Moose/AI/AI_Balancer.lua @@ -33,8 +33,9 @@ -- @field Wrapper.Group#GROUP Test -- @extends Core.Fsm#FSM_SET - ---- Monitors and manages as many replacement AI groups as there are +--- ![Banner Image](..\Images\deprecated.png) +-- +-- Monitors and manages as many replacement AI groups as there are -- CLIENTS in a SET\_CLIENT collection, which are not occupied by human players. -- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions. -- diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index 35604e9f9..8f1f1501b 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -39,6 +39,8 @@ --- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group} -- and automatically engage any airborne enemies that are within a certain range or within a certain zone. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- ![Process](..\Presentations\AI_CAP\Dia3.JPG) -- -- The AI_CAP_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event. diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index d1ac6cdac..8fec1ca3f 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -38,6 +38,9 @@ -- @extends AI.AI_Patrol#AI_PATROL_ZONE --- Implements the core functions to provide Close Air Support in an Engage @{Core.Zone} by an AIR @{Wrapper.Controllable} or @{Wrapper.Group}. +-- +-- ![Banner Image](..\Images\deprecated.png) +-- -- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone. -- -- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index 0bd6ab9ea..eac91c668 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -9,12 +9,14 @@ -- @module AI.AI_Cargo -- @image Cargo.JPG --- @type AI_CARGO +--- @type AI_CARGO -- @extends Core.Fsm#FSM_CONTROLLABLE --- Base class for the dynamic cargo handling capability for AI groups. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Carriers can be mobilized to intelligently transport infantry and other cargo within the simulation. -- The AI_CARGO module uses the @{Cargo.Cargo} capabilities within the MOOSE framework. -- CARGO derived objects must be declared within the mission to make the AI_CARGO object recognize the cargo. diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index fc9037fea..baf202d0d 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -15,6 +15,8 @@ --- Brings a dynamic cargo handling capability for an AI vehicle group. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. -- -- The AI_CARGO_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua index 3e9589d95..ff814d6be 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua @@ -14,6 +14,8 @@ --- Brings a dynamic cargo handling capability for an AI airplane group. +-- +-- ![Banner Image](..\Images\deprecated.png) -- -- Airplane carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation between airbases. -- diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 71b7f9f43..b63c76192 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -22,6 +22,8 @@ -- -- === -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- # The dispatcher concept. -- -- Carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua index 3d98522e1..ca0ec07d8 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua @@ -36,6 +36,8 @@ --- A dynamic cargo transportation capability for AI groups. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. -- -- The AI_CARGO_DISPATCHER_APC module is derived from the AI_CARGO_DISPATCHER module. diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua index d3a7c78ac..b70a43f4c 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua @@ -30,6 +30,8 @@ --- Brings a dynamic cargo handling capability for AI groups. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation. -- -- The AI_CARGO_DISPATCHER_AIRPLANE module is derived from the AI_CARGO_DISPATCHER module. diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua index b219c78b0..1c228e872 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua @@ -31,6 +31,8 @@ --- A dynamic cargo handling capability for AI helicopter groups. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation. -- -- diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua index 6fc670e40..f0afedeb3 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua @@ -29,6 +29,8 @@ --- A dynamic cargo transportation capability for AI groups. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation. -- -- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module. diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 207a1ab8e..61294ba6d 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -15,6 +15,8 @@ --- Brings a dynamic cargo handling capability for an AI helicopter group. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Helicopter carriers can be mobilized to intelligently transport infantry and other cargo within the simulation. -- -- The AI_CARGO_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. diff --git a/Moose Development/Moose/AI/AI_Cargo_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Ship.lua index 5639c52da..259c2009f 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Ship.lua @@ -14,6 +14,8 @@ --- Brings a dynamic cargo handling capability for an AI naval group. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Naval ships can be utilized to transport cargo around the map following naval shipping lanes. -- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. -- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo. diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index ad325ed94..c32312f27 100644 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -25,6 +25,8 @@ -- -- Allows you to interact with escorting AI on your flight and take the lead. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10). -- -- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes. diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua index ff4c0ddfe..d2366568f 100644 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua @@ -23,6 +23,8 @@ -- -- # Developer Note -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE -- Therefore, this class is considered to be deprecated -- diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua index 160c2beed..1b0370b54 100644 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua @@ -21,6 +21,8 @@ --- Models the assignment of AI escorts to player flights upon request using the radio menu. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- # Developer Note -- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE diff --git a/Moose Development/Moose/AI/AI_Escort_Request.lua b/Moose Development/Moose/AI/AI_Escort_Request.lua index 3251d3717..843f75160 100644 --- a/Moose Development/Moose/AI/AI_Escort_Request.lua +++ b/Moose Development/Moose/AI/AI_Escort_Request.lua @@ -25,6 +25,8 @@ -- -- Allows you to interact with escorting AI on your flight and take the lead. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10). -- -- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes. diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index 326d6365b..425585e90 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -40,6 +40,8 @@ --- Build large formations, make AI follow a @{Wrapper.Client#CLIENT} (player) leader or a @{Wrapper.Unit#UNIT} (AI) leader. +-- +-- ![Banner Image](..\Images\deprecated.png) -- -- AI_FORMATION makes AI @{Wrapper.Group#GROUP}s fly in formation of various compositions. -- The AI_FORMATION class models formations in a different manner than the internal DCS formation logic!!! diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index 408e3b1a6..e8d21ed44 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -48,6 +48,8 @@ --- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Controllable} or @{Wrapper.Group}. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) -- -- The AI_PATROL_ZONE is assigned a @{Wrapper.Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event. diff --git a/Moose Development/Moose/Actions/Act_Account.lua b/Moose Development/Moose/Actions/Act_Account.lua index a72a7b445..83fd6bf5c 100644 --- a/Moose Development/Moose/Actions/Act_Account.lua +++ b/Moose Development/Moose/Actions/Act_Account.lua @@ -1,6 +1,6 @@ --- **Actions** - ACT_ACCOUNT_ classes **account for** (detect, count & report) various DCS events occurring on UNITs. -- --- ![Banner Image](..\Presentations\ACT_ACCOUNT\Dia1.JPG) +-- ![Banner Image](..\Images\deprecated.png) -- -- === -- @@ -8,9 +8,11 @@ -- @image MOOSE.JPG do -- ACT_ACCOUNT - + --- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS} - -- + -- + -- ![Banner Image](..\Images\deprecated.png) + -- -- ## ACT_ACCOUNT state machine: -- -- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. @@ -133,7 +135,7 @@ do -- ACT_ACCOUNT -- @param #string Event -- @param #string From -- @param #string To - function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event ) + function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To ) self:__NoMore( 1 ) end diff --git a/Moose Development/Moose/Actions/Act_Assign.lua b/Moose Development/Moose/Actions/Act_Assign.lua index 3b261cfb1..1f99b86e7 100644 --- a/Moose Development/Moose/Actions/Act_Assign.lua +++ b/Moose Development/Moose/Actions/Act_Assign.lua @@ -1,6 +1,8 @@ --- (SP) (MP) (FSM) Accept or reject process for player (task) assignments. -- -- === +-- +-- ![Banner Image](..\Images\deprecated.png) -- -- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS} -- diff --git a/Moose Development/Moose/Actions/Act_Assist.lua b/Moose Development/Moose/Actions/Act_Assist.lua index 2ae132ac1..2017c6ab9 100644 --- a/Moose Development/Moose/Actions/Act_Assist.lua +++ b/Moose Development/Moose/Actions/Act_Assist.lua @@ -1,5 +1,6 @@ --- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. -- +-- ![Banner Image](..\Images\deprecated.png) -- ## ACT_ASSIST state machine: -- -- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. diff --git a/Moose Development/Moose/Actions/Act_Route.lua b/Moose Development/Moose/Actions/Act_Route.lua index b387b6584..548c6a846 100644 --- a/Moose Development/Moose/Actions/Act_Route.lua +++ b/Moose Development/Moose/Actions/Act_Route.lua @@ -2,6 +2,8 @@ -- -- === -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS} -- -- ## ACT_ROUTE state machine: diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index 413beb514..76307dd78 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -2,6 +2,8 @@ -- -- === -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- # 1) MOOSE Cargo System. -- -- #### Those who have used the mission editor, know that the DCS mission editor provides cargo facilities. diff --git a/Moose Development/Moose/Cargo/CargoCrate.lua b/Moose Development/Moose/Cargo/CargoCrate.lua index c64016fd8..b9dcb2761 100644 --- a/Moose Development/Moose/Cargo/CargoCrate.lua +++ b/Moose Development/Moose/Cargo/CargoCrate.lua @@ -22,6 +22,9 @@ do -- CARGO_CRATE -- @type CARGO_CRATE -- @extends Cargo.Cargo#CARGO_REPRESENTABLE + --- + -- ![Banner Image](..\Images\deprecated.png) + -- --- Defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. -- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_CRATE objects to and from carriers. -- diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index 45e1b5948..b756f456c 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -26,6 +26,8 @@ do -- CARGO_GROUP -- @extends Cargo.Cargo#CARGO_REPORTABLE --- Defines a cargo that is represented by a @{Wrapper.Group} object within the simulator. + -- + -- ![Banner Image](..\Images\deprecated.png) -- The cargo can be Loaded, UnLoaded, Boarded, UnBoarded to and from Carriers. -- -- The above cargo classes are used by the following AI_CARGO_ classes to allow AI groups to transport cargo: diff --git a/Moose Development/Moose/Cargo/CargoSlingload.lua b/Moose Development/Moose/Cargo/CargoSlingload.lua index 81bc5d95e..90209e8c0 100644 --- a/Moose Development/Moose/Cargo/CargoSlingload.lua +++ b/Moose Development/Moose/Cargo/CargoSlingload.lua @@ -32,6 +32,8 @@ do -- CARGO_SLINGLOAD -- -- # Developer Note -- + -- ![Banner Image](..\Images\deprecated.png) + -- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE -- Therefore, this class is considered to be deprecated -- diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua index a76469870..bc504d003 100644 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -30,6 +30,8 @@ do -- CARGO_UNIT -- -- # Developer Note -- + -- ![Banner Image](..\Images\deprecated.png) + -- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE -- Therefore, this class is considered to be deprecated -- diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index b1a12e740..81cf8892d 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -974,7 +974,7 @@ do -- Scheduling -- @param #BASE self -- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. -- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. - -- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. + -- @param ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. -- @return #string The Schedule ID of the planned schedule. function BASE:ScheduleOnce( Start, SchedulerFunction, ... ) diff --git a/Moose Development/Moose/Core/ClientMenu.lua b/Moose Development/Moose/Core/ClientMenu.lua index 5e7219add..fea72914d 100644 --- a/Moose Development/Moose/Core/ClientMenu.lua +++ b/Moose Development/Moose/Core/ClientMenu.lua @@ -20,7 +20,7 @@ -- -- @module Core.ClientMenu -- @image Core_Menu.JPG --- last change: Jan 2025 +-- last change: Sept 2025 -- TODO ---------------------------------------------------------------------------------------------------------------- @@ -417,7 +417,7 @@ end CLIENTMENUMANAGER = { ClassName = "CLIENTMENUMANAGER", lid = "", - version = "0.1.6", + version = "0.1.7", name = nil, clientset = nil, menutree = {}, @@ -806,6 +806,16 @@ function CLIENTMENUMANAGER:ResetMenuComplete() return self end +--- Remove the entry and all entries below the given entry from the client's F10 menus. +-- @param #CLIENTMENUMANAGER self +-- @param #CLIENTMENU Entry The entry to remove +-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. +-- @return #CLIENTMENUMANAGER self +function CLIENTMENUMANAGER:DeleteEntry(Entry,Client) + self:T(self.lid.."DeleteEntry") + return self:DeleteF10Entry(Entry,Client) +end + --- Remove the entry and all entries below the given entry from the client's F10 menus. -- @param #CLIENTMENUMANAGER self -- @param #CLIENTMENU Entry The entry to remove diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 4aec2393a..e1a1e732d 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -577,13 +577,19 @@ do -- Zones and Pathlines -- For a rectangular polygon drawing, we have the width (y) and height (x). local w=objectData.width local h=objectData.height + local rotation = UTILS.ToRadian(objectData.angle or 0) - -- Create points from center using with and height (width for y and height for x is a bit confusing, but this is how ED implemented it). - local points={} - points[1]={x=vec2.x-h/2, y=vec2.y+w/2} --Upper left - points[2]={x=vec2.x+h/2, y=vec2.y+w/2} --Upper right - points[3]={x=vec2.x+h/2, y=vec2.y-w/2} --Lower right - points[4]={x=vec2.x-h/2, y=vec2.y-w/2} --Lower left + local sinRot = math.sin(rotation) + local cosRot = math.cos(rotation) + local dx = h / 2 + local dy = w / 2 + + local points = { + { x = -dx * cosRot - (-dy * sinRot) + vec2.x, y = -dx * sinRot + (-dy * cosRot) + vec2.y }, + { x = dx * cosRot - (-dy * sinRot) + vec2.x, y = dx * sinRot + (-dy * cosRot) + vec2.y }, + { x = dx * cosRot - (dy * sinRot) + vec2.x, y = dx * sinRot + (dy * cosRot) + vec2.y }, + { x = -dx * cosRot - (dy * sinRot) + vec2.x, y = -dx * sinRot + (dy * cosRot) + vec2.y }, + } --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY") @@ -872,6 +878,8 @@ end -- @return Wrapper.Group#GROUP The found GROUP. function DATABASE:FindGroup( GroupName ) + if type(GroupName) ~= "string" or GroupName == "" then return end + local GroupFound = self.GROUPS[GroupName] if GroupFound == nil and GroupName ~= nil and self.Templates.Groups[GroupName] == nil then @@ -1110,7 +1118,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) else self.STNS[stn] = UnitTemplate.name - self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) + self:T("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) end end if UnitTemplate.AddPropAircraft.SADL_TN then @@ -1119,7 +1127,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) else self.SADL[sadl] = UnitTemplate.name - self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) + self:T("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) end end end @@ -1380,7 +1388,7 @@ function DATABASE:GetCoalitionFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CoalitionID end - self:E("WARNING: Template does not exist for client "..tostring(ClientName)) + self:T("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end @@ -1392,7 +1400,7 @@ function DATABASE:GetCategoryFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CategoryID end - self:E("WARNING: Template does not exist for client "..tostring(ClientName)) + self:T("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end @@ -1404,7 +1412,7 @@ function DATABASE:GetCountryFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CountryID end - self:E("WARNING: Template does not exist for client "..tostring(ClientName)) + self:T("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end @@ -1697,7 +1705,7 @@ function DATABASE:_EventOnBirth( Event ) if PlayerName then -- Debug info. - self:I(string.format("Player '%s' joined unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName))) + self:I(string.format("Player '%s' joined unit '%s' (%s) of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniTypeName), tostring(Event.IniDCSGroupName))) -- Add client in case it does not exist already. if client == nil or (client and client:CountPlayers() == 0) then diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index c5893325b..7cdec63ac 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1508,7 +1508,9 @@ function EVENT:onEvent( Event ) else if Event.place:isExist() and Object.getCategory(Event.place) ~= Object.Category.SCENERY then Event.Place=AIRBASE:Find(Event.place) - Event.PlaceName=Event.Place:GetName() + if Event.Place then + Event.PlaceName=Event.Place:GetName() + end end end end diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 4571eed25..222866321 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -50,7 +50,7 @@ MARKEROPS_BASE = { ClassName = "MARKEROPS", Tag = "mytag", Keywords = {}, - version = "0.1.3", + version = "0.1.4", debug = false, Casesensitive = true, } @@ -154,14 +154,7 @@ function MARKEROPS_BASE:OnEventMark(Event) self:E("Skipping onEvent. Event or Event.idx unknown.") return true end - --position - local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z} - local coord=COORDINATE:NewFromVec3(vec3) - if self.debug then - local coordtext = coord:ToStringLLDDM() - local text = tostring(Event.text) - local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll() - end + local coalition = Event.MarkCoalition -- decision if Event.id==world.event.S_EVENT_MARK_ADDED then @@ -170,8 +163,14 @@ function MARKEROPS_BASE:OnEventMark(Event) local Eventtext = tostring(Event.text) if Eventtext~=nil then if self:_MatchTag(Eventtext) then - local matchtable = self:_MatchKeywords(Eventtext) - self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) + local coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}) + if self.debug then + local coordtext = coord:ToStringLLDDM() + local text = tostring(Event.text) + local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll() + end + local matchtable = self:_MatchKeywords(Eventtext) + self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) end end elseif Event.id==world.event.S_EVENT_MARK_CHANGE then @@ -180,8 +179,14 @@ function MARKEROPS_BASE:OnEventMark(Event) local Eventtext = tostring(Event.text) if Eventtext~=nil then if self:_MatchTag(Eventtext) then - local matchtable = self:_MatchKeywords(Eventtext) - self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) + local coord=COORDINATE:NewFromVec3({y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}) + if self.debug then + local coordtext = coord:ToStringLLDDM() + local text = tostring(Event.text) + local m = MESSAGE:New(string.format("Mark changed at %s with text: %s",coordtext,text),10,"Info",false):ToAll() + end + local matchtable = self:_MatchKeywords(Eventtext) + self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) end end elseif Event.id==world.event.S_EVENT_MARK_REMOVED then diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 4165bdc57..86dfd1858 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -206,7 +206,7 @@ end function MESSAGE:ToGroup( Group, Settings ) self:F( Group.GroupName ) - if Group then + if Group and Group:IsAlive() then if self.MessageType then local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS @@ -231,7 +231,7 @@ end function MESSAGE:ToUnit( Unit, Settings ) self:F( Unit.IdentifiableName ) - if Unit then + if Unit and Unit:IsAlive() then if self.MessageType then local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS @@ -452,7 +452,7 @@ end _MESSAGESRS = {} --- Set up MESSAGE generally to allow Text-To-Speech via SRS and TTS functions. `SetMSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible. --- @param #string PathToSRS (optional) Path to SRS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" or your configuration file setting. +-- @param #string PathToSRS (optional) Path to SRS TTS Folder, defaults to "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio" or your configuration file setting. -- @param #number Port Port (optional) number of SRS, defaults to 5002 or your configuration file setting. -- @param #string PathToCredentials (optional) Path to credentials file for Google. -- @param #number Frequency Frequency in MHz. Can also be given as a #table of frequencies. @@ -468,13 +468,13 @@ _MESSAGESRS = {} -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend) - _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" _MESSAGESRS.frequency = Frequency or MSRS.frequencies or 243 _MESSAGESRS.modulation = Modulation or MSRS.modulations or radio.modulation.AM @@ -535,7 +535,7 @@ end -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- @@ -567,7 +567,7 @@ end -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.BLUE) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSBlue() -- @@ -589,7 +589,7 @@ end -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.RED) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSRed() -- @@ -611,7 +611,7 @@ end -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only --- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL) +-- MESSAGE.SetMSRS("D:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",5012,nil,127,radio.modulation.FM,"female","en-US",nil,coalition.side.NEUTRAL) -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRSAll() -- diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index fde523c2b..14ec952b4 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -25,7 +25,7 @@ do -- COORDINATE - --- + --- Coordinate class -- @type COORDINATE -- @field #string ClassName Name of the class -- @field #number x Component of the 3D vector. @@ -59,6 +59,10 @@ do -- COORDINATE -- * @{#COORDINATE.SmokeOrange}(): To smoke the point in orange. -- * @{#COORDINATE.SmokeWhite}(): To smoke the point in white. -- * @{#COORDINATE.SmokeGreen}(): To smoke the point in green. + -- * @{#COORDINATE.SetSmokeOffsetDirection}(): To set an offset point direction for smoke. + -- * @{#COORDINATE.SetSmokeOffsetDistance}(): To set an offset point distance for smoke. + -- * @{#COORDINATE.SwitchSmokeOffsetOn}(): To set an offset point for smoke to on. + -- * @{#COORDINATE.SwitchSmokeOffsetOff}(): To set an offset point for smoke to off. -- -- ## 2.2) Flare -- @@ -790,7 +794,9 @@ do -- COORDINATE -- @return DCS#Vec2 Vec2 function COORDINATE:GetRandomVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) - + math.random() + math.random() + math.random() local Theta = 2 * math.pi * math.random() local Radials = math.random() + math.random() if Radials > 1 then @@ -850,6 +856,26 @@ do -- COORDINATE return land.getHeight( Vec2 ) end + --- Returns a table of DCS#Vec3 points representing the terrain profile between two points. + -- @param #COORDINATE self + -- @param Destination DCS#Vec3 Ending point of the profile. + -- @return #table DCS#Vec3 table of the profile + function COORDINATE:GetLandProfileVec3(Destination) + return land.profile(self:GetVec3(), Destination) + end + + --- Returns a table of #COORDINATE representing the terrain profile between two points. + -- @param #COORDINATE self + -- @param Destination #COORDINATE Ending coordinate of the profile. + -- @return #table #COORDINATE table of the profile + function COORDINATE:GetLandProfileCoordinates(Destination) + local points = self:GetLandProfileVec3(Destination:GetVec3()) + local coords = {} + for _, point in ipairs(points) do + table.insert(coords, COORDINATE:NewFromVec3(point)) + end + return coords + end --- Set the heading of the coordinate, if applicable. -- @param #COORDINATE self @@ -2135,14 +2161,112 @@ do -- COORDINATE end - --- Smokes the point in a color. + --- Create colored smoke the point. The smoke we last up to 5 min (DCS limitation) but you can optionally specify a shorter duration or stop it manually. -- @param #COORDINATE self - -- @param Utilities.Utils#SMOKECOLOR SmokeColor - -- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins) - function COORDINATE:Smoke( SmokeColor, name ) - self:F2( { SmokeColor } ) - self.firename = name or "Smoke-"..math.random(1,100000) - trigger.action.smoke( self:GetVec3(), SmokeColor, self.firename ) + -- @param #number SmokeColor Color of smoke, e.g. `SMOKECOLOR.Green` for green smoke. + -- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min. + -- @param #number Delay (Optional) Delay before the smoke is started in seconds. + -- @param #string Name (Optional) Name if you want to stop the smoke early (normal duration: 5mins) + -- @param #boolean Offset (Optional) If true, offset the smokle a bit. + -- @param #number Direction (Optional) If Offset is true this is the direction of the offset, 1-359 (degrees). Default random. + -- @param #number Distance (Optional) If Offset is true this is the distance of the offset in meters. Default random 10-20. + -- @return #COORDINATE self + function COORDINATE:Smoke( SmokeColor, Duration, Delay, Name, Offset,Direction,Distance) + self:F2( { SmokeColor, Name, Duration, Delay, Offset } ) + + SmokeColor=SmokeColor or SMOKECOLOR.Green + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, COORDINATE.Smoke, self, SmokeColor, Duration, 0, Name, Direction,Distance) + else + + -- Create a name which is used to stop the smoke manually + self.firename = Name or "Smoke-"..math.random(1,100000) + + -- Create smoke + if Offset or self.SmokeOffset then + local Angle = Direction or self:GetSmokeOffsetDirection() + local Distance = Distance or self:GetSmokeOffsetDistance() + local newpos = self:Translate(Distance,Angle,true,false) + local newvec3 = newpos:GetVec3() + trigger.action.smoke( newvec3, SmokeColor, self.firename ) + else + trigger.action.smoke( self:GetVec3(), SmokeColor, self.firename ) + end + + -- Stop smoke + if Duration and Duration>0 then + self:ScheduleOnce(Duration, COORDINATE.StopSmoke, self, self.firename ) + end + end + + return self + end + + --- Get the offset direction when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @return #number Direction in degrees. + function COORDINATE:GetSmokeOffsetDirection() + local direction = self.SmokeOffsetDirection or math.random(1,359) + return direction + end + + --- Set the offset direction when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @param #number Direction (Optional) This is the direction of the offset, 1-359 (degrees). Default random. + -- @return #COORDINATE self + function COORDINATE:SetSmokeOffsetDirection(Direction) + if self then + self.SmokeOffsetDirection = Direction or math.random(1,359) + return self + else + COORDINATE.SmokeOffsetDirection = Direction or math.random(1,359) + end + end + + --- Get the offset distance when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @return #number Distance Distance in meters. + function COORDINATE:GetSmokeOffsetDistance() + local distance = self.SmokeOffsetDistance or math.random(10,20) + return distance + end + + --- Set the offset distance when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @param #number Distance (Optional) This is the distance of the offset in meters. Default random 10-20. + -- @return #COORDINATE self + function COORDINATE:SetSmokeOffsetDistance(Distance) + if self then + self.SmokeOffsetDistance = Distance or math.random(10,20) + return self + else + COORDINATE.SmokeOffsetDistance = Distance or math.random(10,20) + end + end + + --- Set the offset on when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @return #COORDINATE self + function COORDINATE:SwitchSmokeOffsetOn() + if self then + self.SmokeOffset = true + return self + else + COORDINATE.SmokeOffset = true + end + end + + --- Set the offset off when using `COORDINATE:Smoke()`. + -- @param #COORDINATE self + -- @return #COORDINATE self + function COORDINATE:SwitchSmokeOffsetOff() + if self then + self.SmokeOffset = false + return self + else + COORDINATE.SmokeOffset = false + end end --- Stops smoking the point in a color. @@ -2154,49 +2278,83 @@ do -- COORDINATE --- Smoke the COORDINATE Green. -- @param #COORDINATE self - function COORDINATE:SmokeGreen() - self:F2() - self:Smoke( SMOKECOLOR.Green ) + -- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min. + -- @param #number Delay (Optional) Delay before the smoke is started in seconds. + -- @return #COORDINATE self + function COORDINATE:SmokeGreen(Duration, Delay) + self:Smoke( SMOKECOLOR.Green, Duration, Delay ) + return self end --- Smoke the COORDINATE Red. -- @param #COORDINATE self - function COORDINATE:SmokeRed() - self:F2() - self:Smoke( SMOKECOLOR.Red ) + -- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min. + -- @param #number Delay (Optional) Delay before the smoke is started in seconds. + -- @return #COORDINATE self + function COORDINATE:SmokeRed(Duration, Delay) + self:Smoke( SMOKECOLOR.Red, Duration, Delay ) + return self end --- Smoke the COORDINATE White. -- @param #COORDINATE self - function COORDINATE:SmokeWhite() - self:F2() - self:Smoke( SMOKECOLOR.White ) + -- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min. + -- @param #number Delay (Optional) Delay before the smoke is started in seconds. + -- @return #COORDINATE self + function COORDINATE:SmokeWhite(Duration, Delay) + self:Smoke( SMOKECOLOR.White, Duration, Delay ) + return self end --- Smoke the COORDINATE Orange. -- @param #COORDINATE self - function COORDINATE:SmokeOrange() - self:F2() - self:Smoke( SMOKECOLOR.Orange ) + -- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min. + -- @param #number Delay (Optional) Delay before the smoke is started in seconds. + -- @return #COORDINATE self + function COORDINATE:SmokeOrange(Duration, Delay) + self:Smoke( SMOKECOLOR.Orange, Duration, Delay ) + return self end --- Smoke the COORDINATE Blue. -- @param #COORDINATE self - function COORDINATE:SmokeBlue() - self:F2() - self:Smoke( SMOKECOLOR.Blue ) + -- @param #number Duration (Optional) Duration of the smoke in seconds. DCS stopps the smoke automatically after 5 min. + -- @param #number Delay (Optional) Delay before the smoke is started in seconds. + -- @return #COORDINATE self + function COORDINATE:SmokeBlue(Duration, Delay) + self:Smoke( SMOKECOLOR.Blue, Duration, Delay ) + return self end --- Big smoke and fire at the coordinate. -- @param #COORDINATE self - -- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke). - -- @param #number density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. - -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeAndFire( preset, density, name ) - self:F2( { preset=preset, density=density } ) - density=density or 0.5 - self.firename = name or "Fire-"..math.random(1,10000) - trigger.action.effectSmokeBig( self:GetVec3(), preset, density, self.firename ) + -- @param #number Preset Smoke preset (1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke). + -- @param #number Density (Optional) Smoke density. Number in [0,...,1]. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. + -- @param #string Name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + -- @return #COORDINATE self + function COORDINATE:BigSmokeAndFire( Preset, Density, Duration, Delay, Name ) + self:F2( { preset=Preset, density=Density } ) + + Preset=Preset or BIGSMOKEPRESET.SmallSmokeAndFire + Density=Density or 0.5 + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, COORDINATE.BigSmokeAndFire, self, Preset, Density, Duration, 0, Name) + else + + self.firename = Name or "Fire-"..math.random(1,10000) + + trigger.action.effectSmokeBig( self:GetVec3(), Preset, Density, self.firename ) + + -- Stop smoke + if Duration and Duration>0 then + self:ScheduleOnce(Duration, COORDINATE.StopBigSmokeAndFire, self, self.firename ) + end + end + + return self end --- Stop big smoke and fire at the coordinate. @@ -2209,82 +2367,98 @@ do -- COORDINATE --- Small smoke and fire at the coordinate. -- @param #COORDINATE self - -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. - -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeAndFireSmall( density, name ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, density, name) + -- @param #number Density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. + -- @param #string Name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. + -- @return #COORDINATE self + function COORDINATE:BigSmokeAndFireSmall( Density, Duration, Delay, Name ) + self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, Density, Duration, Delay, Name) + return self end --- Medium smoke and fire at the coordinate. -- @param #COORDINATE self -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeAndFireMedium( density, name ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, density, name) + -- @return #COORDINATE self + function COORDINATE:BigSmokeAndFireMedium( Density, Duration, Delay, Name ) + self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, Density, Duration, Delay, Name) + return self end --- Large smoke and fire at the coordinate. -- @param #COORDINATE self -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeAndFireLarge( density, name ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, density, name) + -- @return #COORDINATE self + function COORDINATE:BigSmokeAndFireLarge( Density, Duration, Delay, Name ) + self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, Density, Duration, Delay, Name) + return self end --- Huge smoke and fire at the coordinate. -- @param #COORDINATE self -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeAndFireHuge( density, name ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, density, name) + -- @return #COORDINATE self + function COORDINATE:BigSmokeAndFireHuge( Density, Duration, Delay, Name ) + self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, Density, Duration, Delay, Name) + return self end --- Small smoke at the coordinate. -- @param #COORDINATE self -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeSmall( density, name ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, density, name) + -- @return #COORDINATE self + function COORDINATE:BigSmokeSmall( Density, Duration, Delay, Name ) + self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, Density, Duration, Delay, Name) + return self end --- Medium smoke at the coordinate. -- @param #COORDINATE self -- @param number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeMedium( density, name ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, density, name) + -- @return #COORDINATE self + function COORDINATE:BigSmokeMedium( Density, Duration, Delay, Name ) + self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, Density, Duration, Delay, Name) + return self end --- Large smoke at the coordinate. -- @param #COORDINATE self -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeLarge( density, name ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, density,name) + -- @return #COORDINATE self + function COORDINATE:BigSmokeLarge( Density, Duration, Delay, Name ) + self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, Density, Duration, Delay, Name) + return self end --- Huge smoke at the coordinate. -- @param #COORDINATE self -- @param #number density (Optional) Smoke density. Number between 0 and 1. Default 0.5. + -- @param #number Duration (Optional) Duration of the smoke and fire in seconds. + -- @param #number Delay (Optional) Delay before the smoke and fire is started in seconds. -- @param #string name (Optional) Name of the fire to stop it later again if not using the same COORDINATE object. Defaults to "Fire-" plus a random 5-digit-number. - function COORDINATE:BigSmokeHuge( density, name ) - self:F2( { density=density } ) - density=density or 0.5 - self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, density,name) + -- @return #COORDINATE self + function COORDINATE:BigSmokeHuge( Density, Duration, Delay, Name ) + self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, Density, Duration, Delay, Name) + return self end --- Flares the point in a color. @@ -2938,8 +3112,10 @@ do -- COORDINATE local sunrise=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, true, Tdiff) local sunset=UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tdiff) - if sunrise == "N/R" then return false end - if sunrise == "N/S" then return true end + if type(sunrise) == "string" or type(sunset) == "string" then + if sunrise == "N/R" then return false end + if sunset == "N/S" then return true end + end local time=UTILS.ClockToSeconds(clock) @@ -2957,6 +3133,11 @@ do -- COORDINATE -- Todays sun set in sec. local sunset=self:GetSunset(true) + + if type(sunrise) == "string" or type(sunset) == "string" then + if sunrise == "N/R" then return false end + if sunset == "N/S" then return true end + end -- Seconds passed since midnight. local time=UTILS.SecondsOfToday() @@ -3655,7 +3836,26 @@ do -- COORDINATE function COORDINATE:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) return COORDINATE:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) end - + + +--- Search for clear zones in a given area. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param #number SearchRadius Radius of the search area. +-- @param #number PosRadius Required clear radius around each position. +-- @param #number NumPositions Number of positions to find. +-- @return #table A table of Core.Point#COORDINATE that are clear of map objects within the given PosRadius. nil if no positions are found. + function COORDINATE:GetSimpleZones(SearchRadius, PosRadius, NumPositions) + local clearPositions = UTILS.GetSimpleZones(self:GetVec3(), SearchRadius, PosRadius, NumPositions) + if clearPositions and #clearPositions > 0 then + local coords = {} + for _, pos in pairs(clearPositions) do + local coord = COORDINATE:NewFromVec2(pos) + table.insert(coords, coord) + end + return coords + end + return nil + end + end do diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index 5eb58e731..7f0102943 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -175,7 +175,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr local Name = Info.name or "?" local ErrorHandler = function( errmsg ) - env.info( "Error in timer function: " .. errmsg ) + env.info( "Error in timer function: " .. errmsg or "" ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) end @@ -326,7 +326,7 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData -- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing. - if Schedule.ScheduleID then + if Schedule and Schedule.ScheduleID then self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) ) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 6d41edf12..5ee275fd5 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -958,7 +958,26 @@ do -- SET_BASE return ObjectNames end + + --- Get a *new* set table that only contains alive objects. + -- @param #SET_BASE self + -- @return #table Set table of alive objects. + function SET_BASE:GetAliveSet() + --self:F2() + local AliveSet = {} + -- Clean the Set before returning with only the alive Objects. + for ObjectName, Object in pairs( self.Set ) do + if Object then + if Object:IsAlive() then + AliveSet[#AliveSet+1] = Object + end + end + end + + return AliveSet or {} + end + end do @@ -1125,25 +1144,25 @@ do end - --- Get a *new* set that only contains alive groups. + --- Get a *new* set table that only contains alive groups. -- @param #SET_GROUP self - -- @return #SET_GROUP Set of alive groups. + -- @return #table Set of alive groups. function SET_GROUP:GetAliveSet() --self:F2() - local AliveSet = SET_GROUP:New() - + --local AliveSet = SET_GROUP:New() + local AliveSet = {} -- Clean the Set before returning with only the alive Groups. for GroupName, GroupObject in pairs( self.Set ) do local GroupObject = GroupObject -- Wrapper.Group#GROUP if GroupObject then if GroupObject:IsAlive() then - AliveSet:Add( GroupName, GroupObject ) + AliveSet[GroupName] = GroupObject end end end - return AliveSet.Set or {} + return AliveSet or {} end --- Returns a report of of unit types. @@ -2595,18 +2614,16 @@ do -- SET_UNIT --- Gets the alive set. -- @param #SET_UNIT self - -- @return #table Table of SET objects + -- @return #table Table of alive UNIT objects -- @return #SET_UNIT AliveSet function SET_UNIT:GetAliveSet() local AliveSet = SET_UNIT:New() -- Clean the Set before returning with only the alive Groups. - for GroupName, GroupObject in pairs(self.Set) do - local GroupObject=GroupObject --Wrapper.Client#CLIENT - + for GroupName, GroupObject in pairs(self.Set) do if GroupObject and GroupObject:IsAlive() then - AliveSet:Add(GroupName, GroupObject) + AliveSet[GroupName] = GroupObject end end @@ -4784,18 +4801,16 @@ do -- SET_CLIENT -- @return #table Table of SET objects function SET_CLIENT:GetAliveSet() - local AliveSet = SET_CLIENT:New() + local AliveSet = {} -- Clean the Set before returning with only the alive Groups. - for GroupName, GroupObject in pairs(self.Set) do - local GroupObject=GroupObject --Wrapper.Client#CLIENT - + for GroupName, GroupObject in pairs(self.Set) do if GroupObject and GroupObject:IsAlive() then - AliveSet:Add(GroupName, GroupObject) + AliveSet[GroupName] = GroupObject end end - return AliveSet.Set or {} + return AliveSet or {} end --- [User] Add a custom condition function. @@ -6676,6 +6691,8 @@ do -- SET_ZONE -- -- -- Stop watching after 1 hour -- zoneset:__TriggerStop(3600) + -- -- Call :SetPartlyInside() on any zone (or SET_ZONE) if you want GROUPs to count as inside when any of their units enters even if they are far apart. + -- -- Make sure to call :SetPartlyInside() before :Trigger()!. function SET_ZONE:Trigger(Objects) --self:I("Added Set_Zone Trigger") self:AddTransition("*","TriggerStart","TriggerRunning") @@ -6726,6 +6743,20 @@ do -- SET_ZONE -- @param Core.Zone#ZONE_BASE Zone The zone left. end + --- Toggle “partly-inside” handling for every zone in the set when those zones are used with :Trigger(). + -- * Call with no argument or **true** → enable for all. + -- * Call with **false** → disable again (handy if it was enabled before). + -- @param #SET_ZONE self + -- @return #SET_ZONE self + function SET_ZONE:SetPartlyInside(state) + for _,Zone in pairs(self.Set) do + if Zone.SetPartlyInside then + Zone:SetPartlyInside(state) + end + end + return self + end + --- (Internal) Check the assigned objects for being in/out of the zone -- @param #SET_ZONE self -- @param #boolean fromstart If true, do the init of the objects @@ -6761,8 +6792,13 @@ do -- SET_ZONE -- has not been tagged previously - wasn't in set! obj.TriggerInZone[_zone.ZoneName] = false end - -- is obj in zone? - local inzone = _zone:IsCoordinateInZone(obj:GetCoordinate()) + -- is obj in this zone? + local inzone + if _zone.PartlyInside and obj.ClassName == "GROUP" then + inzone = obj:IsAnyInZone(_zone) -- TRUE as soon as any unit is inside + else + inzone = _zone:IsCoordinateInZone(obj:GetCoordinate()) -- original centroid test + end --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) if inzone and not obj.TriggerInZone[_zone.ZoneName] then -- wasn't in zone before @@ -7829,6 +7865,28 @@ do -- SET_OPSGROUP return self end + --- Iterate the SET_OPSGROUP and count how many GROUPs and UNITs are alive. + -- @param #SET_GROUP self + -- @return #number The number of GROUPs alive. + -- @return #number The number of UNITs alive. + function SET_OPSGROUP:CountAlive() + local CountG = 0 + local CountU = 0 + + local Set = self:GetSet() + + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + if GroupData and GroupData:IsAlive() then + CountG = CountG + 1 + -- Count Units. + CountU = CountU + GroupData:GetGroup():CountAliveUnits() + end + + end + + return CountG, CountU + end + --- Finds an OPSGROUP based on the group name. -- @param #SET_OPSGROUP self -- @param #string GroupName Name of the group. diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index c81aea59d..f58937439 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1049,6 +1049,23 @@ function SPAWN:InitSetUnitAbsolutePositions(Positions) return self end + +--- Uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +-- @param #SPAWN self +-- @param #boolean OnOff Enable/disable the feature. +-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units. +-- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger. +-- @return #SPAWN +function SPAWN:InitValidateAndRepositionGroundUnits(OnOff, MaxRadius, Spacing) + self.SpawnValidateAndRepositionGroundUnits = OnOff + self.SpawnValidateAndRepositionGroundUnitsRadius = MaxRadius + self.SpawnValidateAndRepositionGroundUnitsSpacing = Spacing + return self +end + --- This method is rather complicated to understand. But I'll try to explain. -- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. @@ -1829,7 +1846,13 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if self.SpawnHiddenOnMap then SpawnTemplate.hidden=self.SpawnHiddenOnMap end - + + if self.SpawnValidateAndRepositionGroundUnits then + local units = SpawnTemplate.units + local gPos = { x = SpawnTemplate.x, y = SpawnTemplate.y } + UTILS.ValidateAndRepositionGroundUnits(units, gPos, self.SpawnValidateAndRepositionGroundUnitsRadius, self.SpawnValidateAndRepositionGroundUnitsSpacing) + end + -- Set country, coalition and category. SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index f603450d7..06a9179e1 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -149,6 +149,7 @@ function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) self.CategoryID = CategoryID self.CoalitionID = CoalitionID self.SpawnIndex = 0 + self.StaticCopyFrom = SpawnTemplateName else error( "SPAWNSTATIC:New: There is no static declared in the mission editor with SpawnTemplatePrefix = '" .. tostring(SpawnTemplateName) .. "'" ) end @@ -302,12 +303,16 @@ end -- @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. +-- @param #boolean DynamicSpawns If true, allow Dynamic Spawns +-- @param #boolean DynamicHotStarts If true, and DynamicSpawns is true, then allow Dynamic Spawns with hot starts. -- @return #SPAWNSTATIC self -function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation) +function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation, DynamicSpawns,DynamicHotStarts) self.InitFarp=true self.InitFarpCallsignID=CallsignID or 1 self.InitFarpFreq=Frequency or 127.5 self.InitFarpModu=Modulation or 0 + self.InitFarpDynamicSpawns = DynamicSpawns + self.InitFarpDynamicHotStarts = (DynamicSpawns == true and DynamicHotStarts == true) and true or nil return self end @@ -373,6 +378,20 @@ function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle) return self end +--- Uses Disposition and other fallback logic to find a better and valid ground spawn position. +--- NOTE: This is not a spawn randomizer. +--- It will try to a find clear ground location avoiding trees, water, roads, runways, map scenery, other statics and other units in the area. +--- Uses the initial position if it's a valid location. +-- @param #SPAWNSTATIC self +-- @param #boolean OnOff Enable/disable the feature. +-- @param #number MaxRadius (Optional) Max radius to search for a valid ground location in meters. Default is 10 times the max radius of the static. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitValidateAndRepositionStatic(OnOff, MaxRadius) + self.ValidateAndRepositionStatic = OnOff + self.ValidateAndRepositionStaticMaxRadius = MaxRadius + return self +end + --- Allows to place a CallFunction hook when a new static spawns. -- The provided method will be called when a new group is spawned, including its given parameters. -- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned. @@ -459,8 +478,9 @@ end function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName) -- Spawn the new static at the center of the zone. - local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName ) - + --local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName ) + local Static = self:SpawnFromCoordinate(Zone:GetCoordinate(), Heading, NewName) + return Static end @@ -538,6 +558,14 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- Add static to the game. local Static=nil --DCS#StaticObject + if self.ValidateAndRepositionStatic then + local validPos = UTILS.ValidateAndRepositionStatic(CountryID, Template.category, Template.type, Template, Template.shape_name, self.ValidateAndRepositionStaticMaxRadius) + if validPos then + Template.x = validPos.x + Template.y = validPos.y + end + end + if self.InitFarp then local TemplateGroup={} @@ -549,6 +577,13 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name + + if self.InitFarpDynamicSpawns == true then + TemplateGroup.units[1].dynamicSpawn = true + if self.InitFarpDynamicHotStarts == true then + TemplateGroup.units[1].allowHotStart = true + end + end self:T("Spawning FARP") self:T({Template=Template}) @@ -556,7 +591,8 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- ED's dirty way to spawn FARPS. Static=coalition.addGroup(CountryID, -1, TemplateGroup) - + --Static=coalition.addStaticObject(CountryID, Template) + -- 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 = { @@ -594,6 +630,18 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments)) end + + if self.StaticCopyFrom ~= nil then + mystatic.StaticCopyFrom = self.StaticCopyFrom + end + local TemplateGroup={} + TemplateGroup.units={} + TemplateGroup.units[1]=Template + TemplateGroup.x=Template.x + TemplateGroup.y=Template.y + TemplateGroup.name=Template.name + _DATABASE:_RegisterStaticTemplate( TemplateGroup, self.CoalitionID, self.CategoryID, CountryID ) + return mystatic end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index f4977e284..e434ea296 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -70,6 +70,7 @@ -- @field #table Table of any trigger zone properties from the ME. The key is the Name of the property, and the value is the property's Value. -- @field #number Surface Type of surface. Only determined at the center of the zone! -- @field #number Checktime Check every Checktime seconds, used for ZONE:Trigger() +-- @field #boolean PartlyInside When called, a GROUP is considered inside as soon as any of its units enters the zone even if they are far apart. -- @extends Core.Fsm#FSM @@ -548,6 +549,19 @@ function ZONE_BASE:GetZoneProbability() return self.ZoneProbability end +--- Get the coordinate on the radius of the zone nearest to Outsidecoordinate. Useto e.g. find an ingress point. +-- @param #ZONE_BASE self +-- @param Core.Point#COORDINATE Outsidecoordinate The coordinate outside of the zone from where to look. +-- @return Core.Point#COORDINATE CoordinateOnRadius +function ZONE_BASE:FindNearestCoordinateOnRadius(Outsidecoordinate) + local Vec1 = self:GetVec2() + local Radius = self:GetRadius() + local Vec2 = Outsidecoordinate:GetVec2() + local Point = UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) + local rc = COORDINATE:NewFromVec2(Point) + return rc +end + --- Get the zone taking into account the randomization probability of a zone to be selected. -- @param #ZONE_BASE self -- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. @@ -613,6 +627,8 @@ end -- -- -- Stop watching the zone after 1 hour -- triggerzone:__TriggerStop(3600) +-- -- Call :SetPartlyInside() if you use SET_GROUP to count as inside when any of their units enters even when they are far apart. +-- -- Make sure to call :SetPartlyInside() before :Trigger()! function ZONE_BASE:Trigger(Objects) --self:I("Added Zone Trigger") self:SetStartState("TriggerStopped") @@ -681,6 +697,16 @@ function ZONE_BASE:Trigger(Objects) end + --- Toggle “partly-inside” handling for this zone. To be used before :Trigger(). + -- * Default:* flag is **false** until you call the method. + -- * Call with no argument or with **true** → enable. + -- * Call with **false** → disable again (handy if it was enabled before). + -- @param #ZONE_BASE self + -- @return #ZONE_BASE self + function ZONE_BASE:SetPartlyInside(state) + self.PartlyInside = state or not ( state == false ) + return self + end --- (Internal) Check the assigned objects for being in/out of the zone -- @param #ZONE_BASE self -- @param #boolean fromstart If true, do the init of the objects @@ -719,7 +745,12 @@ function ZONE_BASE:_TriggerCheck(fromstart) obj.TriggerInZone[self.ZoneName] = false end -- is obj in zone? - local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) + local inzone + if self.PartlyInside and obj.ClassName == "GROUP" then + inzone = obj:IsAnyInZone(self) -- TRUE if any unit is inside + else + inzone = self:IsCoordinateInZone(obj:GetCoordinate()) -- original barycentre test + end --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) if inzone and obj.TriggerInZone[self.ZoneName] then -- just count @@ -1163,15 +1194,13 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local function EvaluateZone( ZoneObject ) --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 - if ZoneObject then + if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then -- Get object category. local ObjectCategory = Object.getCategory(ZoneObject) if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then - local CoalitionDCSUnit = ZoneObject:getCoalition() - local Include = false if not UnitCategories then -- Anything found is included. @@ -1523,6 +1552,26 @@ function ZONE_RADIUS:IsVec3InZone( Vec3 ) return InZone end +--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param #ZONE_RADIUS self +-- @param #number PosRadius Required clear radius around each position. +-- @param #number NumPositions Number of positions to find. +-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found. +function ZONE_RADIUS:GetClearZonePositions(PosRadius, NumPositions) + return UTILS.GetClearZonePositions(self, PosRadius, NumPositions) +end + + +--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param #ZONE_RADIUS self +-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200)) +-- @param #number NumPositions (Optional) Number of positions to find. (Default 50) +-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found. +-- @return #number Assigned radius for the found zones. nil if no clear positions are found. +function ZONE_RADIUS:GetRandomClearZoneCoordinate(PosRadius, NumPositions) + return UTILS.GetRandomClearZoneCoordinate(self, PosRadius, NumPositions) +end + --- Returns a random Vec2 location within the zone. -- @param #ZONE_RADIUS self -- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. @@ -1534,6 +1583,10 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) local Vec2 = self:GetVec2() local _inner = inner or 0 local _outer = outer or self:GetRadius() + + math.random() + math.random() + math.random() if surfacetypes and type(surfacetypes)~="table" then surfacetypes={surfacetypes} @@ -1895,6 +1948,21 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) return self end +--- Updates the current location from a @{Wrapper.Group}. +-- @param #ZONE_UNIT self +-- @param Wrapper.Group#GROUP Group (optional) Update from this Unit, if nil, update from the UNIT this zone is based on. +-- @return self +function ZONE_UNIT:UpdateFromUnit(Unit) + if Unit and Unit:IsAlive() then + local vec2 = Unit:GetVec2() + self.LastVec2 = vec2 + elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive() then + local ZoneVec2 = self.ZoneUNIT:GetVec2() + self.LastVec2 = ZoneVec2 + end + return self +end + --- Returns the current location of the @{Wrapper.Unit#UNIT}. -- @param #ZONE_UNIT self @@ -2032,6 +2100,22 @@ function ZONE_GROUP:GetVec2() return ZoneVec2 end +--- Updates the current location from a @{Wrapper.Group}. +-- @param #ZONE_GROUP self +-- @param Wrapper.Group#GROUP Group (optional) Update from this Group, if nil, update from the GROUP this zone is based on. +-- @return self +function ZONE_GROUP:UpdateFromGroup(Group) + if Group and Group:IsAlive() then + local vec2 = Group:GetVec2() + self.Vec2 = vec2 + elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive() then + local ZoneVec2 = self._.ZoneGROUP:GetVec2() + self.Vec2 = ZoneVec2 + self._.ZoneVec2Cache = ZoneVec2 + end + return self +end + --- Returns a random location within the zone of the @{Wrapper.Group}. -- @param #ZONE_GROUP self -- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. @@ -2501,6 +2585,26 @@ function ZONE_POLYGON_BASE:Flush() return self end +--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param #ZONE_POLYGON_BASE self +-- @param #number PosRadius Required clear radius around each position. +-- @param #number NumPositions Number of positions to find. +-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found. +function ZONE_POLYGON_BASE:GetClearZonePositions(PosRadius, NumPositions) + return UTILS.GetClearZonePositions(self, PosRadius, NumPositions) +end + + +--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param #ZONE_POLYGON_BASE self +-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200)) +-- @param #number NumPositions (Optional) Number of positions to find. (Default 50) +-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found. +-- @return #number Assigned radius for the found zones. nil if no clear positions are found. +function ZONE_POLYGON_BASE:GetRandomClearZoneCoordinate(PosRadius, NumPositions) + return UTILS.GetRandomClearZoneCoordinate(self, PosRadius, NumPositions) +end + --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self -- @param #boolean UnBound If true, the tyres will be destroyed. @@ -2879,6 +2983,11 @@ end function ZONE_POLYGON_BASE:GetRandomVec2() -- make sure we assign weights to the triangles based on their surface area, otherwise -- we'll be more likely to generate random points in smaller triangles + + math.random() + math.random() + math.random() + local weights = {} for _, triangle in pairs(self._Triangles) do weights[triangle] = triangle.SurfaceArea / self.SurfaceArea @@ -3218,12 +3327,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) local vectors = self:GetBoundingSquare() - local minVec3 = {x=vectors.x1, y=0, z=vectors.y1} - local maxVec3 = {x=vectors.x2, y=0, z=vectors.y2} - - local minmarkcoord = COORDINATE:NewFromVec3(minVec3) - local maxmarkcoord = COORDINATE:NewFromVec3(maxVec3) - local ZoneRadius = minmarkcoord:Get2DDistance(maxmarkcoord)/2 + local ZoneRadius = UTILS.VecDist2D({x=vectors.x1, y=vectors.y1}, {x=vectors.x2, y=vectors.y2})/2 -- self:I("Scan Radius:" ..ZoneRadius) local CenterVec3 = self:GetCoordinate():GetVec3() @@ -3247,14 +3351,12 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) local function EvaluateZone( ZoneObject ) - if ZoneObject then + if ZoneObject and self:IsVec3InZone(ZoneObject:getPoint()) then local ObjectCategory = Object.getCategory(ZoneObject) if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then - local CoalitionDCSUnit = ZoneObject:getCoalition() - local Include = false if not UnitCategories then -- Anything found is included. @@ -3286,7 +3388,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) end -- trying with box search - if ObjectCategory == Object.Category.SCENERY and self:IsVec3InZone(ZoneObject:getPoint()) then + if ObjectCategory == Object.Category.SCENERY then local SceneryType = ZoneObject:getTypeName() local SceneryName = ZoneObject:getName() self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 8e01b8a73..ca7ec0a95 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -198,7 +198,7 @@ end -- env do -- radio - ---@type radio + --@type radio -- @field #radio.modulation modulation --- diff --git a/Moose Development/Moose/Functional/AICSAR.lua b/Moose Development/Moose/Functional/AICSAR.lua index a05f47127..7ec2f73dc 100644 --- a/Moose Development/Moose/Functional/AICSAR.lua +++ b/Moose Development/Moose/Functional/AICSAR.lua @@ -22,7 +22,7 @@ -- === -- -- ### Author: **Applevangelist** --- Last Update Sept 2023 +-- Last Update July 2025 -- -- === -- @module Functional.AICSAR @@ -57,6 +57,8 @@ -- @field #number Speed Default speed setting for the helicopter FLIGHTGROUP is 100kn. -- @field #boolean UseEventEject In case Event LandingAfterEjection isn't working, use set this to true. -- @field #number Delay In case of UseEventEject wait this long until we spawn a landed pilot. +-- @field #boolean UseRescueZone If true, use a rescue zone and not the max distance to FARP/MASH +-- @field Core.Zone#ZONE_RADIUS RescueZone Use this zone as operational area for the AICSAR instance. -- @extends Core.Fsm#FSM @@ -153,10 +155,10 @@ -- To set up AICSAR for SRS TTS output, add e.g. the following to your script: -- -- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required) --- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\google.json") +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\google.json") -- -- -- alternatively for MS Desktop TTS (voices need to be installed locally first!) --- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female") +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female") -- -- -- define a different voice for the downed pilot(s) -- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male") @@ -177,7 +179,7 @@ -- -- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so: -- --- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,nil,5002) +-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio",270,radio.modulation.AM,nil,5002) -- -- or -- @@ -191,7 +193,7 @@ -- @field #AICSAR AICSAR = { ClassName = "AICSAR", - version = "0.1.16", + version = "0.1.18", lid = "", coalition = coalition.side.BLUE, template = "", @@ -236,6 +238,8 @@ AICSAR = { Altitude = 1500, UseEventEject = false, Delay = 100, + UseRescueZone = false, + RescueZone = nil, } -- TODO Messages @@ -304,8 +308,9 @@ AICSAR.RadioLength = { -- @param #string Helotemplate Helicopter template name. -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start. -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue. +-- @param #number Helonumber Max number of alive Ai Helos at the same time. Defaults to three. -- @return #AICSAR self -function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) +function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone,Helonumber) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) @@ -373,7 +378,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- limit number of available helos at the same time self.limithelos = true - self.helonumber = 3 + self.helonumber = Helonumber or 3 -- localization self:InitLocalization() @@ -524,10 +529,20 @@ function AICSAR:InitLocalization() return self end +--- [User] Use a defined zone as area of operation and not the distance to FARP. +-- @param #AICSAR self +-- @param Core.Zone#ZONE Zone The operational zone to use. Downed pilots in this area will be rescued. Can be any known #ZONE type. +-- @return #AICSAR self +function AICSAR:SetUsingRescueZone(Zone) + self.UseRescueZone = true + self.RescueZone = Zone + return self +end + --- [User] Switch sound output on and use SRS output for sound files. -- @param #AICSAR self -- @param #boolean OnOff Switch on (true) or off (false). --- @param #string Path Path to your SRS Server Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" +-- @param #string Path Path to your SRS Server External Audio Component, e.g. "C:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\\\ExternalAudio" -- @param #number Frequency Defaults to 243 (guard) -- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM -- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor. @@ -538,7 +553,7 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) self.SRSRadio = OnOff and true self.SRSTTSRadio = false self.SRSFrequency = Frequency or 243 - self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRS:SetLabel("ACSR") self.SRS:SetCoalition(self.coalition) self.SRSModulation = Modulation or radio.modulation.AM @@ -556,7 +571,7 @@ end -- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()` -- @param #AICSAR self -- @param #boolean OnOff Switch on (true) or off (false). --- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone" +-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #number Frequency (Optional) Defaults to 243 (guard) -- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM -- @param #number Port (Optional) Port of the SRS, defaults to 5002. @@ -570,7 +585,7 @@ function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Cultur self.SRSTTSRadio = OnOff and true self.SRSRadio = false self.SRSFrequency = Frequency or 243 - self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRSModulation = Modulation or radio.modulation.AM self.SRSPort = Port or MSRS.port or 5002 if OnOff then @@ -693,7 +708,7 @@ function AICSAR:_EjectEventHandler(EventData) local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) local _country = _event.initiator:getCountry() local _coalition = coalition.getCountryCoalition( _country ) - local data = UTILS.DeepCopy(EventData) + --local data = UTILS.DeepCopy(EventData) Unit.destroy(_event.initiator) -- shagrat remove static Pilot model self:ScheduleOnce(self.Delay,self._DelayedSpawnPilot,self,_LandingPos,_coalition) end @@ -708,7 +723,14 @@ end -- @return #AICSAR self function AICSAR:_DelayedSpawnPilot(_LandingPos,_coalition) - local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) + local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) + if self.UseRescueZone == true and self.RescueZone ~= nil then + if self.RescueZone:IsCoordinateInZone(_LandingPos) then + distancetofarp = self.maxdistance - 10 + else + distancetofarp = self.maxdistance + 10 + end + end -- Mayday Message local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local text = "" @@ -795,7 +817,13 @@ function AICSAR:_EventHandler(EventData, FromEject) -- DONE: add distance check local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate()) - + if self.UseRescueZone == true and self.RescueZone ~= nil then + if self.RescueZone:IsCoordinateInZone(_LandingPos) then + distancetofarp = self.maxdistance - 10 + else + distancetofarp = self.maxdistance + 10 + end + end -- Mayday Message local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local text = "" @@ -817,7 +845,6 @@ function AICSAR:_EventHandler(EventData, FromEject) if _coalition == self.coalition then if self.verbose then MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) - -- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog() end if self.SRSRadio then local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) @@ -869,6 +896,7 @@ function AICSAR:_GetFlight() :InitUnControlled(true) :OnSpawnGroup( function(Group) + Group:OptionPreferVerticalLanding() self:__HeloOnDuty(1,Group) end ) @@ -892,7 +920,7 @@ function AICSAR:_InitMission(Pilot,Index) --local pilotset = SET_GROUP:New() --pilotset:AddGroup(Pilot) - -- Cargo transport assignment. + -- Cargo transport assignment. local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone) --opstransport:SetVerbosity(3) @@ -934,6 +962,10 @@ function AICSAR:_InitMission(Pilot,Index) helo:__UnloadingDone(5) end + function helo:OnAfterLandAtAirbase(From,Event,To,airbase) + helo:Despawn(2) + end + self.helos[Index] = helo return self @@ -984,7 +1016,9 @@ function AICSAR:_CheckHelos() local name = helo:GetName() self:T("Helo group "..name.." in state "..state) if state == "Arrived" then - helo:__Stop(5) + --helo:__Stop(5) + helo.OnAfterDead = nil + helo:Despawn(35) self.helos[_index] = nil end else @@ -1025,7 +1059,7 @@ function AICSAR:_CheckQueue(OpsGroup) if self:_CheckInMashZone(_pilot) then self:T("Pilot" .. _pilot.GroupName .. " rescued!") if OpsGroup then - OpsGroup:Despawn(10) + --OpsGroup:Despawn(10) else _pilot:Destroy(true,10) end diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 9fed56cfc..1772e24a3 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -105,7 +105,7 @@ AUTOLASE = { debug = false, smokemenu = true, RoundingPrecision = 0, - increasegroundawareness = true, + increasegroundawareness = false, MonitorFrequency = 30, } @@ -216,7 +216,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.smokemenu = true self.threatmenu = true self.RoundingPrecision = 0 - self.increasegroundawareness = true + self.increasegroundawareness = false self.MonitorFrequency = 30 self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) @@ -493,7 +493,7 @@ end --- (User) Function enable sending messages via SRS. -- @param #AUTOLASE self -- @param #boolean OnOff Switch usage on and off --- @param #string Path Path to SRS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone +-- @param #string Path Path to SRS TTS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio -- @param #number Frequency Frequency to send, e.g. 243 -- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM -- @param #string Label (Optional) Short label to be used on the SRS Client Overlay @@ -508,7 +508,7 @@ end function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation,Label,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) if OnOff then self.useSRS = true - self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSPath = Path or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.SRSFreq = Frequency or 271 self.SRSMod = Modulation or radio.modulation.AM self.Gender = Gender or MSRS.gender or "male" @@ -1020,7 +1020,7 @@ function AUTOLASE:_Prescient() self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) for _,_static in pairs(Statics) do -- DCS static object here local static = STATIC:Find(_static) - if static and static:GetCoalition() ~= self.coalition then + if static and static:GetCoalition() ~= self.coalition and static:GetCoordinate() then local IsLOS = position:IsLOS(static:GetCoordinate()) if IsLOS then unit:KnowUnit(static,true,true) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 0ebe76f4a..2e1ff3b18 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: Mar 2025 +-- Last Update: August 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -62,7 +62,9 @@ -- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. -- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. -- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White --- @field #number checkcounter Counter for SAM Table refreshes +-- @field #number checkcounter Counter for SAM Table refreshes. +-- @field #number DLinkCacheTime Seconds after which cached contacts in DLink will decay. +-- @field #boolean logsamstatus Log SAM status in dcs.log every cycle if true -- @extends Core.Base#BASE @@ -74,10 +76,9 @@ -- -- * Moose derived Modular, Automatic and Network capable Targeting and Interception System. -- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy. --- * **Automatic mode** (default since 0.8) will set-up your SAM site network automatically for you --- * **Classic mode** behaves like before --- * Leverage evasiveness from SEAD, leverage attack range setting --- * Automatic setup of SHORAD based on groups of the class "short-range" +-- * **Automatic mode** (default) will set-up your SAM site network automatically for you. +-- * Leverage evasiveness from SEAD, leverage attack range setting. +-- * Automatic setup of SHORAD based on groups of the class "short-range". -- -- # 0. Base considerations and naming conventions -- @@ -107,10 +108,15 @@ -- * Patriot -- * Rapier -- * Roland +-- * IRIS-T SLM +-- * Pantsir S1 +-- * TOR M2 +-- * C-RAM -- * Silkworm (though strictly speaking this is a surface to ship missile) -- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19 -- * From IDF mod: STUNNER IDFA, TAMIR IDFA (Note all caps!) --- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2 +-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2, SAMP/T Block 1, SAMP/T Block 1INT, SAMP/T Block2 +-- * Other Mods: Nike -- -- * From SMA: RBS98M, RBS70, RBS90, RBS90M, RBS103A, RBS103B, RBS103AM, RBS103BM, Lvkv9040M -- **NOTE** If you are using the Swedish Military Assets (SMA), please note that the **group name** for RBS-SAM types also needs to contain the keyword "SMA" @@ -124,19 +130,20 @@ -- * SA-2 (with V759 missile, e.g. "Red SAM SA-2 HDS") -- * SA-2 (with HQ-2 launcher, use HQ-2 in the group name, e.g. "Red SAM HQ-2" ) -- * SA-3 (with V601P missile, e.g. "Red SAM SA-3 HDS") --- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS") --- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS") --- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS") --- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS") +-- * SA-10B (overlap with other SA-10 types, e.g. "Red SAM SA-10B HDS" with 5P85CE launcher) +-- * SA-10C (overlap with other SA-10 types, e.g. "Red SAM SA-10C HDS" with 5P85SE launcher) +-- * SA-12 (launcher dependent range, e.g. "Red SAM SA-12 HDS 2" for the 9A82 variant and "Red SAM SA-12 HDS 1" for the 9A83 variant) +-- * SA-23 (launcher dependent range, e.g. "Red SAM SA-23 HDS 2" for the 9A82ME variant and "Red SAM SA-23 HDS 1" for the 9A83ME variant) +-- * SAMP/T (launcher dependent range, e.g. "Blue SAM SAMPT Block 1 HDS" for Block 1, "Blue SAM SAMPT Block 1INT HDS", "Blue SAM SAMPT Block 2 HDS") -- -- The other HDS types work like the rest of the known SAM systems. -- -- # 0.1 Set-up in the mission editor -- --- Set up your SAM sites in the mission editor. Name the groups using a systematic approach like above. --- Set up your EWR system in the mission editor. Name the groups using a systematic approach like above. Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. +-- Set up your SAM sites in the mission editor. Name the groups using a systematic approach like above.Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. -- Search Radars usually have "SR" or "STR" in their names. Use the encyclopedia in the mission editor to inform yourself. --- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one** group per SAM location. SA-15 TOR systems offer a good missile defense. +-- Set up your SHORAD systems. They need to be **close** to (i.e. around) the SAM sites to be effective. Use **one unit ** per group (multiple groups) for the SAM location. +-- Else, evasive manoevers might club up all defenders in one place. Red SA-15 TOR systems offer a good missile defense. -- -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. -- @@ -188,7 +195,7 @@ -- -- ## 2.1 Auto mode features -- --- ### 2.1.1 You can now add Accept-, Reject- and Conflict-Zones to your setup, e.g. to consider borders or de-militarized zones: +-- ### 2.1.1 You can add Accept-, Reject- and Conflict-Zones to your setup, e.g. to consider borders or de-militarized zones: -- -- -- Parameters are tables of Core.Zone#ZONE objects! -- -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when @@ -205,9 +212,6 @@ -- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "point" or if the range is less than 5km or if the type is AAA. -- -- ### 2.1.4 Advanced features --- --- -- Option to switch off auto mode **before** you start MANTIS (not recommended) --- mybluemantis.automode = false -- -- -- Option to set the scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. -- -- also see engagerange below. @@ -220,6 +224,12 @@ -- -- -- For some scenarios, like Cold War, it might be useful not to activate SAMs if friendly aircraft are around to avoid death by friendly fire. -- mybluemantis.checkforfriendlies = true +-- +-- ### 2.1.6 Shoot & Scoot +-- +-- -- Option to make the (driveable) SHORAD units drive around and shuffle positions +-- -- We use a SET_ZONE for that, number of zones to consider defaults to three, Random is true for random coordinates and Formation is e.g. "Vee". +-- mybluemantis:AddScootZones(ZoneSet, Number, Random, Formation) -- -- # 3. Default settings [both modes unless stated otherwise] -- @@ -242,26 +252,8 @@ -- E.g. mymantis:SetAdvancedMode( true, 90 ) -- -- Use this option if you want to make use of or allow advanced SEAD tactics. --- --- # 5. Integrate SHORAD [classic mode, not necessary in automode, not recommended for manual setup] --- --- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs manually. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in --- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so: --- --- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() --- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue") --- -- now set up MANTIS --- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") --- mymantis:AddShorad(myshorad,720) --- mymantis:Start() -- --- If you systematically name your SHORAD groups starting with "Blue SHORAD" you'll need exactly **one** SHORAD instance to manage all SHORAD groups. --- --- (Optionally) you can remove the link later on with --- --- mymantis:RemoveShorad() --- --- # 6. Integrated SEAD +-- # 5. Integrated SEAD -- -- MANTIS is using @{Functional.Sead#SEAD} internally to both detect and evade HARM attacks. No extra efforts needed to set this up! -- Once a HARM attack is detected, MANTIS (via SEAD) will shut down the radars of the attacked SAM site and take evasive action by moving the SAM @@ -288,6 +280,7 @@ MANTIS = { ClassName = "MANTIS", name = "mymantis", + version = "0.9.34", SAM_Templates_Prefix = "", SAM_Group = nil, EWR_Templates_Prefix = "", @@ -336,6 +329,8 @@ MANTIS = { SmokeDecoy = false, SmokeDecoyColor = SMOKECOLOR.White, checkcounter = 1, + DLinkCacheTime = 120, + logsamstatus = false, } --- Advanced state enumerator @@ -374,7 +369,7 @@ MANTIS.radiusscale[MANTIS.SamType.POINT] = 3 MANTIS.SamData = { ["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B - ["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot" }, + ["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot str" }, ["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" }, ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" }, ["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" }, @@ -382,7 +377,8 @@ MANTIS.SamData = { ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" }, ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"}, ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, - ["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Point", Radar="Roland" }, + ["Roland"] = { Range=6, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, + ["Gepard"] = { Range=5, Blindspot=0, Height=4, Type="Point", Radar="Gepard" }, ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" }, @@ -393,14 +389,21 @@ MANTIS.SamData = { ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, + ["C-RAM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="HEMTT_C-RAM_Phalanx", Point="true" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, - ["SA-17"] = { Range=50, Blindspot=3, Height=30, Type="Medium", Radar="SA-17" }, + ["SA-17"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17" }, ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, - ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, + ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, + ["NIKE"] = { Range=155, Blindspot=6, Height=30, Type="Long", Radar="HIPAR" }, + ["Dog Ear"] = { Range=11, Blindspot=0, Height=9, Type="Point", Radar="Dog Ear", Point="true" }, + -- CH Added to DCS core 2.9.19.x + ["Pantsir S1"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1" , Point="true" }, + ["Tor M2"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, + ["IRIS-T SLM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, } --- SAM data HDS @@ -416,13 +419,17 @@ MANTIS.SamDataHDS = { -- group name MUST contain HDS to ID launcher type correctly! ["SA-2 HDS"] = { Range=56, Blindspot=7, Height=30, Type="Medium", Radar="V759" }, ["SA-3 HDS"] = { Range=20, Blindspot=6, Height=30, Type="Short", Radar="V-601P" }, - ["SA-10C HDS 2"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85DE ln"}, -- V55RUD - ["SA-10C HDS 1"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD - ["SA-12 HDS 2"] = { Range=100, Blindspot=10, Height=25, Type="Long" , Radar="S-300V 9A82 l"}, - ["SA-12 HDS 1"] = { Range=75, Blindspot=1, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, + ["SA-10B HDS"] = { Range=90, Blindspot=5, Height=25, Type="Long" , Radar="5P85CE ln"}, -- V55RUD + ["SA-10C HDS"] = { Range=75, Blindspot=5, Height=25, Type="Long" , Radar="5P85SE ln"}, -- V55RUD + ["SA-17 HDS"] = { Range=50, Blindspot=3, Height=50, Type="Medium", Radar="SA-17 " }, + ["SA-12 HDS 2"] = { Range=100, Blindspot=13, Height=30, Type="Long" , Radar="S-300V 9A82 l"}, + ["SA-12 HDS 1"] = { Range=75, Blindspot=6, Height=25, Type="Long" , Radar="S-300V 9A83 l"}, ["SA-23 HDS 2"] = { Range=200, Blindspot=5, Height=37, Type="Long", Radar="S-300VM 9A82ME" }, ["SA-23 HDS 1"] = { Range=100, Blindspot=1, Height=50, Type="Long", Radar="S-300VM 9A83ME" }, ["HQ-2 HDS"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, + ["SAMPT Block 1 HDS"] = { Range=120, Blindspot=1, Height=20, Type="long", Radar="SAMPT_MLT_Blk1" }, -- Block 1 Launcher + ["SAMPT Block 1INT HDS"] = { Range=150, Blindspot=1, Height=25, Type="long", Radar="SAMPT_MLT_Blk1NT" }, -- Block 1-INT Launcher + ["SAMPT Block 2 HDS"] = { Range=200, Blindspot=10, Height=70, Type="long", Radar="SAMPT_MLT_Blk2" }, -- Block 2 Launcher } --- SAM data SMA @@ -462,15 +469,15 @@ MANTIS.SamDataCH = { -- https://www.currenthill.com/ -- group name MUST contain CHM to ID launcher type correctly! ["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" }, - ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, + ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Point", Radar="PantsirS1", Point="true" }, ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, ["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" }, ["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, ["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, ["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, - ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, - ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" }, - ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, + ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2", Point="true" }, + ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Point", Radar="TorM2K", Point="true" }, + ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Point", Radar="TorM2M", Point="true" }, ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, @@ -625,7 +632,8 @@ do self.advAwacs = false end - + self:SetDLinkCacheTime() + -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -658,6 +666,8 @@ do table.insert(self.ewr_templates,awacs) end + self.logsamstatus = false + self:T({self.ewr_templates}) self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition) @@ -687,9 +697,6 @@ do -- counter for SAM table updates self.checkcounter = 1 - -- TODO Version - -- @field #string version - self.version="0.9.27" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -886,7 +893,11 @@ do self.AcceptZones = AcceptZones or {} self.RejectZones = RejectZones or {} self.ConflictZones = ConflictZones or {} - if #self.AcceptZones > 0 or #self.RejectZones > 0 or #self.ConflictZones > 0 then + self.AcceptZonesNo = UTILS.TableLength(self.AcceptZones) + self.RejectZonesNo = UTILS.TableLength(self.RejectZones) + self.ConflictZonesNo = UTILS.TableLength(self.ConflictZones) + self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) + if self.AcceptZonesNo > 0 or self.RejectZonesNo > 0 or self.ConflictZonesNo > 0 then self.usezones = true end return self @@ -1039,6 +1050,16 @@ do end return self end + + --- Function to set how long INTEL DLINK remembers contacts. + -- @param #MANTIS self + -- @param #number seconds Remember this many seconds, at least 5 seconds. + -- @return #MANTIS self + function MANTIS:SetDLinkCacheTime(seconds) + self.DLinkCacheTime = math.abs(seconds or 120) + if self.DLinkCacheTime < 5 then self.DLinkCacheTime = 5 end + return self + end --- Function to set the detection interval -- @param #MANTIS self @@ -1268,7 +1289,8 @@ do self:T(self.lid.."_CheckCoordinateInZones") local inzone = false -- acceptzones - if #self.AcceptZones > 0 then + self:T(string.format("AcceptZonesNo = %d | RejectZonesNo = %d | ConflictZonesNo = %d",self.AcceptZonesNo,self.RejectZonesNo,self.ConflictZonesNo)) + if self.AcceptZonesNo > 0 then for _,_zone in pairs(self.AcceptZones) do local zone = _zone -- Core.Zone#ZONE if zone:IsCoordinateInZone(coord) then @@ -1279,7 +1301,7 @@ do end end -- rejectzones - if #self.RejectZones > 0 and inzone then -- maybe in accept zone, but check the overlaps + if self.RejectZonesNo > 0 then for _,_zone in pairs(self.RejectZones) do local zone = _zone -- Core.Zone#ZONE if zone:IsCoordinateInZone(coord) then @@ -1290,7 +1312,7 @@ do end end -- conflictzones - if #self.ConflictZones > 0 and not inzone then -- if not already accepted, might be in conflict zones + if self.ConflictZonesNo > 0 then for _,_zone in pairs(self.ConflictZones) do local zone = _zone -- Core.Zone#ZONE if zone:IsCoordinateInZone(coord) then @@ -1356,6 +1378,7 @@ do end -- check accept/reject zones local zonecheck = true + self:T("self.usezones = "..tostring(self.usezones)) if self.usezones then -- DONE zonecheck = self:_CheckCoordinateInZones(coord) @@ -1431,7 +1454,9 @@ do --IntelTwo:SetClusterRadius(5000) IntelTwo:Start() - local IntelDlink = INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,300) + local CacheTime = self.DLinkCacheTime or 120 + local IntelDlink = INTEL_DLINK:New({IntelOne,IntelTwo},self.name.." DLINK",22,CacheTime) + IntelDlink:__Start(1) self:SetUsingDLink(IntelDlink) @@ -1493,7 +1518,7 @@ do elseif chm then SAMData = self.SamDataCH end - --self:T("Looking to auto-match for "..grpname) + --self:I("Looking to auto-match for "..grpname) for _,_unit in pairs(units) do local unit = _unit -- Wrapper.Unit#UNIT local type = string.lower(unit:GetTypeName()) @@ -1694,7 +1719,9 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) - local radaralive = group:IsSAM() + -- TODO the below might stop working at some point after some hours, needs testing + --local radaralive = group:IsSAM() + local radaralive = true table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG and radaralive then @@ -1791,7 +1818,7 @@ do if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then activeshorad = true end - if IsInZone and not suppressed and not activeshorad then --check any target in zone and not currently managed by SEAD + if IsInZone and (not suppressed) and (not activeshorad) then --check any target in zone and not currently managed by SEAD if samgroup:IsAlive() then -- switch on SAM local switch = false @@ -1823,7 +1850,7 @@ do -- link in to SHORAD if available -- DONE: Test integration fully if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early - local Shorad = self.Shorad + local Shorad = self.Shorad --Functional.Shorad#SHORAD local radius = self.checkradius local ontime = self.ShoradTime Shorad:WakeUpShorad(name, radius, ontime) @@ -1856,7 +1883,7 @@ do end --end alive end --end check end --for loop - if self.debug or self.verbose then + if self.debug or self.verbose or self.logsamstatus then for _,_status in pairs(self.SamStateTracker) do if _status == "GREEN" then instatusgreen=instatusgreen+1 @@ -1877,8 +1904,9 @@ do -- @param #MANTIS self -- @param Functional.Detection#DETECTION_AREAS detection Detection object -- @param #boolean dlink + -- @param #boolean reporttolog -- @return #MANTIS self - function MANTIS:_Check(detection,dlink) + function MANTIS:_Check(detection,dlink,reporttolog) self:T(self.lid .. "Check") --get detected set local detset = detection:GetDetectedItemCoordinates() @@ -1905,7 +1933,8 @@ do local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) end - if self.debug or self.verbose then + + local function GetReport() local statusreport = REPORT:New("\nMANTIS Status "..self.name) statusreport:Add("+-----------------------------+") statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) @@ -1914,7 +1943,15 @@ do statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads)) end statusreport:Add("+-----------------------------+") + return statusreport + end + + if self.debug or self.verbose then + local statusreport = GetReport() MESSAGE:New(statusreport:Text(),10):ToAll():ToLog() + elseif reporttolog == true then + local statusreport = GetReport() + MESSAGE:New(statusreport:Text(),10):ToLog() end return self end @@ -2022,7 +2059,7 @@ do self:T({From, Event, To}) -- check detection if not self.state2flag then - self:_Check(self.Detection,self.DLink) + self:_Check(self.Detection,self.DLink,self.logsamstatus) end local EWRAlive = self:_CheckAnyEWRAlive() @@ -2093,7 +2130,7 @@ do if self.debug and self.verbose then self:I(self.lid .. "Status Report") for _name,_state in pairs(self.SamStateTracker) do - self:I(string.format("Site %s\tStatus %s",_name,_state)) + self:I(string.format("Site %s | Status %s",_name,_state)) end end local interval = self.detectinterval * -1 diff --git a/Moose Development/Moose/Functional/MissileTrainer.lua b/Moose Development/Moose/Functional/MissileTrainer.lua index f52363ba2..42626071c 100644 --- a/Moose Development/Moose/Functional/MissileTrainer.lua +++ b/Moose Development/Moose/Functional/MissileTrainer.lua @@ -53,6 +53,8 @@ -- -- # Developer Note -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE. -- Therefore, this class is considered to be deprecated and superseded by the [Functional.Fox](https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Functional.Fox.html) class, which provides the same functionality. -- diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 62b85e828..6c0143823 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -603,7 +603,7 @@ RANGE.MenuF10Root = nil --- Range script version. -- @field #string version -RANGE.version = "2.8.0" +RANGE.version = "2.8.1" -- TODO list: -- TODO: Verbosity level for messages. @@ -2032,10 +2032,10 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV -- Smoke impact point of bomb. if playerData and playerData.smokebombimpact and insidezone then - if playerData and playerData.delaysmoke then - timer.scheduleFunction( self._DelayedSmoke, { coord = impactcoord, color = playerData.smokecolor }, timer.getTime() + self.TdelaySmoke ) + if playerData.delaysmoke then + impactcoord:Smoke(playerData.smokecolor, 30, self.TdelaySmoke) else - impactcoord:Smoke( playerData.smokecolor ) + impactcoord:Smoke(playerData.smokecolor, 30) end end @@ -2102,7 +2102,12 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV result.attackHdg = attackHdg result.attackVel = attackVel result.attackAlt = attackAlt - result.date=os and os.date() or "n/a" + if os and os.date then + result.date=os.date() + else + self:E(self.lid.."os or os.date() not available") + result.date = "n/a" + end -- Add to table. table.insert( _results, result ) @@ -2635,13 +2640,6 @@ end -- Display Messages ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Start smoking a coordinate with a delay. --- @param #table _args Argements passed. -function RANGE._DelayedSmoke( _args ) - _args.coord:Smoke(_args.color) - --trigger.action.smoke( _args.coord:GetVec3(), _args.color ) -end - --- Display top 10 stafing results of a specific player. -- @param #RANGE self -- @param #string _unitName Name of the player unit. diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index c4a0c8e20..2c49243f8 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -321,7 +321,9 @@ function SCORING:New( GameName, SavePath, AutoSave ) -- Create the CSV file. self.AutoSavePath = SavePath self.AutoSave = AutoSave or true - self:OpenCSV( GameName ) + if self.AutoSave == true then + self:OpenCSV( GameName ) + end return self @@ -985,6 +987,7 @@ function SCORING:_EventOnHit( Event ) local TargetUnitCoalition = nil local TargetUnitCategory = nil local TargetUnitType = nil + local TargetIsScenery = false if Event.IniDCSUnit then @@ -1025,6 +1028,12 @@ function SCORING:_EventOnHit( Event ) TargetCategory = Event.TgtCategory TargetType = Event.TgtTypeName + -- Scenery hit + if (not TargetCategory) and TargetUNIT ~= nil and TargetUnit:IsInstanceOf("SCENERY") then + TargetCategory = Unit.Category.STRUCTURE + TargetIsScenery = true + end + TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] TargetUnitCategory = _SCORINGCategory[TargetCategory] TargetUnitType = TargetType @@ -1117,17 +1126,22 @@ function SCORING:_EventOnHit( Event ) MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - else + elseif TargetIsScenery ~= true then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) + elseif TargetIsScenery == true then + MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object." .. " Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, + MESSAGE.Type.Update ) + :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) + :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) end self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) end else -- A scenery object was hit. - MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit scenery object.", + MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit nothing special.", MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) @@ -1923,7 +1937,7 @@ function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, TargetUnitType = TargetUnitType or "" TargetUnitName = TargetUnitName or "" - if lfs and io and os and self.AutoSave then + if lfs and io and os and self.AutoSave == true and self.CSVFile ~= nil then self.CSVFile:write( '"' .. self.GameName .. '"' .. ',' .. '"' .. self.RunTime .. '"' .. ',' .. diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index 74c2b930e..9d5168748 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -1,521 +1,600 @@ ---- **Functional** - TIRESIAS - manages AI behaviour. --- --- === --- --- The @{#TIRESIAS} class is working in the back to keep your large-scale ground units in check. --- --- ## Features: --- --- * Designed to keep CPU and Network usage lower on missions with a lot of ground units. --- * Does not affect ships to keep the Navy guys happy. --- * Does not affect OpsGroup type groups. --- * Distinguishes between SAM groups, AAA groups and other ground groups. --- * Exceptions can be defined to keep certain actions going. --- * Works coalition-independent in the back --- * Easy setup. --- --- === --- --- ## Missions: --- --- ### [TIRESIAS](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master) --- --- === --- --- ### Author : **applevangelist ** --- --- @module Functional.Tiresias --- @image Functional.Tiresias.jpg --- --- Last Update: Dec 2023 +----- **Functional** - TIRESIAS - manages AI behaviour (OPTIMIZED VERSION). -------------------------------------------------------------------------- ---- **TIRESIAS** class, extends Core.Base#BASE --- @type TIRESIAS --- @field #string ClassName --- @field #booelan debug --- @field #string version --- @field #number Interval --- @field Core.Set#SET_GROUP GroundSet --- @field #number Coalition --- @field Core.Set#SET_GROUP VehicleSet --- @field Core.Set#SET_GROUP AAASet --- @field Core.Set#SET_GROUP SAMSet --- @field Core.Set#SET_GROUP ExceptionSet --- @field Core.Set#SET_OPSGROUP OpsGroupSet --- @field #number AAARange --- @field #number HeloSwitchRange --- @field #number PlaneSwitchRange --- @field Core.Set#SET_GROUP FlightSet --- @field #boolean SwitchAAA --- @extends Core.Fsm#FSM +---- === + +--- The @{#TIRESIAS} class is working in the back to keep your large-scale ground units in check. +-- +-- -- Features: +-- +-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units. +-- * Does not affect ships to keep the Navy guys happy. +-- * Does not affect OpsGroup type groups. +-- * Distinguishes between SAM groups, AAA groups and other ground groups. +-- * Exceptions can be defined to keep certain actions going. +-- * Works coalition-independent in the back +-- * Easy setup. +-- +-- === +-- +-- ## Optimizations Applied: +-- +-- * Cached frequently used functions and constants +-- * Reduced string concatenations and formatting +-- * Optimized loop structures and conditions +-- * Pre-allocated tables where possible +-- * Reduced function call overhead +-- * Improved memory management +-- +---- === +-- +---- #-- Author : **applevangelist ** (Optimized by AI) --- --- @type TIRESIAS.Data --- @field #string type --- @field #number range --- @field #boolean invisible --- @field #boolean AIOff --- @field #boolean exception +-- @module Functional.Tiresias +-- @image Functional.Tiresias.jpg +--- Last Update: Oct 2025 ---- *Tiresias, Greek demi-god and shapeshifter, blinded by the Gods, works as oracle for you.* (Wiki) +--- **TIRESIAS** class, extends Core.Base#BASE +-- @type TIRESIAS +-- @field #string ClassName +-- @field #boolean debug +-- @field #string version +-- @field #number Interval +-- @field Core.Set#SET_GROUP GroundSet +-- @field #number Coalition +-- @field Core.Set#SET_GROUP VehicleSet +-- @field Core.Set#SET_GROUP AAASet +-- @field Core.Set#SET_GROUP SAMSet +-- @field Core.Set#SET_GROUP ExceptionSet +-- @field Core.Set#SET_OPSGROUP OpsGroupSet +-- @field #number AAARange +-- @field #number HeloSwitchRange +-- @field #number PlaneSwitchRange +-- @field Core.Set#SET_GROUP FlightSet +-- @field #boolean SwitchAAA +-- @field #string lid +-- @field #table _cached_zones +-- @field #table _cached_groupsets +-- @extends Core.Fsm#FSM + +--- +-- @type TIRESIAS.Data +-- @field #string type +-- @field #number range +-- @field #boolean invisible +-- @field #boolean AIOff +-- @field #boolean exception + +--- +-- *Tiresias, Greek demi-god and shapeshifter, blinded by the Gods, works as oracle for you.* (Wiki) -- --- === +-- === -- --- ## TIRESIAS Concept --- --- * Designed to keep CPU and Network usage lower on missions with a lot of ground units. --- * Does not affect ships to keep the Navy guys happy. --- * Does not affect OpsGroup type groups. --- * Distinguishes between SAM groups, AAA groups and other ground groups. --- * Exceptions can be defined in SET_GROUP objects to keep certain actions going. --- * Works coalition-independent in the back --- * Easy setup. --- --- ## Setup --- --- Setup is a one-liner: --- --- local blinder = TIRESIAS:New() --- --- Optionally you can set up exceptions, e.g. for convoys driving around --- --- local exceptionset = SET_GROUP:New():FilterCoalitions("red"):FilterPrefixes("Convoy"):FilterStart() --- local blinder = TIRESIAS:New() --- blinder:AddExceptionSet(exceptionset) --- --- Options --- --- -- Setup different radius for activation around helo and airplane groups (applies to AI and humans) --- blinder:SetActivationRanges(10,25) -- defaults are 10, and 25 +-- ## TIRESIAS Concept -- --- -- Setup engagement ranges for AAA (non-advanced SAM units like Flaks etc) and if you want them to be AIOff --- blinder:SetAAARanges(60,true) -- defaults are 60, and true +-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units. +-- * Does not affect ships to keep the Navy guys happy. +-- * Does not affect OpsGroup type groups. +-- * Distinguishes between SAM groups, AAA groups and other ground groups. +-- * Exceptions can be defined in SET_GROUP objects to keep certain actions going. +-- * Works coalition-independent in the back +-- * Easy setup. -- --- @field #TIRESIAS +-- ## Setup +-- -- Setup is a one-liner: +-- +-- local blinder = TIRESIAS:New() +-- +-- -- Optionally you can set up exceptions, e.g. for convoys driving around +-- +-- local exceptionset = SET_GROUP:New():FilterCoalitions(" red" ):FilterPrefixes(" Convoy" ):FilterStart() +-- local blinder = TIRESIAS:New() +-- blinder:AddExceptionSet(exceptionset) +-- +-- -- Options +-- +-- -- Setup different radius for activation around helo and airplane groups (applies to AI and humans) +-- blinder:SetActivationRanges(10,25) -- defaults are 10, and 25 +-- +-- -- Setup engagement ranges for AAA (non-advanced SAM units like Flaks etc) and if you want them to be AIOff +-- blinder:SetAAARanges(60,true) -- defaults are 60, and true +-- +--- +-- @field #TIRESIAS TIRESIAS = { - ClassName = "TIRESIAS", - debug = false, - version = "0.0.5", - Interval = 20, - GroundSet = nil, - VehicleSet = nil, - AAASet = nil, - SAMSet = nil, - ExceptionSet = nil, - AAARange = 60, -- 60% - HeloSwitchRange = 10, -- NM - PlaneSwitchRange = 25, -- NM - SwitchAAA = true, -} + ClassName = "TIRESIAS", + debug = false, + version = " 0.0.8" , + Interval = 20, + GroundSet = nil, + VehicleSet = nil, + AAASet = nil, + SAMSet = nil, + ExceptionSet = nil, + AAARange = 60, -- 60% + HeloSwitchRange = 10, -- NM + PlaneSwitchRange = 25, -- NM + SwitchAAA = true, + _cached_zones = {}, -- Cache for zone objects + _cached_groupsets = {}, -- Cache for group_set objects + } ---- [USER] Create a new Tiresias object and start it up. --- @param #TIRESIAS self --- @return #TIRESIAS self +--- +-- [USER] Create a new Tiresias object and start it up. +-- @param #TIRESIAS self +-- @return #TIRESIAS self function TIRESIAS:New() - -- Inherit everything from FSM class. - local self = BASE:Inherit(self, FSM:New()) -- #TIRESIAS - - --- FSM Functions --- - - -- Start State. - self:SetStartState("Stopped") - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") -- TIRESIAS status update. - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - - self.ExceptionSet = SET_GROUP:New():Clear(false) - - self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) - - self.lid = string.format("TIRESIAS %s | ",self.version) - - self:I(self.lid.."Managing ground groups!") - - --- Triggers the FSM event "Stop". Stops TIRESIAS and all its event handlers. - -- @function [parent=#TIRESIAS] Stop - -- @param #TIRESIAS self - - --- Triggers the FSM event "Stop" after a delay. Stops TIRESIAS and all its event handlers. - -- @function [parent=#TIRESIAS] __Stop - -- @param #TIRESIAS self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Start". Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance. - -- @function [parent=#TIRESIAS] Start - -- @param #TIRESIAS self - - --- Triggers the FSM event "Start" after a delay. Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance. - -- @function [parent=#TIRESIAS] __Start - -- @param #TIRESIAS self - -- @param #number delay Delay in seconds. - - self:__Start(1) + -- Inherit everything from FSM class. + local self = BASE:Inherit(self, FSM:New()) -- #TIRESIAS + + --- FSM Functions --- + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- TIRESIAS status update. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + self.ExceptionSet = SET_GROUP:New() --:Clear(false) + self._cached_zones = {} -- Initialize zone cache + + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + + -- Cache the log identifier to avoid string concatenation in loops + self.lid = "TIRESIAS " .. self.version .. " | " + + self:I(self.lid .. "Managing ground groups!") + + --- Triggers the FSM event "Stop". Stops TIRESIAS and all its event handlers. + -- @function [parent=#TIRESIAS] Stop + -- @param #TIRESIAS self + + --- Triggers the FSM event "Stop" after a delay. Stops TIRESIAS and all its event handlers. + -- @function [parent=#TIRESIAS] __Stop + -- @param #TIRESIAS self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Start". Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance. + -- @function [parent=#TIRESIAS] Start + -- @param #TIRESIAS self + + --- Triggers the FSM event "Start" after a delay. Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance. + -- @function [parent=#TIRESIAS] __Start + -- @param #TIRESIAS self + -- @param #number delay Delay in seconds. + + self:__Start(1) + return self end -------------------------------------------------------------------------------------------------------------- --- --- Helper Functions --- -------------------------------------------------------------------------------------------------------------- +----- ----[USER] Set activation radius for Helos and Planes in Nautical Miles. --- @param #TIRESIAS self --- @param #number HeloMiles Radius around a Helicopter in which AI ground units will be activated. Defaults to 10NM. --- @param #number PlaneMiles Radius around an Airplane in which AI ground units will be activated. Defaults to 25NM. --- @return #TIRESIAS self -function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles) +--- +-- Helper Functions +--- + +--- [USER] Set activation radius for Helos and Planes in Nautical Miles. +-- @param #TIRESIAS self +-- @param #number HeloMiles Radius around a Helicopter in which AI ground units will be activated. Defaults to 10NM. +-- @param #number PlaneMiles Radius around an Airplane in which AI ground units will be activated. Defaults to 25NM. +-- @return #TIRESIAS self +function TIRESIAS:SetActivationRanges(HeloMiles, PlaneMiles) self.HeloSwitchRange = HeloMiles or 10 self.PlaneSwitchRange = PlaneMiles or 25 + -- Clear zone cache when ranges change + self._cached_zones = {} return self end ---[USER] Set AAA Ranges - AAA equals non-SAM systems which qualify as AAA in DCS world. --- @param #TIRESIAS self --- @param #number FiringRange The engagement range that AAA units will be set to. Can be 0 to 100 (percent). Defaults to 60. --- @param #boolean SwitchAAA Decide if these system will have their AI switched off, too. Defaults to true. --- @return #TIRESIAS self -function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA) +-- @param #TIRESIAS self +-- @param #number FiringRange The engagement range that AAA units will be set to. Can be 0 to 100 (percent). Defaults to 60. +-- @param #boolean SwitchAAA Decide if these system will have their AI switched off, too. Defaults to true. +-- @return #TIRESIAS self +function TIRESIAS:SetAAARanges(FiringRange, SwitchAAA) self.AAARange = FiringRange or 60 self.SwitchAAA = (SwitchAAA == false) and false or true return self end --- [USER] Add a SET_GROUP of GROUP objects as exceptions. Can be done multiple times. Does **not** work work for GROUP objects spawned into the SET after start, i.e. the groups need to exist in the game already. --- @param #TIRESIAS self --- @param Core.Set#SET_GROUP Set to add to the exception list. --- @return #TIRESIAS self +-- @param #TIRESIAS self +-- @param Core.Set#SET_GROUP Set to add to the exception list. +-- @return #TIRESIAS self function TIRESIAS:AddExceptionSet(Set) - self:T(self.lid.."AddExceptionSet") + self:T(self.lid .. " AddExceptionSet" ) + + if not self.ExceptionSet then + self.ExceptionSet = SET_GROUP:New() + end + local exceptions = self.ExceptionSet + + -- Cache the exception data structure for reuse + local exception_data = { + type = " Exception" , + exception = true, + } + Set:ForEachGroupAlive( function(grp) - if not grp.Tiresias then - grp.Tiresias = { -- #TIRESIAS.Data - type = "Exception", - exception = true, - } - exceptions:AddGroup(grp,true) + --local inAAASet = self.AAASet:IsIncludeObject(grp) + --local inVehSet = self.VehicleSet:IsIncludeObject(grp) + --local inSAMSet = self.SAMSet:IsIncludeObject(grp) + if grp:IsGround() and (not grp.Tiresias) then --and (not inAAASet) and (not inVehSet) and (not inSAMSet) then + grp.Tiresias = exception_data + exceptions:AddGroup(grp, true) + BASE:T(" TIRESIAS: Added exception group: " .. grp:GetName()) end - BASE:T("TIRESIAS: Added exception group: "..grp:GetName()) end - ) + ) return self end ---- [INTERNAL] Filter Function --- @param Wrapper.Group#GROUP Group --- @return #boolean isin +--- [INTERNAL] Filter Function - Optimized with cached calls +-- @param Wrapper.Group#GROUP Group +-- @return #boolean isin function TIRESIAS._FilterNotAAA(Group) - local grp = Group -- Wrapper.Group#GROUP - local isaaa = grp:IsAAA() - if isaaa == true and grp:IsGround() and not grp:IsShip() then - return false -- remove from SET - else - return true -- keep in SET + local grp = Group -- Wrapper.Group#GROUP + -- Cache method calls to reduce overhead + local is_air = grp:IsAir() + local is_ship = grp:IsShip() + local is_AAA = grp:IsAAA() + if is_air or grp:IsShip() then -- air or ship - no AAA + return true -- keep in SET end + return not is_AAA -- remove AAA, keep others end ---- [INTERNAL] Filter Function --- @param Wrapper.Group#GROUP Group --- @return #boolean isin +--- [INTERNAL] Filter Function - Optimized with cached calls +-- @param Wrapper.Group#GROUP Group +-- @return #boolean isin function TIRESIAS._FilterNotSAM(Group) - local grp = Group -- Wrapper.Group#GROUP - local issam = grp:IsSAM() - if issam == true and grp:IsGround() and not grp:IsShip() then - return false -- remove from SET - else - return true -- keep in SET + local grp = Group -- Wrapper.Group#GROUP + -- Cache method calls to reduce overhead + local is_air = grp:IsGround() + local is_ship = grp:IsShip() + local is_SAM = grp:IsSAM() + if is_air or grp:IsShip() then + return true -- keep in SET end + return not is_SAM -- remove SAM, keep others end ---- [INTERNAL] Filter Function --- @param Wrapper.Group#GROUP Group --- @return #boolean isin +--- [INTERNAL] Filter Function - Optimized with cached calls +-- @param Wrapper.Group#GROUP Group +-- @return #boolean isin function TIRESIAS._FilterAAA(Group) - local grp = Group -- Wrapper.Group#GROUP - local isaaa = grp:IsAAA() - if isaaa == true and grp:IsGround() and not grp:IsShip() then - return true -- remove from SET - else - return false -- keep in SET + local grp = Group -- Wrapper.Group#GROUP + -- Cache method calls to reduce overhead + local is_ground = grp:IsGround() + if (not is_ground) or grp:IsShip() then + return false -- not AAA end + return grp:IsAAA() -- only AAA end ---- [INTERNAL] Filter Function --- @param Wrapper.Group#GROUP Group --- @return #boolean isin +--- [INTERNAL] Filter Function - Optimized with cached calls +-- @param Wrapper.Group#GROUP Group +-- @return #boolean isin function TIRESIAS._FilterSAM(Group) - local grp = Group -- Wrapper.Group#GROUP - local issam = grp:IsSAM() - if issam == true and grp:IsGround() and not grp:IsShip() then - return true -- remove from SET - else - return false -- keep in SET + local grp = Group -- Wrapper.Group#GROUP + -- Cache method calls to reduce overhead + local is_ground = grp:IsGround() + if (not is_ground) or grp:IsShip() then + return false -- not SAM end + return grp:IsSAM() -- only SAM end ---- [INTERNAL] Init Groups --- @param #TIRESIAS self --- @return #TIRESIAS self +--- [INTERNAL] Init Groups - Optimized with reduced function calls +-- @param #TIRESIAS self +-- @return #TIRESIAS self function TIRESIAS:_InitGroups() - self:T(self.lid.."_InitGroups") - -- Set all groups invisible/motionless - local EngageRange = self.AAARange - local SwitchAAA = self.SwitchAAA - --- AAA - self.AAASet:ForEachGroupAlive( - function(grp) - if not grp.Tiresias then - grp:OptionEngageRange(EngageRange) +self:T(self.lid .. " _InitGroups" ) + +-- Cache frequently used values +local EngageRange = self.AAARange +local SwitchAAA = self.SwitchAAA + +-- Pre-create data structures to avoid repeated table creation +local aaa_data_template = { + type = " AAA" , + invisible = true, + range = EngageRange, + exception = false, + AIOff = SwitchAAA, + } + +local vehicle_data_template = { + type = " Vehicle" , + invisible = true, + AIOff = true, + exception = false, + } + +local sam_data_template = { + type = " SAM" , + invisible = true, + exception = false, + } + +--- AAA - Optimized loop +self.AAASet:ForEachGroupAlive( + function(grp) + local tiresias_data = grp.Tiresias + if not tiresias_data then + grp:OptionEngageRange(EngageRange) + grp:SetCommandInvisible(true) + if SwitchAAA then + grp:SetAIOff() + grp:EnableEmission(false) + end + grp.Tiresias = aaa_data_template + elseif not tiresias_data.exception == true then + if not tiresias_data.invisible == true then grp:SetCommandInvisible(true) - if SwitchAAA then + tiresias_data.invisible = true + if SwitchAAA == true then grp:SetAIOff() grp:EnableEmission(false) - end - grp.Tiresias = { -- #TIRESIAS.Data - type = "AAA", - invisible = true, - range = EngageRange, - exception = false, - AIOff = SwitchAAA, - } - end - if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias.invisible == false then - grp:SetCommandInvisible(true) - grp.Tiresias.invisible = true - if SwitchAAA then - grp:SetAIOff() - grp:EnableEmission(false) - grp.Tiresias.AIOff = true - end + tiresias_data.AIOff = true end end - --BASE:I(string.format("Init/Switch off AAA %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) end + end ) - --- Vehicles - self.VehicleSet:ForEachGroupAlive( - function(grp) - if not grp.Tiresias then + +--- Vehicles - Optimized loop +self.VehicleSet:ForEachGroupAlive( + function(grp) + local tiresias_data = grp.Tiresias + if not tiresias_data then + grp:SetAIOff() + grp:SetCommandInvisible(true) + grp.Tiresias = vehicle_data_template + elseif not tiresias_data.exception == true then + if not tiresias_data.invisible then + grp:SetCommandInvisible(true) grp:SetAIOff() - grp:SetCommandInvisible(true) - grp.Tiresias = { -- #TIRESIAS.Data - type = "Vehicle", - invisible = true, - AIOff = true, - exception = false, - } + tiresias_data.invisible = true + tiresias_data.AIOff = true end - if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias and grp.Tiresias.invisible == false then - grp:SetCommandInvisible(true) - grp:SetAIOff() - grp.Tiresias.invisible = true - grp.Tiresias.AIOff = true - end - end - --BASE:I(string.format("Init/Switch off Vehicle %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) - end + end + end ) - --- SAM - self.SAMSet:ForEachGroupAlive( - function(grp) - if not grp.Tiresias then + +--- SAM - Optimized loop +self.SAMSet:ForEachGroupAlive( + function(grp) + local tiresias_data = grp.Tiresias + if not tiresias_data then + grp:SetCommandInvisible(true) + grp.Tiresias = sam_data_template + elseif not tiresias_data.exception == true then + if not tiresias_data.invisible then grp:SetCommandInvisible(true) - grp.Tiresias = { -- #TIRESIAS.Data - type = "SAM", - invisible = true, - exception = false, - } + tiresias_data.invisible = true end - if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias and grp.Tiresias.invisible == false then - grp:SetCommandInvisible(true) - grp.Tiresias.invisible = true - end - end - --BASE:I(string.format("Init/Switch off SAM %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) end + end ) - return self + +return self end ---- [INTERNAL] Event handler function --- @param #TIRESIAS self --- @param Core.Event#EVENTDATA EventData --- @return #TIRESIAS self +--- [INTERNAL] Event handler function - Optimized +-- @param #TIRESIAS self +-- @param Core.Event#EVENTDATA EventData +-- @return #TIRESIAS self function TIRESIAS:_EventHandler(EventData) - self:T(string.format("%s Event = %d",self.lid, EventData.id)) - local event = EventData -- Core.Event#EVENTDATA + self:T(string.format(" %s Event = %d" , self.lid, EventData.id)) + + local event = EventData -- Core.Event#EVENTDATA if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then - --local _coalition = event.IniCoalition - --if _coalition ~= self.Coalition then - -- return --ignore! - --end - local unitname = event.IniUnitName or "none" - local _unit = event.IniUnit local _group = event.IniGroup if _group and _group:IsAlive() then - local radius = self.PlaneSwitchRange - if _group:IsHelicopter() then - radius = self.HeloSwitchRange - end - self:_SwitchOnGroups(_group,radius) + -- Cache the radius calculation + local radius = _group:IsHelicopter() and self.HeloSwitchRange or self.PlaneSwitchRange + self:_SwitchOnGroups(_group, radius) end end return self end ---- [INTERNAL] Switch Groups Behaviour --- @param #TIRESIAS self --- @param Wrapper.Group#GROUP group --- @param #number radius Radius in NM --- @return #TIRESIAS self -function TIRESIAS:_SwitchOnGroups(group,radius) - self:T(self.lid.."_SwitchOnGroups "..group:GetName().." Radius "..radius.." NM") - local zone = ZONE_GROUP:New("Zone-"..group:GetName(),group,UTILS.NMToMeters(radius)) - local ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() - local count = ground:CountAlive() - if self.debug then - local text = string.format("There are %d groups around this plane or helo!",count) - self:I(text) +--- [INTERNAL] Switch Groups Behaviour - Optimized with zone caching +-- @param #TIRESIAS self +-- @param Wrapper.Group#GROUP group +-- @param #number radius Radius in NM +-- @return #TIRESIAS self +function TIRESIAS:_SwitchOnGroups(group, radius) + self:T(self.lid .. " _SwitchOnGroups " .. group:GetName() .. " Radius " .. radius .. " NM" ) + + -- Use cached zones to reduce object creation + local group_name = group:GetName() + local cache_key = group_name .. " _" .. radius + local zone = self._cached_zones[cache_key] -- Core.Zone#ZONE_RADIUS + --local ground = self._cached_groupsets[cache_key] -- Core.Set#SET_GROUP + + if not zone then + zone = ZONE_GROUP:New(" Zone-" .. group_name, group, UTILS.NMToMeters(radius)) + self._cached_zones[cache_key] = zone + else + -- Update zone center to current group position + zone:UpdateFromGroup(group) end + + --if not ground then + --ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() + --self._cached_groupsets[cache_key] = ground + --else + --ground:FilterZones({zone},true):FilterOnce() + zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) + local ground = zone:GetScannedSetGroup() + --end + + local count = ground:CountAlive() + + if self.debug then + self:I(string.format(" There are %d groups around this plane or helo!" , count)) + end + + if count > 0 then + -- Cache values outside the loop local SwitchAAA = self.SwitchAAA - if ground:CountAlive() > 0 then - ground:ForEachGroupAlive( - function(grp) - local name = grp:GetName() - if grp:GetCoalition() ~= group:GetCoalition() - and grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then - if grp.Tiresias.invisible == true then - grp:SetCommandInvisible(false) - grp.Tiresias.invisible = false - end - if grp.Tiresias.type == "Vehicle" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then - grp:SetAIOn() - grp.Tiresias.AIOff = false - end - if SwitchAAA and grp.Tiresias.type == "AAA" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then - grp:SetAIOn() - grp:EnableEmission(true) - grp.Tiresias.AIOff = false - end - --BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception))) - else - BASE:T("TIRESIAS - This group "..tostring(name).. " has not been initialized or is an exception!") + local group_coalition = group:GetCoalition() + + ground:ForEachGroupAlive( + function(grp) + local tiresias_data = grp.Tiresias + if grp:GetCoalition() ~= group_coalition + and tiresias_data + and tiresias_data.type + and not tiresias_data.exception == true then + + -- Make group visible if invisible + if tiresias_data.invisible == true then + grp:SetCommandInvisible(false) + tiresias_data.invisible = false end + + -- Handle AI activation based on type + local grp_type = tiresias_data.type + if grp_type == "Vehicle" and tiresias_data.AIOff == true then + grp:SetAIOn() + tiresias_data.AIOff = false + elseif SwitchAAA == true and grp_type == "AAA" and tiresias_data.AIOff == true then + grp:SetAIOn() + grp:EnableEmission(true) + tiresias_data.AIOff = false + end + else + BASE:T("TIRESIAS - This group " .. tostring(grp:GetName()) .. " has not been initialized or is an exception!") end - ) + end + ) + end return self end -------------------------------------------------------------------------------------------------------------- --- --- FSM Functions --- -------------------------------------------------------------------------------------------------------------- +----- ---- [INTERNAL] FSM Function --- @param #TIRESIAS self --- @param #string From --- @param #string Event --- @param #string To --- @return #TIRESIAS self +--- +-- FSM Functions +---- + +--- [INTERNAL] FSM Function - Optimized initialization +-- @param #TIRESIAS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #TIRESIAS self function TIRESIAS:onafterStart(From, Event, To) - self:T({From, Event, To}) - - local VehicleSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart() - local AAASet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart() - local SAMSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart() - local OpsGroupSet = SET_OPSGROUP:New():FilterActive(true):FilterStart() - self.FlightSet = SET_GROUP:New():FilterCategories({"plane","helicopter"}):FilterStart() - - local EngageRange = self.AAARange - - local ExceptionSet = self.ExceptionSet - if self.ExceptionSet then - function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object) - BASE:I("TIRESIAS: EXCEPTION Object Added: "..Object:GetName()) - if Object and Object:IsAlive() then - Object.Tiresias = { -- #TIRESIAS.Data - type = "Exception", - exception = true, - } +self:T({From, Event, To}) + +-- Create sets with optimized filters +local VehicleSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart() +local AAASet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart() +local SAMSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart() +local OpsGroupSet = SET_OPSGROUP:New():FilterActive(true):FilterStart() +self.FlightSet = SET_GROUP:New():FilterCategories({" plane" ," helicopter" }):FilterStart() + +-- Cache frequently used values +local EngageRange = self.AAARange +local SwitchAAA = self.SwitchAAA +local ExceptionSet = self.ExceptionSet + +-- Pre-create data templates to reduce object creation +local exception_data = { + type = " Exception" , + exception = true, + } + +local vehicle_data = { + type = " Vehicle" , + invisible = true, + AIOff = true, + exception = false, + } + +local aaa_data = { + type = " AAA" , + invisible = true, + range = EngageRange, + exception = false, + AIOff = SwitchAAA, + } + +local sam_data = { + type = " SAM" , + invisible = true, + exception = false, + } + +if ExceptionSet then + function ExceptionSet:OnAfterAdded(From, Event, To, ObjectName, Object) + BASE:I(" TIRESIAS: EXCEPTION Object Added: " .. Object:GetName()) + if Object and Object:IsAlive() then + Object.Tiresias = exception_data Object:SetAIOn() Object:SetCommandInvisible(false) Object:EnableEmission(true) - end - end - - local OGS = OpsGroupSet:GetAliveSet() - for _,_OG in pairs(OGS or {}) do - local OG = _OG -- Ops.OpsGroup#OPSGROUP - local grp = OG:GetGroup() - ExceptionSet:AddGroup(grp,true) - end - - function OpsGroupSet:OnAfterAdded(From,Event,To,ObjectName,Object) - local grp = Object:GetGroup() - ExceptionSet:AddGroup(grp,true) end end + + -- Process existing OpsGroups more efficiently + local OGS = OpsGroupSet:GetAliveSet() + for _, _OG in pairs(OGS or {}) do + local OG = _OG -- Ops.OpsGroup#OPSGROUP + local grp = OG:GetGroup() + ExceptionSet:AddGroup(grp, true) + end - function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object) - BASE:I("TIRESIAS: VEHCILE Object Added: "..Object:GetName()) - if Object and Object:IsAlive() then + function OpsGroupSet:OnAfterAdded(From, Event, To, ObjectName, Object) + local grp = Object:GetGroup() + ExceptionSet:AddGroup(grp, true) + end +end + +-- Optimized event handlers with pre-created data objects +function VehicleSet:OnAfterAdded(From, Event, To, ObjectName, Object) + BASE:T(" TIRESIAS: VEHICLE Object Added: " .. Object:GetName()) + if Object and Object:IsAlive() then + Object:SetAIOff() + Object:SetCommandInvisible(true) + Object.Tiresias = vehicle_data + end +end + +function AAASet:OnAfterAdded(From, Event, To, ObjectName, Object) + if Object and Object:IsAlive() then + BASE:I(" TIRESIAS: AAA Object Added: " .. Object:GetName()) + Object:OptionEngageRange(EngageRange) + Object:SetCommandInvisible(true) + if SwitchAAA then Object:SetAIOff() - Object:SetCommandInvisible(true) - Object.Tiresias = { -- #TIRESIAS.Data - type = "Vehicle", - invisible = true, - AIOff = true, - exception = false, - } + Object:EnableEmission(false) end + Object.Tiresias = aaa_data end +end - local SwitchAAA = self.SwitchAAA - - function AAASet:OnAfterAdded(From,Event,To,ObjectName,Object) - if Object and Object:IsAlive() then - BASE:I("TIRESIAS: AAA Object Added: "..Object:GetName()) - Object:OptionEngageRange(EngageRange) - Object:SetCommandInvisible(true) - if SwitchAAA then - Object:SetAIOff() - Object:EnableEmission(false) - end - Object.Tiresias = { -- #TIRESIAS.Data - type = "AAA", - invisible = true, - range = EngageRange, - exception = false, - AIOff = SwitchAAA, - } - end - end - - function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object) - if Object and Object:IsAlive() then - BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName()) - Object:SetCommandInvisible(true) - Object.Tiresias = { -- #TIRESIAS.Data - type = "SAM", - invisible = true, - exception = false, - } - end +function SAMSet:OnAfterAdded(From, Event, To, ObjectName, Object) + if Object and Object:IsAlive() then + BASE:T(" TIRESIAS: SAM Object Added: " .. Object:GetName()) + Object:SetCommandInvisible(true) + Object.Tiresias = sam_data end +end + -- Store references self.VehicleSet = VehicleSet self.AAASet = AAASet self.SAMSet = SAMSet @@ -523,71 +602,76 @@ function TIRESIAS:onafterStart(From, Event, To) self:_InitGroups() - self:__Status(1) + self:__Status(1) return self end --- [INTERNAL] FSM Function --- @param #TIRESIAS self --- @param #string From --- @param #string Event --- @param #string To --- @return #TIRESIAS self +-- @param #TIRESIAS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #TIRESIAS self function TIRESIAS:onbeforeStatus(From, Event, To) self:T({From, Event, To}) - if self:GetState() == "Stopped" then - return false - end - return self + return self:GetState() ~= " Stopped" end ---- [INTERNAL] FSM Function --- @param #TIRESIAS self --- @param #string From --- @param #string Event --- @param #string To --- @return #TIRESIAS self +--- [INTERNAL] FSM Function - Optimized status processing +-- @param #TIRESIAS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #TIRESIAS self function TIRESIAS:onafterStatus(From, Event, To) self:T({From, Event, To}) - if self.debug then + + if self.debug then local count = self.VehicleSet:CountAlive() local AAAcount = self.AAASet:CountAlive() local SAMcount = self.SAMSet:CountAlive() - local text = string.format("Overall: %d | Vehicles: %d | AAA: %d | SAM: %d",count+AAAcount+SAMcount,count,AAAcount,SAMcount) - self:I(text) + self:I(string.format(" Overall: %d | Vehicles: %d | AAA: %d | SAM: %d" , + count + AAAcount + SAMcount, count, AAAcount, SAMcount)) end + self:_InitGroups() - if self.FlightSet:CountAlive() > 0 then + + -- Process flight groups more efficiently + local flight_count = self.FlightSet:CountAlive() + if flight_count > 0 then local Set = self.FlightSet:GetAliveSet() - for _,_plane in pairs(Set) do + -- Cache range values outside loop + local helo_range = self.HeloSwitchRange + local plane_range = self.PlaneSwitchRange + + for _, _plane in pairs(Set or {}) do local plane = _plane -- Wrapper.Group#GROUP - local radius = self.PlaneSwitchRange - if plane:IsHelicopter() then - radius = self.HeloSwitchRange - end - self:_SwitchOnGroups(_plane,radius) + local radius = plane:IsHelicopter() and helo_range or plane_range + self:_SwitchOnGroups(plane, radius) end end - if self:GetState() ~= "Stopped" then + + if self:GetState() ~= " Stopped" then self:__Status(self.Interval) end + return self end --- [INTERNAL] FSM Function --- @param #TIRESIAS self --- @param #string From --- @param #string Event --- @param #string To --- @return #TIRESIAS self +-- @param #TIRESIAS self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @return #TIRESIAS self function TIRESIAS:onafterStop(From, Event, To) self:T({From, Event, To}) self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + -- Clear zone cache on stop to free memory + self._cached_zones = {} return self end -------------------------------------------------------------------------------------------------------------- --- --- End --- -------------------------------------------------------------------------------------------------------------- +----- +---- End +----- \ No newline at end of file diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 29682074f..807f57f98 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3153,7 +3153,7 @@ end -- @param #WAREHOUSE self -- @return Core.Point#COORDINATE The coordinate of the warehouse. function WAREHOUSE:GetCoordinate() - return self.warehouse:GetCoordinate() + return self.warehouse:GetCoord() end --- Get 3D vector of warehouse static. @@ -4247,6 +4247,16 @@ function WAREHOUSE:_AssetItemInfo(asset) self:T3({Template=asset.template}) end +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +--- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #boolean Enabled Enable/disable the feature. +function WAREHOUSE:SetValidateAndRepositionGroundUnits(Enabled) + self.ValidateAndRepositionGroundUnits = Enabled +end + --- On after "NewAsset" event. A new asset has been added to the warehouse stock. -- @param #WAREHOUSE self -- @param #string From From state. @@ -5965,6 +5975,10 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, late template.y = coord.z template.alt = coord.y + if self.ValidateAndRepositionGroundUnits then + UTILS.ValidateAndRepositionGroundUnits(template.units) + end + -- Spawn group. local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP @@ -6893,7 +6907,7 @@ function WAREHOUSE:_CheckConquered() for _,_unit in pairs(units) do local unit=_unit --Wrapper.Unit#UNIT - local distance=coord:Get2DDistance(unit:GetCoordinate()) + local distance=coord:Get2DDistance(unit:GetCoord()) -- Filter only alive groud units. Also check distance again, because the scan routine might give some larger distances. if unit:IsGround() and unit:IsAlive() and distance <= radius then diff --git a/Moose Development/Moose/Functional/ZoneGoalCargo.lua b/Moose Development/Moose/Functional/ZoneGoalCargo.lua index 4397f621c..10b91e12f 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCargo.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCargo.lua @@ -7,6 +7,8 @@ -- -- # Developer Note -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE -- Therefore, this class is considered to be deprecated -- diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index 15e1b3b05..c856158e9 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -116,7 +116,6 @@ __Moose.Include( 'Ops\\Operation.lua' ) __Moose.Include( 'Ops\\FlightControl.lua' ) __Moose.Include( 'Ops\\PlayerRecce.lua' ) __Moose.Include( 'Ops\\EasyGCICAP.lua' ) -__Moose.Include( 'Ops\\EasyA2G.lua' ) __Moose.Include( 'AI\\AI_Balancer.lua' ) __Moose.Include( 'AI\\AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index f53f953d0..107808898 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -2798,7 +2798,7 @@ function ATIS:onafterBroadcast( From, Event, To ) end _RUNACT = subtitle - alltext = alltext .. ";\n" .. subtitle + --alltext = alltext .. ";\n" .. subtitle -- Runway length. if self.rwylength then diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index f181b02ea..1e11793e3 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -159,6 +159,8 @@ AIRWING = { -- @field #number refuelsystem Refueling system type: `0=Unit.RefuelingSystem.BOOM_AND_RECEPTACLE`, `1=Unit.RefuelingSystem.PROBE_AND_DROGUE`. -- @field #number noccupied Number of flights on this patrol point. -- @field Wrapper.Marker#MARKER marker F10 marker. +-- @field #boolean IsZonePoint flag for using a (moving) zone as point for patrol etc. +-- @field Core.Zone#ZONE_BASE patrolzone in case Patrol coordinate was handed as zone, store here. --- Patrol zone. -- @type AIRWING.PatrolZone @@ -187,13 +189,14 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.9.6" +AIRWING.version="0.9.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Check that airbase has enough parking spots if a request is BIG. +-- DONE: Allow (moving) zones as base for patrol points. -- DONE: Spawn in air ==> Needs WAREHOUSE update. -- DONE: Spawn hot. -- DONE: Make special request to transfer squadrons to anther airwing (or warehouse). @@ -807,13 +810,22 @@ function AIRWING:_PatrolPointMarkerText(point) end --- Update marker of the patrol point. +-- @param #AIRWING self -- @param #AIRWING.PatrolData point Patrol point table. function AIRWING:UpdatePatrolPointMarker(point) - if self.markpoints then -- sometimes there's a direct call from #OPSGROUP + + if self and self.markpoints then -- sometimes there's a direct call from #OPSGROUP local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed) - - point.marker:UpdateText(text, 1) + + if point.IsZonePoint and point.IsZonePoint == true and point.patrolzone then + -- update position + local Coordinate = point.patrolzone:GetCoordinate() + point.marker:UpdateCoordinate(Coordinate) + point.marker:UpdateText(text, 1.5) + else + point.marker:UpdateText(text, 1) + end end end @@ -821,7 +833,7 @@ end --- Create a new generic patrol point. -- @param #AIRWING self -- @param #string Type Patrol point type, e.g. "CAP" or "AWACS". Default "Unknown". --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Default 10-15 NM away from the location of the airwing. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Orbit altitude in feet. Default random between Angels 10 and 20. -- @param #number Heading Heading in degrees. Default random (0, 360] degrees. -- @param #number LegLength Length of race-track orbit in NM. Default 15 NM. @@ -830,14 +842,16 @@ end -- @return #AIRWING.PatrolData Patrol point table. function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) - -- Check if a zone was passed instead of a coordinate. - if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE") then - Coordinate=Coordinate:GetCoordinate() - end - local patrolpoint={} --#AIRWING.PatrolData patrolpoint.type=Type or "Unknown" patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10, 15)), math.random(360)) + if Coordinate and Coordinate:IsInstanceOf("ZONE_BASE") then + patrolpoint.IsZonePoint = true + patrolpoint.patrolzone = Coordinate + patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate() + else + patrolpoint.IsZonePoint = false + end patrolpoint.heading=Heading or math.random(360) patrolpoint.leg=LegLength or 15 patrolpoint.altitude=Altitude or math.random(10,20)*1000 @@ -847,7 +861,7 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL if self.markpoints then patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll() - AIRWING.UpdatePatrolPointMarker(patrolpoint) + self:UpdatePatrolPointMarker(patrolpoint) end return patrolpoint @@ -855,7 +869,7 @@ end --- Add a patrol Point for CAP missions. -- @param #AIRWING self --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Orbit altitude in feet. -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. @@ -872,7 +886,7 @@ end --- Add a patrol Point for RECON missions. -- @param #AIRWING self --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Orbit altitude in feet. -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. @@ -889,7 +903,7 @@ end --- Add a patrol Point for TANKER missions. -- @param #AIRWING self --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Orbit altitude in feet. -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. @@ -907,7 +921,7 @@ end --- Add a patrol Point for AWACS missions. -- @param #AIRWING self --- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Orbit altitude in feet. -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. @@ -974,6 +988,46 @@ function AIRWING:SetTakeoffAir() return self end +--- Set the aircraft of the AirWing to land straight in. +-- @param #AIRWING self +-- @return #FLIGHTGROUP self +function AIRWING:SetLandingStraightIn() + self.OptionLandingStraightIn = true + return self +end + +--- Set the aircraft of the AirWing to land in pairs for groups > 1 aircraft. +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:SetLandingForcePair() + self.OptionLandingForcePair = true + return self +end + +--- Set the aircraft of the AirWing to NOT land in pairs. +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:SetLandingRestrictPair() + self.OptionLandingRestrictPair = true + return self +end + +--- Set the aircraft of the AirWing to land after overhead break. +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:SetLandingOverheadBreak() + self.OptionLandingOverheadBreak = true + return self +end + +--- [Helicopter] Set the aircraft of the AirWing to prefer vertical takeoff and landing. +-- @param #AIRWING self +-- @return #AIRWING self +function AIRWING:SetOptionPreferVerticalLanding() + self.OptionPreferVerticalLanding = true + return self +end + --- Set despawn after landing. Aircraft will be despawned after the landing event. -- Can help to avoid DCS AI taxiing issues. -- @param #AIRWING self @@ -1136,6 +1190,10 @@ function AIRWING:_GetPatrolData(PatrolPoints, RefuelSystem) for _,_patrolpoint in pairs(PatrolPoints) do local patrolpoint=_patrolpoint --#AIRWING.PatrolData + if patrolpoint.IsZonePoint and patrolpoint.IsZonePoint == true and patrolpoint.patrolzone then + -- update + patrolpoint.coord = patrolpoint.patrolzone:GetCoordinate() + end if (RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem) or RefuelSystem==nil or patrolpoint.refuelsystem==nil then return patrolpoint end @@ -1195,7 +1253,7 @@ function AIRWING:CheckCAP() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(missionCAP) @@ -1247,7 +1305,7 @@ function AIRWING:CheckRECON() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(missionRECON) @@ -1292,7 +1350,7 @@ function AIRWING:CheckTANKER() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(mission) @@ -1311,7 +1369,7 @@ function AIRWING:CheckTANKER() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(mission) @@ -1349,7 +1407,7 @@ function AIRWING:CheckAWACS() patrol.noccupied=patrol.noccupied+1 - if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end + if self.markpoints then self:UpdatePatrolPointMarker(patrol) end self:AddMission(mission) @@ -1464,7 +1522,21 @@ function AIRWING:onafterFlightOnMission(From, Event, To, FlightGroup, Mission) self:T(self.lid..string.format("Group %s on %s mission %s", FlightGroup:GetName(), Mission:GetType(), Mission:GetName())) if self.UseConnectedOpsAwacs and self.ConnectedOpsAwacs then self.ConnectedOpsAwacs:__FlightOnMission(2,FlightGroup,Mission) - end + end + -- Landing Options + if self.OptionLandingForcePair then + FlightGroup:SetOptionLandingForcePair() + elseif self.OptionLandingOverheadBreak then + FlightGroup:SetOptionLandingOverheadBreak() + elseif self.OptionLandingRestrictPair then + FlightGroup:SetOptionLandingRestrictPair() + elseif self.OptionLandingStraightIn then + FlightGroup:SetOptionLandingStraightIn() + end + -- Landing Options Helo + if self.OptionPreferVerticalLanding then + FlightGroup:SetOptionPreferVertical() + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 0eadc412c..8b8644165 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -21,7 +21,7 @@ -- * Multiple carrier support due to object oriented approach. -- * Unlimited number of players. -- * Persistence of player results (optional). LSO grading data is saved to csv file. --- * Trap sheet (optional). +-- * Trap sheet (optional). -- * Finite State Machine (FSM) implementation. -- -- **Supported Carriers:** @@ -32,12 +32,15 @@ -- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_\(CVN-73\)) (CVN-73) [Super Carrier Module] -- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module] -- * [USS Forrestal](https://en.wikipedia.org/wiki/USS_Forrestal_\(CV-59\)) (CV-59) [Heatblur Carrier Module] +-- * [Essex Class](https://en.wikipedia.org/wiki/Essex-class_aircraft_carrier) (CV-11) [Magnitude 3 Carrier Module] -- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_\(R12\)) (R12) -- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_\(R05\)) (R05) -- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_\(LHA-1\)) (LHA-1) -- * [USS America](https://en.wikipedia.org/wiki/USS_America_\(LHA-6\)) (LHA-6) -- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) -- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_\(L02\)) (L02) +-- * BONHOMMERICHARD [VWV Mod] +-- * ENTERPRISE66 [VWV Mod] -- -- **Supported Aircraft:** -- @@ -47,6 +50,7 @@ -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) -- * [T-45C Goshawk](https://forum.dcs.world/topic/203816-vnao-t-45-goshawk/) (VNAO mod) (Player & AI) -- * [FE/A-18E/F/G Superhornet](https://forum.dcs.world/topic/316971-cjs-super-hornet-community-mod-v20-official-thread/) (CJS mod) (Player & AI) +-- * [F4U-1D Corsair](https://forum.dcs.world/forum/781-f4u-1d/) (Player & AI) -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -1283,6 +1287,8 @@ AIRBOSS = { -- @field #string RHINOE F/A-18E Superhornet (mod). -- @field #string RHINOF F/A-18F Superhornet (mod). -- @field #string GROWLER FEA-18G Superhornet (mod). +-- @field #string CORSAIR F4U-1D Corsair. +-- @field #string CORSAIR_CW F4U-1D Corsair Mk.4 (clipped wing). AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", @@ -1299,6 +1305,8 @@ AIRBOSS.AircraftCarrier={ RHINOE="FA-18E", RHINOF="FA-18F", GROWLER="EA-18G", + CORSAIR="F4U-1D", + CORSAIR_CW="F4U-1D CW", } --- Carrier types. @@ -1310,6 +1318,11 @@ AIRBOSS.AircraftCarrier={ -- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module] -- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module] -- @field #string VINSON USS Carl Vinson (CVN-70) [Deprecated!] +-- @field #string ESSEX Essex class carrier (e.g. USS Yorktown (CV-10)) [Magnitude 3 Carrier Module] +-- @field #string BONHOMMERICHARD USS Bon Homme Richard carrier [VWV Mod] +-- @field #string ESSEXSCB125 Generic Essex class carrier with angled deck (SCB-125 upgrade) [VWV Mod] +-- @field #string ENTERPRISE66 USS Enterprise in the 1966 configuration [VWV Mod] +-- @field #string ENTERPRISEMODERN USS Enterprise in a modern configuration [Derived VWV Mod] -- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier] -- @field #string INVINCIBLE HMS Invincible (R05) [V/STOL Carrier] -- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier] @@ -1324,7 +1337,12 @@ AIRBOSS.CarrierType = { TRUMAN = "CVN_75", STENNIS = "Stennis", FORRESTAL = "Forrestal", + ENTERPRISE66 = "USS Enterprise 1966", + ENTERPRISEMODERN = "cvn-65", VINSON = "VINSON", + ESSEX = "Essex", + BONHOMMERICHARD = "USS Bon Homme Richard", + ESSEXSCB125 = "essex_scb125", HERMES = "HERMES81", INVINCIBLE = "hms_invincible", TARAWA = "LHA_Tarawa", @@ -1436,6 +1454,7 @@ AIRBOSS.PatternStep = { GROOVE_IC = "Groove In Close", GROOVE_AR = "Groove At the Ramp", GROOVE_IW = "Groove In the Wires", + GROOVE_IWs = "Groove In the Wires stopped?", -- VNAO Edit - Added GROOVE_AL = "Groove Abeam Landing Spot", GROOVE_LC = "Groove Level Cross", BOLTER = "Bolter Pattern", @@ -1731,10 +1750,10 @@ AIRBOSS.Difficulty = { -- @field #table trapsheet Groove data table recorded every 0.5 seconds. -- @field #boolean trapon If true, save trap sheets. -- @field #string debriefschedulerID Debrief scheduler ID. --- +-- -- @field Sound.SRS#MSRS SRS -- @field Sound.SRS#MSRSQUEUE SRSQ --- +-- -- @extends #AIRBOSS.FlightGroup --- Main group level radio menu: F10 Other/Airboss. @@ -1747,7 +1766,7 @@ AIRBOSS.MenuF10Root = nil --- Airboss class version. -- @field #string version -AIRBOSS.version = "1.3.3" +AIRBOSS.version = "1.4.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1912,6 +1931,9 @@ function AIRBOSS:New( carriername, alias ) -- Set max section members. Default 2. self:SetMaxSectionSize() + -- Set max section distance. Default 100 meters. + self:SetMaxSectionDistance() + -- Set max flights per stack. Default is 2. self:SetMaxFlightsPerStack() @@ -2007,9 +2029,19 @@ function AIRBOSS:New( carriername, alias ) self:_InitNimitz() elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then self:_InitForrestal() + elseif self.carriertype == AIRBOSS.CarrierType.ENTERPRISE66 then + self:_InitEnterprise() + elseif self.carriertype == AIRBOSS.CarrierType.ENTERPRISEMODERN then + self:_InitEnterprise() elseif self.carriertype == AIRBOSS.CarrierType.VINSON then -- Carl Vinson is legacy now. self:_InitStennis() + elseif self.carriertype == AIRBOSS.CarrierType.ESSEX then + self:_InitEssex() + elseif self.carriertype == AIRBOSS.CarrierType.BONHOMMERICHARD then + self:_InitBonHommeRichard() + elseif self.carriertype == AIRBOSS.CarrierType.ESSEXSCB125 then + self:_InitEssexSCB125() elseif self.carriertype == AIRBOSS.CarrierType.HERMES then -- Hermes parameters. self:_InitHermes() @@ -2398,6 +2430,16 @@ end -- USER API Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Set the carrier illumination mode. +-- @param #AIRBOSS self +-- @param #number Mode Options are: -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +-- @return #AIRBOSS self +function AIRBOSS:SetCarrierIllumination(Mode) + self.carrier:SetCarrierIlluminationMode(Mode) + return self +end + + --- Set welcome messages for players. -- @param #AIRBOSS self -- @param #boolean Switch If true, display welcome message to player. @@ -2539,7 +2581,7 @@ function AIRBOSS:AddRecoveryWindow( starttime, stoptime, case, holdingoffset, tu return self end if Tstop <= Tnow then - string.format( "WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.", UTILS.SecondsToClock( Tstop ), UTILS.SecondsToClock( Tnow ) ) + string.format( "WARNING: Recovery stop time %s already over. Tnow=%s! Recovery window rejected.", UTILS.SecondsToClock( Tstop ), UTILS.SecondsToClock( Tnow ) ) return self end @@ -2863,23 +2905,28 @@ end function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW) --Check if V/STOL Carrier - if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then + if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or + self.carriertype == AIRBOSS.CarrierType.HERMES or + self.carriertype == AIRBOSS.CarrierType.TARAWA or + self.carriertype == AIRBOSS.CarrierType.AMERICA or + self.carriertype == AIRBOSS.CarrierType.JCARLOS or + self.carriertype == AIRBOSS.CarrierType.CANBERRA then - -- allow a larger GSE for V/STOL operations --Pene Testing - self.gle._max=_max or 0.7 - self.gle.High=High or 1.4 - self.gle.HIGH=HIGH or 1.9 - self.gle._min=_min or -0.5 - self.gle.Low=Low or -1.2 - self.gle.LOW=LOW or -1.5 - -- CVN values + -- allow a larger GSE for V/STOL operations --Pene Testing + self.gle._max=_max or 0.7 + self.gle.High=High or 1.4 + self.gle.HIGH=HIGH or 1.9 + self.gle._min=_min or -0.5 + self.gle.Low=Low or -1.2 + self.gle.LOW=LOW or -1.5 else - self.gle._max=_max or 0.4 - self.gle.High=High or 0.8 - self.gle.HIGH=HIGH or 1.5 - self.gle._min=_min or -0.3 - self.gle.Low=Low or -0.6 - self.gle.LOW=LOW or -0.9 + -- CVN values + self.gle._max=_max or 0.4 + self.gle.High=High or 0.8 + self.gle.HIGH=HIGH or 1.5 + self.gle._min=_min or -0.3 + self.gle.Low=Low or -0.6 + self.gle.LOW=LOW or -0.9 end return self @@ -3062,11 +3109,11 @@ end --- Set up SRS for usage without sound files -- @param #AIRBOSS self --- @param #string PathToSRS Path to SRS folder, e.g. "C:\\Program Files\\DCS-SimpleRadio-Standalone". +-- @param #string PathToSRS Path to SRS folder, e.g. "C:\\Program Files\\DCS-SimpleRadio\\ExternalAudio". -- @param #number Port Port of the SRS server, defaults to 5002. -- @param #string Culture (Optional, Airboss Culture) Culture, defaults to "en-US". -- @param #string Gender (Optional, Airboss Gender) Gender, e.g. "male" or "female". Defaults to "male". --- @param #string Voice (Optional, Airboss Voice) Set to use a specific voice. Will **override gender and culture** settings. +-- @param #string Voice (Optional, Airboss Voice) Set to use a specific voice. Will **override gender and culture** settings. -- @param #string GoogleCreds (Optional) Path to Google credentials, e.g. "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourgooglekey.json". -- @param #number Volume (Optional) E.g. 0.75. Defaults to 1.0 (loudest). -- @param #table AltBackend (Optional) See MSRS for details. @@ -3081,27 +3128,29 @@ function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volum self.SRS:SetCulture(Culture or "en-US") --self.SRS:SetFrequencies(Frequencies) self.SRS:SetGender(Gender or "male") - self.SRS:SetPath(PathToSRS) - self.SRS:SetPort(Port or 5002) + --self.SRS:SetPath(PathToSRS) + self.SRS:SetPort(Port or MSRS.port or 5002) self.SRS:SetLabel(self.AirbossRadio.alias or "AIRBOSS") self.SRS:SetCoordinate(self.carrier:GetCoordinate()) self.SRS:SetVolume(Volume or 1) --self.SRS:SetModulations(Modulations) if GoogleCreds then - self.SRS:SetProviderOptionsGoogle(GoogleCreds,GoogleCreds) - self.SRS:SetProvider(MSRS.Provider.GOOGLE) + self.SRS:SetGoogle(GoogleCreds) end if Voice then self.SRS:SetVoice(Voice) end - self.SRS:SetVolume(Volume or 1.0) + if (not Voice) and self.SRS and self.SRS:GetProvider() == MSRS.Provider.GOOGLE then + self.SRS.voice = MSRS.poptions["gcloud"].voice or MSRS.Voices.Google.Standard.en_US_Standard_B + end + --self.SRS:SetVolume(Volume or 1.0) -- SRSQUEUE self.SRSQ = MSRSQUEUE:New("AIRBOSS") self.SRSQ:SetTransmitOnlyWithPlayers(true) - if not self.PilotRadio then + if not self.PilotRadio then self:SetSRSPilotVoice() end - return self + return self end --- Set LSO radio frequency and modulation. Default frequency is 264 MHz AM. @@ -3344,6 +3393,22 @@ function AIRBOSS:SetMaxSectionSize( nmax ) return self end +--- Set maximum distance up to which section members are allowed (default: 100 meters). +-- @param #AIRBOSS self +-- @param #number dmax Max distance in meters (default 100 m). Minimum is 10 m, maximum is 5000 m. +-- @return #AIRBOSS self +function AIRBOSS:SetMaxSectionDistance( dmax ) + if dmax then + if dmax < 10 then + dmax = 10 + elseif dmax > 5000 then + dmax = 5000 + end + end + self.maxsectiondistance = dmax or 100 + return self +end + --- Set max number of flights per stack. All members of a section count as one "flight". -- @param #AIRBOSS self -- @param #number nmax Number of max allowed flights per stack. Default is two. Minimum is one, maximum is 4. @@ -3623,7 +3688,6 @@ function AIRBOSS:onafterStart( From, Event, To ) self:HandleEvent( EVENTS.PlayerLeaveUnit, self._PlayerLeft ) self:HandleEvent( EVENTS.MissionEnd ) self:HandleEvent( EVENTS.RemoveUnit ) - self:HandleEvent( EVENTS.UnitLost, self.OnEventRemoveUnit ) -- self.StatusScheduler=SCHEDULER:New(self) -- self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5) @@ -4470,7 +4534,7 @@ function AIRBOSS:_InitStennis() -- Early break. self.BreakEarly.name = "Early Break" self.BreakEarly.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. - self.BreakEarly.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakEarly.Xmax = UTILS.NMToMeters( 7 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? -- VNAO EDIT - original value 5 self.BreakEarly.Zmin = -UTILS.NMToMeters( 2 ) -- Not more than 2 NM port. self.BreakEarly.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. self.BreakEarly.LimitXmin = 0 -- Check and next step 0.2 NM port and in front of boat. @@ -4481,7 +4545,7 @@ function AIRBOSS:_InitStennis() -- Late break. self.BreakLate.name = "Late Break" self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0. - self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? + self.BreakLate.Xmax = UTILS.NMToMeters( 7 ) -- Not more than 5 NM in front of the boat. Enough for late breaks? -- VNAO EDIT - original value 5 self.BreakLate.Zmin = -UTILS.NMToMeters( 2 ) -- Not more than 2 NM port. self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard. self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat. @@ -4492,7 +4556,7 @@ function AIRBOSS:_InitStennis() -- Abeam position. self.Abeam.name = "Abeam Position" self.Abeam.Xmin = -UTILS.NMToMeters( 5 ) -- Not more then 5 NM astern of boat. Should be LIG call anyway. - self.Abeam.Xmax = UTILS.NMToMeters( 5 ) -- Not more then 5 NM ahead of boat. + self.Abeam.Xmax = UTILS.NMToMeters( 7 ) -- Not more then 5 NM ahead of boat. --VNAO EDIT - original value 5 self.Abeam.Zmin = -UTILS.NMToMeters( 2 ) -- Not more than 2 NM port. self.Abeam.Zmax = 500 -- Not more than 500 m starboard. Must be port! self.Abeam.LimitXmin = -200 -- Check and next step 200 meters behind the ship. @@ -4571,7 +4635,7 @@ function AIRBOSS:_InitNimitz() self.carrierparam.wire1 = 55 -- Distance from stern to first wire. self.carrierparam.wire2 = 67 self.carrierparam.wire3 = 79 - self.carrierparam.wire4 = 92 + self.carrierparam.wire4 = 96 -- VNAO Edit - original value was 92 -- Landing distance. self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.wire3 @@ -4610,6 +4674,100 @@ function AIRBOSS:_InitForrestal() end +--- Init parameters for Enterprise carrier. +-- @param #AIRBOSS self +function AIRBOSS:_InitEnterprise() + -- Using Forrestal as template + self:_InitForrestal() + + self.carrierparam.sterndist = -164.30 + self.carrierparam.deckheight = 19.52 + + self.carrierparam.totlength = 335 + self.carrierparam.rwylength = 223 + + -- Wires. + self.carrierparam.wire1 = 57.7 + self.carrierparam.wire2 = 69.6 + self.carrierparam.wire3 = 79.5 + self.carrierparam.wire4 = 90.0 + +end + +--- Init parameters for Essec class carriers. +-- @param #AIRBOSS self +function AIRBOSS:_InitEssex() + + -- Init Nimitz as default. + self:_InitNimitz() + + -- Carrier Parameters. + self.carrierparam.sterndist = -126 + self.carrierparam.deckheight = 19.27 --DCS World\CoreMods\tech\M3 WWII PTO units\Database\Essex_Class_Carrier_1944.lua + + -- Total size of the carrier (approx as rectangle). + self.carrierparam.totlength = 268 + self.carrierparam.totwidthport = 23 + self.carrierparam.totwidthstarboard = 23 + + -- Landing runway. + self.carrierparam.rwyangle = 0.0 + self.carrierparam.rwylength = 265 + self.carrierparam.rwywidth = 20 + + -- Wires. + self.carrierparam.wire1 = 21.9 + self.carrierparam.wire2 = 28.3 + self.carrierparam.wire3 = 34.7 + self.carrierparam.wire4 = 41.1 + self.carrierparam.wire5 = 47.4 + self.carrierparam.wire6 = 53.7 + self.carrierparam.wire7 = 59.0 + + self.carrierparam.wire8 = 64.1 + self.carrierparam.wire9 = 72.7 + self.carrierparam.wire10 = 78.0 + self.carrierparam.wire11 = 85.5 + + self.carrierparam.wire12 = 105.9 + self.carrierparam.wire13 = 113.3 + self.carrierparam.wire14 = 121.0 + self.carrierparam.wire15 = 128.5 + + -- Landing distance. + self.carrierparam.landingdist = self.carrierparam.sterndist+self.carrierparam.wire3 + +end + +--- Init parameters for CVA-31 Bon Homme Richard carriers. +-- @param #AIRBOSS self +function AIRBOSS:_InitBonHommeRichard() + -- Init Essex as default + self:_InitEssex() + + self.carrierparam.deckheight = 16.95 + + -- Landing runway. + -- from BHR EssexRunwayAndRoutes.lua + self.carrierparam.rwyangle = -11.4 + self.carrierparam.rwylength = 97 + self.carrierparam.rwywidth = 20 + + -- Wires. + self.carrierparam.wire1 = 40.4 -- Distance from stern to first wire. Original from Frank - 42 + self.carrierparam.wire2 = 45 + self.carrierparam.wire3 = 51 + self.carrierparam.wire4 = 58.1 +end + +--- Init parameters for Generic Essex SC125 class carriers. +-- @param #AIRBOSS self +function AIRBOSS:_InitEssexSCB125() + -- Init Bon Homme Richard as default + self:_InitBonHommeRichard() + +end + --- Init parameters for R12 HMS Hermes carrier. -- @param #AIRBOSS self function AIRBOSS:_InitHermes() @@ -5019,6 +5177,7 @@ function AIRBOSS:SetVoiceOversLSOByRaynor( mizfolder ) self.LSOCall.N8.duration = 0.38 self.LSOCall.N9.duration = 0.34 self.LSOCall.PADDLESCONTACT.duration = 0.91 + self.LSOCall.POWERsoft.duration=0.9 -- VNAO Edit - Added self.LSOCall.POWER.duration = 0.45 self.LSOCall.RADIOCHECK.duration = 0.90 self.LSOCall.RIGHTFORLINEUP.duration = 0.70 @@ -5077,6 +5236,7 @@ function AIRBOSS:SetVoiceOversLSOByFF( mizfolder ) self.LSOCall.N9.duration = 0.40 self.LSOCall.PADDLESCONTACT.duration = 1.00 self.LSOCall.POWER.duration = 0.50 + self.LSOCall.POWERsoft.duration=0.9 -- VNAO Edit - Added self.LSOCall.RADIOCHECK.duration = 1.10 self.LSOCall.RIGHTFORLINEUP.duration = 0.80 self.LSOCall.ROGERBALL.duration = 1.00 @@ -5169,6 +5329,7 @@ function AIRBOSS:_InitVoiceOvers() HIGH = { file = "LSO-High", suffix = "ogg", loud = true, subtitle = "You're high", duration = 0.65, subduration = 1 }, LOW = { file = "LSO-Low", suffix = "ogg", loud = true, subtitle = "You're low", duration = 0.50, subduration = 1 }, POWER = { file = "LSO-Power", suffix = "ogg", loud = true, subtitle = "Power", duration = 0.50, subduration = 1 }, -- duration 0.45 was too short + POWERsoft={ file="LSO-Power-soft", suffix="ogg", loud=false, subtitle="Power-soft", duration=0.90, subduration=1 }, -- VNAO Edit - Added SLOW = { file = "LSO-Slow", suffix = "ogg", loud = true, subtitle = "You're slow", duration = 0.65, subduration = 1 }, FAST = { file = "LSO-Fast", suffix = "ogg", loud = true, subtitle = "You're fast", duration = 0.70, subduration = 1 }, ROGERBALL = { file = "LSO-RogerBall", suffix = "ogg", loud = false, subtitle = "Roger ball", duration = 1.00, subduration = 2 }, @@ -5312,7 +5473,8 @@ function AIRBOSS:_GetAircraftAoA( playerData ) local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B - local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B + local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B + local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW -- Table with AoA values. local aoa = {} -- #AIRBOSS.AircraftAoA @@ -5329,13 +5491,13 @@ function AIRBOSS:_GetAircraftAoA( playerData ) elseif tomcat then -- F-14A/B Tomcat parameters (taken from NATOPS). Converted from units 0-30 to degrees. -- Currently assuming a linear relationship with 0=-10 degrees and 30=+40 degrees as stated in NATOPS. - aoa.SLOW = self:_AoAUnit2Deg( playerData, 17.0 ) -- 18.33 --17.0 units - aoa.Slow = self:_AoAUnit2Deg( playerData, 16.0 ) -- 16.67 --16.0 units - aoa.OnSpeedMax = self:_AoAUnit2Deg( playerData, 15.5 ) -- 15.83 --15.5 units + aoa.SLOW = self:_AoAUnit2Deg( playerData, 17.5 ) -- 18.33 --17.0 units -- VNAO Edit - Original value 17 + aoa.Slow = self:_AoAUnit2Deg( playerData, 16.5 ) -- 16.67 --16.0 units -- VNAO Edit - Original value 16 + aoa.OnSpeedMax = self:_AoAUnit2Deg( playerData, 16.0 ) -- 15.83 --15.5 units -- VNAO Edit - Original value 15.5 aoa.OnSpeed = self:_AoAUnit2Deg( playerData, 15.0 ) -- 15.0 --15.0 units - aoa.OnSpeedMin = self:_AoAUnit2Deg( playerData, 14.5 ) -- 14.17 --14.5 units - aoa.Fast = self:_AoAUnit2Deg( playerData, 14.0 ) -- 13.33 --14.0 units - aoa.FAST = self:_AoAUnit2Deg( playerData, 13.0 ) -- 11.67 --13.0 units + aoa.OnSpeedMin = self:_AoAUnit2Deg( playerData, 14.0 ) -- 14.17 --14.5 units -- VNAO Edit - Original value 14.5 + aoa.Fast = self:_AoAUnit2Deg( playerData, 13.5 ) -- 13.33 --14.0 units -- VNAO Edit - Original value 14 + aoa.FAST = self:_AoAUnit2Deg( playerData, 12.5 ) -- 11.67 --13.0 units -- VNAO Edit - Original value 13 elseif goshawk then -- T-45C Goshawk parameters. aoa.SLOW = 8.00 -- 19 @@ -5349,15 +5511,14 @@ function AIRBOSS:_GetAircraftAoA( playerData ) -- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390 -- Note that these are arbitrary UNITS and not degrees. We need a conversion formula! -- Github repo suggests they simply use a factor of two to get from degrees to units. - aoa.SLOW = 9.50 -- =19.0/2 - aoa.Slow = 9.25 -- =18.5/2 - aoa.OnSpeedMax = 9.00 -- =18.0/2 - aoa.OnSpeed = 8.75 -- =17.5/2 8.1 - aoa.OnSpeedMin = 8.50 -- =17.0/2 - aoa.Fast = 8.25 -- =17.5/2 - aoa.FAST = 8.00 -- =16.5/2 + aoa.SLOW = 10.50 -- =19.0/2 -- VNAO Edit - Original value 9.50 + aoa.Slow = 9.50 -- =18.5/2 -- VNAO Edit - Original value 9.25 + aoa.OnSpeedMax = 9.25 -- =18.0/2 -- VNAO Edit - Original value 9.00 + aoa.OnSpeed = 8.75 -- =17.5/2 8.1 -- VNAO Edit - Original value 8.75 + aoa.OnSpeedMin = 8.25 -- =17.0/2 -- VNAO Edit - Original value 8.50 + aoa.Fast = 8.00 -- =17.5/2 -- VNAO Edit - Original value 8.25 + aoa.FAST = 7.00 -- =16.5/2 -- VNAO Edit - Original value 8.0 elseif harrier then - -- AV-8B Harrier parameters. Tuning done on the Fast AoA to allow for abeam and ninety at Nozzles 55. Pene testing aoa.SLOW = 16.0 aoa.Slow = 13.5 @@ -5366,7 +5527,15 @@ function AIRBOSS:_GetAircraftAoA( playerData ) aoa.OnSpeedMin = 9.5 aoa.Fast = 8.0 aoa.FAST = 7.5 - + elseif corsair then + -- F4U-1D Corsair parameters. + aoa.SLOW = 16.0 + aoa.Slow = 13.5 + aoa.OnSpeedMax = 12.5 + aoa.OnSpeed = 10.0 + aoa.OnSpeedMin = 9.5 + aoa.Fast = 8.0 + aoa.FAST = 7.5 end return aoa @@ -5479,6 +5648,7 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C + local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW -- Return values. local alt @@ -5538,6 +5708,9 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) elseif goshawk then alt = UTILS.FeetToMeters( 800 ) speed = UTILS.KnotsToMps( 300 ) + elseif corsair then + alt = UTILS.FeetToMeters( 300 ) + speed = UTILS.KnotsToMps( 120 ) end elseif step == AIRBOSS.PatternStep.BREAKENTRY then @@ -5551,6 +5724,9 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) elseif goshawk then alt = UTILS.FeetToMeters( 800 ) speed = UTILS.KnotsToMps( 300 ) + elseif corsair then + alt = UTILS.FeetToMeters( 200 ) + speed = UTILS.KnotsToMps( 110 ) end elseif step == AIRBOSS.PatternStep.EARLYBREAK then @@ -5559,6 +5735,9 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 800 ) elseif skyhawk then alt = UTILS.FeetToMeters( 600 ) + elseif corsair then + alt = UTILS.FeetToMeters( 200 ) + speed = UTILS.KnotsToMps( 100 ) end elseif step == AIRBOSS.PatternStep.LATEBREAK then @@ -5567,6 +5746,9 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 800 ) elseif skyhawk then alt = UTILS.FeetToMeters( 600 ) + elseif corsair then + alt = UTILS.FeetToMeters( 150 ) + speed = UTILS.KnotsToMps( 100 ) end elseif step == AIRBOSS.PatternStep.ABEAM then @@ -5575,6 +5757,9 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 600 ) elseif skyhawk then alt = UTILS.FeetToMeters( 500 ) + elseif corsair then + alt = UTILS.FeetToMeters( 150 ) + speed = UTILS.KnotsToMps( 90 ) end aoa = aoaac.OnSpeed @@ -5599,6 +5784,9 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 500 ) elseif harrier then alt = UTILS.FeetToMeters( 425 ) + elseif corsair then + alt = UTILS.FeetToMeters( 90 ) + speed = UTILS.KnotsToMps( 90 ) end aoa = aoaac.OnSpeed @@ -5611,6 +5799,8 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 430 ) -- Tomcat should be a bit higher as it intercepts the GS a bit higher. elseif skyhawk then alt = UTILS.FeetToMeters( 370 ) -- ? + elseif corsair then + alt = UTILS.FeetToMeters( 80 ) end -- Harrier wont get into wake pos. Runway is not angled and it stays port. @@ -5626,6 +5816,8 @@ function AIRBOSS:_GetAircraftParameters( playerData, step ) alt = UTILS.FeetToMeters( 300 ) -- ? elseif harrier then alt=UTILS.FeetToMeters(312)-- 300-325 ft + elseif corsair then + alt = UTILS.FeetToMeters( 80 ) end aoa = aoaac.OnSpeed @@ -6502,6 +6694,8 @@ function AIRBOSS:_LandAI( flight ) Speed = UTILS.KnotsToKmph( 175 ) elseif flight.actype == AIRBOSS.AircraftCarrier.S3B or flight.actype == AIRBOSS.AircraftCarrier.S3BTANKER then Speed = UTILS.KnotsToKmph( 140 ) + elseif flight.actype == AIRBOSS.AircraftCarrier.CORSAIR or flight.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW then + Speed = UTILS.KnotsToKmph( 100 ) end -- Carrier position. @@ -6756,6 +6950,9 @@ function AIRBOSS:_AddMarshalGroup( flight, stack ) -- Convert to clock string. local Ccharlie = UTILS.SecondsToClock( flight.Tcharlie ) + -- Make sure brc is never above 360 + brc = brc % 360 + -- Combined marshal call. self:_MarshalCallArrived( flight.onboard, flight.case, brc, alt, Ccharlie, P ) @@ -7409,7 +7606,7 @@ function AIRBOSS:_InitPlayer( playerData, step ) playerData.landed = false playerData.Tlso = timer.getTime() playerData.Tgroove = nil - playerData.TIG0 = nil + playerData.TIG0 = 0 --changed to prevent errors in script when player is not in correct spot playerData.wire = nil playerData.flag = -100 playerData.debriefschedulerID = nil @@ -7948,8 +8145,7 @@ end --- Check current player status. -- @param #AIRBOSS self function AIRBOSS:_CheckPlayerStatus() - - -- Loop over all players. + -- Loop over all players. for _playerName, _playerData in pairs( self.players ) do local playerData = _playerData -- #AIRBOSS.PlayerData @@ -7965,6 +8161,32 @@ function AIRBOSS:_CheckPlayerStatus() -- TODO: This might cause problems if the CCA is set to be very small! if unit:IsInZone( self.zoneCCA ) then + -- VNAO Edit - Added wrapped up call to LSO grading + if playerData.step==AIRBOSS.PatternStep.WAKE then-- VNAO Edit - Added + if math.abs(playerData.unit:GetRoll())>35 and math.abs(playerData.unit:GetRoll())<=40 then-- VNAO Edit - Added + playerData.wrappedUpAtWakeLittle = true -- VNAO Edit - Added + elseif math.abs(playerData.unit:GetRoll()) >40 and math.abs(playerData.unit:GetRoll())<=45 then-- VNAO Edit - Added + playerData.wrappedUpAtWakeFull = true-- VNAO Edit - Added + elseif math.abs(playerData.unit:GetRoll()) >45 then-- VNAO Edit - Added + playerData.wrappedUpAtWakeUnderline = true -- VNAO Edit - Added + elseif math.abs(playerData.unit:GetRoll()) <20 and math.abs(playerData.unit:GetRoll()) >=10 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments + playerData.AAatWakeLittle = true -- VNAO Edit - Added + elseif math.abs(playerData.unit:GetRoll()) <10 and math.abs(playerData.unit:GetRoll()) >=2 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments + playerData.AAatWakeFull = true -- VNAO Edit - Added + elseif math.abs(playerData.unit:GetRoll()) <2 then -- VNAO Edit - Added a new AA comment based on discussion with Lipps today, and going to replace the AA at the X with the original LUL comments + playerData.AAatWakeUnderline = true -- VNAO Edit - Added + else -- VNAO Edit - Added + end -- VNAO Edit - Added + + if math.abs(playerData.unit:GetAoA())>= 15 then -- VNAO Edit - Added + playerData.AFU = true -- VNAO Edit - Added + elseif math.abs(playerData.unit:GetAoA())<= 5 then -- VNAO Edit - Added + playerData.AFU = true -- VNAO Edit - Added + else -- VNAO Edit - Added + end -- VNAO Edit - Added + end-- VNAO Edit - Added + + -- Display aircraft attitude and other parameters as message text. if playerData.attitudemonitor then self:_AttitudeMonitor( playerData ) @@ -8195,8 +8417,8 @@ end function AIRBOSS:_SetTimeInGroove( playerData ) -- Set time in the groove - if playerData.TIG0 then - playerData.Tgroove = timer.getTime() - playerData.TIG0 + if playerData.TIG0 then + playerData.Tgroove = timer.getTime() - playerData.TIG0 - 1.5 -- VNAO Edit - Subtracting an extra 1.5 else playerData.Tgroove = 999 end @@ -8724,13 +8946,13 @@ function AIRBOSS:OnEventRemoveUnit( EventData ) -- Nil checks. if EventData == nil then - self:E( self.lid .. "ERROR: EventData=nil in event REMOVEUNIT!" ) - self:E( EventData ) + self:T( self.lid .. "ERROR: EventData=nil in event REMOVEUNIT!" ) + self:T( EventData ) return end if EventData.IniUnit == nil then - self:E( self.lid .. "ERROR: EventData.IniUnit=nil in event REMOVEUNIT!" ) - self:E( EventData ) + self:T( self.lid .. "ERROR: EventData.IniUnit=nil in event REMOVEUNIT!" ) + self:T( EventData ) return end @@ -9317,7 +9539,19 @@ function AIRBOSS:_DirtyUp( playerData ) if inzone then -- Hint for player about altitude, AoA etc. - self:_PlayerHint( playerData ) + playerData.Tgroove = timer.getTime() - playerData.TIG0 - 1.5 -- VNAO Edit - Subtracting an extra 1.5 + + -- VNAO Edit - Added wrapped up call to LSO grading + playerData.wrappedUpAtWakeLittle = false -- VNAO Edit - Added + playerData.wrappedUpAtWakeFull = false -- VNAO Edit - Added + playerData.wrappedUpAtWakeUnderline = false -- VNAO Edit - Added + playerData.wrappedUpAtStartLittle = false -- VNAO Edit - Added + playerData.wrappedUpAtStartFull = false -- VNAO Edit - Added + playerData.wrappedUpAtStartUnderline = false -- VNAO Edit - Added + playerData.AAatWakeLittle = false -- VNAO Edit - Added + playerData.AAatWakeFull = false -- VNAO Edit - Added + playerData.AAatWakeUnderline = false -- VNAO Edit - Added + playerData.AFU = false -- VNAO Edit - Added -- Radio call "Say/Fly needles". Delayed by 10/15 seconds. if playerData.actype == AIRBOSS.AircraftCarrier.HORNET @@ -9428,7 +9662,6 @@ function AIRBOSS:_BreakEntry( playerData ) -- Hint for player about altitude, AoA etc. self:_PlayerHint( playerData ) - -- Next step: Early Break. self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.EARLYBREAK ) @@ -9569,6 +9802,18 @@ end -- @param #AIRBOSS.PlayerData playerData Player data table. function AIRBOSS:_Ninety( playerData ) + -- VNAO Edit - Added wrapped up call to LSO grading + playerData.wrappedUpAtWakeLittle = false -- VNAO Edit - Added + playerData.wrappedUpAtWakeFull = false -- VNAO Edit - Added + playerData.wrappedUpAtWakeUnderline = false -- VNAO Edit - Added + playerData.wrappedUpAtStartLittle = false -- VNAO Edit - Added + playerData.wrappedUpAtStartFull = false -- VNAO Edit - Added + playerData.wrappedUpAtStartUnderline = false -- VNAO Edit - Added + playerData.AAatWakeLittle = false -- VNAO Edit - Added + playerData.AAatWakeFull = false -- VNAO Edit - Added + playerData.AAatWakeUnderline = false -- VNAO Edit - Added + playerData.AFU = false -- VNAO Edit - Added + -- Get distances between carrier and player unit (parallel and perpendicular to direction of movement of carrier) local X, Z = self:_GetDistances( playerData.unit ) @@ -9672,6 +9917,9 @@ function AIRBOSS:_GetGrooveData( playerData ) groovedata.AoA = playerData.unit:GetAoA() groovedata.GSE = self:_Glideslope( playerData.unit ) groovedata.LUE = self:_Lineup( playerData.unit, true ) + groovedata.LUEwire = self:_LineupWIRE( playerData.unit, true ) -- VNAO Edit - Added + groovedata.LeftNozzle = self:_NozzleArgumentLeft( playerData.unit ) -- VNAO Edit - Added + groovedata.RightNozzle = self:_NozzleArgumentRight( playerData.unit ) -- VNAO Edit - Added groovedata.Roll = playerData.unit:GetRoll() groovedata.Pitch = playerData.unit:GetPitch() groovedata.Yaw = playerData.unit:GetYaw() @@ -9753,6 +10001,7 @@ function AIRBOSS:_Groove( playerData ) local RIM = UTILS.NMToMeters( 0.500 ) -- In the Middle 0.50 = 926 m (middle one third of the glideslope) local RIC = UTILS.NMToMeters( 0.250 ) -- In Close 0.25 = 463 m (last one third of the glideslope) local RAR = UTILS.NMToMeters( 0.040 ) -- At the Ramp. 0.04 = 75 m + local RIW = UTILS.NMToMeters( -0.020 ) -- In the wires. 0.04 = 75 m -- VNAO Edit - Added -- Groove data. local groovedata = self:_GetGrooveData( playerData ) @@ -9776,7 +10025,8 @@ function AIRBOSS:_Groove( playerData ) local glideslopeError = groovedata.GSE local AoA = groovedata.AoA - if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 and playerData.unit:IsInZone( self:_GetZoneLineup() )) then + -- if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 or playerData.unit:IsInZone( self:_GetZoneLineup() )) then -- VNAO Edit - Commented out + if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 3.5 or playerData.unit:IsInZone( self:_GetZoneLineup() )) then -- VNAO Edit - Added -- Start time in groove playerData.TIG0 = timer.getTime() @@ -9826,7 +10076,8 @@ function AIRBOSS:_Groove( playerData ) else self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.GROOVE_IW ) end - + elseif rho <= RIW and playerData.step == AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Added + playerData.groove.IW = UTILS.DeepCopy( groovedata ) -- VNAO Edit - Added elseif rho <= RAR and playerData.step == AIRBOSS.PatternStep.GROOVE_AL then -- Store data. @@ -9961,23 +10212,75 @@ function AIRBOSS:_Groove( playerData ) -- Distance in NM. local d = UTILS.MetersToNM( rho ) + -- VNAO Edit - Added wrapped up call to LSO grading + if playerData.case ~=3 then -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - playerData.case ~= 3, proceeding with checks") -- VNAO Edit - Added + if playerData.wrappedUpAtWakeUnderline == true or playerData.wrappedUpAtStartUnderline == true then -- VNAO Edit - Added + gd.WrappedUp="_WU_" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - UNDERLINE WU comment should be added for player: "..playerData.name) -- VNAO Edit - Added + elseif playerData.wrappedUpAtWakeUnderline == false and playerData.wrappedUpAtStartUnderline == false then -- VNAO Edit - Added + if playerData.wrappedUpAtWakeFull == true or playerData.wrappedUpAtStartFull == true then -- VNAO Edit - Added + gd.WrappedUp="WU" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - FULL WU comment should be added for player: "..playerData.name) -- VNAO Edit - Added + elseif playerData.wrappedUpAtStartFull == false then -- VNAO Edit - Added + if playerData.wrappedUpAtWakeLittle == true or playerData.wrappedUpAtStartLittle == true then -- VNAO Edit - Added + gd.WrappedUp="(WU)" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - little WU comment should be added for player: "..playerData.name) -- VNAO Edit - Added + end -- VNAO Edit - Added + end -- VNAO Edit - Added + else -- VNAO Edit - Added + -- gd.WrappedUp="" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - NO WU comment should be added for player: "..playerData.name) -- VNAO Edit - Added + end -- VNAO Edit - Added + + if playerData.AAatWakeUnderline == true then -- VNAO Edit - Added + gd.AngledApch="_AA_" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - UNDERLINE AA comment should be added for player: "..playerData.name) -- VNAO Edit - Added + elseif playerData.AAatWakeUnderline == false then -- VNAO Edit - Added + if playerData.AAatWakeFull == true then -- VNAO Edit - Added + gd.AngledApch="AA" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - FULL AA comment should be added for player: "..playerData.name) -- VNAO Edit - Added + elseif playerData.AAatWakeFull == false then -- VNAO Edit - Added + if playerData.AAatWakeLittle == true then -- VNAO Edit - Added + gd.AngledApch="(AA)" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - little AA comment should be added for player: "..playerData.name) -- VNAO Edit - Added + end -- VNAO Edit - Added + end -- VNAO Edit - Added + else -- VNAO Edit - Added + -- gd.AngledApch="" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - NO AA comment should be added for player: "..playerData.name) -- VNAO Edit - Added + end -- VNAO Edit - Added + + if playerData.AFU == true then -- VNAO Edit - Added + gd.AFU="AFU" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - AFU comment should be added for player: "..playerData.name) -- VNAO Edit - Added + else -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - NO AFU comment should be added for player: "..playerData.name) -- VNAO Edit - Added + end -- VNAO Edit - Added + else -- VNAO Edit - Added + -- gd.WrappedUp="" -- VNAO Edit - Added + -- gd.AngledApch="" -- VNAO Edit - Added + --gd.AFU="" -- VNAO Edit - Added + -- env.info("ADAM AIRBOSS EDIT- function AIRBOSS:_Groove(playerData) - CASE 3, don't add WU, AA or AFU comments at all for player: "..playerData.name) -- VNAO Edit - Added + end + -- Drift on lineup. if rho >= RAR and rho <= RIM then if gd.LUE > 0.22 and lineupError < -0.22 then env.info " Drift Right across centre ==> DR-" - gd.Drift = " DR" + gd.Drift = "DR" self:T( self.lid .. string.format( "Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) ) elseif gd.LUE < -0.22 and lineupError > 0.22 then env.info " Drift Left ==> DL-" - gd.Drift = " DL" + gd.Drift = "DL" self:T( self.lid .. string.format( "Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) ) elseif gd.LUE > 0.13 and lineupError < -0.14 then env.info " Little Drift Right across centre ==> (DR-)" - gd.Drift = " (DR)" + gd.Drift = "(DR)" self:T( self.lid .. string.format( "Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) ) elseif gd.LUE < -0.13 and lineupError > 0.14 then env.info " Little Drift Left across centre ==> (DL-)" - gd.Drift = " (DL)" + gd.Drift = "(DL)" self:E( self.lid .. string.format( "Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError ) ) end end @@ -10297,6 +10600,9 @@ function AIRBOSS:_GetSternCoord() elseif self.carriertype == AIRBOSS.CarrierType.FORRESTAL then -- Forrestal self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 7.5, FB + 90, true, true ) + elseif self.carriertype == AIRBOSS.CarrierType.ESSEX then + -- Forrestal + self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( -1, FB + 90, true, true ) else -- Nimitz SC: translate 8 meters starboard wrt Final bearing. self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 9.5, FB + 90, true, true ) @@ -10495,13 +10801,13 @@ function AIRBOSS:_Trapped( playerData ) -- Message to player. local text = string.format( "Trapped %d-wire.", wire ) if wire == 3 then - text = text .. " Well done!" + text = text .. " " -- VNAO Edit - Removed comment text elseif wire == 2 then - text = text .. " Not bad, maybe you even get the 3rd next time." + text = text .. " " -- VNAO Edit - Removed comment text elseif wire == 4 then - text = text .. " That was scary. You can do better than this!" + text = text .. " " -- VNAO Edit - Removed comment text elseif wire == 1 then - text = text .. " Try harder next time!" + text = text .. " " -- VNAO Edit - Removed comment text end -- Message to player. @@ -11250,6 +11556,41 @@ function AIRBOSS:_AttitudeMonitor( playerData ) text = text .. string.format( "\nGamma=%.1f° | Rho=%.1f°", relhead, phi ) end + -- VNAO Edit: for testing the damn line up in the wires! + -- VNAO Edit: F-14A Nozzle: R-433, L-434, R Burner- 435, L Burner - 436 + local lueWire = self:_LineupWIRE( playerData.unit, true ) + text = text .. string.format( "\nLineUpForWireCalls=%.2f° | lineup for Groove calls=%.2f°", lueWire or 0, lue or 0)-- VNAO Edit - Added + + local unitClient = Unit.getByName(unit:GetName()) -- VNAO Edit - Added + local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET -- VNAO Edit - Added + local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B -- VNAO Edit - Added + + if hornet then -- VNAO Edit - Added + local nozzlePosL = 0 -- VNAO Edit - Added + local burnerPosL = unitClient:getDrawArgumentValue(28) -- VNAO Edit - Added + if burnerPosL < 0.2 then -- VNAO Edit - Added + nozzlePosL = unitClient:getDrawArgumentValue(89) -- VNAO Edit - Added + else -- VNAO Edit - Added + nozzlePosL = 0 -- VNAO Edit - Added + end -- VNAO Edit - Added + + local nozzlePosR = 0 -- VNAO Edit - Added + local burnerPosR = unitClient:getDrawArgumentValue(29) -- VNAO Edit - Added + if burnerPosR < 0.2 then -- VNAO Edit - Added + nozzlePosR = unitClient:getDrawArgumentValue(90) -- VNAO Edit - Added + else -- VNAO Edit - Added + nozzlePosR = 0 -- VNAO Edit - Added + end -- VNAO Edit - Added + + text = text .. string.format( "\n Left Nozzle position=%.2f | Right Nozzle position=%.2f ", nozzlePosL, nozzlePosR ) -- VNAO Edit - Added + end -- VNAO Edit - Added + + if tomcat then -- VNAO Edit - Added + local nozzlePosL = unitClient:getDrawArgumentValue(434) -- VNAO Edit - Added + local nozzlePosR = unitClient:getDrawArgumentValue(433) -- VNAO Edit - Added + text = text .. string.format( "\n Left Nozzle position=%.2f | Right Nozzle position=%.2f ", nozzlePosL, nozzlePosR ) -- VNAO Edit - Added + end -- VNAO Edit - Added + MESSAGE:New( text, 1, nil, true ):ToClient( playerData.client ) end @@ -11382,6 +11723,111 @@ function AIRBOSS:_Lineup( unit, runway ) return lineup end +-- VNAO Edit - Added this function +--- Get line up of player wrt to carrier. +-- @param #AIRBOSS self +-- @param Wrapper.Unit#UNIT unit Aircraft unit. +-- @param #boolean runway If true, include angled runway. +-- @return #number Line up with runway heading in degrees. 0 degrees = perfect line up. +1 too far left. -1 too far right. +function AIRBOSS:_LineupWIRE( unit, runway ) -- VNAO Edit - Added + + -- Landing coordinate + local landingcoord = self:_GetOptLandingCoordinateWIRE() -- VNAO Edit - Added + + -- Vector to landing coord. + local A = landingcoord:GetVec3() -- VNAO Edit - Added + + -- Vector to player. + local B = unit:GetVec3() -- VNAO Edit - Added + + -- Vector from player to carrier. + local C = UTILS.VecSubstract( A, B ) -- VNAO Edit - Added + + -- Only in 2D plane. + C.y = 0.0 -- VNAO Edit - Added + + -- Orientation of carrier. + local X = self.carrier:GetOrientationX() -- VNAO Edit - Added + X.y = 0.0 -- VNAO Edit - Added + + -- Rotate orientation to angled runway. + if runway then -- VNAO Edit - Added + X = UTILS.Rotate2D( X, -self.carrierparam.rwyangle ) -- VNAO Edit - Added + end -- VNAO Edit - Added + + -- Projection of player pos on x component. + local x = UTILS.VecDot( X, C ) -- VNAO Edit - Added + + -- Orientation of carrier. + local Z = self.carrier:GetOrientationZ() -- VNAO Edit - Added + Z.y = 0.0 -- VNAO Edit - Added + + -- Rotate orientation to angled runway. + if runway then -- VNAO Edit - Added + Z = UTILS.Rotate2D( Z, -self.carrierparam.rwyangle ) -- VNAO Edit - Added + end -- VNAO Edit - Added + + -- Projection of player pos on z component. + local z = UTILS.VecDot( Z, C ) -- VNAO Edit - Added + + --- + local lineup = math.deg( math.atan2( z, x ) ) -- VNAO Edit - Added + + return lineup -- VNAO Edit - Added +end -- VNAO Edit - Added + +-- VNAO Edit - Added this function +--- Get L/R Nozzle Position from Argument and L/R Burner Argument +-- @param #AIRBOSS self +-- @param Wrapper.Unit#UNIT unit Aircraft unit. +-- @return #number Left Nozzle position argument +function AIRBOSS:_NozzleArgumentLeft( unit ) -- VNAO Edit - Added + --Arguments for HORNET L burner and R burner are 28 and 29 respectively and >.2 indicates burner active + --Nozzle position greater than 0.3 for the hornet indicates either idle or burner + --if Lnoz > 0.6 and Rnoz > 0.6 is current check for EG + local unitClient = Unit.getByName(unit:GetName()) -- VNAO Edit - Added + local typeName = unit:GetTypeName() -- VNAO Edit - Added + local nozzlePosL = 0 -- VNAO Edit - Added + local burnerPosL = 0 -- VNAO Edit - Added + if typeName == "FA-18C_hornet" then -- VNAO Edit - Added + burnerPosL = unitClient:getDrawArgumentValue(28) -- VNAO Edit - Added + if burnerPosL < 0.2 then -- VNAO Edit - Added + nozzlePosL = unitClient:getDrawArgumentValue(89) -- VNAO Edit - Added + else -- VNAO Edit - Added + nozzlePosL = 0 -- VNAO Edit - Added + end -- VNAO Edit - Added + elseif typeName == "F-14A-135-GR" or typeName == "F-14B" then -- VNAO Edit - Added + nozzlePosL = unitClient:getDrawArgumentValue(434) -- VNAO Edit - Added + end -- VNAO Edit - Added + + return nozzlePosL -- VNAO Edit - Added +end -- VNAO Edit - Added + +-- VNAO Edit - Added this function +--- Get L/R Nozzle Position from Argument and L/R Burner Argument +-- @param #AIRBOSS self +-- @param Wrapper.Unit#UNIT unit Aircraft unit. +-- @return #number Right Nozzle position argument +function AIRBOSS:_NozzleArgumentRight( unit ) -- VNAO Edit - Added + local unitClient = Unit.getByName(unit:GetName()) -- VNAO Edit - Added + local typeName = unit:GetTypeName() -- VNAO Edit - Added + local nozzlePosR = 0 -- VNAO Edit - Added + local burnerPosR = 0 -- VNAO Edit - Added + + if typeName == "FA-18C_hornet" then -- VNAO Edit - Added + burnerPosR = unitClient:getDrawArgumentValue(29) -- VNAO Edit - Added + if burnerPosR < 0.2 then -- VNAO Edit - Added + nozzlePosR = unitClient:getDrawArgumentValue(90) -- VNAO Edit - Added + else -- VNAO Edit - Added + nozzlePosR = 0 -- VNAO Edit - Added + end -- VNAO Edit - Added + elseif typeName == "F-14A-135-GR" or typeName == "F-14B" then -- VNAO Edit - Added + nozzlePosR = unitClient:getDrawArgumentValue(433) -- VNAO Edit - Added + end -- VNAO Edit - Added + return nozzlePosR -- VNAO Edit - Added +end -- VNAO Edit - Added + + --- Get altitude of aircraft wrt carrier deck. Should give zero when the aircraft touched down. -- @param #AIRBOSS self -- @param Wrapper.Unit#UNIT unit Aircraft unit. @@ -11447,6 +11893,39 @@ function AIRBOSS:_GetOptLandingCoordinate() return self.landingcoord end +-- VNAO Edit - dded this whole function +--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I, HMS Invincible, and HMS Hermes and Invincible it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot. +-- @param #AIRBOSS self +-- @return Core.Point#COORDINATE Optimal landing coordinate. +function AIRBOSS:_GetOptLandingCoordinateWIRE() + + -- Start with stern coordiante. + self.landingcoord:UpdateFromCoordinate( self:_GetSternCoord() ) + + -- Final bearing. + local FB=self:GetFinalBearing(false) + + -- Cse + local case=self.case + + -- set Case III V/STOL abeam landing spot over deck -- Pene Testing + + + + -- Ideally we want to land between 2nd and 3rd wire. + if self.carrierparam.wire3 then + -- We take the position of the 3rd wire to approximately account for the length of the aircraft. + self.landingcoord:Translate( self.carrierparam.wire3 + 500, FB, true, true )-- adding 80 meter to wire to see if this is far enough to keep tracking a good lineup error (50 meters not enough), went from 250 meters to 500 meters 12/23/23, the more distance out front the less the error sensitivity down deck. I've checked the location of the landing spot 500 meters out front of 3 wire on CL with flares and smoke, appears to be dead on. Not sure why there's still this small difference between lined up left and right differences. + end + + -- Add 2 meters to account for aircraft height. + self.landingcoord.y = self.landingcoord.y + 2 + + + --self.landingcoord:FlareGreen() --for testing the lineup spot translated out in front of the carrier landing area by 500 meters. Appears good. + return self.landingcoord +end + --- Get landing spot on Tarawa and others. -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Primary landing spot coordinate. @@ -11479,10 +11958,12 @@ function AIRBOSS:GetHeading( magnetic ) hdg = hdg - self.magvar end - -- Adjust negative values. - if hdg < 0 then - hdg = hdg + 360 - end + -- -- Adjust negative values. + -- if hdg < 0 then + -- hdg = hdg + 360 + -- end + + hdg = hdg % 360 -- using this to replace the above function to prevent negative values and BRC higher than 360 return hdg end @@ -11598,7 +12079,7 @@ function AIRBOSS:GetHeadingIntoWind_old( vdeck, magnetic, coord ) local function adjustDegreesForWindSpeed(windSpeed) local degreesAdjustment = 0 -- the windspeeds are in m/s - + -- +0 degrees at 15m/s = 37kts -- +0 degrees at 14m/s = 35kts -- +0 degrees at 13m/s = 33kts @@ -11613,7 +12094,7 @@ function AIRBOSS:GetHeadingIntoWind_old( vdeck, magnetic, coord ) -- +20 degrees at 4m/s = 26kts -- +20 degrees at 3m/s = 26kts -- +30 degrees at 2m/s = 26kts 1s - + if windSpeed > 0 and windSpeed < 3 then degreesAdjustment = 30 elseif windSpeed >= 3 and windSpeed < 5 then @@ -11625,7 +12106,7 @@ function AIRBOSS:GetHeadingIntoWind_old( vdeck, magnetic, coord ) elseif windSpeed >= 13 then degreesAdjustment = 0 end - + return degreesAdjustment end @@ -11684,60 +12165,60 @@ function AIRBOSS:GetHeadingIntoWind_new( vdeck, magnetic, coord ) local h=self:GetHeading(magnetic) return h, math.min(vdeck, Vmax) end - + -- Convert wind speed to knots. vwind=UTILS.MpsToKnots(vwind) - + -- Wind to in knots. local windto=(windfrom+180)%360 - + -- Offset angle in rad. We also define the rotation to be clock-wise, which requires a minus sign. local alpha=math.rad(-Offset) - + -- Constant. local C = math.sqrt(math.cos(alpha)^2 / math.sin(alpha)^2 + 1) - + -- Upper limit of desired speed due to max boat speed. local vdeckMax=vwind + math.cos(alpha) * Vmax - + -- Lower limit of desired speed due to min boat speed. local vdeckMin=vwind + math.cos(alpha) * Vmin - - + + -- Speed of ship so it matches the desired speed. local v=0 - - -- Angle wrt. to wind TO-direction + + -- Angle wrt. to wind TO-direction local theta=0 if vdeck>vdeckMax then -- Boat cannot go fast enough - + -- Set max speed. v=Vmax - + -- Calculate theta. theta = math.asin(v/(vwind*C)) - math.asin(-1/C) - + elseif vdeckvwind then -- Too little wind - + -- Set theta to 90° theta=math.pi/2 - + -- Set speed. v = math.sqrt(vdeck^2 - vwind^2) - + else -- Normal case theta = math.asin(vdeck * math.sin(alpha) / vwind) @@ -11746,9 +12227,10 @@ function AIRBOSS:GetHeadingIntoWind_new( vdeck, magnetic, coord ) -- Magnetic heading. local magvar= magnetic and self.magvar or 0 - + -- Ship heading so cross wind is min for the given wind. - local intowind = (540 + (windto - magvar + math.deg(theta) )) % 360 + -- local intowind = (540 + (windto - magvar + math.deg(theta) )) % 360 -- VNAO Edit: Using old heading into wind algorithm + local intowind = self:GetHeadingIntoWind_old(vdeck,magnetic) -- VNAO Edit: Using old heading into wind algorithm return intowind, v end @@ -12024,6 +12506,10 @@ function AIRBOSS:_LSOadvice( playerData, glideslopeError, lineupError ) -- "Power." self:RadioTransmission( self.LSORadio, self.LSOCall.POWER, false, nil, nil, true ) advice = advice + self.LSOCall.POWER.duration + elseif glideslopeError= 25 then --Circuit added + grade = "_LIG_" --Circuit added + -- Time in groove for AV-8B elseif playerData.actype == AIRBOSS.AircraftCarrier.AV8B and t < 55 then -- VSTOL Late Hover stop selection too fast to Abeam LDG Spot AV-8B. grade = "FAST V/STOL Groove" @@ -12140,7 +12633,7 @@ function AIRBOSS:_EvalGrooveTime( playerData ) end -- The unicorn! - if t >= 16.4 and t <= 16.6 then + if t >= 16.49 and t <= 16.6 then grade = "_OK_" end @@ -12164,21 +12657,28 @@ function AIRBOSS:_LSOgrade( playerData ) local function count( base, pattern ) return select( 2, string.gsub( base, pattern, "" ) ) end - + local TIG = "" -- Analyse flight data and convert to LSO text. + if playerData.Tgroove and playerData.Tgroove <= 360 and playerData.case < 3 then --Circuit Added + TIG = self:_EvalGrooveTime( playerData ) or "N/A" --Circuit Added + end --Circuit Added local GXX, nXX = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.XX ) local GIM, nIM = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.IM ) local GIC, nIC = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.IC ) local GAR, nAR = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.AR ) + local GIW, nIW = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.IW ) -- VNAO Edit - Added -- VTOL approach, which is graded differently (currently only Harrier). local vtol=playerData.actype==AIRBOSS.AircraftCarrier.AV8B -- Put everything together. - local G = GXX .. " " .. GIM .. " " .. " " .. GIC .. " " .. GAR + local G = GXX .. " " .. GIM .. " " .. " " .. GIC .. " " .. GAR .. " " .. GIW .. " " .. TIG -- VNAO Edit - Added .. " " .. GIW Circuit added TIG + + local gradeWithDeviations = GXX .. "[" .. nXX .. "] " .. GIM .. "[" .. nIM .. "] " .. GIC .. "[" .. nIC .. "] " .. GAR .. "[" .. nAR .. "] " .. GIW .. "[" .. nIW .. "]" -- VNAO Edit - Added + env.info("LSO Grade [with deviation count]: " .. gradeWithDeviations) -- VNAO Edit - Added -- Count number of minor/small nS, normal nN and major/large deviations nL. - local N=nXX+nIM+nIC+nAR + local N=nXX+nIM+nIC+nAR+nIW -- VNAO Edit - Added +nIW local nL=count(G, '_')/2 local nS=count(G, '%(') local nN=N-nS-nL @@ -12186,17 +12686,30 @@ function AIRBOSS:_LSOgrade( playerData ) -- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn. local Tgroove=playerData.Tgroove - local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false + local TgrooveUnicorn=Tgroove and (Tgroove>=16.49 and Tgroove<=16.59) or false -- VNAO Edit - Original values 15.0/18.99 local TgrooveVstolUnicorn=Tgroove and (Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false local grade local points - if N == 0 and (TgrooveUnicorn or TgrooveVstolUnicorn or playerData.case==3) then - -- No deviations, should be REALLY RARE! - grade = "_OK_" - points = 5.0 - G = "Unicorn" - else + -- if N == 0 and (TgrooveUnicorn or TgrooveVstolUnicorn or playerData.case==3) then -- VNAO Edit - Removed TgrooveUnicorn and case 3 as a factor + if N == 0 and TgrooveVstolUnicorn then -- VNAO Edit - Removed TgrooveUnicorn and case 3 as a factor + -- No deviations, should be REALLY RARE! + grade = "_OK_" + points = 5.0 + G = "Unicorn" + end -- VNAO Edit - Added + if N==0 and TgrooveUnicorn then -- VNAO Edit - Added + -- No deviations, should be REALLY RARE! -- VNAO Edit - Added + if playerData.wire == 3 then -- VNAO Edit - Added + grade="_OK_" -- VNAO Edit - Added + points=5.0 -- VNAO Edit - Added + G="Unicorn" -- VNAO Edit - Added + else -- VNAO Edit - Added + grade="OK" -- VNAO Edit - Added + points=4.0 -- VNAO Edit - Added + end -- VNAO Edit - Added + + else if vtol then @@ -12206,7 +12719,7 @@ function AIRBOSS:_LSOgrade( playerData ) -- Normal laning part at the beginning local Gb = GXX .. " " .. GIM - -- Number of deviations that occurred at the the beginning of the landing (XX or IM). These are graded like in non-VTOL landings, i.e. on deviations is + -- Number of deviations that occurred at the the beginning of the landing (XX or IM). These are graded like in non-VTOL landings, i.e. on deviations is local N=nXX+nIM local nL=count(Gb, '_')/2 local nS=count(Gb, '%(') @@ -12224,7 +12737,7 @@ function AIRBOSS:_LSOgrade( playerData ) if nL>0 or nLv>1 then -- Larger deviations at XX or IM or at least one larger deviation IC or AR==> "No grade" 2.0 points. - -- In other words, we allow one larger deviation at IC+AR + -- In other words, we allow one larger deviation at IC+AR grade="--" points=2.0 elseif nN>0 or nNv>1 or nLv==1 then @@ -12271,6 +12784,7 @@ function AIRBOSS:_LSOgrade( playerData ) text = text .. "# of normal deviations = " .. nN .. "\n" text = text .. "# of small deviations ( = " .. nS .. "\n" self:T2( self.lid .. text ) + env.info(text)-- VNAO Edit - Added -- Special cases. if playerData.wop then @@ -12339,6 +12853,25 @@ function AIRBOSS:_LSOgrade( playerData ) end end + + -- -- VNAO EDIT: Subtract 1pt from overall grade if it is a 1 wire. If it's already a 1pt pass, ignore. + -- if playerData.wire == 1 and points > 1 then -- VNAO EDIT: added + -- if points == 4 then -- VNAO EDIT: added + -- points = 3 -- VNAO EDIT: added + -- grade = "(OK)" -- VNAO EDIT: added + -- elseif points == 3 then -- VNAO EDIT: added + -- points = 2 -- VNAO EDIT: added + -- grade = "--" -- VNAO EDIT: added + -- end -- VNAO EDIT: added + -- end -- VNAO EDIT: added + + -- Circuit edit only take points awary from a 1 wire if there are more than 4 other deviations + if playerData.wire == 1 and points >= 3 and N > 4 then + points = points -1 + end + + env.info("Returning: " .. grade .. " " .. points .. " " .. G) + return grade, points, G end @@ -12372,26 +12905,34 @@ function AIRBOSS:_Flightdata2Text( playerData, groovestep ) local AOA = fdata.AoA local GSE = fdata.GSE local LUE = fdata.LUE + local LUEwire = fdata.LUEwire -- VNAO Edit - Added + local Lnoz = fdata.LeftNozzle -- VNAO Edit - Added + local Rnoz = fdata.RightNozzle -- VNAO Edit - Added local ROL = fdata.Roll + local GT = fdata.GT -- Circuit Added -- Aircraft specific AoA values. local acaoa = self:_GetAircraftAoA( playerData ) -- Angled Approach. - local P = nil - if step == AIRBOSS.PatternStep.GROOVE_XX and ROL <= 4.0 and playerData.case < 3 then - if LUE > self.lue.RIGHT then - P = underline( "AA" ) - elseif LUE > self.lue.RightMed then - P = "AA " - elseif LUE > self.lue.Right then - P = little( "AA" ) + -- VNAO Edit - changed this to regualr LUL at X, made my own Angled Approach check starting at the wake based on angle of bank less than 15 degrees/10 degrees/2 degrees + local P = nil -- VNAO Edit - Added + if step == AIRBOSS.PatternStep.GROOVE_XX and ROL <= 3.5 and playerData.case < 3 then -- VNAO Edit - Changed, original ROL val 4.0 + if LUE > 3.2 then -- VNAO Edit - Original value self.lue.RIGHT + -- P = underline( "AA" ) + P = underline( "LUL") -- VNAO Edit - Added + elseif LUE > 2.2 then -- VNAO Edit - Original value self.lue.RightMed + -- P = "AA " + P="LUL" -- VNAO Edit - Added + elseif LUE > 1.2 then -- VNAO Edit - Original value self.lue.Right + -- P = little( "AA" ) + P= little( "LUL") -- VNAO Edit - Added end end -- Overshoot Start. local O = nil - if step == AIRBOSS.PatternStep.GROOVE_XX then + if step == AIRBOSS.PatternStep.GROOVE_XX and playerData.case < 3 then -- VNAO Edit - Added case 3 check if LUE < self.lue.LEFT then O = underline( "OS" ) elseif LUE < self.lue.Left then @@ -12403,100 +12944,263 @@ function AIRBOSS:_Flightdata2Text( playerData, groovestep ) -- Speed via AoA. Depends on aircraft type. local S = nil - if AOA > acaoa.SLOW then - S = underline( "SLO" ) - elseif AOA > acaoa.Slow then - S = "SLO" - elseif AOA > acaoa.OnSpeedMax then - S = little( "SLO" ) - elseif AOA < acaoa.FAST then - S = underline( "F" ) - elseif AOA < acaoa.Fast then - S = "F" - elseif AOA < acaoa.OnSpeedMin then - S = little( "F" ) - end + local A = nil --circuit moved this line to be seen outside of this scope + if step~=AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Added To avoid getting an AOA or GS grade in the wires... let's just check left or right in the wires + if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff == true and playerData.owo == true then -- VNAO Edit - Added + -- env.info('Adam MOOSE Edit -AR and waved off so do not add AOA or GS errors to comments ') -- VNAO Edit - Added + else -- VNAO Edit - Added + -- Speed via AoA. Depends on aircraft type. + if AOA > acaoa.SLOW then + S = underline( "SLO" ) + elseif AOA > acaoa.Slow then + S = "SLO" + elseif AOA > acaoa.OnSpeedMax then + S = little( "SLO" ) + elseif AOA < acaoa.FAST then + S = underline( "F" ) + elseif AOA < acaoa.Fast then + S = "F" + elseif AOA < acaoa.OnSpeedMin then + S = little( "F" ) + end - -- Glideslope/altitude. Good [-0.3, 0.4] asymmetric! - local A = nil - if GSE > self.gle.HIGH then - A = underline( "H" ) - elseif GSE > self.gle.High then - A = "H" - elseif GSE > self.gle._max then - A = little( "H" ) - elseif GSE < self.gle.LOW then - A = underline( "LO" ) - elseif GSE < self.gle.Low then - A = "LO" - elseif GSE < self.gle._min then - A = little( "LO" ) - end + -- Glideslope/altitude. Good [-0.3, 0.4] asymmetric! + + if GSE > self.gle.HIGH then + A = underline( "H" ) + elseif GSE > self.gle.High then + A = "H" + elseif GSE > self.gle._max then + A = little( "H" ) + elseif GSE < self.gle.LOW then + A = underline( "LO" ) + elseif GSE < self.gle.Low then + A = "LO" + elseif GSE < self.gle._min then + A = little( "LO" ) + end + end -- VNAO Edit - Added + end -- VNAO Edit - Added + + local stepMod=self:_GS(step) -- VNAO Edit - Added - -- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5] local D = nil - if LUE > self.lue.RIGHT then - D = underline( "LUL" ) - elseif LUE > self.lue.Right then - D = "LUL" - elseif LUE > self.lue._max then - D = little( "LUL" ) - elseif playerData.case < 3 then - if LUE < self.lue.LEFT and step ~= AIRBOSS.PatternStep.GROOVE_XX then - D = underline( "LUR" ) - elseif LUE < self.lue.Left and step ~= AIRBOSS.PatternStep.GROOVE_XX then - D = "LUR" - elseif LUE < self.lue._min and step ~= AIRBOSS.PatternStep.GROOVE_XX then - D = little( "LUR" ) - end - elseif playerData.case == 3 then - if LUE < self.lue.LEFT then - D = underline( "LUR" ) - elseif LUE < self.lue.Left then - D = "LUR" - elseif LUE < self.lue._min then - D = little( "LUR" ) - end + local DW = nil + local Rol = nil -- VNAO Edit - Added + local Noz = nil -- VNAO Edit - Added + -- VNAO Edit - Now replacing LUL with AA at Groove start for case 1 and 2, to fix problem with getting a full LUL with an (AA) and still getting a 4.0 OK + -- if playerData.case < 3 then -- VNAO Edit - Added + if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff == true and playerData.owo == true then + -- env.info('Adam MOOSE Edit -AR and waved off so do not add LU errors to comments ') + else + if LUE > self.lue.RIGHT and step ~= AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Changed + D = underline( "LUL" ) -- VNAO Edit - Changed + elseif LUE > self.lue.Right and step ~= AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Changed + D = "LUL" -- VNAO Edit - Changed + elseif LUE > self.lue._max and step ~= AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Changed + D = little( "LUL" ) -- VNAO Edit - Changed + elseif LUE < self.lue.LEFT and step ~= AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Changed + D = underline( "LUR" ) -- VNAO Edit - Changed + elseif LUE < self.lue.Left and step ~= AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Changed + D = "LUR" -- VNAO Edit - Changed + elseif LUE < self.lue._min and step ~= AIRBOSS.PatternStep.GROOVE_XX and step~=AIRBOSS.PatternStep.GROOVE_IW then -- VNAO Edit - Changed + D = little( "LUR" ) -- VNAO Edit - Changed + end -- VNAO Edit - Changed end + --[[ + elseif playerData.case == 3 then -- VNAO Edit - Changed + if LUE>self.lue.RIGHT then -- VNAO Edit - Changed + D=underline("LUL") -- VNAO Edit - Changed + elseif LUE>self.lue.Right then -- VNAO Edit - Changed + D="LUL" -- VNAO Edit - Changed + elseif LUE>self.lue._max then -- VNAO Edit - Changed + D=little("LUL") -- VNAO Edit - Changed + elseif LUE < self.lue.LEFT then -- VNAO Edit - Changed + D = underline( "LUR" ) -- VNAO Edit - Changed + elseif LUE < self.lue.Left then -- VNAO Edit - Changed + D = "LUR" -- VNAO Edit - Changed + elseif LUE < self.lue._min then -- VNAO Edit - Changed + D = little( "LUR" ) -- VNAO Edit - Changed + end -- VNAO Edit - Changed + end -- VNAO Edit - Changed +--]] + if step == AIRBOSS.PatternStep.GROOVE_IW and playerData.waveoff == false and playerData.owo == false then -- VNAO Edit - Added check for waveoff so we don't get any IN THE WIRE comments if its a WO + -- env.info('Adam MOOSE Edit -IW code: checking for Landing Left or Landing Right, lue: '..LUEwire) + -- env.info("Adam MOOSE Edit -IW code: ROLL IN THE WIRES: "..ROL) + + if LUEwire>1.2 then -- VNAO Edit - Added by using real time attitude monitor and using the ladders as a reference for distance down the deck, focusing on ladder rungs 4-8 but using specifiically values at ((rung 4 12/24/23) 12/24/23), average rung 3 and 4 after testing 12/27 + -- env.info("Adam MOOSE Edit -IW code: Line up > 1.2, _LL_") + DW=underline("LL")-- VNAO Edit - Added + elseif LUEwire>0.4 then -- VNAO Edit - Added by using real time attitude monitor and using the ladders as a reference for distance down the deck, focusing on ladder rungs 4-8 but using specifiically values at (rung 4 12/24/23), average rung 3 and 4 after testing 12/27 + -- env.info("Adam MOOSE Edit -IW code: Line up > 0.4, LL") + DW="LL"-- VNAO Edit - Added + elseif LUEwire>0.25 then -- VNAO Edit - Added by using real time attitude monitor and using the ladders as a reference for distance down the deck, focusing on ladder rungs 4-8 but using specifiically values at (rung 4 12/24/23), average rung 3 and 4 after testing 12/27 + -- env.info("Adam MOOSE Edit -IW code: Line up > 0.25, (LL)") + DW=little("LL")-- VNAO Edit - Added + elseif LUEwire<-1.17 then -- VNAO Edit - Added by using real time attitude monitor and using the ladders as a reference for distance down the deck, focusing on ladder rungs 4-8 but using specifiically values at (rung 4 12/24/23), average rung 3 and 4 after testing 12/27 + -- env.info("Adam MOOSE Edit -IW code: Line up < -1.17, _LR_") + DW=underline("LR")-- VNAO Edit - Added + elseif LUEwire<-0.46 then -- VNAO Edit - Added by using real time attitude monitor and using the ladders as a reference for distance down the deck, focusing on ladder rungs 4-8 but using specifiically values at (rung 4 12/24/23), average rung 3 and 4 after testing 12/27 + -- env.info("Adam MOOSE Edit -IW code: Line up < -0.46, LR") + DW="LR"-- VNAO Edit - Added + elseif LUEwire<-0.25 then -- VNAO Edit - Added by using real time attitude monitor and using the ladders as a reference for distance down the deck, focusing on ladder rungs 4-8 but using specifiically values at (rung 4 12/24/23), average rung 3 and 4 after testing 12/27 + -- env.info("Adam MOOSE Edit -IW code: Line up < -0.25, (LR)") + DW=little("LR")-- VNAO Edit - Added + else-- VNAO Edit - Added + -- env.info('Adam MOOSE Edit -IW code: checking for Landing Left or Landing Right: NO LINEUP ERROR DECTECTED ') + -- env.info("Adam MOOSE Edit -IW code: NO LINEUP ERORR DECTECTED ") + end-- VNAO Edit - Added + + if ROL > 5 and ROL <= 10 then-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: ROLL GREATER THAN 5 DEGREES, maybe a (LRWD) ") + Rol=little("LRWD")-- VNAO Edit - Added + elseif ROL > 10 and ROL <= 15 then-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: ROLL GREATER THAN 10 DEGREES, maybe a LRWD ") + Rol=("LRWD")-- VNAO Edit - Added + elseif ROL > 15 then-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: ROLL GREATER THAN 20 DEGREES, maybe a _LRWD_ ") + Rol=underline("LRWD")-- VNAO Edit - Added + elseif ROL < -5 and ROL >= -10 then-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: ROLL GREATER THAN 5 DEGREES, maybe a (LLWD) ") + Rol=little("LLWD")-- VNAO Edit - Added + elseif ROL < -10 and ROL >= -15 then-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: ROLL GREATER THAN 10 DEGREES, maybe a LLWD ") + Rol=("LLWD")-- VNAO Edit - Added + elseif ROL < -15 then-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: ROLL GREATER THAN 20 DEGREES, maybe a _LLWD_ ") + Rol=underline("LLWD")-- VNAO Edit - Added + else-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: ROLL is acceptable, less than 5 degrees left or right ") + end-- VNAO Edit - Added + + + + local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET-- VNAO Edit - Added + local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B-- VNAO Edit - Added + + if hornet then-- VNAO Edit - Added + if Lnoz > 0.6 and Rnoz > 0.6 then -- VNAO Edit - Added check them both, it's possilbe there could be a single engine landing and one is in idle perhaps? + -- env.info("Adam MOOSE Edit -IW code: Throttles maybe close to idle? EGIW? L:"..Lnoz.." R: "..Rnoz) + Noz = underline("EG")-- VNAO Edit - Added + else-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: Throttles ok position- L:"..Lnoz.." R: "..Rnoz) + end-- VNAO Edit - Added + end-- VNAO Edit - Added + + --[[ if tomcat then-- VNAO Edit - Added + if Lnoz > 0.9 and Rnoz > 0.9 then -- VNAO Edit - Added Appears that when engines are idle nozzle arguments are Zero. Anything more than idle is close to 1 (0.95 to be exact) and Burner reduces to 0.3 + -- env.info("Adam MOOSE Edit -IW code: Throttles maybe close to idle? EGIW? L:"..Lnoz.." R: "..Rnoz) + Noz = underline("EG")-- VNAO Edit - Added + else-- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code: Throttles ok position- L:"..Lnoz.." R: "..Rnoz) + end-- VNAO Edit - Added + end-- VNAO Edit - Added ]] + + if playerData.Tgroove and playerData.Tgroove <= 360 and playerData.case < 3 then --Circuit Added + local grooveTime = playerData.Tgroove --Circuit Added + if grooveTime > 19 or grooveTime < 15 then --Circuit Added + GT = "" --Circuit Added + end --Circuit Added + end --Circuit Added + + end-- VNAO Edit - Added -- Compile. local G = "" local n = 0 - -- Fly trough. + + -- VNAO Edit - Added WU, AA and AFU calls to LSO grading + if stepMod == "XX" then -- VNAO Edit - Added + if playerData.case < 3 then -- VNAO Edit - Added + if fdata.WrappedUp then -- VNAO Edit - Added + env.info("Adding WrappedUp deviation.") + G=G..fdata.WrappedUp -- VNAO Edit - Added + n=n + 1 -- VNAO Edit - Added + -- env.info('MOOSE/AIRBOSS- MOD - GRADE.... you`RE GETTING WU`D') + end -- VNAO Edit - Added + if fdata.AngledApch then -- VNAO Edit - Added + env.info("Adding AngledApch deviation.") + G=G..fdata.AngledApch -- VNAO Edit - Added + n=n+1 -- VNAO Edit - Added + -- env.info('Adam MOOSE Edit -AA code: Function- _Flightdata2Text: Trying to add AA comment only to the "Start" in comments for player: '..playerData.name) + -- env.info('MOOSE/AIRBOSS- MOD - GRADE.... you`RE GETTING AA`D') + end -- VNAO Edit - Added + if fdata.AFU then -- VNAO Edit - Added + env.info("Adding AFU deviation.") + G=G..fdata.AFU -- VNAO Edit - Added + n=n+1 -- VNAO Edit - Added + -- env.info('Adam MOOSE Edit -AFU code: Function- _Flightdata2Text: Trying to add AFU comment only to the "Start" in comments for player: '..playerData.name) + -- env.info('MOOSE/AIRBOSS- MOD - GRADE....you`RE GETTING AFU`D') + end + end -- VNAO Edit - Added + end -- VNAO Edit - Added + + -- Fly through. if fdata.FlyThrough then G = G .. fdata.FlyThrough end -- Angled Approach - doesn't affect score, advisory only. - if P then - G = G .. P - n = n - end + -- if P then -- VNAO Edit - Commented out + -- G = G .. P -- VNAO Edit - Commented out + -- n = n + 1 -- VNAO Edit - Added + 1 -- VNAO Edit - Commented out + -- end -- VNAO Edit - Commented out -- Speed. if S then + env.info("Adding speed deviation.")--VNAO Added G = G .. S n = n + 1 end -- Glide slope. if A then + env.info("Adding altitude deviation.")--VNAO Added G = G .. A n = n + 1 end -- Line up. if D then + env.info("Adding line up deviation.")--VNAO Added G = G .. D n = n + 1 end -- Drift in Lineup if fdata.Drift then + env.info("Adding drift deviation.")--VNAO Added G = G .. fdata.Drift n = n -- Drift doesn't affect score, advisory only. end -- Overshoot. if O then + env.info("Adding overshoot deviation.")--VNAO Added G = G .. O n = n + 1 end + if DW then -- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code:TRYING TO ADD COMMENT LANDED LEFT/RIGHT OF CENTER LINE ") -- VNAO Edit - Added + env.info("Adding landed L/R deviation.") + G = G .. DW -- VNAO Edit - Added + n = n + 1 -- VNAO Edit - Added + end -- VNAO Edit - Added + + if Rol then -- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code:TRYING TO ADD COMMENT LANDED LEFT/RIGHT WING DOWN ") -- VNAO Edit - Added + env.info("Adding landed rol deviation.") + G = G .. Rol -- VNAO Edit - Added + n = n + 1 -- VNAO Edit - Added + end -- VNAO Edit - Added + + if Noz then -- VNAO Edit - Added + -- env.info("Adam MOOSE Edit -IW code:TRYING TO ADD COMMENT EG ") -- VNAO Edit - Added + env.info("Adding eased guns deviation.") + G = G .. Noz -- VNAO Edit - Added + n = n + 1-- try to add 3 to get an automatic no grade. -- VNAO Edit - Added + end -- VNAO Edit - Added + + if GT then --Circuit Added + G = G .. GT --Circuit Added + n = n + 1 --Circuit Added + end --Circuit Added + -- Add current step. local step = self:_GS( step ) step = step:gsub( "XX", "X" ) @@ -13211,6 +13915,7 @@ function AIRBOSS:_Debrief( playerData ) Points = points end + -- My LSO grade. local mygrade = {} -- #AIRBOSS.LSOgrade mygrade.grade = grade @@ -13267,7 +13972,8 @@ function AIRBOSS:_Debrief( playerData ) -- Time in the groove. Only Case I/II and not pattern WO. if playerData.Tgroove and playerData.Tgroove <= 360 and playerData.case < 3 then - text = text .. string.format( "\nTime in the groove %.1f seconds: %s", playerData.Tgroove, self:_EvalGrooveTime( playerData ) ) + -- text = text .. string.format( "\nTime in the groove %.1f seconds: %s", playerData.Tgroove, self:_EvalGrooveTime( playerData ) ) --Circuit changed removed groove comment + text = text .. string.format( "\nTime in the groove %.1f seconds.", playerData.Tgroove ) end end @@ -13720,7 +14426,7 @@ function AIRBOSS:CarrierTurnIntoWind( time, vdeck, uturn ) local deltaH = self:_GetDeltaHeading( hdg, hiw ) -- Debug output - self:I( self.lid .. string.format( "Carrier steaming into the wind (%.1f kts). Heading=%03d-->%03d (Delta=%.1f), Speed=%.1f knots, Distance=%.1f NM, Time=%d sec", + self:I( self.lid .. string.format( "Carrier steaming into the wind (%.1f kts). Heading=%03d-->%03d (Delta=%.1f), Speed=%.1f knots, Distance=%.1f NM, Time=%d sec", UTILS.MpsToKnots( vwind ), hdg, hiw, deltaH, speedknots, distNM, speedknots, time ) ) -- Current coordinate. @@ -14932,12 +15638,12 @@ function AIRBOSS:RadioTransmission( radio, call, loud, delay, interval, click, p if radio == nil or call == nil then return end - + if not self.SRS then - + -- Create a new radio transmission item. local transmission = {} -- #AIRBOSS.Radioitem - + transmission.radio = radio transmission.call = call transmission.Tplay = timer.getAbsTime() + (delay or 0) @@ -14945,49 +15651,49 @@ function AIRBOSS:RadioTransmission( radio, call, loud, delay, interval, click, p transmission.isplaying = false transmission.Tstarted = nil transmission.loud = loud and call.loud - + -- Player onboard number if sender has one. if self:_IsOnboard( call.modexsender ) then self:_Number2Radio( radio, call.modexsender, delay, 0.3, pilotcall ) end - + -- Play onboard number if receiver has one. if self:_IsOnboard( call.modexreceiver ) then self:_Number2Radio( radio, call.modexreceiver, delay, 0.3, pilotcall ) end - + -- Add transmission to the right queue. local caller = "" if radio.alias == "LSO" then - + table.insert( self.RQLSO, transmission ) - + caller = "LSOCall" - + -- Schedule radio queue checks. if not self.RQLid then self:T( self.lid .. string.format( "Starting LSO radio queue." ) ) self.RQLid = self.radiotimer:Schedule( nil, AIRBOSS._CheckRadioQueue, { self, self.RQLSO, "LSO" }, 0.02, 0.05 ) end - + elseif radio.alias == "MARSHAL" then - + table.insert( self.RQMarshal, transmission ) - + caller = "MarshalCall" - + if not self.RQMid then self:T( self.lid .. string.format( "Starting Marhal radio queue." ) ) self.RQMid = self.radiotimer:Schedule( nil, AIRBOSS._CheckRadioQueue, { self, self.RQMarshal, "MARSHAL" }, 0.02, 0.05 ) end - + end - + -- Append radio click sound at the end of the transmission. if click then self:RadioTransmission( radio, self[caller].CLICK, false, delay ) end - + else -- SRS transmission @@ -14998,7 +15704,7 @@ function AIRBOSS:RadioTransmission( radio, call, loud, delay, interval, click, p local voice = nil local gender = nil local culture = nil - + if radio.alias == "AIRBOSS" then frequency = self.AirbossRadio.frequency modulation = self.AirbossRadio.modulation @@ -15006,13 +15712,13 @@ function AIRBOSS:RadioTransmission( radio, call, loud, delay, interval, click, p gender = self.AirbossRadio.gender culture = self.AirbossRadio.culture end - + if radio.alias == "MARSHAL" then voice = self.MarshalRadio.voice gender = self.MarshalRadio.gender culture = self.MarshalRadio.culture end - + if radio.alias == "LSO" then frequency = self.LSORadio.frequency modulation = self.LSORadio.modulation @@ -15020,7 +15726,7 @@ function AIRBOSS:RadioTransmission( radio, call, loud, delay, interval, click, p gender = self.LSORadio.gender culture = self.LSORadio.culture end - + if pilotcall then voice = self.PilotRadio.voice gender = self.PilotRadio.gender @@ -15034,16 +15740,16 @@ function AIRBOSS:RadioTransmission( radio, call, loud, delay, interval, click, p modulation = self.AirbossRadio.modulation radio.alias = "AIRBOSS" end - + local volume = nil - + if loud then volume = 1.0 end - + --local text = tostring(call.modexreceiver).."; "..radio.alias.."; "..call.subtitle local text = call.subtitle - self:T(self.lid..text) + self:T(self.lid..text) local srstext = self:_GetNiceSRSText(text) self.SRSQ:NewTransmission(srstext, call.duration, self.SRS, nil, 0.1, nil, call.subtitle, call.subduration, frequency, modulation, gender, culture, voice, volume, radio.alias) end @@ -15063,11 +15769,11 @@ function AIRBOSS:SetSRSPilotVoice( Voice, Gender, Culture ) self.PilotRadio.voice = Voice or MSRS.Voices.Microsoft.David self.PilotRadio.gender = Gender or "male" self.PilotRadio.culture = Culture or "en-US" - + if (not Voice) and self.SRS and self.SRS:GetProvider() == MSRS.Provider.GOOGLE then self.PilotRadio.voice = MSRS.Voices.Google.Standard.en_US_Standard_J end - + return self end @@ -15381,44 +16087,44 @@ function AIRBOSS:MessageToPlayer( playerData, message, sender, receiver, duratio -- SCHEDULER:New(nil, self.MessageToPlayer, {self, playerData, message, sender, receiver, duration, clear}, delay) self:ScheduleOnce( delay, self.MessageToPlayer, self, playerData, message, sender, receiver, duration, clear ) else - + if not self.SRS then -- Wait until previous sound finished. local wait = 0 - + -- Onboard number to get the attention. if receiver == playerData.onboard then - + -- Which voice over number to use. if sender and (sender == "LSO" or sender == "MARSHAL" or sender == "AIRBOSS") then - + -- User sound of board number. wait = wait + self:_Number2Sound( playerData, sender, receiver ) - + end end - + -- Negative. if string.find( text:lower(), "negative" ) then local filename = self:_RadioFilename( self.MarshalCall.NEGATIVE, false, "MARSHAL" ) USERSOUND:New( filename ):ToGroup( playerData.group, wait ) wait = wait + self.MarshalCall.NEGATIVE.duration end - + -- Affirm. if string.find( text:lower(), "affirm" ) then local filename = self:_RadioFilename( self.MarshalCall.AFFIRMATIVE, false, "MARSHAL" ) USERSOUND:New( filename ):ToGroup( playerData.group, wait ) wait = wait + self.MarshalCall.AFFIRMATIVE.duration end - + -- Roger. if string.find( text:lower(), "roger" ) then local filename = self:_RadioFilename( self.MarshalCall.ROGER, false, "MARSHAL" ) USERSOUND:New( filename ):ToGroup( playerData.group, wait ) wait = wait + self.MarshalCall.ROGER.duration end - + -- Play click sound to end message. if wait > 0 then local filename = self:_RadioFilename( self.MarshalCall.CLICK ) @@ -15431,7 +16137,7 @@ function AIRBOSS:MessageToPlayer( playerData, message, sender, receiver, duratio local voice = self.MarshalRadio.voice local gender = self.MarshalRadio.gender local culture = self.MarshalRadio.culture - + if not sender then sender = "AIRBOSS" end if string.find(sender,"AIRBOSS" ) then @@ -16722,6 +17428,17 @@ function AIRBOSS:_RequestEmergency( _unitName ) -- Cleared. text = "affirmative, you can bypass the pattern and are cleared for final approach!" + -- VNAO Edit - Added wrapped up call to LSO grading + playerData.wrappedUpAtWakeLittle = false -- VNAO Edit - Added + playerData.wrappedUpAtWakeFull = false -- VNAO Edit - Added + playerData.wrappedUpAtWakeUnderline = false -- VNAO Edit - Added + playerData.wrappedUpAtStartLittle = false -- VNAO Edit - Added + playerData.wrappedUpAtStartFull = false -- VNAO Edit - Added + playerData.wrappedUpAtStartUnderline = false -- VNAO Edit - Added + playerData.AAatWakeLittle = false -- VNAO Edit - Added + playerData.AAatWakeFull = false -- VNAO Edit - Added + playerData.AAatWakeUnderline = false -- VNAO Edit - Added + -- Now, if player is in the marshal or waiting queue he will be removed. But the new leader should stay in or not. local lead = self:_GetFlightLead( playerData ) @@ -17049,7 +17766,7 @@ function AIRBOSS:_RemoveSectionMember( playerData, sectionmember ) return false end ---- Set all flights within 100 meters to be part of my section. +--- Set all flights within maxsectiondistance meters to be part of my section (default: 100 meters). -- @param #AIRBOSS self -- @param #string _unitName Name of the player unit. function AIRBOSS:_SetSection( _unitName ) @@ -17067,7 +17784,7 @@ function AIRBOSS:_SetSection( _unitName ) local mycoord = _unit:GetCoordinate() -- Max distance up to which section members are allowed. - local dmax = 100 + local dmax = self.maxsectiondistance -- Check if player is in Marshal or pattern queue already. local text @@ -18604,8 +19321,4 @@ function AIRBOSS:onafterLSOGrade(From, Event, To, playerData, grade) self.funkmanSocket:SendTable(result) end -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +end \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 98d42aa37..a425eacae 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -397,6 +397,7 @@ AUFTRAG = { conditionPush = {}, conditionSuccessSet = false, conditionFailureSet = false, + repeatDelay = 1, } --- Global mission counter. @@ -1320,13 +1321,19 @@ end -- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. -- @param #number Speed Orbit indicated airspeed in knots at the set altitude ASL. Default 350 KIAS. -- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West). --- @param #number Leg Length of race-track in NM. Default 10 NM. +-- @param #number Leg Length of race-track in NM. Default 10 NM. Set to 0 for a simple circular orbit. -- @param #number RefuelSystem Refueling system (0=boom, 1=probe). This info is *only* for AIRWINGs so they launch the right tanker type. -- @return #AUFTRAG self function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSystem) - + + local mission + if Leg == 0 then + mission=AUFTRAG:NewORBIT_CIRCLE(Coordinate,Altitude,Speed) + else + mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate,Altitude,Speed,Heading,Leg) + end -- Create ORBIT first. - local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) + --local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) -- Mission type TANKER. mission.type=AUFTRAG.Type.TANKER @@ -1428,7 +1435,7 @@ function AUFTRAG:NewCAP(ZoneCAP, Altitude, Speed, Coordinate, Heading, Leg, Targ mission:_SetLogID() -- DCS task parameters: - mission.engageZone=ZoneCAP + mission.engageZone=ZoneCAP or Coordinate mission.engageTargetTypes=TargetTypes or {"Air"} -- Mission options: @@ -1715,9 +1722,45 @@ function AUFTRAG:NewSEAD(Target, Altitude) return mission end +--- **[AIR]** Create a SEAD in Zone mission. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE TargetZone The target zone to attack. +-- @param #number Altitude Engage altitude in feet. Default 25000 ft. +-- @param #table TargetTypes Table of string of DCS known target types, defaults to {"Air Defence"}. See [DCS Target Attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) +-- @param #number Duration Engage this much time when the AUFTRAG starts executing. +-- @return #AUFTRAG self +function AUFTRAG:NewSEADInZone(TargetZone, Altitude, TargetTypes, Duration) + + local mission=AUFTRAG:New(AUFTRAG.Type.SEAD) + + --mission:_TargetFromObject(TargetZone) + + -- DCS Task options: + mission.engageWeaponType=ENUMS.WeaponFlag.Auto + mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) + mission.engageZone = TargetZone + mission.engageTargetTypes = TargetTypes or {"Air Defence"} + + -- Mission options: + mission.missionTask=ENUMS.MissionTask.SEAD + mission.missionAltitude=mission.engageAltitude + mission.missionFraction=0.2 + mission.optionROE=ENUMS.ROE.OpenFire + mission.optionROT=ENUMS.ROT.EvadeFire + + mission.categories={AUFTRAG.Category.AIRCRAFT} + + mission.DCStask=mission:GetDCSMissionTask() + + mission:SetDuration(Duration or 1800) + + return mission +end + --- **[AIR]** Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate. -- @param #AUFTRAG self --- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object. +-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object. -- @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 @@ -1749,11 +1792,12 @@ end --- **[AIR]** Create a BOMBING mission. Flight will drop bombs a specified coordinate. -- See [DCS task bombing](https://wiki.hoggitworld.com/view/DCS_task_bombing). -- @param #AUFTRAG self --- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object. +-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC, SET_GROUP, SET_UNIT, SET_STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. -- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options. +-- @param #boolean Divebomb If true, use a dive bombing attack approach. -- @return #AUFTRAG self -function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType) +function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType, Divebomb) local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) @@ -1770,6 +1814,7 @@ function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType) mission.missionFraction=0.5 mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.NoReaction -- No reaction is better. + mission.optionDivebomb = Divebomb or nil -- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed. mission.dTevaluate=5*60 @@ -2279,8 +2324,9 @@ end -- @param #number Speed Speed in knots. -- @param #number Altitude Altitude in feet. Only for airborne units. Default 2000 feet ASL. -- @param #string Formation Formation used by ground units during patrol. Default "Off Road". +-- @param #number StayInZoneTime Stay this many seconds in the zone when done, only then drive back. -- @return #AUFTRAG self -function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation) +function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation, StayInZoneTime) local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) @@ -2294,6 +2340,7 @@ function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation) mission.optionROE=ENUMS.ROE.ReturnFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto + mission.StayInZoneTime = StayInZoneTime mission.missionFraction=0.1 mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil @@ -2966,6 +3013,16 @@ function AUFTRAG:SetRepeat(Nrepeat) return self end + +--- **[LEGION, COMMANDER, CHIEF]** Set the repeat delay in seconds after a mission is successful/failed. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level. +-- @param #AUFTRAG self +-- @param #number Nrepeat Repeat delay in seconds. Default 1. +-- @return #AUFTRAG self +function AUFTRAG:SetRepeatDelay(RepeatDelay) + self.repeatDelay = RepeatDelay + return self +end + --- **[LEGION, COMMANDER, CHIEF]** Set how many times the mission is repeated if it fails. Only valid if the mission is handled by a LEGION (AIRWING, BRIGADE, FLEET) or higher level. -- @param #AUFTRAG self -- @param #number Nrepeat Number of repeats. Default 0. @@ -3961,6 +4018,23 @@ function AUFTRAG:IsOver() return over end +--- Check if mission is repeatable. +-- @param #AUFTRAG self +-- @return #boolean If true, mission is repeatable. +function AUFTRAG:IsRepeatable() + local repeatmeS=self.repeatedSuccess0 then + self:T(self.lid..string.format("CheckGroupsDone: Mission is STARTED state %s [FSM=%s] and count of alive OPSGROUP > zero. Mission NOT DONE!", self.status, self:GetState())) + return false + end return true end @@ -5160,7 +5241,7 @@ function AUFTRAG:onafterSuccess(From, Event, To) -- Repeat mission. self:T(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N)) - self:Repeat() + self:__Repeat(self.repeatDelay) else @@ -5202,7 +5283,7 @@ function AUFTRAG:onafterFailed(From, Event, To) -- Repeat mission. self:T(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N)) - self:Repeat() + self:__Repeat(self.repeatDelay) else @@ -6108,10 +6189,13 @@ function AUFTRAG:GetDCSMissionTask() -- BOMBING Mission -- --------------------- - local DCStask=CONTROLLABLE.TaskBombing(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, Divebomb) + local coords = self.engageTarget:GetCoordinates() + for _, coord in pairs(coords) do + local DCStask = CONTROLLABLE.TaskBombing(nil, coord:GetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, self.optionDivebomb) + + table.insert(DCStasks, DCStask) + end - table.insert(DCStasks, DCStask) - elseif self.type==AUFTRAG.Type.STRAFING then ---------------------- @@ -6147,8 +6231,16 @@ function AUFTRAG:GetDCSMissionTask() ----------------- -- CAP Mission -- ----------------- - - local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil, self.engageZone:GetVec2(), self.engageZone:GetRadius(), self.engageTargetTypes, Priority) + + local Vec2 = self.engageZone:GetVec2() + local Radius + if self.engageZone:IsInstanceOf("COORDINATE") then + Radius = UTILS.NMToMeters(20) + else + Radius = self.engageZone:GetRadius() + end + + local DCStask=CONTROLLABLE.EnRouteTaskEngageTargetsInZone(nil, Vec2, Radius, self.engageTargetTypes, Priority) table.insert(self.enrouteTasks, DCStask) @@ -6302,18 +6394,47 @@ function AUFTRAG:GetDCSMissionTask() -- Add enroute task SEAD. Disabled that here because the group enganges everything on its route. --local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil, self.TargetType) --table.insert(self.enrouteTasks, DCStask) - - self:_GetDCSAttackTask(self.engageTarget, DCStasks) - + + if self.engageZone then + + --local DCStask=CONTROLLABLE.EnRouteTaskSEAD(nil, self.engageTargetTypes) + --table.insert(self.enrouteTasks, DCStask) + self.engageZone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) + local ScanUnitSet = self.engageZone:GetScannedSetUnit() + local SeadUnitSet = SET_UNIT:New() + for _,_unit in pairs (ScanUnitSet.Set) do + local unit = _unit -- Wrapper.Unit#UNTI + if unit and unit:IsAlive() and unit:HasSEAD() then + self:T("Adding UNIT for SEAD: "..unit:GetName()) + local task = CONTROLLABLE.TaskAttackUnit(nil,unit,GroupAttack,AI.Task.WeaponExpend.ALL,1,Direction,self.engageAltitude,2956984318) + table.insert(DCStasks, task) + SeadUnitSet:AddUnit(unit) + end + end + self.engageTarget = TARGET:New(SeadUnitSet) + --local OrbitTask = CONTROLLABLE.TaskOrbitCircle(nil,self.engageAltitude,self.missionSpeed,self.engageZone:GetCoordinate()) + --local Point = self.engageZone:GetVec2() + --local OrbitTask = CONTROLLABLE.TaskOrbitCircleAtVec2(nil,Point,self.engageAltitude,self.missionSpeed) + --table.insert(DCStasks, OrbitTask) + + else + + self:_GetDCSAttackTask(self.engageTarget, DCStasks) + + end + elseif self.type==AUFTRAG.Type.STRIKE then -------------------- -- STRIKE Mission -- -------------------- - local DCStask=CONTROLLABLE.TaskAttackMapObject(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) + local coords = self.engageTarget:GetCoordinates() + for _, coord in pairs(coords) do + local DCStask=CONTROLLABLE.TaskAttackMapObject(nil, coord:GetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) - table.insert(DCStasks, DCStask) + table.insert(DCStasks, DCStask) + end elseif self.type==AUFTRAG.Type.TANKER or self.type==AUFTRAG.Type.RECOVERYTANKER then diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 09aa3324b..062f32d7b 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -17,7 +17,7 @@ -- === -- -- ### Author: **applevangelist** --- @date Last Update Jan 2025 +-- @date Last Update July 2025 -- @module Ops.AWACS -- @image OPS_AWACS.jpg @@ -237,7 +237,7 @@ do -- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm. -- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25) -- -- Set up SRS on port 5010 - change the below to your path and port --- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010) +-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio","female","en-GB",5010) -- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON" -- testawacs:SetRejectionZone(ZONE:FindByName("Red Border")) -- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS. @@ -255,7 +255,7 @@ do -- -- The CAP station zone is called "Fremont". We will be on 255 AM. Note the Orbit Zone is given as *nil* in the `New()`-Statement -- local testawacs = AWACS:New("GCI Senaki",AwacsAW,"blue",AIRBASE.Caucasus.Senaki_Kolkhi,nil,ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM ) -- -- Set up SRS on port 5010 - change the below to your path and port --- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010) +-- testawacs:SetSRS("C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio","female","en-GB",5010) -- -- Add a "red" border we don't want to cross, set up in the mission editor with a late activated helo named "Red Border#ZONE_POLYGON" -- testawacs:SetRejectionZone(ZONE:FindByName("Red Border")) -- -- Our CAP flight will have the callsign "Ford", we want 4 AI planes, Time-On-Station is four hours, doing 300 kn IAS. @@ -509,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.71", -- #string + version = "0.2.73", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -1123,7 +1123,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self.EscortMissionReplacement = {} -- SRS - self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.Gender = "female" self.Culture = "en-GB" self.Voice = nil @@ -1242,6 +1242,8 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station self:AddTransition("*", "Intercept", "*") self:AddTransition("*", "InterceptSuccess", "*") self:AddTransition("*", "InterceptFailure", "*") + self:AddTransition("*", "VIDSuccess", "*") + self:AddTransition("*", "VIDFailure", "*") self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -1365,18 +1367,38 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station -- @param #string To To state. --- On After "InterceptSuccess" event. Intercept successful. - -- @function [parent=#AWACS] OnAfterIntercept + -- @function [parent=#AWACS] OnAfterInterceptSuccess -- @param #AWACS self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- On After "InterceptFailure" event. Intercept failure. - -- @function [parent=#AWACS] OnAfterIntercept + -- @function [parent=#AWACS] OnAfterInterceptFailure -- @param #AWACS self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. + + --- On After "VIDSuccess" event. Intercept successful. + -- @function [parent=#AWACS] OnAfterVIDSuccess + -- @param #AWACS self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number GID Managed group ID (Player) + -- @param Wrapper.Group#GROUP Group (Player) Group done the VID + -- @param #AWACS.ManagedContact Contact The contact that was VID'd + + --- On After "VIDFailure" event. Intercept failure. + -- @function [parent=#AWACS] OnAfterVIDFailure + -- @param #AWACS self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #number GID Managed group ID (Player) + -- @param Wrapper.Group#GROUP Group (Player) Group done the VID + -- @param #AWACS.ManagedContact Contact The contact that was VID'd return self end @@ -1574,6 +1596,16 @@ function AWACS:SetLocale(Locale) return self end +--- [User] Set own coordinate for BullsEye. +-- @param #AWACS self +-- @param Core.Point#COORDINATE +-- @return #AWACS self +function AWACS:SetBullsCoordinate(Coordinate) + self:T(self.lid.."SetBullsCoordinate") + self.AOCoordinate = Coordinate + return self +end + --- [User] Set the max mission range flights can be away from their home base. -- @param #AWACS self -- @param #number NM Distance in nautical miles @@ -1999,7 +2031,9 @@ function AWACS:SetAdditionalZone(Zone, Draw) self.BorderZone = Zone if self.debug then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) - MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) + if self.AllowMarkers then + MARKER:New(Zone:GetCoordinate(),"Defensive Zone"):ToCoalition(self.coalition) + end elseif Draw then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) end @@ -2019,7 +2053,9 @@ function AWACS:SetRejectionZone(Zone,Draw) --MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToAll() elseif self.debug then Zone:DrawZone(self.coalition,{1,0.64,0},1,{1,0.64,0},0.2,1,true) - MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) + if self.AllowMarkers then + MARKER:New(Zone:GetCoordinate(),"Rejection Zone"):ToCoalition(self.coalition) + end end return self end @@ -2091,7 +2127,7 @@ end --- [User] Set AWACS SRS TTS details - see @{Sound.SRS} for details. `SetSRS()` will try to use as many attributes configured with @{Sound.SRS#MSRS.LoadConfigFile}() as possible. -- @param #AWACS self --- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #string Gender Defaults to "male" -- @param #string Culture Defaults to "en-US" -- @param #number Port Defaults to 5002 @@ -2104,7 +2140,7 @@ end -- @return #AWACS self function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend) self:T(self.lid.."SetSRS") - self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" self.Gender = Gender or MSRS.gender or "male" self.Culture = Culture or MSRS.culture or "en-US" self.Port = Port or MSRS.port or 5002 @@ -3263,12 +3299,14 @@ function AWACS:_VID(Group,Declaration) local vidpos = self.gettext:GetEntry("VIDPOS",self.locale) text = string.format(vidpos,Callsign,self.callsigntxt, Declaration) self:T(text) + self:__VIDSuccess(3,GID,group,cluster) else -- too far away self:T("Contact VID not close enough") local vidneg = self.gettext:GetEntry("VIDNEG",self.locale) text = string.format(vidneg,Callsign,self.callsigntxt) self:T(text) + self:__VIDFailure(3,GID,group,cluster) end self:_NewRadioEntry(text,text,GID,Outcome,true,true,false,true) end @@ -4070,10 +4108,14 @@ function AWACS:_CreateAnchorStackFromMarker(Name,Coord) if self.debug then AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end else local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end end self.AnchorStacks:Push(AnchorStackOne,newname) @@ -4116,10 +4158,14 @@ function AWACS:_CreateAnchorStack() --self.AnchorStacks:Flush() AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end else local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end end self.AnchorStacks:Push(AnchorStackOne,newname) else @@ -4143,10 +4189,14 @@ function AWACS:_CreateAnchorStack() if self.debug then AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end else local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end end self.AnchorStacks:Push(AnchorStackOne,newname) end @@ -5078,10 +5128,14 @@ function AWACS:AddCAPAirWing(AirWing,Zone) if self.debug then AnchorStackOne.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end else local stationtag = string.format("Station: %s\nCoordinate: %s",newname,self.StationZone:GetCoordinate():ToStringLLDDM()) - AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + AnchorStackOne.AnchorMarker=MARKER:New(AnchorStackOne.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end end self.AnchorStacks:Push(AnchorStackOne,newname) AirWing.HasOwnStation = true @@ -5924,23 +5978,35 @@ function AWACS:onafterStart(From, Event, To) self.OpsZone:DrawZone(self.coalition,{1,0,0},1,{1,0,0},0.2,5,true) local AOCoordString = self.AOCoordinate:ToStringLLDDM() local Rocktag = string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) - MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) + if self.AllowMarkers then + MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) + end self.StationZone:DrawZone(self.coalition,{0,0,1},1,{0,0,1},0.2,5,true) local stationtag = string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) if not self.GCI then - MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end self.OrbitZone:DrawZone(self.coalition,{0,1,0},1,{0,1,0},0.2,5,true) - MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) + if self.AllowMarkers then + MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) + end end else local AOCoordString = self.AOCoordinate:ToStringLLDDM() local Rocktag = string.format("FEZ: %s\nBulls Coordinate: %s",self.AOName,AOCoordString) - MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) + if self.AllowMarkers then + MARKER:New(self.AOCoordinate,Rocktag):ToCoalition(self.coalition) + end if not self.GCI then - MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) + if self.AllowMarkers then + MARKER:New(self.OrbitZone:GetCoordinate(),"AIC Orbit Zone"):ToCoalition(self.coalition) + end end local stationtag = string.format("Station: %s\nCoordinate: %s",self.StationZoneName,self.StationZone:GetCoordinate():ToStringLLDDM()) - MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + if self.AllowMarkers then + MARKER:New(self.StationZone:GetCoordinate(),stationtag):ToCoalition(self.coalition) + end end if not self.GCI then diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index e7805f25a..5538b3689 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1,25 +1,25 @@ --- **Ops** - Combat Search and Rescue. -- -- === --- +-- -- **CSAR** - MOOSE based Helicopter CSAR Operations. --- +-- -- === --- +-- -- ## Missions:--- **Ops** -- Combat Search and Rescue. -- -- === --- +-- -- **CSAR** - MOOSE based Helicopter CSAR Operations. --- +-- -- === --- +-- -- ## Missions: -- -- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/CSAR) --- +-- -- === --- +-- -- **Main Features:** -- -- * MOOSE-based Helicopter CSAR Operations for Players. @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update Jan 2025 +-- Last Update Oct 2025 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -49,24 +49,24 @@ -- === -- -- # CSAR Concept --- +-- -- * MOOSE-based Helicopter CSAR Operations for Players. -- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. --- * No need for extra MIST loading. +-- * No need for extra MIST loading. -- * Additional events to tailor your mission. -- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASVAC requests). --- +-- -- ## 0. Prerequisites --- +-- -- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. -- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". --- +-- -- Example sound files are here: [Moose Sound](https://github.com/FlightControl-Master/MOOSE_SOUND/tree/master/CTLD%20CSAR) --- +-- -- ## 1. Basic Setup --- +-- -- A basic setup example is the following: --- +-- -- -- Instantiate and start a CSAR for the blue side, with template "Downed Pilot" and alias "Luftrettung" -- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") -- -- options @@ -74,9 +74,9 @@ -- my_csar.invisiblecrew = false -- downed pilot spawn is visible -- -- start the FSM -- my_csar:__Start(5) --- +-- -- ## 2. Options --- +-- -- The following options are available (with their defaults). Only set the ones you want changed: -- -- mycsar.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. @@ -87,7 +87,7 @@ -- mycsar.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. -- mycsar.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. -- mycsar.enableForAI = false -- set to false to disable AI pilots from being rescued. --- mycsar.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to mycsar.extractDistance in meters. +-- mycsar.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to mycsar.extractDistance in meters. -- mycsar.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal. -- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible. @@ -95,14 +95,14 @@ -- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. Will also try to add ZONE and STATIC objects with this prefix once at startup. -- mycsar.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- mycsar.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. --- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. +-- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. -- mycsar.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. -- mycsar.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. --- mycsar.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- mycsar.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- mycsar.verbose = 0 -- set to > 1 for stats output for debugging. -- -- limit amount of downed pilots spawned by **ejection** events -- mycsar.limitmaxdownedpilots = true --- mycsar.maxdownedpilots = 10 +-- mycsar.maxdownedpilots = 10 -- -- allow to set far/near distance for approach and optionally pilot must open doors -- mycsar.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- mycsar.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters @@ -119,18 +119,18 @@ -- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each -- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it). -- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins). --- +-- -- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups --- +-- -- -- Parameter: Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups. -- -- Needs to be set before starting the CSAR instance. -- local myset = SET_GROUP:New():FilterPrefixes("Helikopter"):FilterCoalitions("red"):FilterStart() -- mycsar:SetOwnSetPilotGroups(myset) --- +-- -- ## 2.2 SRS Features and Other Features --- +-- -- mycsar.useSRS = false -- Set true to use FF\'s SRS integration --- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\ExternalAudio\\" -- adjust your own path in your SRS installation -- server(!) -- mycsar.SRSchannel = 300 -- radio channel -- mycsar.SRSModulation = radio.modulation.AM -- modulation -- mycsar.SRSport = 5002 -- and SRS Server port @@ -147,88 +147,88 @@ -- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. -- mycsar.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane -- mycsar.CreateRadioBeacons = true -- set to false to disallow creating ADF radio beacons. --- +-- -- ## 3. Results --- +-- -- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: --- +-- -- mycsar.rescues -- number of successful landings *with* saved pilots -- mycsar.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) --- +-- -- ## 4. Events -- -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. -- These are: --- --- ### 4.1. PilotDown. --- +-- +-- ### 4.1. PilotDown. +-- -- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: --- +-- -- function my_csar:OnAfterPilotDown(from, event, to, spawnedgroup, frequency, groupname, coordinates_text) -- ... your code here ... -- end --- --- ### 4.2. Approach. --- +-- +-- ### 4.2. Approach. +-- -- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: --- +-- -- function my_csar:OnAfterApproach(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.3. Boarded. --- +-- +-- ### 4.3. Boarded. +-- -- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: --- +-- -- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname, description) -- ... your code here ... -- end --- --- ### 4.4. Returning. --- +-- +-- ### 4.4. Returning. +-- -- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: --- +-- -- function my_csar:OnAfterReturning(from, event, to, heliname, groupname) -- ... your code here ... -- end --- --- ### 4.5. Rescued. --- +-- +-- ### 4.5. Rescued. +-- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: --- +-- -- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname, pilotssaved) -- ... your code here ... --- end +-- end -- -- ## 5. Spawn downed pilots at location to be picked up. --- +-- -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: --- +-- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- -- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat --- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) --- +-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) +-- -- ## 6. Save and load downed pilots - Persistance --- +-- -- You can save and later load back downed pilots to make your mission persistent. -- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts. -- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you. --- +-- -- Use the following options to manage your saves: --- +-- -- mycsar.enableLoadSave = true -- allow auto-saving and loading of files -- mycsar.saveinterval = 600 -- save every 10 minutes -- mycsar.filename = "missionsave.csv" -- example filename -- mycsar.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path --- +-- -- Then use an initial load at the beginning of your mission: --- +-- -- mycsar:__Load(10) --- +-- -- **Caveat:** --- Dropped troop noMessage and forcedesc parameters aren't saved. +-- Dropped troop noMessage and forcedesc parameters aren't saved. -- -- @field #CSAR CSAR = { @@ -254,7 +254,7 @@ CSAR = { hoverStatus = {}, -- tracks status of a helis hover above a downed pilot pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for pilotLives = {}, -- tracks how many lives a pilot has - useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below csarPrefix = {}, template = nil, mash = {}, @@ -263,6 +263,7 @@ CSAR = { rescuedpilots = 0, limitmaxdownedpilots = true, maxdownedpilots = 10, + useFIFOLimitReplacement = false, -- If true, it will remove the oldest downed pilot when a new one is added, if the limit is reached. allheligroupset = nil, topmenuname = "CSAR", ADFRadioPwr = 1000, @@ -299,11 +300,12 @@ CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 CSAR.AircraftType["Mi-8MTV2"] = 12 -CSAR.AircraftType["Mi-8MT"] = 12 -CSAR.AircraftType["Mi-24P"] = 8 +CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 -CSAR.AircraftType["Bell-47"] = 2 +CSAR.AircraftType["Bell-47"] = 2 CSAR.AircraftType["UH-60L"] = 10 +CSAR.AircraftType["UH-60L_DAP"] = 2 CSAR.AircraftType["AH-64D_BLK_II"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2 CSAR.AircraftType["MH-60R"] = 10 @@ -313,7 +315,7 @@ CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.30" +CSAR.version="1.0.34" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -333,12 +335,12 @@ CSAR.version="1.0.30" -- @param #string Alias An *optional* alias how this object is called in the logs etc. -- @return #CSAR self function CSAR:New(Coalition, Template, Alias) - + -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #CSAR - + BASE:T({Coalition, Template, Alias}) - + --set Coalition if Coalition and type(Coalition)=="string" then if Coalition=="blue" then @@ -357,12 +359,12 @@ function CSAR:New(Coalition, Template, Alias) self.coalition = Coalition self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) end - + -- Set alias. if Alias then self.alias=tostring(Alias) else - self.alias="Red Cross" + self.alias="Red Cross" if self.coalition then if self.coalition==coalition.side.RED then self.alias="IFRC" @@ -371,10 +373,10 @@ function CSAR:New(Coalition, Template, Alias) end end end - + -- Set some string id for output to DCS.log file. self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") - + -- Start State. self:SetStartState("Stopped") @@ -384,12 +386,12 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "Status", "*") -- CSAR status update. self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. - self:AddTransition("*", "Landed", "*") -- CSAR heli landed + self:AddTransition("*", "Landed", "*") -- CSAR heli landed self:AddTransition("*", "Boarded", "*") -- Pilot boarded. self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. self:AddTransition("*", "KIA", "*") -- Pilot killed in action. - self:AddTransition("*", "Load", "*") -- CSAR load event. + self:AddTransition("*", "Load", "*") -- CSAR load event. self:AddTransition("*", "Save", "*") -- CSAR save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -410,7 +412,7 @@ function CSAR:New(Coalition, Template, Alias) self.woundedGroups = {} -- contains the new group of units self.downedPilots = {} -- Replacement woundedGroups self.downedpilotcounter = 1 - + -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH self.rescuedpilots = 0 -- counter for saved pilots @@ -420,9 +422,9 @@ function CSAR:New(Coalition, Template, Alias) self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal - self.invisiblecrew = false -- Set to true to make wounded crew insvisible - self.messageTime = 15 -- Time to show longer messages for in seconds - self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS + self.invisiblecrew = false -- Set to true to make wounded crew insvisible + self.messageTime = 15 -- Time to show longer messages for in seconds + self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter self.loadtimemax = 135 -- seconds @@ -431,11 +433,11 @@ function CSAR:New(Coalition, Template, Alias) self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued. self.max_units = 6 --max number of pilots that can be carried - self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot self.mashprefix = {"MASH"} -- prefixes used to find MASHes - + self.autosmoke = false -- automatically smoke location when heli is near self.autosmokedistance = 2000 -- distance for autosmoke -- added 0.1.4 @@ -448,39 +450,39 @@ function CSAR:New(Coalition, Template, Alias) self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters self.pilotmustopendoors = false -- switch to true to enable check on open doors self.suppressmessages = false - + -- added 0.1.11r1 self.rescuehoverheight = 20 self.rescuehoverdistance = 10 - + -- added 0.1.12 self.countryblue= country.id.USA self.countryred = country.id.RUSSIA self.countryneutral = country.id.UN_PEACEKEEPERS - + -- added 0.1.3 self.csarUsePara = false -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection - + -- added 0.1.4 self.wetfeettemplate = nil self.usewetfeet = false - + -- added 1.0.15 self.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane - - self.ADFRadioPwr = 1000 - + + self.ADFRadioPwr = 500 + -- added 1.0.16 self.PilotWeight = 80 - + -- Own SET_GROUP if any self.UserSetGroup = nil - + -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) self.useSRS = false -- Use FF\'s SRS integration - self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone" -- adjust your own path in your server(!) + self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation self.SRSport = 5002 -- port @@ -492,20 +494,20 @@ function CSAR:New(Coalition, Template, Alias) self.CSARVoice = MSRS.Voices.Google.Standard.en_US_Standard_A self.CSARVoiceMS = MSRS.Voices.Microsoft.Hedda self.coordinate = nil -- Core.Point#COORDINATE - + local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CSAR_%s_Persist.csv",AliaS) - + -- load and save downed pilots self.enableLoadSave = false self.filepath = nil self.saveinterval = 600 - + ------------------------ --- Pseudo Functions --- ------------------------ - - --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. + + --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. -- @function [parent=#CSAR] Start -- @param #CSAR self @@ -530,7 +532,7 @@ function CSAR:New(Coalition, Template, Alias) -- @function [parent=#CSAR] __Status -- @param #CSAR self -- @param #number delay Delay in seconds. - -- + -- -- --- Triggers the FSM event "Load". -- @function [parent=#CSAR] Load -- @param #CSAR self @@ -539,7 +541,7 @@ function CSAR:New(Coalition, Template, Alias) -- @function [parent=#CSAR] __Load -- @param #CSAR self -- @param #number delay Delay in seconds. - + --- Triggers the FSM event "Save". -- @function [parent=#CSAR] Load -- @param #CSAR self @@ -548,7 +550,7 @@ function CSAR:New(Coalition, Template, Alias) -- @function [parent=#CSAR] __Save -- @param #CSAR self -- @param #number delay Delay in seconds. - + --- On After "PilotDown" event. Downed Pilot detected. -- @function [parent=#CSAR] OnAfterPilotDown -- @param #CSAR self @@ -560,7 +562,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Leadername Name of the #UNIT of the downed pilot. -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. -- @param #string Playername Player name if any given. Might be nil! - + --- On After "Aproach" event. Heli close to downed Pilot. -- @function [parent=#CSAR] OnAfterApproach -- @param #CSAR self @@ -569,8 +571,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. -- @param #string Woundedgroupname Name of the downed pilot\'s group. - - --- On After "Landed" event. Heli landed at an airbase. + + --- On After "Landed" event. Heli landed at an airbase. -- @function [parent=#CSAR] OnAfterLanded -- @param #CSAR self -- @param #string From From state. @@ -578,8 +580,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string HeliName Name of the #UNIT which has landed. -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where the heli landed. - - --- On After "Boarded" event. Downed pilot boarded heli. + + --- On After "Boarded" event. Downed pilot boarded heli. -- @function [parent=#CSAR] OnAfterBoarded -- @param #CSAR self -- @param #string From From state. @@ -589,7 +591,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Woundedgroupname Name of the downed pilot\'s group. -- @param #string Description Descriptive name of the group. - --- On After "Returning" event. Heli can return home with downed pilot(s). + --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning -- @param #CSAR self -- @param #string From From state. @@ -597,8 +599,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. -- @param #string Woundedgroupname Name of the downed pilot\'s group. - - --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. + + --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued -- @param #CSAR self -- @param #string From From state. @@ -607,7 +609,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. - + --- On After "KIA" event. Pilot is dead. -- @function [parent=#CSAR] OnAfterKIA -- @param #CSAR self @@ -615,7 +617,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Pilotname Name of the pilot KIA. - + --- FSM Function OnAfterLoad. -- @function [parent=#CSAR] OnAfterLoad -- @param #CSAR self @@ -624,7 +626,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for loading. Default is "CSAR__Persist.csv". - + --- FSM Function OnAfterSave. -- @function [parent=#CSAR] OnAfterSave -- @param #CSAR self @@ -633,7 +635,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for saving. Default is "CSAR__Persist.csv". - + return self end @@ -655,7 +657,7 @@ end -- @return #CSAR self. function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet,BeaconName) self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) - + -- create new entry local DownedPilot = {} -- #CSAR.DownedPilot DownedPilot.desc = Description or "" @@ -671,7 +673,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.alive = true DownedPilot.wetfeet = Wetfeet or false DownedPilot.BeaconName = BeaconName - + -- Add Pilot local PilotTable = self.downedPilots local counter = self.downedpilotcounter @@ -687,14 +689,14 @@ end --- (Internal) Count pilots on board. -- @param #CSAR self -- @param #string _heliName --- @return #number count +-- @return #number count function CSAR:_PilotsOnboard(_heliName) self:T(self.lid .. " _PilotsOnboard") - local count = 0 + local count = 0 if self.inTransitGroups[_heliName] then - for _, _group in pairs(self.inTransitGroups[_heliName]) do - count = count + 1 - end + for _, _group in pairs(self.inTransitGroups[_heliName]) do + count = count + 1 + end end return count end @@ -704,15 +706,15 @@ end -- @param #string _unitname Name of unit. -- @return #boolean Outcome function CSAR:_DoubleEjection(_unitname) - if self.lastCrash[_unitname] then - local _time = self.lastCrash[_unitname] - if timer.getTime() - _time < 10 then - self:E(self.lid.."Caught double ejection!") - return true - end + if self.lastCrash[_unitname] then + local _time = self.lastCrash[_unitname] + if timer.getTime() - _time < 10 then + self:E(self.lid.."Caught double ejection!") + return true end - self.lastCrash[_unitname] = timer.getTime() - return false + end + self.lastCrash[_unitname] = timer.getTime() + return false end --- (User) Add a PLAYERTASK - FSM events will check success @@ -743,8 +745,8 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet) for i=1,10 do math.random(i,10000) end - if point:IsSurfaceTypeWater() or wetfeet then - point.y = 0 + if point:IsSurfaceTypeWater() or wetfeet then + point.y = 0 end local template = self.template if self.usewetfeet and wetfeet then @@ -769,29 +771,29 @@ end function CSAR:_AddSpecialOptions(group) self:T(self.lid.." _AddSpecialOptions") self:T({group}) - + local immortalcrew = self.immortalcrew local invisiblecrew = self.invisiblecrew if immortalcrew then local _setImmortal = { - id = 'SetImmortal', - params = { - value = true - } + id = 'SetImmortal', + params = { + value = true + } } group:SetCommand(_setImmortal) end if invisiblecrew then local _setInvisible = { - id = 'SetInvisible', - params = { - value = true - } + id = 'SetInvisible', + params = { + value = true + } } - group:SetCommand(_setInvisible) + group:SetCommand(_setInvisible) end - + group:OptionAlarmStateGreen() group:OptionROEHoldFire() return self @@ -806,7 +808,7 @@ end -- @param #string _unitName Unitname -- @param #string _playerName Playername -- @param #number _freq Frequency --- @param #boolean noMessage +-- @param #boolean noMessage -- @param #string _description Description -- @param #boolean forcedesc Use the description only for the pilot track entry -- @return Wrapper.Group#GROUP PilotInField Pilot GROUP object @@ -817,31 +819,31 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local template = self.template local wetfeet = false - + local surface = _point:GetSurfaceType() if surface == land.SurfaceType.WATER then wetfeet = true end - + if not _freq then _freq = self:_GenerateADFFrequency() if not _freq then _freq = 333000 end --noob catch - end - + end + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq,wetfeet) - + local _typeName = _typeName or "Pilot" - + if not noMessage then if _freq ~= 0 then --shagrat different CASEVAC msg - self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) - else - self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime) + else + self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime) + end end - end - + local BeaconName - + if _playerName then BeaconName = _playerName..math.random(1,10000) elseif _unitName then @@ -849,37 +851,37 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla else BeaconName = "Ghost-1-1"..math.random(1,10000) end - - if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 + + if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName) end - + self:_AddSpecialOptions(_spawnedGroup) local _text = _description if not forcedesc then if _playerName ~= nil then - if _freq ~= 0 then --shagrat - _text = "Pilot " .. _playerName - else - _text = "TIC - " .. _playerName - end + if _freq ~= 0 then --shagrat + _text = "Pilot " .. _playerName + else + _text = "TIC - " .. _playerName + end elseif _unitName ~= nil then - if _freq ~= 0 then --shagrat - _text = "AI Pilot of " .. _unitName - else - _text = "TIC - " .. _unitName + if _freq ~= 0 then --shagrat + _text = "AI Pilot of " .. _unitName + else + _text = "TIC - " .. _unitName + end end end - end self:T({_spawnedGroup, _alias}) - + local _GroupName = _spawnedGroup:GetName() or _alias self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet,BeaconName) self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. - + return _spawnedGroup, _alias end @@ -896,7 +898,7 @@ end function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() - + local _triggerZone = nil if type(_zone) == "string" then _triggerZone = ZONE:New(_zone) -- trigger to use as reference position @@ -905,16 +907,16 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _triggerZone = _zone -- is already a zone end end - + if _triggerZone == nil then self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) return end - + local _description = _description or "PoW" local unitname = unitname or "Old Rusty" local typename = typename or "Phantom II" - + local pos = {} if _randomPoint then local _pos = _triggerZone:GetRandomPointVec3() @@ -922,7 +924,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ else pos = _triggerZone:GetCoordinate() end - + local _country = 0 if _coalition == coalition.side.BLUE then _country = self.countryblue @@ -931,9 +933,9 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ else _country = self.countryneutral end - + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc) - + return self end @@ -948,7 +950,7 @@ end -- @param #string Typename (optional) Type of plane. -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: --- +-- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) @@ -967,14 +969,14 @@ end -- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitname, typename, forcedesc) --shagrat added internal Function _SpawnCASEVAC self:T(self.lid .. " _SpawnCASEVAC") - + local _description = _description or "CASEVAC" local unitname = unitname or "CASEVAC" local typename = typename or "Ground Commander" - + local pos = {} pos = _Point - + local _country = 0 if _coalition == coalition.side.BLUE then _country = self.countryblue @@ -985,7 +987,7 @@ function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitn end --shagrat set frequency to 0 as "flag" for no beacon self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, 0, _nomessage, _description, forcedesc) - + return self end @@ -999,10 +1001,10 @@ end -- @param #string Typename (optional) Type of plane. -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: --- +-- -- -- Create casualty "CASEVAC" at coordinate Core.Point#COORDINATE for the blue coalition. -- my_csar:SpawnCASEVAC( coordinate, coalition.side.BLUE ) -function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) +function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) return self end --shagrat end added CASEVAC @@ -1012,57 +1014,57 @@ end --shagrat end added CASEVAC function CSAR:_EventHandler(EventData) self:T(self.lid .. " _EventHandler") self:T({Event = EventData.id}) - + local _event = EventData -- Core.Event#EVENTDATA - - -- no Player + + -- no Player if self.enableForAI == false and _event.IniPlayerName == nil then - return self - end - - -- no event + return self + end + + -- no event if _event == nil or _event.initiator == nil then return self - - -- take off + + -- take off elseif _event.id == EVENTS.Takeoff then -- taken off self:T(self.lid .. " Event unit - Takeoff") - + local _coalition = _event.IniCoalition if _coalition ~= self.coalition then - return self --ignore! + return self --ignore! end - + if _event.IniGroupName then - self.takenOff[_event.IniUnitName] = true + self.takenOff[_event.IniUnitName] = true end - + return self - - -- player enter unit + + -- player enter unit elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit self:T(self.lid .. " Event unit - Player Enter") - + local _coalition = _event.IniCoalition self:T("Coalition = "..UTILS.GetCoalitionName(_coalition)) if _coalition ~= self.coalition then - return self --ignore! + return self --ignore! end - + if _event.IniPlayerName then - self.takenOff[_event.IniPlayerName] = nil + self.takenOff[_event.IniPlayerName] = nil end - + -- jumped into flying plane? self:T("Taken Off: "..tostring(_event.IniUnit:InAir(true))) - + if _event.IniUnit:InAir(true) then self.takenOff[_event.IniPlayerName] = true end - + local _unit = _event.IniUnit local _group = _event.IniGroup - + local function IsBronco(Group) local grp = Group -- Wrapper.Group#GROUP local typename = grp:GetTypeName() @@ -1070,186 +1072,208 @@ function CSAR:_EventHandler(EventData) if typename == "Bronco-OV-10A" then return true end return false end - + if _unit:IsHelicopter() or _group:IsHelicopter() or IsBronco(_group) then self:_AddMedevacMenuItem() - end - - return self - - elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then - -- Pilot dead - - self:T(self.lid .. " Event unit - Pilot Dead") - - local _unit = _event.IniUnit - local _unitname = _event.IniUnitName - local _group = _event.IniGroup - - if _unit == nil then - return self -- error! - end - - local _coalition = _event.IniCoalition - if _coalition ~= self.coalition then - return self --ignore! - end - - -- Catch multiple events here? - if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then - if self:_DoubleEjection(_unitname) then - return self - end + end - else - self:T(self.lid .. " Pilot has not taken off, ignore") - end - - return self - - elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then - if _event.id == EVENTS.PilotDead and self.csarOncrash == false then - return self - end - self:T(self.lid .. " Event unit - Pilot Ejected") - - local _unit = _event.IniUnit - local _unitname = _event.IniUnitName - local _group = _event.IniGroup - - self:T({_unit.UnitName, _unitname, _group.GroupName}) - - if _unit == nil then - self:T("Unit NIL!") - return self -- error! - end - - --local _coalition = _unit:GetCoalition() -- nil now for some reason - local _coalition = _group:GetCoalition() - if _coalition ~= self.coalition then - self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition)) - return self --ignore! - end - - - self:T("Airborne: "..tostring(_group:IsAirborne())) - self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName])) - - if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then - self:T(self.lid .. " Pilot has not taken off, ignore") - -- return self -- give up, pilot hasnt taken off - end - + return self + + elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then + -- Pilot dead + + self:T(self.lid .. " Event unit - Pilot Dead") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return self -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return self --ignore! + end + + -- Catch multiple events here? + if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then - self:T("Double Ejection!") return self end - - -- limit no of pilots in the field. - if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then - self:T("Maxed Downed Pilot!") - return self - end - - - -- TODO: Over water check --- EVENTS.LandingAfterEjection NOT triggered by DCS, so handle csarUsePara = true case - -- might create dual pilots in edge cases - - local wetfeet = false - + + else + self:T(self.lid .. " Pilot has not taken off, ignore") + end + + return self + + elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then + if _event.id == EVENTS.PilotDead and self.csarOncrash == false then + return self + end + self:T(self.lid .. " Event unit - Pilot Ejected") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + self:T({_unit.UnitName, _unitname, _group.GroupName}) + + if _unit == nil then + self:T("Unit NIL!") + return self -- error! + end + + --local _coalition = _unit:GetCoalition() -- nil now for some reason + local _coalition = _group:GetCoalition() + if _coalition ~= self.coalition then + self:T("Wrong coalition! Coalition = "..UTILS.GetCoalitionName(_coalition)) + return self --ignore! + end + + + self:T("Airborne: "..tostring(_group:IsAirborne())) + self:T("Taken Off: "..tostring(self.takenOff[_event.IniUnitName])) + + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then + self:T(self.lid .. " Pilot has not taken off, ignore") + -- return self -- give up, pilot hasnt taken off + end + + if self:_DoubleEjection(_unitname) then + self:T("Double Ejection!") + return self + end + + local initdcscoord = nil local initcoord = nil - if _event.id == EVENTS.Ejection then + if _event.id == EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord = _event.TgtDCSUnit:getPoint() initcoord = COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) - else + elseif _event.IniDCSUnit then initdcscoord = _event.IniDCSUnit:getPoint() initcoord = COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) end - + + -- Remove downed pilot if already exists to replace with new one. + if _event.IniPlayerName then + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local _foundPilot = nil + for _,_pilot in pairs(PilotTable) do + if _pilot.player == _event.IniPlayerName and _pilot.alive == true then + _foundPilot = _pilot + break + end + end + if _foundPilot then + self:T("Downed pilot already exists!") + _foundPilot.group:Destroy(false) + self:_RemoveNameFromDownedPilots(_foundPilot.name) + self:_CheckDownedPilotTable() + end + end + + -- limit no of pilots in the field. + if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then + self:T("Maxed Downed Pilot!") + return self + end + + + -- TODO: Over water check --- EVENTS.LandingAfterEjection NOT triggered by DCS, so handle csarUsePara = true case + -- might create dual pilots in edge cases + + local wetfeet = false + --local surface = _unit:GetCoordinate():GetSurfaceType() local surface = initcoord:GetSurfaceType() - + if surface == land.SurfaceType.WATER then self:T("Wet feet!") wetfeet = true - end - -- all checks passed, get going. + end + -- all checks passed, get going. if self.csarUsePara == false or (self.csarUsePara and wetfeet ) then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") + self:_AddCsar(_coalition, _unit:GetCountry(), initcoord , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, self.suppressmessages, "none") return self end - + elseif _event.id == EVENTS.Land then - self:T(self.lid .. " Landing") - - if _event.IniUnitName then - self.takenOff[_event.IniUnitName] = nil + self:T(self.lid .. " Landing") + + if _event.IniUnitName then + self.takenOff[_event.IniUnitName] = nil + end + + if self.allowFARPRescue then + + local _unit = _event.IniUnit -- Wrapper.Unit#UNIT + + if _unit == nil then + self:T(self.lid .. " Unit nil on landing") + return self -- error! end - - if self.allowFARPRescue then - - local _unit = _event.IniUnit -- Wrapper.Unit#UNIT - - if _unit == nil then - self:T(self.lid .. " Unit nil on landing") - return self -- error! - end - - --local _coalition = _event.IniCoalition - local _coalition = _event.IniGroup:GetCoalition() - if _coalition ~= self.coalition then - self:T(self.lid .. " Wrong coalition") - return self --ignore! - end - - self.takenOff[_event.IniUnitName] = nil - - local _place = _event.Place -- Wrapper.Airbase#AIRBASE - - if _place == nil then - self:T(self.lid .. " Landing Place Nil") - return self -- error! - end - - -- anyone on board? - if self.inTransitGroups[_event.IniUnitName] == nil then - -- ignore - return self - end - - if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then - self:__Landed(2,_event.IniUnitName, _place) - self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true) - else - self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) - end - end - - return self + + --local _coalition = _event.IniCoalition + local _coalition = _event.IniGroup:GetCoalition() + if _coalition ~= self.coalition then + self:T(self.lid .. " Wrong coalition") + return self --ignore! end - + + self.takenOff[_event.IniUnitName] = nil + + local _place = _event.Place -- Wrapper.Airbase#AIRBASE + + if _place == nil then + self:T(self.lid .. " Landing Place Nil") + return self -- error! + end + + -- anyone on board? + if self.inTransitGroups[_event.IniUnitName] == nil then + -- ignore + return self + end + + if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:__Landed(2,_event.IniUnitName, _place) + local IsHeloBase = false + local ABName = _place:GetName() + if ABName and string.find(ABName,"^H") then IsHeloBase = true end -- if name starts with an H it's an (possibly elevated) helo base on current maps + self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) + else + self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) + end + end + + return self + end + ---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location if (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then self:T("LANDING_AFTER_EJECTION") local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p) local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute' - local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" + local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot" local _country = _event.initiator:getCountry() local _coalition = coalition.getCountryCoalition( _country ) self:T("Country = ".._country.." Coalition = ".._coalition) if _coalition == self.coalition then local _freq = self:_GenerateADFFrequency() self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq}) - self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location. + self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, self.suppressmessages, "none")--shagrat add CSAR at Parachute location. Unit.destroy(_event.initiator) -- shagrat remove static Pilot model - end + end end - + return self end @@ -1267,38 +1291,38 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage, _pla local _freqk = _freq / 1000 local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) local _leadername = _leader:GetName() - + if not _nomessage then - if _freq ~= 0 then --shagrat - local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' - if self.coordtype ~= 2 then --not MGRS - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) - else - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) - local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) - local _text = string.format("%s requests SAR at %s, beacon at %.2f kilo hertz", _groupName, coordtext, _freqk) - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) + if _freq ~= 0 then --shagrat + local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' + if self.coordtype ~= 2 then --not MGRS + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + else + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) + local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) + local _text = string.format("%s requests SAR at %s, beacon at %.2f kilo hertz", _groupName, coordtext, _freqk) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) + end + else --shagrat CASEVAC msg + local _text = string.format("Pickup Zone at %s.", _coordinatesText ) + if self.coordtype ~= 2 then --not MGRS + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) + else + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) + local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) + local _text = string.format("Pickup Zone at %s.", coordtext ) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) + end end - else --shagrat CASEVAC msg - local _text = string.format("Pickup Zone at %s.", _coordinatesText ) - if self.coordtype ~= 2 then --not MGRS - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) - else - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true) - local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true) - local _text = string.format("Pickup Zone at %s.", coordtext ) - self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false) - end - end end - + for _,_heliName in pairs(self.csarUnits) do self:_CheckWoundedGroupStatus(_heliName, _groupName) end - -- trigger FSM event + -- trigger FSM event self:__PilotDown(2,_downedGroup, _freqk, _groupName, _coordinatesText, _playername) - + return self end @@ -1316,7 +1340,7 @@ function CSAR:_CheckNameInDownedPilots(name) found = true table = _pilot break - end + end end return found, table end @@ -1346,13 +1370,13 @@ end -- @return #CSAR self function CSAR:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) if not ShortCallsign or ShortCallsign == false then - self.ShortCallsign = false + self.ShortCallsign = false else - self.ShortCallsign = true + self.ShortCallsign = true end self.Keepnumber = Keepnumber or false self.CallsignTranslations = CallsignTranslations - return self + return self end --- (Internal) Check if a name is in downed pilot table and remove it. @@ -1385,13 +1409,13 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...not found in list!") return end - + local _woundedGroup = _downedpilot.group - if _woundedGroup ~= nil and _woundedGroup:IsAlive() then + if _woundedGroup ~= nil and _woundedGroup:IsAlive() then local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT - + local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking - + if _heliUnit == nil then self.heliVisibleMessage[_lookupKeyHeli] = nil self.heliCloseMessage[_lookupKeyHeli] = nil @@ -1399,15 +1423,15 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...heliunit nil!") return end - + local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) -- autosmoke if (self.autosmoke == true) and (_distance < self.autosmokedistance) and (_distance ~= -1) then - self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) + self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) end - + if _distance < self.approachdist_near and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then -- we\'re close, reschedule @@ -1417,21 +1441,21 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then -- message once if self.heliVisibleMessage[_lookupKeyHeli] == nil then - local _pilotName = _downedpilot.desc - if self.autosmoke == true then - local dist = self.autosmokedistance / 1000 - local disttext = string.format("%.0fkm",dist) - if _SETTINGS:IsImperial() then - local dist = UTILS.MetersToNM(self.autosmokedistance) - disttext = string.format("%.0fnm",dist) - end - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", self:_GetCustomCallSign(_heliName), _pilotName, disttext), self.messageTime,false,true) - else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) + local _pilotName = _downedpilot.desc + if self.autosmoke == true then + local dist = self.autosmokedistance / 1000 + local disttext = string.format("%.0fkm",dist) + if _SETTINGS:IsImperial() then + local dist = UTILS.MetersToNM(self.autosmokedistance) + disttext = string.format("%.0fnm",dist) end - --mark as shown for THIS heli and THIS group - self.heliVisibleMessage[_lookupKeyHeli] = true - end + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", self:_GetCustomCallSign(_heliName), _pilotName, disttext), self.messageTime,false,true) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) + end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end self.heliCloseMessage[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away @@ -1439,11 +1463,11 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:__Approach(-10,heliname,woundedgroupname) end else - self:T("...Downed Pilot KIA?!") - if not _downedpilot.alive then - --self:__KIA(1,_downedpilot.name) - self:_RemoveNameFromDownedPilots(_downedpilot.name, true) - end + self:T("...Downed Pilot KIA?!") + if not _downedpilot.alive then + --self:__KIA(1,_downedpilot.name) + self:_RemoveNameFromDownedPilots(_downedpilot.name, true) + end end return self end @@ -1457,11 +1481,11 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) -- have we popped smoke already in the last 5 mins local _lastSmoke = self.smokeMarkers[_woundedGroupName] if _lastSmoke == nil or timer.getTime() > _lastSmoke then - - local _smokecolor = self.smokecolor - local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360) ) --shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot - _smokecoord:Smoke(_smokecolor) - self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time + + local _smokecolor = self.smokecolor + local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360) ) --shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot + _smokecoord:Smoke(_smokecolor) + self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time end return self end @@ -1478,43 +1502,43 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam local _heliName = _heliUnit:GetName() local _groups = self.inTransitGroups[_heliName] local _unitsInHelicopter = self:_PilotsOnboard(_heliName) - + -- init table if there is none for this helicopter if not _groups then - self.inTransitGroups[_heliName] = {} - _groups = self.inTransitGroups[_heliName] + self.inTransitGroups[_heliName] = {} + _groups = self.inTransitGroups[_heliName] end - + -- if the heli can\'t pick them up, show a message and return local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] if _maxUnits == nil then _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, self:_GetCustomCallSign(_heliName), _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true) - return self + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, self:_GetCustomCallSign(_heliName), _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true) + return self end - + local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) local grouptable = downedgrouptable --#CSAR.DownedPilot self.inTransitGroups[_heliName][_woundedGroupName] = - { + { originalUnit = grouptable.originalUnit, woundedGroup = _woundedGroupName, side = self.coalition, desc = grouptable.desc, player = grouptable.player, - } + } _woundedGroup:Destroy(false) self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,true,true) - + self:_UpdateUnitCargoMass(_heliName) - + self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc) - + return self end @@ -1568,136 +1592,136 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG local _woundedLeader = _woundedGroup local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking - + local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot local _pilotName = _pilotable.desc - + local _reset = true - + if (_distance < 500) then - self:T(self.lid .. "[Pickup Debug] Helo closer than 500m: ".._lookupKeyHeli) - if self.heliCloseMessage[_lookupKeyHeli] == nil then - if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) - else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) - end - self.heliCloseMessage[_lookupKeyHeli] = true + self:T(self.lid .. "[Pickup Debug] Helo closer than 500m: ".._lookupKeyHeli) + if self.heliCloseMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) end - self:T(self.lid .. "[Pickup Debug] Checking landed vs Hover for ".._lookupKeyHeli) - -- have we landed close enough? - if not _heliUnit:InAir() then - self:T(self.lid .. "[Pickup Debug] Helo landed: ".._lookupKeyHeli) - if self.pilotRuntoExtractPoint == true then - if (_distance < self.extractDistance) then - local _time = self.landedStatus[_lookupKeyHeli] - self:T(self.lid .. "[Pickup Debug] Check pilot running or arrived ".._lookupKeyHeli) - if _time == nil then - self:T(self.lid .. "[Pickup Debug] Pilot running not arrived yet ".._lookupKeyHeli) - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) - _time = self.landedStatus[_lookupKeyHeli] - _woundedGroup:OptionAlarmStateGreen() - self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) - self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false) - else - _time = self.landedStatus[_lookupKeyHeli] - 10 - self.landedStatus[_lookupKeyHeli] = _time - end - --if _time <= 0 or _distance < self.loadDistance then - self:T(self.lid .. "[Pickup Debug] Pilot close enough? ".._lookupKeyHeli) - if _distance < self.loadDistance + 5 or _distance <= 13 then - self:T(self.lid .. "[Pickup Debug] Pilot close enough - YES ".._lookupKeyHeli) - if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) - self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) - return false - else - self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) - self.landedStatus[_lookupKeyHeli] = nil - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return true - end - end + self.heliCloseMessage[_lookupKeyHeli] = true + end + self:T(self.lid .. "[Pickup Debug] Checking landed vs Hover for ".._lookupKeyHeli) + -- have we landed close enough? + if not _heliUnit:InAir() then + self:T(self.lid .. "[Pickup Debug] Helo landed: ".._lookupKeyHeli) + if self.pilotRuntoExtractPoint == true then + if (_distance < self.extractDistance) then + local _time = self.landedStatus[_lookupKeyHeli] + self:T(self.lid .. "[Pickup Debug] Check pilot running or arrived ".._lookupKeyHeli) + if _time == nil then + self:T(self.lid .. "[Pickup Debug] Pilot running not arrived yet ".._lookupKeyHeli) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) + _time = self.landedStatus[_lookupKeyHeli] + _woundedGroup:OptionAlarmStateGreen() + self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false) + else + _time = self.landedStatus[_lookupKeyHeli] - 10 + self.landedStatus[_lookupKeyHeli] = _time + end + --if _time <= 0 or _distance < self.loadDistance then + self:T(self.lid .. "[Pickup Debug] Pilot close enough? ".._lookupKeyHeli) + if _distance < self.loadDistance + 5 or _distance <= 13 then + self:T(self.lid .. "[Pickup Debug] Pilot close enough - YES ".._lookupKeyHeli) + if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) + self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) + return false + else + self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return true end - else - self:T(self.lid .. "[Pickup Debug] Helo landed, pilot NOT set to run to helo ".._lookupKeyHeli) - if (_distance < self.loadDistance) then - self:T(self.lid .. "[Pickup Debug] Helo close enough, door check ".._lookupKeyHeli) - if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then - self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) - return false - else - self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return true - end end end else - self:T(self.lid .. "[Pickup Debug] Helo hovering".._lookupKeyHeli) - local _unitsInHelicopter = self:_PilotsOnboard(_heliName) - local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] - if _maxUnits == nil then - _maxUnits = self.max_units - end - self:T(self.lid .. "[Pickup Debug] Check capacity and close enough for winching ".._lookupKeyHeli) - if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then - -- DONE - make variable - if _distance < self.rescuehoverdistance then - self:T(self.lid .. "[Pickup Debug] Helo hovering close enough ".._lookupKeyHeli) - --check height! - local leaderheight = _woundedLeader:GetHeight() - if leaderheight < 0 then leaderheight = 0 end - local _height = _heliUnit:GetHeight() - leaderheight - - -- DONE - make variable - if _height <= self.rescuehoverheight then - self:T(self.lid .. "[Pickup Debug] Helo hovering low enough ".._lookupKeyHeli) - local _time = self.hoverStatus[_lookupKeyHeli] - - if _time == nil then - self.hoverStatus[_lookupKeyHeli] = 10 - _time = 10 - else - _time = self.hoverStatus[_lookupKeyHeli] - 10 - self.hoverStatus[_lookupKeyHeli] = _time - end - self:T(self.lid .. "[Pickup Debug] Check hover timer ".._lookupKeyHeli) - if _time > 0 then - self:T(self.lid .. "[Pickup Debug] Helo hovering not long enough ".._lookupKeyHeli) - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) - else - self:T(self.lid .. "[Pickup Debug] Helo hovering long enough - door check ".._lookupKeyHeli) - if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) - self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) - return false - else - self.hoverStatus[_lookupKeyHeli] = nil - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - self:T(self.lid .. "[Pickup Debug] Pilot picked up ".._lookupKeyHeli) - return true - end - end - _reset = false - else - self:T(self.lid .. "[Pickup Debug] Helo hovering too high ".._lookupKeyHeli) - self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) - self:T(self.lid .. "[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli) - return false - end - end - + self:T(self.lid .. "[Pickup Debug] Helo landed, pilot NOT set to run to helo ".._lookupKeyHeli) + if (_distance < self.loadDistance) then + self:T(self.lid .. "[Pickup Debug] Helo close enough, door check ".._lookupKeyHeli) + if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then + self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) + return false + else + self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli) + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return true end + end end + else + self:T(self.lid .. "[Pickup Debug] Helo hovering".._lookupKeyHeli) + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + self:T(self.lid .. "[Pickup Debug] Check capacity and close enough for winching ".._lookupKeyHeli) + if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then + -- DONE - make variable + if _distance < self.rescuehoverdistance then + self:T(self.lid .. "[Pickup Debug] Helo hovering close enough ".._lookupKeyHeli) + --check height! + local leaderheight = _woundedLeader:GetHeight() + if leaderheight < 0 then leaderheight = 0 end + local _height = _heliUnit:GetHeight() - leaderheight + + -- DONE - make variable + if _height <= self.rescuehoverheight then + self:T(self.lid .. "[Pickup Debug] Helo hovering low enough ".._lookupKeyHeli) + local _time = self.hoverStatus[_lookupKeyHeli] + + if _time == nil then + self.hoverStatus[_lookupKeyHeli] = 10 + _time = 10 + else + _time = self.hoverStatus[_lookupKeyHeli] - 10 + self.hoverStatus[_lookupKeyHeli] = _time + end + self:T(self.lid .. "[Pickup Debug] Check hover timer ".._lookupKeyHeli) + if _time > 0 then + self:T(self.lid .. "[Pickup Debug] Helo hovering not long enough ".._lookupKeyHeli) + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) + else + self:T(self.lid .. "[Pickup Debug] Helo hovering long enough - door check ".._lookupKeyHeli) + if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) + self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli) + return false + else + self.hoverStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. "[Pickup Debug] Pilot picked up ".._lookupKeyHeli) + return true + end + end + _reset = false + else + self:T(self.lid .. "[Pickup Debug] Helo hovering too high ".._lookupKeyHeli) + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) + self:T(self.lid .. "[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli) + return false + end + end + + end + end end - + if _reset then - self.hoverStatus[_lookupKeyHeli] = nil + self.hoverStatus[_lookupKeyHeli] = nil end - + if _distance < 500 then return true else @@ -1710,22 +1734,23 @@ end -- @param #string heliname Heli name -- @param #string groupname Group name -- @param #boolean isairport If true, EVENT.Landing took place at an airport or FARP --- @param #boolean noreschedule If true, do not try to reschedule this is distances are not ok (coming from landing event) -function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule) +-- @param #boolean noreschedule If true, do not try to reschedule this if distances are not ok (coming from landing event) +-- @param #boolean IsHeloBase If true, landing took place at a Helo Base (name "H ..." on current maps) +function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule, IsHeloBase) self:T(self.lid .. " _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit = self:_GetSARHeli(heliname) local _woundedGroupName = groupname if (_heliUnit == nil) then - --helicopter crashed? - self.inTransitGroups[heliname] = nil - return + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return end if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then - -- Groups already rescued - return + -- Groups already rescued + return end local _dist = self:_GetClosestMASH(_heliUnit) @@ -1734,10 +1759,10 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule) self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance can not be determined!") return end - + self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) - - if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then + + if ( _dist < self.FARPRescueDistance or isairport ) and ((_heliUnit:InAir() == false) or (IsHeloBase == true)) then self:T(self.lid.."[Drop off debug] Distance ok, door check") if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true, true) @@ -1751,8 +1776,8 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule) --queue up if not noreschedule then - self:__Returning(5,heliname,_woundedGroupName, isairport) - self:ScheduleOnce(5,self._ScheduledSARFlight,self,heliname,groupname, isairport, noreschedule) + self:__Returning(5,heliname,_woundedGroupName, isairport) + self:ScheduleOnce(5,self._ScheduledSARFlight,self,heliname,groupname, isairport, noreschedule, IsHeloBase) end return self end @@ -1764,22 +1789,22 @@ function CSAR:_RescuePilots(_heliUnit) self:T(self.lid .. " _RescuePilots") local _heliName = _heliUnit:GetName() local _rescuedGroups = self.inTransitGroups[_heliName] - + if _rescuedGroups == nil then - -- Groups already rescued - return + -- Groups already rescued + return end local PilotsSaved = self:_PilotsOnboard(_heliName) - + self.inTransitGroups[_heliName] = nil - + local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", self:_GetCustomCallSign(_heliName), PilotsSaved) - + self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) - + self:_UpdateUnitCargoMass(_heliName) - + -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) return self @@ -1841,9 +1866,9 @@ function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit) if self.coordtype == 0 then -- Lat/Long DMTM _coordinatesText = _coordinate:ToStringLLDDM() elseif self.coordtype == 1 then -- Lat/Long DMS - _coordinatesText = _coordinate:ToStringLLDMS() + _coordinatesText = _coordinate:ToStringLLDMS() elseif self.coordtype == 2 then -- MGRS - _coordinatesText = _coordinate:ToStringMGRS() + _coordinatesText = _coordinate:ToStringMGRS() else -- Bullseye Metric --(medevac.coordtype == 4 or 3) _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end @@ -1876,15 +1901,15 @@ end -- @param #string _unitName Unit to display to function CSAR:_DisplayActiveSAR(_unitName) self:T(self.lid .. " _DisplayActiveSAR") - local _msg = "Active MEDEVAC/SAR:" + local _msg = "Active MEDEVAC/SAR:" local _heli = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT if _heli == nil then - return + return end - + local _heliSide = self.coalition local _csarList = {} - + local _DownedPilotTable = self.downedPilots self:T({Table=_DownedPilotTable}) for _, _value in pairs(_DownedPilotTable) do @@ -1892,40 +1917,40 @@ function CSAR:_DisplayActiveSAR(_unitName) self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) self:T({Table=_value}) local _woundedGroup = _value.group - if _woundedGroup and _value.alive then - local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup,_heli) - local _helicoord = _heli:GetCoordinate() - local _woundcoord = _woundedGroup:GetCoordinate() - local _distance = self:_GetDistance(_helicoord, _woundcoord) - self:T({_distance = _distance}) - local distancetext = "" - local settings = _SETTINGS - if _heli:GetPlayerName() then - settings = _DATABASE:GetPlayerSettings(_heli:GetPlayerName()) or _SETTINGS - end - if settings:IsImperial() then - distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) - else - distancetext = string.format("%.1fkm", _distance/1000.0) - end - if _value.frequency == 0 or self.CreateRadioBeacons == false then--shagrat insert CASEVAC without Frequency - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) - else - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) - end + if _woundedGroup and _value.alive then + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup,_heli) + local _helicoord = _heli:GetCoordinate() + local _woundcoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_helicoord, _woundcoord) + self:T({_distance = _distance}) + local distancetext = "" + local settings = _SETTINGS + if _heli:GetPlayerName() then + settings = _DATABASE:GetPlayerSettings(_heli:GetPlayerName()) or _SETTINGS + end + if settings:IsImperial() then + distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) + else + distancetext = string.format("%.1fkm", _distance/1000.0) + end + if _value.frequency == 0 or self.CreateRadioBeacons == false then--shagrat insert CASEVAC without Frequency + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) + else + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + end end end - + local function sortDistance(a, b) - return a.dist < b.dist + return a.dist < b.dist end - + table.sort(_csarList, sortDistance) - + for _, _line in pairs(_csarList) do - _msg = _msg .. "\n" .. _line.msg + _msg = _msg .. "\n" .. _line.msg end - + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2, false, false, true) return self end @@ -1942,30 +1967,30 @@ function CSAR:_GetClosestDownedPilot(_heli) local _distance = 0 local _closestGroupInfo = nil local _heliCoord = _heli:GetCoordinate() or _heli:GetCoordinate() - - if _heliCoord == nil then - self:E("****Error obtaining coordinate!") - return nil - end - - local DownedPilotsTable = self.downedPilots - - for _, _groupInfo in UTILS.spairs(DownedPilotsTable) do - --for _, _groupInfo in pairs(DownedPilotsTable) do - local _woundedName = _groupInfo.name - local _tempWounded = _groupInfo.group - - -- check group exists and not moving to someone else - if _tempWounded then - local _tempCoord = _tempWounded:GetCoordinate() - _distance = self:_GetDistance(_heliCoord, _tempCoord) - if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then - _shortestDistance = _distance - _closestGroup = _tempWounded - _closestGroupInfo = _groupInfo - end + if _heliCoord == nil then + self:E("****Error obtaining coordinate!") + return nil + end + + local DownedPilotsTable = self.downedPilots + + for _, _groupInfo in UTILS.spairs(DownedPilotsTable) do + --for _, _groupInfo in pairs(DownedPilotsTable) do + local _woundedName = _groupInfo.name + local _tempWounded = _groupInfo.group + + -- check group exists and not moving to someone else + if _tempWounded then + local _tempCoord = _tempWounded:GetCoordinate() + _distance = self:_GetDistance(_heliCoord, _tempCoord) + + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + _closestGroup = _tempWounded + _closestGroupInfo = _groupInfo end + end end return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } @@ -1978,35 +2003,35 @@ function CSAR:_SignalFlare(_unitName) self:T(self.lid .. " _SignalFlare") local _heli = self:_GetSARHeli(_unitName) if _heli == nil then - return + return end - + local _closest = self:_GetClosestDownedPilot(_heli) local smokedist = 8000 if self.approachdist_far > smokedist then smokedist = self.approachdist_far end if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then - - local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - local _distance = "" - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) - else - _distance = string.format("%.1fkm",_closest.distance/1000) - end - local _msg = string.format("%s - Firing signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) - - local _coord = _closest.pilot:GetCoordinate() - _coord:FlareRed(_clockDir) + + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = "" + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.1fkm",_closest.distance/1000) + end + local _msg = string.format("%s - Firing signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) + + local _coord = _closest.pilot:GetCoordinate() + _coord:FlareRed(_clockDir) else - local _distance = smokedist - local dtext = "" - if _SETTINGS:IsImperial() then - dtext = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) - else - dtext = string.format("%.1fkm",smokedist/1000) - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",dtext), self.messageTime, false, false, true) + local _distance = smokedist + local dtext = "" + if _SETTINGS:IsImperial() then + dtext = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + dtext = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",dtext), self.messageTime, false, false, true) end return self end @@ -2014,7 +2039,7 @@ end --- (Internal) Display info to all SAR groups. -- @param #CSAR self -- @param #string _message Message to display. --- @param #number _side Coalition of message. +-- @param #number _side Coalition of message. -- @param #number _messagetime How long to show. -- @param #boolean ToSRS If true or nil, send to SRS TTS -- @param #boolean ToScreen If true or nil, send to Screen @@ -2034,7 +2059,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen) for _, _unitName in pairs(self.csarUnits) do local _unit = self:_GetSARHeli(_unitName) if _unit and not self.suppressmessages then - self:_DisplayMessageToSAR(_unit, _message, _messagetime) + self:_DisplayMessageToSAR(_unit, _message, _messagetime) end end end @@ -2048,30 +2073,30 @@ function CSAR:_ReqIRStrobe( _unitName ) self:T(self.lid .. " _ReqIRStrobe") local _heli = self:_GetSARHeli(_unitName) if _heli == nil then - return + return end local smokedist = 8000 if smokedist < self.approachdist_far then smokedist = self.approachdist_far end local _closest = self:_GetClosestDownedPilot(_heli) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then - local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - local _distance = string.format("%.1fkm",_closest.distance/1000) - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) - else - _distance = string.format("%.1fkm",_closest.distance/1000) - end - local _msg = string.format("%s - IR Strobe active at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) - _closest.pilot:NewIRMarker(true,self.IRStrobeRuntime or 300) + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = string.format("%.1fkm",_closest.distance/1000) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.1fkm",_closest.distance/1000) + end + local _msg = string.format("%s - IR Strobe active at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) + _closest.pilot:NewIRMarker(true,self.IRStrobeRuntime or 300) else - local _distance = string.format("%.1fkm",smokedist/1000) - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) - else - _distance = string.format("%.1fkm",smokedist/1000) - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) + local _distance = string.format("%.1fkm",smokedist/1000) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) end return self end @@ -2083,32 +2108,32 @@ function CSAR:_Reqsmoke( _unitName ) self:T(self.lid .. " _Reqsmoke") local _heli = self:_GetSARHeli(_unitName) if _heli == nil then - return + return end local smokedist = 8000 if smokedist < self.approachdist_far then smokedist = self.approachdist_far end local _closest = self:_GetClosestDownedPilot(_heli) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then - local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - local _distance = string.format("%.1fkm",_closest.distance/1000) - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) - else - _distance = string.format("%.1fkm",_closest.distance/1000) - end - local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) - self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) - local _coord = _closest.pilot:GetCoordinate() - local color = self.smokecolor - _coord:Smoke(color) + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = string.format("%.1fkm",_closest.distance/1000) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.1fkm",_closest.distance/1000) + end + local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) + local _coord = _closest.pilot:GetCoordinate() + local color = self.smokecolor + _coord:Smoke(color) else - local _distance = string.format("%.1fkm",smokedist/1000) - if _SETTINGS:IsImperial() then - _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) - else - _distance = string.format("%.1fkm",smokedist/1000) - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) + local _distance = string.format("%.1fkm",smokedist/1000) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true) end return self end @@ -2116,56 +2141,50 @@ end --- (Internal) Determine distance to closest MASH. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT --- @return #CSAR self +-- @return #number Distance in meters +-- @return #string MASH Name as string function CSAR:_GetClosestMASH(_heli) self:T(self.lid .. " _GetClosestMASH") local _mashset = self.mash -- Core.Set#SET_GROUP - local _mashes = _mashset:GetSetObjects() -- #table + local MashSets = {} + --local _mashes = _mashset.Set-- #table + table.insert(MashSets,_mashset.Set) + table.insert(MashSets,self.zonemashes.Set) + table.insert(MashSets,self.staticmashes.Set) local _shortestDistance = -1 local _distance = 0 local _helicoord = _heli:GetCoordinate() - - local function GetCloseAirbase(coordinate,Coalition,Category) - - local a=coordinate:GetVec3() - local distmin=math.huge - local airbase=nil - for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do - local b=DCSairbase:getPoint() - - local c=UTILS.VecSubstract(a,b) - local dist=UTILS.VecNorm(c) - - if dist 12 then clock = clock-12 end - end + end return clock end @@ -2323,60 +2342,65 @@ end -- @param #CSAR self -- @param Wrapper.Group#GROUP _group Group #GROUP object. -- @param #number _freq Frequency to use --- @param #string _name Beacon Name to use +-- @param #string BeaconName Beacon Name to use -- @return #CSAR self -function CSAR:_AddBeaconToGroup(_group, _freq, _name) - self:T(self.lid .. " _AddBeaconToGroup") - if self.CreateRadioBeacons == false then return end - local _group = _group - - if _group == nil then - --return frequency to pool of available - for _i, _current in ipairs(self.UsedVHFFrequencies) do - if _current == _freq then - table.insert(self.FreeVHFFrequencies, _freq) - table.remove(self.UsedVHFFrequencies, _i) - end - end - return - end - - if _group:IsAlive() then - local _radioUnit = _group:GetUnit(1) - if _radioUnit then - local name = _radioUnit:GetName() - local Frequency = _freq -- Freq in Hertz - local name = _radioUnit:GetName() - local Sound = "l10n/DEFAULT/"..self.radioSound - local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} - trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,_name) -- Beacon in MP only runs for exactly 30secs straight +function CSAR:_AddBeaconToGroup(_group, _freq, BeaconName) + self:T(self.lid .. " _AddBeaconToGroup") + if self.CreateRadioBeacons == false then return end + local _group = _group + + if _group == nil then + --return frequency to pool of available + for _i, _current in ipairs(self.UsedVHFFrequencies) do + if _current == _freq then + table.insert(self.FreeVHFFrequencies, _freq) + table.remove(self.UsedVHFFrequencies, _i) end end - - return self + return + end + + if _group:IsAlive() then + local _radioUnit = _group:GetUnit(1) + if _radioUnit then + local name = _radioUnit:GetName() + local Frequency = _freq -- Freq in Hertz + --local name = _radioUnit:GetName() + local Sound = "l10n/DEFAULT/"..self.radioSound + local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} + self:I(self.lid..string.format("Added Radio Beacon %d Hertz | Name %s | Position {%d,%d,%d}",Frequency,BeaconName,vec3.x,vec3.y,vec3.z)) + trigger.action.radioTransmission(Sound, vec3, 0, true, Frequency, self.ADFRadioPwr or 500,BeaconName) -- Beacon in MP only runs for exactly 30secs straight + end + end + + return self end --- (Internal) Helper function to (re-)add beacon to downed pilot. -- @param #CSAR self -- @return #CSAR self function CSAR:_RefreshRadioBeacons() - self:T(self.lid .. " _RefreshRadioBeacons") - if self.CreateRadioBeacons == false then return end - if self:_CountActiveDownedPilots() > 0 then - local PilotTable = self.downedPilots - for _,_pilot in pairs (PilotTable) do - self:T({_pilot.name}) - local pilot = _pilot -- #CSAR.DownedPilot - local group = pilot.group - local frequency = pilot.frequency or 0 -- thanks to @Thrud - local bname = pilot.BeaconName or pilot.name..math.random(1,100000) - trigger.action.stopRadioTransmission(bname) - if group and group:IsAlive() and frequency > 0 then - self:_AddBeaconToGroup(group,frequency,bname) + self:T(self.lid .. " _RefreshRadioBeacons") + if self.CreateRadioBeacons == false then return end + if self:_CountActiveDownedPilots() > 0 then + local PilotTable = self.downedPilots + for _,_pilot in pairs (PilotTable) do + self:T({_pilot.name}) + local pilot = _pilot -- #CSAR.DownedPilot + local group = pilot.group + local frequency = pilot.frequency or 0 -- thanks to @Thrud + local bname = pilot.BeaconName or pilot.name..math.random(1,100000) + --trigger.action.stopRadioTransmission(bname) + if group and group:IsAlive() and frequency > 0 then + --self:_AddBeaconToGroup(group,frequency,bname) + else + if frequency > 0 then + trigger.action.stopRadioTransmission(bname) end end end - return self + end + return self end --- (Internal) Helper function to count active downed pilots. @@ -2398,30 +2422,45 @@ end -- @param #CSAR self -- @return #boolean True or false. function CSAR:_ReachedPilotLimit() - self:T(self.lid .. " _ReachedPilotLimit") - local limit = self.maxdownedpilots - local islimited = self.limitmaxdownedpilots - local count = self:_CountActiveDownedPilots() - if islimited and (count >= limit) then - return true - else - return false - end + self:T(self.lid .. " _ReachedPilotLimit") + local limit = self.maxdownedpilots + local islimited = self.limitmaxdownedpilots + local count = self:_CountActiveDownedPilots() + if islimited and (count >= limit) then + if self.useFIFOLimitReplacement then + local oldIndex = -1 + local oldDownedPilot = nil + for _index, _downedpilot in pairs(self.downedPilots) do + oldIndex = _index + oldDownedPilot = _downedpilot + break + end + if oldDownedPilot then + oldDownedPilot.group:Destroy(false) + oldDownedPilot.alive = false + self:_CheckDownedPilotTable() + return false + end + end + return true + else + return false + end end - --- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment. - -- Needs to be set before starting the CSAR instance. - -- @param #CSAR self - -- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups. - -- @return #CSAR self - function CSAR:SetOwnSetPilotGroups(Set) - self.UserSetGroup = Set - return self - end +--- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment. +-- Needs to be set before starting the CSAR instance. +-- @param #CSAR self +-- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups. +-- @return #CSAR self +function CSAR:SetOwnSetPilotGroups(Set) + self.UserSetGroup = Set + return self +end - ------------------------------ - --- FSM internal Functions --- - ------------------------------ +------------------------------ +--- FSM internal Functions --- +------------------------------ --- (Internal) Function called after Start() event. -- @param #CSAR self. @@ -2439,7 +2478,7 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) - + if self.UserSetGroup then self.allheligroupset = self.UserSetGroup elseif self.allowbronco then @@ -2451,12 +2490,13 @@ function CSAR:onafterStart(From, Event, To) else self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end - + self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() - - local staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce() - local zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce() - + + self.staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() + self.zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterStart() + + --[[ if staticmashes:Count() > 0 then for _,_mash in pairs(staticmashes.Set) do self.mash:AddObject(_mash) @@ -2464,14 +2504,17 @@ function CSAR:onafterStart(From, Event, To) end if zonemashes:Count() > 0 then + self:T("Adding zones to self.mash SET") for _,_mash in pairs(zonemashes.Set) do self.mash:AddObject(_mash) end + self:T("Objects in SET: "..self.mash:Count()) end - + --]] + if not self.coordinate then local csarhq = self.mash:GetRandom() - if csarhq then + if csarhq then self.coordinate = csarhq:GetCoordinate() end end @@ -2497,16 +2540,16 @@ function CSAR:onafterStart(From, Event, To) self.msrs:SetLabel("CSAR") self.SRSQueue = MSRSQUEUE:New("CSAR") -- Sound.SRS#MSRSQUEUE end - + self:__Status(-10) - + if self.enableLoadSave then local interval = self.saveinterval local filename = self.filename local filepath = self.filepath self:__Save(interval,filepath,filename) end - + return self end @@ -2515,11 +2558,11 @@ end function CSAR:_CheckDownedPilotTable() local pilots = self.downedPilots local npilots = {} - + for _ind,_entry in pairs(pilots) do local _group = _entry.group if _group:IsAlive() then - npilots[_ind] = _entry + npilots[_ind] = _entry else if _entry.alive then self:__KIA(1,_entry.desc) @@ -2539,12 +2582,12 @@ function CSAR:onbeforeStatus(From, Event, To) self:T({From, Event, To}) -- housekeeping self:_AddMedevacMenuItem() - + if not self.BeaconTimer or (self.BeaconTimer and not self.BeaconTimer:IsRunning()) then self.BeaconTimer = TIMER:New(self._RefreshRadioBeacons,self) self.BeaconTimer:Start(2,self.beaconRefresher) end - + self:_CheckDownedPilotTable() for _,_sar in pairs (self.csarUnits) do local PilotTable = self.downedPilots @@ -2555,7 +2598,7 @@ function CSAR:onbeforeStatus(From, Event, To) local timestamp = entry.timestamp or 0 local now = timer.getAbsTime() if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. - self:_CheckWoundedGroupStatus(_sar,name) + self:_CheckWoundedGroupStatus(_sar,name) end end end @@ -2577,14 +2620,14 @@ function CSAR:onafterStatus(From, Event, To) end local PilotsInFieldN = self:_CountActiveDownedPilots() - + local PilotsBoarded = 0 for _, _unitName in pairs(self.inTransitGroups) do for _,_units in pairs(_unitName) do PilotsBoarded = PilotsBoarded + 1 end end - + if self.verbose > 0 then local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) @@ -2654,7 +2697,7 @@ function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) -- right subtype? if Event == subtype and not task:IsDone() then local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... - if (targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2)) + if (targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2)) or (string.find(task.CSARPilotName,Woundedgroupname)) then if task.Clients:HasUniqueID(playername) then -- success @@ -2668,7 +2711,7 @@ function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) return self end ---- (Internal) Function called before Returning() event. +--- (Internal) Function called before Returning() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -2703,10 +2746,10 @@ function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) local subtype = task:GetSubType() -- right subtype? if Event == subtype and not task:IsDone() then - if task.Clients:HasUniqueID(playername) then - -- success - task:__Success(-1) - end + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end end end ) @@ -2753,7 +2796,7 @@ function CSAR:onbeforeSave(From, Event, To, path, filename) if not self.enableLoadSave then return self end - -- Thanks to @FunkyFranky + -- Thanks to @FunkyFranky -- Check io module is available. if not io then self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") @@ -2777,7 +2820,7 @@ end -- @param #string filename (Optional) File name for saving. Default is Default is "CSAR__Persist.csv". function CSAR:onafterSave(From, Event, To, path, filename) self:T({From, Event, To, path, filename}) - -- Thanks to @FunkyFranky + -- Thanks to @FunkyFranky if not self.enableLoadSave then return self end @@ -2792,7 +2835,7 @@ function CSAR:onafterSave(From, Event, To, path, filename) if lfs then path=self.filepath or lfs.writedir() end - + -- Set file name. filename=filename or self.filename @@ -2800,9 +2843,9 @@ function CSAR:onafterSave(From, Event, To, path, filename) if path~=nil then filename=path.."\\"..filename end - + local pilots = self.downedPilots - + --local data = "LoadedData = {\n" local data = "playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n" local n = 0 @@ -2820,15 +2863,15 @@ function CSAR:onafterSave(From, Event, To, path, filename) local location = group:GetVec3() local unitName = DownedPilot.originalUnit local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq) - + self:I(self.lid.."Saving to CSAR File: " .. txt) - + data = data .. txt end end - + _savefile(filename, data) - + -- AutoSave if self.enableLoadSave then local interval = self.saveinterval @@ -2853,19 +2896,19 @@ function CSAR:onbeforeLoad(From, Event, To, path, filename) end --- Function that check if a file exists. local function _fileexists(name) - local f=io.open(name,"r") - if f~=nil then + local f=io.open(name,"r") + if f~=nil then io.close(f) return true else return false end end - + -- Set file name and path filename=filename or self.filename path = path or self.filepath - + -- Check io module is available. if not io then self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") @@ -2895,7 +2938,7 @@ function CSAR:onbeforeLoad(From, Event, To, path, filename) else self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename)) return false - --return self + --return self end end @@ -2919,11 +2962,11 @@ function CSAR:onafterLoad(From, Event, To, path, filename) f:close() return data end - + -- Set file name and path filename=filename or self.filename path = path or self.filepath - + -- Set path or default. if lfs then path=path or lfs.writedir() @@ -2938,29 +2981,29 @@ function CSAR:onafterLoad(From, Event, To, path, filename) local text=string.format("Loading CSAR state from file %s", filename) MESSAGE:New(text,10):ToAllIf(self.Debug) self:I(self.lid..text) - + local file=assert(io.open(filename, "rb")) - + local loadeddata = {} for line in file:lines() do - loadeddata[#loadeddata+1] = line + loadeddata[#loadeddata+1] = line end file:close() - + -- remove header table.remove(loadeddata, 1) - + for _id,_entry in pairs (loadeddata) do local dataset = UTILS.Split(_entry,",") -- 1=playerName,2=x,3=y,4=z,5=coalition,6=country,7=description,8=typeName,9=unitName,10=freq\n local playerName = dataset[1] - + local vec3 = {} vec3.x = tonumber(dataset[2]) vec3.y = tonumber(dataset[3]) vec3.z = tonumber(dataset[4]) local point = COORDINATE:NewFromVec3(vec3) - + local coalition = tonumber(dataset[5]) local country = tonumber(dataset[6]) local description = dataset[7] @@ -2968,9 +3011,9 @@ function CSAR:onafterLoad(From, Event, To, path, filename) local unitName = dataset[9] local freq = tonumber(dataset[10]) - self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil) + self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, false, description, nil) end - + return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index cd0bc8403..87d394e01 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -20,11 +20,12 @@ -- -- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), bbirchnz (additional code!!) -- ### Repack addition for crates: **Raiden** +-- ### Additional cool features: **Lekaa** -- -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update April 2025 +-- Last Update Oct 2025 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ @@ -146,6 +147,7 @@ CTLD_CARGO = { Location = ZONE:New(Location) end self.Location = Location + self.NoMoveToZone = false return self end @@ -782,6 +784,7 @@ do -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) -- -- additionally, you can limit **where** the stock is available (one location only!) - this one is available in a zone called "Vehicle Store". -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10,nil,nil,"Vehicle Store") +-- -- Tip: if you want the spawned/built group NOT to move to a MOVE zone, replace AddCratesCargo with AddCratesCargoNoMove (same parameters). -- -- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: -- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) @@ -865,7 +868,10 @@ do -- my_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook -- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook -- my_ctld.showstockinmenuitems = false -- When set to true, the menu lines will also show the remaining items in stock (that is, if you set any), downside is that the menu for all will be build every 30 seconds anew. --- +-- my_ctld.onestepmenu = false -- When set to true, the menu will create Drop and build, Get and load, Pack and remove, Pack and load, Pack. it will be a 1 step solution. +-- my_ctld.VehicleMoveFormation = AI.Task.VehicleFormation.VEE -- When a group moves to a MOVE zone, then it takes this formation. Can be a table of formations, which are then randomly chosen. Defaults to "Vee". +-- my_ctld.validateAndRepositionUnits = false -- Uses Disposition and other logic to find better ground positions for ground units avoiding trees, water, roads, runways, map scenery, statics and other units in the area. (Default is false) +-- -- ## 2.1 CH-47 Chinook support -- -- The Chinook comes with the option to use the ground crew menu to load and unload cargo into the Helicopter itself for better immersion. As well, it can sling-load cargo from ground. The cargo you can actually **create** @@ -1292,6 +1298,7 @@ CTLD = { LoadedGroupsTable = {}, keeploadtable = true, allowCATransport = false, + VehicleMoveFormation = AI.Task.VehicleFormation.VEE, } ------------------------------ @@ -1391,6 +1398,7 @@ CTLD.UnitTypeCapabilities = { ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers. --Actually it's longer, but the center coord is off-center of the model. ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats + ["UH-60L_DAP"] = {type="UH-60L_DAP", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 16, cargoweightlimit = 500}, -- UH-60L DAP is an attack helo but can do limited CSAR and CTLD ["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo @@ -1412,7 +1420,7 @@ CTLD.FixedWingTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.2.33" +CTLD.version="1.3.38" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1479,6 +1487,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event. self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event. + self:AddTransition("*", "CratesPacked", "*") -- CTLD repack event. self:AddTransition("*", "HelicopterLost", "*") -- CTLD lost event. self:AddTransition("*", "Load", "*") -- CTLD load event. self:AddTransition("*", "Loaded", "*") -- CTLD load event. @@ -1549,15 +1558,20 @@ function CTLD:New(Coalition, Prefixes, Alias) self.smokedistance = 2000 self.movetroopstowpzone = true self.movetroopsdistance = 5000 + self.returntroopstobase = true -- if set to false, troops would stay after deployment inside a load zone. self.troopdropzoneradius = 100 + self.VehicleMoveFormation = AI.Task.VehicleFormation.VEE + -- added support Hercules Mod 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 - + + self.validateAndRepositionUnits = false -- 280 kph or 150kn eq 77 mps + -- message suppression self.suppressmessages = false @@ -1591,6 +1605,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.subcats = {} self.subcatsTroop = {} self.showstockinmenuitems = false + self.onestepmenu = false -- disallow building in loadzones self.nobuildinloadzones = true @@ -1756,6 +1771,17 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. -- @return #CTLD self + + --- FSM Function OnBeforeCratesPacked. + -- @function [parent=#CTLD] OnBeforeCratesPacked + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate that was repacked. + -- @return #CTLD self --- FSM Function OnBeforeTroopsRTB. -- @function [parent=#CTLD] OnBeforeTroopsRTB @@ -1843,6 +1869,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param CargoName The name of the cargo being built. -- @return #CTLD self --- FSM Function OnAfterCratesRepairStarted. Info event that a repair has been started. @@ -1885,6 +1912,17 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. -- @return #CTLD self + + --- FSM Function OnAfterCratesPacked. + -- @function [parent=#CTLD] OnAfterCratesPacked + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate that was repacked. + -- @return #CTLD self --- FSM Function OnAfterTroopsRTB. -- @function [parent=#CTLD] OnAfterTroopsRTB @@ -2072,6 +2110,9 @@ function CTLD:_EventHandler(EventData) local _group = event.IniGroup local _unit = event.IniUnit self:_RefreshLoadCratesMenu(_group, _unit) + if self:IsFixedWing(_unit) and self.enableFixedWing then + self:_RefreshDropCratesMenu(_group, _unit) + end end elseif event.id == EVENTS.PlayerLeaveUnit or event.id == EVENTS.UnitLost then -- remove from pilot table @@ -2821,8 +2862,12 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end -- altered heading logic -- DONE: right standard deviation? - rheading = UTILS.RandomGaussian(0,30,-90,90,100) - rheading = math.fmod((heading + rheading), 360) + if self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) then + rheading = math.random(20,60) + else + rheading = UTILS.RandomGaussian(0, 30, -90, 90, 100) + end + rheading=math.fmod((heading+rheading),360) cratecoord = position:Translate(cratedistance,rheading) else cratedistance = (row-1)*6 @@ -2913,10 +2958,10 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) if drop then text = string.format("Crates for %s have been dropped!",cratename) self:__CratesDropped(1, Group, Unit, droppedcargo) + else + self:_SendMessage(text, 10, false, Group) end - self:_SendMessage(text, 10, false, Group) self:_RefreshLoadCratesMenu(Group, Unit) - return self end @@ -3287,6 +3332,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) self:_RefreshLoadCratesMenu(Group, Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) + self:__CratesPickedUp(1, Group, Unit, loaded.Cargo) end end return self @@ -3564,7 +3610,7 @@ end 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 + if _name and (typename==_name or string.find(typename,_name,1,true))then return true end end @@ -3576,11 +3622,14 @@ end -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:IsHook(Unit) - if Unit and string.find(Unit:GetTypeName(),"CH.47") then - return true - else - return false - end + if not Unit then return false end + local typeName = Unit:GetTypeName() + if not typeName then return false end + if string.find(typeName, "CH.47") then + return true + else + return false + end end --- (Internal) Function to set troops positions of a template to a nice circle @@ -3630,7 +3679,7 @@ function CTLD:_UnloadTroops(Group, Unit) inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then - droppingatbase = true + droppingatbase = self.returntroopstobase end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters @@ -3694,6 +3743,7 @@ function CTLD:_UnloadTroops(Group, Unit) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter],type) @@ -3763,89 +3813,103 @@ end -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit function CTLD:_UnloadCrates(Group, Unit) - self:T(self.lid .. " _UnloadCrates") - - if not self.dropcratesanywhere then -- #1570 - -- check if we are in DROP zone - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) - if not inzone then - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - if not self.debug then - return self - end - end - end - -- Door check - if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) - if not self.debug then return self end - end - -- check for hover unload - local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters - local IsHerc = self:IsFixedWing(Unit) - local IsHook = self:IsHook(Unit) - if IsHerc and (not IsHook) then - -- no hover but airdrop here - hoverunload = self:IsCorrectFlightParameters(Unit) - end - -- check if we\'re landed - local grounded = not self:IsUnitInAir(Unit) - -- Get what we have loaded - local unitname = Unit:GetName() - if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then - local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - -- looking for crate - local cargotable = loadedcargo.Cargo - for _,_cargo in pairs (cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and (not cargo:WasDropped() or self.allowcratepickupagain) then - -- unload crates - self:_GetCrates(Group, Unit, cargo, 1, true) - cargo:SetWasDropped(true) - cargo:SetHasMoved(true) - end - end - -- cleanup load list - local loaded = {} -- #CTLD.LoadedCargo - loaded.Troopsloaded = 0 - loaded.Cratesloaded = 0 - loaded.Cargo = {} + self:T(self.lid .. " _UnloadCrates") - for _,_cargo in pairs (cargotable) do - local cargo = _cargo -- #CTLD_CARGO - local type = cargo:GetType() -- #CTLD_CARGO.Enum - local size = cargo:GetCratesNeeded() - if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then - table.insert(loaded.Cargo,_cargo) - loaded.Troopsloaded = loaded.Troopsloaded + size - end - if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then - table.insert(loaded.Cargo,_cargo) - loaded.Cratesloaded = loaded.Cratesloaded + size + if not self.dropcratesanywhere then -- #1570 + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + if not self.debug then + return self + end end end - self.Loaded_Cargo[unitname] = nil - self.Loaded_Cargo[unitname] = loaded - - self:_UpdateUnitCargoMass(Unit) - self:_RefreshDropCratesMenu(Group,Unit) - else - if IsHerc then - self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) + if not self.debug then return self end + end + local hoverunload = self:IsCorrectHover(Unit) + local IsHerc = self:IsFixedWing(Unit) + local IsHook = self:IsHook(Unit) + if IsHerc and (not IsHook) then + hoverunload = self:IsCorrectFlightParameters(Unit) + end + local grounded = not self:IsUnitInAir(Unit) + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + local loadedcargo = self.Loaded_Cargo[unitname] or {} + local cargotable = loadedcargo.Cargo + local droppedCount = {} + local neededMap = {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo + local type = cargo:GetType() + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and (not cargo:WasDropped() or self.allowcratepickupagain) then + self:_GetCrates(Group, Unit, cargo, 1, true) + cargo:SetWasDropped(true) + cargo:SetHasMoved(true) + local cname = cargo:GetName() or "Unknown" + droppedCount[cname] = (droppedCount[cname] or 0) + 1 + if not neededMap[cname] then + neededMap[cname] = cargo:GetCratesNeeded() or 1 + end + end + end + for cname,count in pairs(droppedCount) do + local needed = neededMap[cname] or 1 + if needed > 1 then + local full = math.floor(count/needed) + local left = count % needed + if full > 0 and left == 0 then + self:_SendMessage(string.format("Dropped %d %s.",full,cname),10,false,Group) + elseif full > 0 and left > 0 then + self:_SendMessage(string.format("Dropped %d %s(s), with %d leftover crate(s).",full,cname,left),10,false,Group) + else + self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.",count,needed,cname),15,false,Group) + end + else + self:_SendMessage(string.format("Dropped %d %s(s).",count,cname),10,false,Group) + end + end + local loaded = {} + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo + local type = cargo:GetType() + local size = cargo:GetCratesNeeded() + if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then + table.insert(loaded.Cargo,_cargo) + loaded.Troopsloaded = loaded.Troopsloaded + size + end + if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + size + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + + self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group,Unit) else - self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - end + if IsHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + end + end + return self end - return self -end --- (Internal) Function to build nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #boolean Engineering If true build is by an engineering team. -function CTLD:_BuildCrates(Group, Unit,Engineering) +-- @param #boolean MultiDrop If true and not engineering or FOB, vary position a bit. +function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop) self:T(self.lid .. " _BuildCrates") -- avoid users trying to build from flying Hercs if self:IsFixedWing(Unit) and self.enableFixedWing and not Engineering then @@ -3939,12 +4003,13 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) if build.CanBuild then self:_CleanUpCrates(crates,build,number) if self.buildtime and self.buildtime > 0 then - local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate()) + local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,build,false,Group:GetCoordinate(),MultiDrop) buildtimer:Start(self.buildtime) self:_SendMessage(string.format("Build started, ready in %d seconds!",self.buildtime),15,false,Group) - self:__CratesBuildStarted(1,Group,Unit) + self:__CratesBuildStarted(1,Group,Unit,build.Name) + self:_RefreshDropTroopsMenu(Group,Unit) else - self:_BuildObjectFromCrates(Group,Unit,build) + self:_BuildObjectFromCrates(Group,Unit,build,false,nil,MultiDrop) end end end @@ -3983,13 +4048,15 @@ function CTLD:_PackCratesNearby(Group, Unit) _Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu - return self + self:__CratesPacked(1,Group,Unit,_entry) + return true end end end end end - return self + self:_SendMessage("Nothing to pack at this distance pilot!",10,false,Group) + return false end --- (Internal) Function to repair nearby vehicles / FOBs @@ -4082,7 +4149,8 @@ end -- @param #CTLD.Buildable Build -- @param #boolean Repair If true this is a repair and not a new build -- @param Core.Point#COORDINATE RepairLocation Location for repair (e.g. where the destroyed unit was) -function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) +-- @param #boolean MultiDrop if true and not a repair, vary location a bit if not a FOB +function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,MultiDrop) self:T(self.lid .. " _BuildObjectFromCrates") -- Spawn-a-crate-content if Group and Group:IsAlive() or (RepairLocation and not Repair) then @@ -4099,7 +4167,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) if type(temptable) == "string" then temptable = {temptable} end - local zone = nil + local zone = nil -- Core.Zone#ZONE_RADIUS if RepairLocation and not Repair then -- timed build zone = ZONE_RADIUS:New(string.format("Build zone-%d",math.random(1,10000)),RepairLocation:GetVec2(),100) @@ -4108,6 +4176,10 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) end --local randomcoord = zone:GetRandomCoordinate(35):GetVec2() local randomcoord = Build.Coord or zone:GetRandomCoordinate(35):GetVec2() + if MultiDrop and (not Repair) and canmove then + -- coordinate may be the same, avoid + local randomcoord = zone:GetRandomCoordinate(35):GetVec2() + end if Repair then randomcoord = RepairLocation:GetVec2() end @@ -4118,11 +4190,13 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) --:InitRandomizeUnits(true,20,2) :InitDelayOff() + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) end @@ -4138,6 +4212,17 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) return self end +--- (Internal) Function to get a vehicle formation for a moving group +-- @param #CTLD self +-- @return #string Formation +function CTLD:_GetVehicleFormation() + local VehicleMoveFormation = self.VehicleMoveFormation or AI.Task.VehicleFormation.VEE + if type(self.VehicleMoveFormation)=="table" then + VehicleMoveFormation = self.VehicleMoveFormation[math.random(1,#self.VehicleMoveFormation)] + end + return VehicleMoveFormation +end + --- (Internal) Function to move group to WP zone. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The Group to move. @@ -4152,18 +4237,20 @@ function CTLD:_MoveGroupToZone(Group) -- yes, we can ;) local groupname = Group:GetName() local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE - local coordinate = zonecoord:GetVec2() + local formation = self:_GetVehicleFormation() + --local coordinate = zonecoord:GetVec2() Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) - Group:OptionROEOpenFirePossible() - Group:RouteToVec2(coordinate,5) + Group:OptionROEOpenFire() + Group:RouteGroundTo(zonecoord,25,formation) end return self end --- (Internal) Housekeeping - Cleanup crates when build -- @param #CTLD self +-- -- @param #table Crates Table of #CTLD_CARGO objects near the unit. -- @param #CTLD.Buildable Build Table build object. -- @param #number Number Number of objects in Crates (found) to limit search. @@ -4199,310 +4286,467 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) return self end +--- (Internal) Helper - Drop **all** loaded crates nearby and build them. +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +function CTLD:_DropAndBuild(Group,Unit) + if self.nobuildinloadzones then + if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) then + self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) + return self + end + end + self:_UnloadCrates(Group,Unit) + timer.scheduleFunction(function() self:_BuildCrates(Group,Unit,false,true) end,{},timer.getTime()+1) + end + + --- (Internal) Helper - Drop a **single** crate set and build it. +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +-- @param number setIndex Index of the crate-set to drop + function CTLD:_DropSingleAndBuild(Group,Unit,setIndex) + if self.nobuildinloadzones then + if self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) then + self:_SendMessage("You cannot build in a loading area, Pilot!",10,false,Group) + return self + end + end + self:_UnloadSingleCrateSet(Group,Unit,setIndex) + timer.scheduleFunction(function() self:_BuildCrates(Group,Unit,false) end,{},timer.getTime()+1) + end + +--- (Internal) Helper - Pack crates near the unit and load them. +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +function CTLD:_PackAndLoad(Group,Unit) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) + return self + end + if not self:_PackCratesNearby(Group,Unit) then + return self + end + timer.scheduleFunction(function() self:_LoadCratesNearby(Group,Unit) end,{},timer.getTime()+1) + return self + end + +--- (Internal) Helper - Pack crates near the unit and then remove them. +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +function CTLD:_PackAndRemove(Group,Unit) + if not self:_PackCratesNearby(Group,Unit) then + return self + end + timer.scheduleFunction(function() self:_RemoveCratesNearby(Group,Unit) end,{},timer.getTime()+1) + return self +end + +--- (Internal) Helper - get and load in one step +-- @param Wrapper.Group#GROUP Group The calling group +-- @param Wrapper.Unit#UNIT Unit The calling unit +function CTLD:_GetAndLoad(Group,Unit,cargoObj) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) + return self + end + self:_GetCrates(Group,Unit,cargoObj) + + timer.scheduleFunction(function() self:_LoadSingleCrateSet(Group,Unit,cargoObj.Name) end,{},timer.getTime()+1) +end + +-- @param Wrapper.Group#GROUP Group The player’s group that triggered the action +-- @param Wrapper.Unit#UNIT Unit The unit performing the pack-and-load +function CTLD:_GetAllAndLoad(Group,Unit) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!",10,false,Group) + return self + end + + timer.scheduleFunction(function() self:_LoadCratesNearby(Group,Unit) end,{},timer.getTime()+1) +end + --- (Internal) Housekeeping - Function to refresh F10 menus. -- @param #CTLD self -- @return #CTLD self function CTLD:_RefreshF10Menus() - self:T(self.lid .. " _RefreshF10Menus") - - -- 1) Gather all the pilot groups from our Set - local PlayerSet = self.PilotGroups - local PlayerTable = PlayerSet:GetSetObjects() - - -- 2) Rebuild the self.CtldUnits table - local _UnitList = {} - for _, groupObj in pairs(PlayerTable) do - local firstUnit = groupObj:GetFirstUnitAlive() - if firstUnit then - if firstUnit:IsPlayer() then - if firstUnit:IsHelicopter() or (self.enableFixedWing and self:IsFixedWing(firstUnit)) then - local _unit = firstUnit:GetName() - _UnitList[_unit] = _unit + self:T(self.lid .. " _RefreshF10Menus") + self.onestepmenu = self.onestepmenu or false -- hybrid toggle (default = false) + + -- 1) Gather all the pilot groups from our Set + local PlayerSet = self.PilotGroups + local PlayerTable = PlayerSet:GetSetObjects() + + -- 2) Rebuild the self.CtldUnits table + local _UnitList = {} + for _, groupObj in pairs(PlayerTable) do + local firstUnit = groupObj:GetFirstUnitAlive() + if firstUnit then + if firstUnit:IsPlayer() then + if firstUnit:IsHelicopter() or (self.enableFixedWing and self:IsFixedWing(firstUnit)) then + local _unit = firstUnit:GetName() + _UnitList[_unit] = _unit + end end end end - end - - -- 3) CA Units - if self.allowCATransport and self.CATransportSet then - for _,_clientobj in pairs(self.CATransportSet.Set) do - local client = _clientobj -- Wrapper.Client#CLIENT - if client:IsGround() then - local cname = client:GetName() - self:T(self.lid.."Adding: "..cname) - _UnitList[cname] = cname + + -- 3) CA Units + if self.allowCATransport and self.CATransportSet then + for _,_clientobj in pairs(self.CATransportSet.Set) do + local client = _clientobj -- Wrapper.Client#CLIENT + if client:IsGround() then + local cname = client:GetName() + self:T(self.lid.."Adding: "..cname) + _UnitList[cname] = cname + end end end + + self.CtldUnits = _UnitList + + -- subcats? + if self.usesubcats then + for _id,_cargo in pairs(self.Cargo_Crates) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcats[entry.Subcategory] then + self.subcats[entry.Subcategory] = entry.Subcategory + end + end + for _id,_cargo in pairs(self.Cargo_Statics) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcats[entry.Subcategory] then + self.subcats[entry.Subcategory] = entry.Subcategory + end + end + for _id,_cargo in pairs(self.Cargo_Troops) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcatsTroop[entry.Subcategory] then + self.subcatsTroop[entry.Subcategory] = entry.Subcategory + end + end + end + + local menucount = 0 + local menus = {} + for _, _unitName in pairs(self.CtldUnits) do + if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then + self:T(self.lid.."Menu not done yet for ".._unitName) + local _unit = UNIT:FindByName(_unitName) + if not _unit and self.allowCATransport then + _unit = CLIENT:FindByName(_unitName) + end + if _unit and _unit:IsAlive() then + local _group = _unit:GetGroup() + if _group then + self:T(self.lid.."Unit and Group exist") + local capabilities = self:_GetUnitCapabilities(_unit) + local cantroops = capabilities.troops + local cancrates = capabilities.crates + local unittype = _unit:GetTypeName() + local isHook = self:IsHook(_unit) + local nohookswitch = true + --local nohookswitch = not (isHook and self.enableChinookGCLoading) + -- Clear old topmenu if it existed + if _group.CTLDTopmenu then + _group.CTLDTopmenu:Remove() + _group.CTLDTopmenu = nil + end + local toptroops = nil + local topcrates = nil + local topmenu = MENU_GROUP:New(_group, "CTLD", nil) + _group.CTLDTopmenu = topmenu + + if cantroops then + local toptroops = MENU_GROUP:New(_group, "Manage Troops", topmenu) + local troopsmenu = MENU_GROUP:New(_group, "Load troops", toptroops) + _group.MyTopTroopsMenu = toptroops + + if self.usesubcats then + local subcatmenus = {} + for catName, _ in pairs(self.subcatsTroop) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, troopsmenu) + end + for _, cargoObj in pairs(self.Cargo_Troops) do + if not cargoObj.DontShowInMenu then + local stock = cargoObj:GetStock() + local menutext = cargoObj.Name + if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, menutext, subcatmenus[cargoObj.Subcategory], self._LoadTroops, self, _group, _unit, cargoObj) + end + end + else + for _, cargoObj in pairs(self.Cargo_Troops) do + if not cargoObj.DontShowInMenu then + local stock = cargoObj:GetStock() + local menutext = cargoObj.Name + if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, menutext, troopsmenu, self._LoadTroops, self, _group, _unit, cargoObj) + end + end + end + local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh() + MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() + MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() + local uName=_unit:GetName() + local loadedData=self.Loaded_Cargo[uName] + if loadedData and loadedData.Cargo then + for i,cargoObj in ipairs(loadedData.Cargo) do + if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) and not cargoObj:WasDropped() then + local name=cargoObj:GetName() or "Unknown" + local needed=cargoObj:GetCratesNeeded() or 1 + local cID=cargoObj:GetID() + local line=string.format("Drop: %s",name,needed,cID) + MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh() + end + end + end + end + if cancrates then + local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) + _group.MyTopCratesMenu = topcrates + + -- Build the “Get Crates” sub-menu items + local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + + if self.onestepmenu then + if self.usesubcats then + local subcatmenus = {} + for catName,_ in pairs(self.subcats) do + subcatmenus[catName] = MENU_GROUP:New(_group,catName,cratesmenu) + end + for _,cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local needed = cargoObj:GetCratesNeeded() or 1 + local txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + local mSet = MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) + MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) + MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) + end + end + for _,cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local needed = cargoObj:GetCratesNeeded() or 1 + local txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + local mSet = MENU_GROUP:New(_group,txt,subcatmenus[cargoObj.Subcategory]) + MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) + MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) + end + end + else + for _,cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local needed = cargoObj:GetCratesNeeded() or 1 + local txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + local mSet = MENU_GROUP:New(_group,txt,cratesmenu) + MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) + MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) + end + end + for _,cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local needed = cargoObj:GetCratesNeeded() or 1 + local txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock>=0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + local mSet = MENU_GROUP:New(_group,txt,cratesmenu) + MENU_GROUP_COMMAND:New(_group,"Get",mSet,self._GetCrates,self,_group,_unit,cargoObj) + MENU_GROUP_COMMAND:New(_group,"Get and Load",mSet,self._GetAndLoad,self,_group,_unit,cargoObj) + + end + end + end + else + if self.usesubcats == true then + local subcatmenus = {} + for catName, _ in pairs(self.subcats) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu) -- fixed variable case + end + for _, cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local needed = cargoObj:GetCratesNeeded() or 1 + local txt = string.format("%d crate%s %s (%dkg)", needed, needed==1 and "" or "s", cargoObj.Name, cargoObj.PerCrateMass or 0) + 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 needed = cargoObj:GetCratesNeeded() or 1 + local txt = string.format("%d crate%s %s (%dkg)", needed, needed==1 and "" or "s", cargoObj.Name, cargoObj.PerCrateMass or 0) + 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 needed = cargoObj:GetCratesNeeded() or 1 + local txt = string.format("%d crate%s %s (%dkg)", needed, needed==1 and "" or "s", cargoObj.Name, cargoObj.PerCrateMass or 0) + 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 needed = cargoObj:GetCratesNeeded() or 1 + local txt = string.format("%d crate%s %s (%dkg)", needed, needed==1 and "" or "s", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) + end + end + end + end + + local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) + _group.MyLoadCratesMenu=loadCratesMenu + MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) + + local dropCratesMenu = MENU_GROUP:New(_group,"Drop Crates",topcrates) + topcrates.DropCratesMenu = dropCratesMenu + + if not self.nobuildmenu then + MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() + end + + local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) + MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) + + if self.onestepmenu then + local mPack=MENU_GROUP:New(_group,"Pack crates",topcrates) + MENU_GROUP_COMMAND:New(_group,"Pack",mPack,self._PackCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Pack and Load",mPack,self._PackAndLoad,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Pack and Remove",mPack,self._PackAndRemove,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) + else + MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) + end + + local uName = _unit:GetName() + local loadedData = self.Loaded_Cargo[uName] + if loadedData and loadedData.Cargo then + local cargoByName = {} + for _, cgo in pairs(loadedData.Cargo) do + if cgo and (not cgo:WasDropped()) then + local cname = cgo:GetName() + local cneeded = cgo:GetCratesNeeded() + cargoByName[cname] = cargoByName[cname] or { count=0, needed=cneeded } + cargoByName[cname].count = cargoByName[cname].count + 1 + end + end + for name, info in pairs(cargoByName) do + local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) + MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) + end + end + end + + ----------------------------------------------------- + -- Misc sub‐menus + ----------------------------------------------------- + MENU_GROUP_COMMAND:New(_group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Inventory", topmenu, self._ListInventory, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit) + + local smoketopmenu = MENU_GROUP:New(_group, "Smokes, Flares, Beacons", topmenu) + MENU_GROUP_COMMAND:New(_group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokeself = MENU_GROUP:New(_group, "Drop smoke now", smoketopmenu) + MENU_GROUP_COMMAND:New(_group, "Red smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Red) + MENU_GROUP_COMMAND:New(_group, "Blue smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Blue) + MENU_GROUP_COMMAND:New(_group, "Green smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Green) + MENU_GROUP_COMMAND:New(_group, "Orange smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Orange) + MENU_GROUP_COMMAND:New(_group, "White smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.White) + + MENU_GROUP_COMMAND:New(_group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() + + if self:IsFixedWing(_unit) then + MENU_GROUP_COMMAND:New(_group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + else + MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + end + + -- Mark we built the menu + self.MenusDone[_unitName] = true + self:_RefreshLoadCratesMenu(_group,_unit) + self:_RefreshDropCratesMenu(_group,_unit) + + end -- if _group + end -- if _unit + else + self:T(self.lid .. " Menus already done for this group!") + end + end -- for all pilot units + + return self end - self.CtldUnits = _UnitList - - -- subcats? - if self.usesubcats then - for _id,_cargo in pairs(self.Cargo_Crates) do - local entry = _cargo -- #CTLD_CARGO - if not self.subcats[entry.Subcategory] then - self.subcats[entry.Subcategory] = entry.Subcategory - end - end - for _id,_cargo in pairs(self.Cargo_Statics) do - local entry = _cargo -- #CTLD_CARGO - if not self.subcats[entry.Subcategory] then - self.subcats[entry.Subcategory] = entry.Subcategory - end - end - for _id,_cargo in pairs(self.Cargo_Troops) do - local entry = _cargo -- #CTLD_CARGO - if not self.subcatsTroop[entry.Subcategory] then - self.subcatsTroop[entry.Subcategory] = entry.Subcategory - end - end - end - - local menucount = 0 - local menus = {} - for _, _unitName in pairs(self.CtldUnits) do - if (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then - self:T(self.lid.."Menu not done yet for ".._unitName) - local _unit = UNIT:FindByName(_unitName) - if not _unit and self.allowCATransport then - _unit = CLIENT:FindByName(_unitName) - end - if _unit and _unit:IsAlive() then - local _group = _unit:GetGroup() - if _group then - self:T(self.lid.."Unit and Group exist") - local capabilities = self:_GetUnitCapabilities(_unit) - local cantroops = capabilities.troops - local cancrates = capabilities.crates - local unittype = _unit:GetTypeName() - local isHook = self:IsHook(_unit) - local nohookswitch = true - --local nohookswitch = not (isHook and self.enableChinookGCLoading) - -- Clear old topmenu if it existed - if _group.CTLDTopmenu then - _group.CTLDTopmenu:Remove() - _group.CTLDTopmenu = nil - end - local toptroops = nil - local topcrates = nil - local topmenu = MENU_GROUP:New(_group, "CTLD", nil) - _group.CTLDTopmenu = topmenu - - if cantroops then - local toptroops = MENU_GROUP:New(_group, "Manage Troops", topmenu) - local troopsmenu = MENU_GROUP:New(_group, "Load troops", toptroops) - _group.MyTopTroopsMenu = toptroops - - if self.usesubcats then - local subcatmenus = {} - for catName, _ in pairs(self.subcatsTroop) do - subcatmenus[catName] = MENU_GROUP:New(_group, catName, troopsmenu) - end - for _, cargoObj in pairs(self.Cargo_Troops) do - if not cargoObj.DontShowInMenu then - local stock = cargoObj:GetStock() - local menutext = cargoObj.Name - if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, menutext, subcatmenus[cargoObj.Subcategory], self._LoadTroops, self, _group, _unit, cargoObj) - end - end - else - for _, cargoObj in pairs(self.Cargo_Troops) do - if not cargoObj.DontShowInMenu then - local stock = cargoObj:GetStock() - local menutext = cargoObj.Name - if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, menutext, troopsmenu, self._LoadTroops, self, _group, _unit, cargoObj) - - end - end - end - local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh() - MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() - MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() - local uName=_unit:GetName() - local loadedData=self.Loaded_Cargo[uName] - if loadedData and loadedData.Cargo then - for i,cargoObj in ipairs(loadedData.Cargo) do - if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) and not cargoObj:WasDropped() then - local name=cargoObj:GetName() or "Unknown" - local needed=cargoObj:GetCratesNeeded() or 1 - local cID=cargoObj:GetID() - local line=string.format("Drop: %s",name,needed,cID) - MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh() - end - end - end - end - if cancrates then - local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) - _group.MyTopCratesMenu = topcrates - - -- Build the “Get Crates” sub-menu items - local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates) - if self.usesubcats then - local subcatmenus = {} - for catName, _ in pairs(self.subcats) do - subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu) - end - for _, cargoObj in pairs(self.Cargo_Crates) do - if not cargoObj.DontShowInMenu then - local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) - if cargoObj.Location then txt = txt.."[R]" end - local stock = cargoObj:GetStock() - if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) - end - end - for _, cargoObj in pairs(self.Cargo_Statics) do - if not cargoObj.DontShowInMenu then - local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) - if cargoObj.Location then txt = txt.."[R]" end - local stock = cargoObj:GetStock() - if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) - end - end - else - for _, cargoObj in pairs(self.Cargo_Crates) do - if not cargoObj.DontShowInMenu then - local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) - if cargoObj.Location then txt = txt.."[R]" end - local stock = cargoObj:GetStock() - if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) - end - end - for _, cargoObj in pairs(self.Cargo_Statics) do - if not cargoObj.DontShowInMenu then - local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) - if cargoObj.Location then txt = txt.."[R]" end - local stock = cargoObj:GetStock() - if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end - MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) - end - end - end - - local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) - _group.MyLoadCratesMenu=loadCratesMenu - MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) - MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) - - local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates) - topcrates.DropCratesMenu=dropCratesMenu - - if not self.nobuildmenu then - MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() - end - - local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) - MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) - - MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) - - local uName = _unit:GetName() - local loadedData = self.Loaded_Cargo[uName] - if loadedData and loadedData.Cargo then - local cargoByName = {} - for _, cgo in pairs(loadedData.Cargo) do - if cgo and (not cgo:WasDropped()) then - local cname = cgo:GetName() - local cneeded = cgo:GetCratesNeeded() - cargoByName[cname] = cargoByName[cname] or { count=0, needed=cneeded } - cargoByName[cname].count = cargoByName[cname].count + 1 - end - end - for name, info in pairs(cargoByName) do - local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) - MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) - end - end - end - - - - ----------------------------------------------------- - -- Misc sub‐menus - ----------------------------------------------------- - MENU_GROUP_COMMAND:New(_group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "Inventory", topmenu, self._ListInventory, self, _group, _unit) - MENU_GROUP_COMMAND:New(_group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit) - - local smoketopmenu = MENU_GROUP:New(_group, "Smokes, Flares, Beacons", topmenu) - MENU_GROUP_COMMAND:New(_group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokeself = MENU_GROUP:New(_group, "Drop smoke now", smoketopmenu) - MENU_GROUP_COMMAND:New(_group, "Red smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Red) - MENU_GROUP_COMMAND:New(_group, "Blue smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Blue) - MENU_GROUP_COMMAND:New(_group, "Green smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Green) - MENU_GROUP_COMMAND:New(_group, "Orange smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Orange) - MENU_GROUP_COMMAND:New(_group, "White smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.White) - - MENU_GROUP_COMMAND:New(_group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) - MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) - MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() - - if self:IsFixedWing(_unit) then - MENU_GROUP_COMMAND:New(_group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() - else - MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() - end - - -- Mark we built the menu - self.MenusDone[_unitName] = true - self:_RefreshLoadCratesMenu(_group, _unit) - self:_RefreshDropCratesMenu(_group,_unit) - - end -- if _group - end -- if _unit - else - self:T(self.lid .. " Menus already done for this group!") - end - end -- for all pilot units - - return self -end - --- (Internal) Function to refresh the menu for load crates. Triggered from land/getcrate/pack and more -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. -- @param Wrapper.Unit#UNIT Unit The calling unit. -- @return #CTLD self -function CTLD:_RefreshLoadCratesMenu(Group, Unit) - if not Group.MyLoadCratesMenu then return end - Group.MyLoadCratesMenu:RemoveSubMenus() - - local d = self.CrateDistance or 35 - local nearby, n = self:_FindCratesNearby(Group, Unit, d, true, true) - if n == 0 then - MENU_GROUP_COMMAND:New(Group, "No crates found! Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) - return - end - MENU_GROUP_COMMAND:New(Group, "Load ALL", Group.MyLoadCratesMenu, self._LoadCratesNearby, self, Group, Unit) - local cargoByName = {} - for _, crate in pairs(nearby) do - local cName = crate:GetName() - cargoByName[cName] = cargoByName[cName] or {} - table.insert(cargoByName[cName], crate) - end - - for cName, cList in pairs(cargoByName) do - local needed = cList[1]:GetCratesNeeded() or 1 - local found = #cList - - local line - if found >= needed then - line = string.format("Load %s", cName) - else - MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) - line = string.format("Load %s (%d/%d)", cName, found, needed) +function CTLD:_RefreshLoadCratesMenu(Group,Unit) + if not Group.MyLoadCratesMenu then return end + Group.MyLoadCratesMenu:RemoveSubMenus() + + local d=self.CrateDistance or 35 + local nearby,n=self:_FindCratesNearby(Group,Unit,d,true,true) + if n==0 then + MENU_GROUP_COMMAND:New(Group,"No crates found! Rescan?",Group.MyLoadCratesMenu,function() self:_RefreshLoadCratesMenu(Group,Unit) end) + return + end + MENU_GROUP_COMMAND:New(Group,"Load ALL",Group.MyLoadCratesMenu,self._LoadCratesNearby,self,Group,Unit) + + local cargoByName={} + for _,crate in pairs(nearby) do + local name=crate:GetName() + cargoByName[name]=cargoByName[name] or{} + table.insert(cargoByName[name],crate) + end + + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + label=string.format("%d. Load %s",lineIndex,cName) + i=i+needed + else + label=string.format("%d. Load %s (%d/%d)",lineIndex,cName,left,needed) + i=#list+1 + end + MENU_GROUP_COMMAND:New(Group,label,Group.MyLoadCratesMenu,self._LoadSingleCrateSet,self,Group,Unit,cName) + lineIndex=lineIndex+1 + end end - MENU_GROUP_COMMAND:New(Group, line, Group.MyLoadCratesMenu, self._LoadSingleCrateSet, self, Group, Unit, cName) end -end + --- -- Loads exactly `CratesNeeded` crates for one cargoName in range. @@ -4712,7 +4956,17 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) cObj:SetWasDropped(true) cObj:SetHasMoved(true) end - +local cname = crateObj:GetName() or "Unknown" +local count = #chunk +if needed > 1 then +if count == needed then + self:_SendMessage(string.format("Dropped %d %s.", 1, cname), 10, false, Group) +else + self:_SendMessage(string.format("Dropped %d/%d crate(s) of %s.", count, needed, cname), 15, false, Group) +end +else +self:_SendMessage(string.format("Dropped %d %s(s).", count, cname), 10, false, Group) +end -- Rebuild the cargo list to remove the dropped crates local loadedData = self.Loaded_Cargo[unitName] if loadedData and loadedData.Cargo then @@ -4745,78 +4999,137 @@ end -- @param Wrapper.Unit#UNIT Unit The calling unit. -- @return #CTLD self function CTLD:_RefreshDropCratesMenu(Group, Unit) - if not Group.CTLDTopmenu then return end - local topCrates = Group.MyTopCratesMenu - if not topCrates then return end - if topCrates.DropCratesMenu then - topCrates.DropCratesMenu:RemoveSubMenus() - else - topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) - end - local dropCratesMenu = topCrates.DropCratesMenu - local loadedData = self.Loaded_Cargo[Unit:GetName()] - if not loadedData or not loadedData.Cargo then - MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) - return - end + if not Group.CTLDTopmenu then return end + local topCrates = Group.MyTopCratesMenu + if not topCrates then return end + if topCrates.DropCratesMenu then + topCrates.DropCratesMenu:RemoveSubMenus() + else + topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) + end + + local dropCratesMenu = topCrates.DropCratesMenu + local loadedData = self.Loaded_Cargo[Unit:GetName()] + if not loadedData or not loadedData.Cargo then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end + + local cargoByName={} + local dropableCrates=0 + for _,cObj in ipairs(loadedData.Cargo) do + if cObj and not cObj:WasDropped() then + local cType=cObj:GetType() + if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then + local name=cObj:GetName()or"Unknown" + cargoByName[name]=cargoByName[name]or{} + table.insert(cargoByName[name],cObj) + dropableCrates=dropableCrates+1 + end + end + end + + if dropableCrates==0 then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end + + ---------------------------------------------------------------------- + -- DEFAULT (“classic”) versus ONE-STEP behaviour + ---------------------------------------------------------------------- + if not self.onestepmenu then + -------------------------------------------------------------------- + -- classic menu + -------------------------------------------------------------------- + MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) + + self.CrateGroupList=self.CrateGroupList or{} + self.CrateGroupList[Unit:GetName()]={} + + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + local chunk={} + for n=i,i+needed-1 do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s",lineIndex,cName) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=i+needed + else + local chunk={} + for n=i,#list do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=#list+1 + end + lineIndex=lineIndex+1 + end + end + + else + -------------------------------------------------------------------- + -- one-step (enhanced) menu + -------------------------------------------------------------------- + local mAll=MENU_GROUP:New(Group,"Drop ALL crates",dropCratesMenu) + MENU_GROUP_COMMAND:New(Group,"Drop",mAll,self._UnloadCrates,self,Group,Unit) + if not ( self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) ) then + MENU_GROUP_COMMAND:New(Group,"Drop and build",mAll,self._DropAndBuild,self,Group,Unit) + end - local cargoByName={} - local dropableCrates=0 - for _,cObj in ipairs(loadedData.Cargo) do - if cObj and not cObj:WasDropped() then - local cType=cObj:GetType() - if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then - local name=cObj:GetName()or"Unknown" - cargoByName[name]=cargoByName[name]or{} - table.insert(cargoByName[name],cObj) - dropableCrates=dropableCrates+1 + self.CrateGroupList=self.CrateGroupList or{} + self.CrateGroupList[Unit:GetName()]={} + + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + local chunk={} + for n=i,i+needed-1 do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s",lineIndex,cName) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + local mSet=MENU_GROUP:New(Group,label,dropCratesMenu) + MENU_GROUP_COMMAND:New(Group,"Drop",mSet,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + if not ( self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) ) then + MENU_GROUP_COMMAND:New(Group,"Drop and build",mSet,self._DropSingleAndBuild,self,Group,Unit,setIndex) + end + i=i+needed + else + local chunk={} + for n=i,#list do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=#list+1 + end + lineIndex=lineIndex+1 + end end end end - if dropableCrates==0 then - MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) - return - end - - MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) - self.CrateGroupList=self.CrateGroupList or{} - self.CrateGroupList[Unit:GetName()]={} - - local lineIndex=1 - for cName,list in pairs(cargoByName) do - local needed=list[1]:GetCratesNeeded() or 1 - table.sort(list,function(a,b)return a:GetID()=needed then - local chunk={} - for n=i,i+needed-1 do - table.insert(chunk,list[n]) - end - local label=string.format("%d. %s",lineIndex,cName) - table.insert(self.CrateGroupList[Unit:GetName()],chunk) - local setIndex=#self.CrateGroupList[Unit:GetName()] - MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) - i=i+needed - else - local chunk={} - for n=i,#list do - table.insert(chunk,list[n]) - end - local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) - table.insert(self.CrateGroupList[Unit:GetName()],chunk) - local setIndex=#self.CrateGroupList[Unit:GetName()] - MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) - i=#list+1 - end - lineIndex=lineIndex+1 - end - end -end - --- (Internal) Function to unload a single Troop group by ID. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. @@ -4832,7 +5145,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP) end if inzone then - droppingatbase = true + droppingatbase = self.returntroopstobase end if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then @@ -4864,7 +5177,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) return self end - -- Drop ONLY the FIRST cargo in that chunk + -- Drop 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) @@ -4917,6 +5230,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) @@ -4925,6 +5239,8 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) 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) @@ -5121,6 +5437,52 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub return self end +--- Identical to AddCratesCargo, but registers the cargo so the spawned/built group does not move to MOVE zones. +--- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #string Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. +-- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. +-- @param #number NoCrates Number of crates needed to build this cargo. +-- @param #number PerCrateMass Mass in kg of each crate +-- @param #number Stock Number of buildable groups in stock. Nil for unlimited. +-- @param #string SubCategory Name of sub-category (optional). +-- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. +-- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. +-- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH58D"}. +-- @param #string Category Static category name (optional). If set, spawn cargo crate with an alternate category type, e.g. "Cargos". +-- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape, e.g. "iso_container". +-- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape, e.g. "iso_container_cargo". +-- @return #CTLD self +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) + self:T(self.lid .. " AddCratesCargoNoMove") + if not self:_CheckTemplates(Templates) then + self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" ) + return self + end + self.CargoCounter = self.CargoCounter + 1 + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) + cargo.NoMoveToZone = true + if UnitTypes then + cargo:AddUnitTypeName(UnitTypes) + end + cargo:SetStaticTypeAndShape("Cargos",self.basetype) + if TypeName then + cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) + end + table.insert(self.Cargo_Crates,cargo) + self.templateToCargoName = self.templateToCargoName or {} + if type(Templates)=="table" then + for _,t in pairs(Templates) do self.templateToCargoName[t] = Name end + else + self.templateToCargoName[Templates] = Name + end + self.nomovetozone_names = self.nomovetozone_names or {} + self.nomovetozone_names[Name] = true + if SubCategory and self.usesubcats ~= true then self.usesubcats=true end + return self +end + --- User function - Add *generic* static-type loadable as cargo. This type will create cargo that needs to be loaded, moved and dropped. -- @param #CTLD self -- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1". @@ -5365,8 +5727,14 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship return self end end - - local ctldzone = {} -- #CTLD.CargoZone + + local exists = true + local ctldzone = self:GetCTLDZone(Name, Type) -- #CTLD.CargoZone + if not ctldzone then + exists = false + ctldzone = {} + end + ctldzone.active = Active or false ctldzone.color = Color or SMOKECOLOR.Red ctldzone.name = Name or "NONE" @@ -5392,11 +5760,56 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship ctldzone.shiplength = Shiplength or 100 ctldzone.shipwidth = Shipwidth or 10 end - - self:AddZone(ctldzone) + + if not exists then + self:AddZone(ctldzone) + end return self end + +--- User function - find #CTLD.CargoZone zone by name. +-- @param #CTLD self +-- @param #string Name Name of this zone. +-- @param #string Type Type of this zone, #CTLD.CargoZoneType +-- @return #CTLD.CargoZone self +function CTLD:GetCTLDZone(Name, Type) + + if Type == CTLD.CargoZoneType.LOAD then + for _, z in pairs(self.pickupZones) do + if z.name == Name then + return z + end + end + elseif Type == CTLD.CargoZoneType.DROP then + for _, z in pairs(self.dropOffZones) do + if z.name == Name then + return z + end + end + elseif Type == CTLD.CargoZoneType.SHIP then + for _, z in pairs(self.shipZones) do + if z.name == Name then + return z + end + end + elseif Type == CTLD.CargoZoneType.BEACON then + for _, z in pairs(self.droppedBeacons) do + if z.name == Name then + return z + end + end + else + for _, z in pairs(self.wpZones) do + if z.name == Name then + return z + end + end + end + + return nil +end + --- User function - Creates and adds a #CTLD.CargoZone zone for this CTLD instance from an Airbase or FARP name. -- Zones of type LOAD: Players load crates and troops here. -- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. @@ -5662,6 +6075,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype) if Zonetype == CTLD.CargoZoneType.SHIP then self:T("Checking Type Ship: "..zonename) local ZoneUNIT = UNIT:FindByName(zonename) + if not ZoneUNIT then return false end zonecoord = ZoneUNIT:GetCoordinate() zoneradius = czone.shiplength zonewidth = czone.shipwidth @@ -5739,16 +6153,23 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) for index,cargozone in pairs(zones[i]) do local CZone = cargozone --#CTLD.CargoZone local zonename = CZone.name - local zone = nil + local zone = nil -- Core.Zone#ZONE_RADIUS + local airbasezone = false if i == 4 then zone = UNIT:FindByName(zonename) else zone = ZONE:FindByName(zonename) if not zone then zone = AIRBASE:FindByName(zonename):GetZone() + airbasezone = true end end local zonecoord = zone:GetCoordinate() + -- Avoid smoke/flares on runways + if (i==1 or 1==3) and airbasezone==true and zone:IsInstanceOf("ZONE_BASE") then + zonecoord = zone:GetRandomCoordinate(inner,outer,{land.SurfaceType.LAND}) + end + if zonecoord then local active = CZone.active local color = CZone.color local distance = self:_GetDistance(zonecoord,unitcoord) @@ -5765,6 +6186,7 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) smoked = true end + end end end if not smoked then @@ -6618,6 +7040,7 @@ end local alias = string.format("%s-%d", _template, math.random(1,100000)) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(randompositions,20,2) + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitDelayOff() :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) @@ -6771,12 +7194,14 @@ end if canmove then self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitDelayOff() :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() + :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) end @@ -6846,6 +7271,16 @@ end local filepath = self.filepath self:__Save(interval,filepath,filename) end + + if type(self.VehicleMoveFormation) == "table" then + local Formations = {} + for _,_formation in pairs(self.VehicleMoveFormation) do + table.insert(Formations,_formation) + end + self.VehicleMoveFormation = nil + self.VehicleMoveFormation = Formations + end + return self end @@ -6983,13 +7418,17 @@ end -- right subtype? if Event == subtype and not task:IsDone() then local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... - --self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")}) - local okaygroup = string.find(Groupname,task:GetProperty("ExtractName"),1,true) - if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and okaygroup then - if task.Clients:HasUniqueID(playername) then - -- success - task:__Success(-1) + self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")}) + if task:GetProperty("ExtractName") then + local okaygroup = string.find(Groupname,task:GetProperty("ExtractName"),1,true) + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and okaygroup then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end end + else + self:T({Text="'ExtractName' Property not set",Name=Groupname,Property=task.Type}) end end end @@ -7112,8 +7551,11 @@ end -- @return #CTLD self function CTLD:onafterCratesBuild(From, Event, To, Group, Unit, Vehicle) self:T({From, Event, To}) - if self.movetroopstowpzone then - self:_MoveGroupToZone(Vehicle) + if self.movetroopstowpzone and Vehicle then + local cg = self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) + if not (cg and (cg.NoMoveToZone or (self.nomovetozone_names and self.nomovetozone_names[cg:GetName()]))) then + self:_MoveGroupToZone(Vehicle) + end end return self end diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 26f54e66d..c18d838d6 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -88,7 +88,7 @@ COHORT = { --- COHORT class version. -- @field #string version -COHORT.version="0.3.6" +COHORT.version="0.3.7" --- Global variable to store the unique(!) cohort names _COHORTNAMES={} @@ -100,6 +100,7 @@ _COHORTNAMES={} -- DONE: Create FLOTILLA class. -- DONE: Added check for properties. -- DONE: Make general so that PLATOON and SQUADRON can inherit this class. +-- DONE: Better setting of call signs. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -515,10 +516,12 @@ end -- @param #COHORT self -- @param #number Callsign Callsign from CALLSIGN.Aircraft, e.g. "Chevy" for CALLSIGN.Aircraft.CHEVY. -- @param #number Index Callsign index, Chevy-**1**. +-- @param #string CallsignString (optional) Set this for tasks like TANKER, AWACS or KIOWA and the like, which have special names. E.g. "Darkstar" or "Roughneck". -- @return #COHORT self -function COHORT:SetCallsign(Callsign, Index) +function COHORT:SetCallsign(Callsign, Index, CallsignString) self.callsignName=Callsign self.callsignIndex=Index + self.callsignClearName=CallsignString self.callsign={} self.callsign.NumberSquad=Callsign self.callsign.NumberGroup=Index @@ -679,7 +682,16 @@ end function COHORT:GetCallsign(Asset) if self.callsignName then - + --[[ + ["callsign"] = + { + [2] = 1, + ["name"] = "Darkstar11", + [3] = 1, + [1] = 5, + [4] = "Darkstar11", + }, -- end of ["callsign"] + ]] Asset.callsign={} for i=1,Asset.nunits do @@ -695,12 +707,16 @@ function COHORT:GetCallsign(Asset) else self.callsigncounter=self.callsigncounter+1 end + callsign["name"] = self.callsignClearName or UTILS.GetCallsignName(self.callsignName) or "None" + callsign["name"] = string.format("%s%d%d",callsign["name"],callsign[2],callsign[3]) + callsign[4] = callsign["name"] Asset.callsign[i]=callsign self:T3({callsign=callsign}) - --TODO: there is also a table entry .name, which is a string. + --DONE: there is also a table entry .name, which is a string. + --UTILS.PrintTableToLog(callsign) end diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index b7d30f298..89a2f50c0 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -136,6 +136,7 @@ COMMANDER = { awacsZones = {}, tankerZones = {}, limitMission = {}, + maxMissionsAssignPerCycle = 1, } --- COMMANDER class version. @@ -1535,6 +1536,8 @@ function COMMANDER:CheckMissionQueue() end end + local missionsAssigned = 0 + -- Loop over missions in queue. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -1594,9 +1597,12 @@ function COMMANDER:CheckMissionQueue() -- Recruited assets but no requested escort available. Unrecruit assets! LEGION.UnRecruitAssets(assets, mission) end - - -- Only ONE mission is assigned. - return + + missionsAssigned = missionsAssigned + 1 + if missionsAssigned >= (self.maxMissionsAssignPerCycle or 1) then + return + end + end else @@ -1611,6 +1617,16 @@ function COMMANDER:CheckMissionQueue() end +--- Set how many missions can be assigned in a single status iteration. (eg. This is useful for persistent missions where you need to load all AUFTRAGs on mission start and then change it back to default) +--- Warning: Increasing this value will increase the number of missions started per iteration and thus may lead to performance issues if too many missions are started at once. +-- @param #COMMANDER self +-- @param #number Number of missions assigned per status iteration. Default is 1. +-- @return #COMMANDER self. +function COMMANDER:SetMaxMissionsAssignPerCycle(MaxMissionsAssignPerCycle) + self.maxMissionsAssignPerCycle = MaxMissionsAssignPerCycle or 1 + return self +end + --- Get cohorts. -- @param #COMMANDER self -- @param #table Legions Special legions. @@ -1670,9 +1686,12 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation) for _,_legion in pairs(Legions or {}) do local legion=_legion --Ops.Legion#LEGION - -- Check that runway is operational. - local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true - + -- Check that runway is operational. + local Runway=true + if legion:IsAirwing() then + Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition() + end + -- Legion has to be running. if legion:IsRunning() and Runway then @@ -1703,9 +1722,12 @@ function COMMANDER:_GetCohorts(Legions, Cohorts, Operation) for _,_legion in pairs(self.legions) do local legion=_legion --Ops.Legion#LEGION - -- Check that runway is operational. - local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true - + -- Check that runway is operational. + local Runway=true + if legion:IsAirwing() then + Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition() + end + -- Legion has to be running. if legion:IsRunning() and Runway then diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 4fcd7c2cb..8a902cad5 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -1,13 +1,18 @@ ------------------------------------------------------------------------- -- Easy CAP/GCI Class, based on OPS classes ------------------------------------------------------------------------- --- Documentation +-- +-- ## Documentation: -- -- https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Ops.EasyGCICAP.html -- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/EasyGCICAP). +-- ------------------------------------------------------------------------- -- Date: September 2023 --- Last Update: July 2024 +-- Last Update: Aug 2025 ------------------------------------------------------------------------- -- --- **Ops** - Easy GCI & CAP Manager @@ -70,6 +75,11 @@ -- @field #boolean DespawnAfterLanding -- @field #boolean DespawnAfterHolding -- @field #list ListOfAuftrag +-- @field #string defaulttakeofftype Take off type +-- @field #number FuelLowThreshold +-- @field #number FuelCriticalThreshold +-- @field #boolean showpatrolpointmarks +-- @field #table EngageTargetTypes -- @extends Core.Fsm#FSM --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. @@ -223,7 +233,12 @@ EASYGCICAP = { ReadyFlightGroups = {}, DespawnAfterLanding = false, DespawnAfterHolding = true, - ListOfAuftrag = {} + ListOfAuftrag = {}, + defaulttakeofftype = "hot", + FuelLowThreshold = 25, + FuelCriticalThreshold = 10, + showpatrolpointmarks = false, + EngageTargetTypes = {"Air"}, } --- Internal Squadron data type @@ -256,10 +271,11 @@ EASYGCICAP = { -- @field #number Speed -- @field #number Heading -- @field #number LegLength +-- @field Core.Zone#ZONE_BASE Zone --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.18" +EASYGCICAP.version="0.1.30" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -312,6 +328,11 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.DespawnAfterLanding = false self.DespawnAfterHolding = true self.ListOfAuftrag = {} + self.defaulttakeofftype = "hot" + self.FuelLowThreshold = 25 + self.FuelCriticalThreshold = 10 + self.showpatrolpointmarks = false + self.EngageTargetTypes = {"Air"} -- Set some string id for output to DCS.log file. self.lid=string.format("EASYGCICAP %s | ", self.alias) @@ -336,6 +357,63 @@ end -- Functions ------------------------------------------------------------------------- +--- Get a specific managed AirWing by name +-- @param #EASYGCICAP self +-- @param #string AirbaseName Airbase name of the home of this wing. +-- @return Ops.AirWing#AIRWING Airwing or nil if not found +function EASYGCICAP:GetAirwing(AirbaseName) + self:T(self.lid.."GetAirwing") + if self.wings[AirbaseName] then + return self.wings[AirbaseName][1] + end + return nil +end + +--- Get a table of all managed AirWings +-- @param #EASYGCICAP self +-- @return #table Table of Ops.AirWing#AIRWING Airwings +function EASYGCICAP:GetAirwingTable() + self:T(self.lid.."GetAirwingTable") + local Wingtable = {} + for _,_object in pairs(self.wings or {}) do + table.insert(Wingtable,_object[1]) + end + return Wingtable +end + +--- Set "fuel low" threshold for CAP and INTERCEPT flights. +-- @param #EASYGCICAP self +-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 25. +-- @return #EASYGCICAP self +function EASYGCICAP:SetFuelLow(Percent) + self:T(self.lid.."SetFuelLow") + self.FuelLowThreshold = Percent or 25 + return self +end + +--- Set markers on the map for Patrol Points. +-- @param #EASYGCICAP self +-- @param #boolean onoff Set to true to switch markers on. +-- @return #EASYGCICAP self +function EASYGCICAP:ShowPatrolPointMarkers(onoff) + if onoff then + self.showpatrolpointmarks = true + else + self.showpatrolpointmarks = false + end + return self +end + +--- Set "fuel critical" threshold for CAP and INTERCEPT flights. +-- @param #EASYGCICAP self +-- @param #number Percent RTB if fuel at this percent. Values: 1..100, defaults to 10. +-- @return #EASYGCICAP self +function EASYGCICAP:SetFuelCritical(Percent) + self:T(self.lid.."SetFuelCritical") + self.FuelCriticalThreshold = Percent or 10 + return self +end + --- Set CAP formation. -- @param #EASYGCICAP self -- @param #number Formation Formation to fly, defaults to ENUMS.Formation.FixedWing.FingerFour.Group @@ -356,7 +434,7 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch) return self end ---- Count alive missions in our internal stack. +--- (internal) Count alive missions in our internal stack. -- @param #EASYGCICAP self -- @return #number count function EASYGCICAP:_CountAliveAuftrags() @@ -400,6 +478,16 @@ function EASYGCICAP:SetDefaultRepeatOnFailure(Retries) return self end +--- Add default take off type for the airwings. +-- @param #EASYGCICAP self +-- @param #string Takeoff Can be "hot", "cold", or "air" - default is "hot". +-- @return #EASYGCICAP self +function EASYGCICAP:SetDefaultTakeOffType(Takeoff) + self:T(self.lid.."SetDefaultTakeOffType") + self.defaulttakeofftype = Takeoff or "hot" + return self +end + --- Set default CAP Speed in knots -- @param #EASYGCICAP self -- @param #number Speed Speed defaults to 300 @@ -523,6 +611,17 @@ function EASYGCICAP:SetCapStartTimeVariation(Start, End) return self end + +--- Set which target types CAP flights will prefer to engage, defaults to {"Air"} +-- @param #EASYGCICAP self +-- @param #table types Table of comma separated #string entries, defaults to {"Air"} (everything that flies and is not a weapon). Useful other options are e.g. {"Bombers"}, {"Fighters"}, +-- or {"Helicopters"} or combinations like {"Bombers", "Fighters", "UAVs"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes). +-- @return #EASYGCICAP self +function EASYGCICAP:SetCAPEngageTargetTypes(types) + self.EngageTargetTypes = types or {"Air"} + return self +end + --- Add an AirWing to the manager -- @param #EASYGCICAP self -- @param #string Airbasename @@ -569,6 +668,13 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local DespawnAfterLanding = self.DespawnAfterLanding local DespawnAfterHolding = self.DespawnAfterHolding + -- Check STATIC name + local check = STATIC:FindByName(Airbasename,false) or UNIT:FindByName(Airbasename) + if check == nil then + MESSAGE:New(self.lid.."There's no warehouse static on the map (wrong naming?) for airbase "..tostring(Airbasename).."!",30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end + -- Create Airwing local CAP_Wing = AIRWING:New(Airbasename,Alias) CAP_Wing:SetVerbosityLevel(0) @@ -578,6 +684,10 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) CAP_Wing:SetRespawnAfterDestroyed() CAP_Wing:SetNumberCAP(self.capgrouping) CAP_Wing:SetCapCloseRaceTrack(true) + + if self.showpatrolpointmarks then + CAP_Wing:ShowPatrolPointMarkers(true) + end if self.capOptionVaryStartTime then CAP_Wing:SetCapStartTimeVariation(self.capOptionVaryStartTime,self.capOptionVaryEndTime) @@ -596,9 +706,8 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) if #self.ManagedREC > 0 then CAP_Wing:SetNumberRecon(1) end - --local PatrolCoordinateKutaisi = ZONE:New(CapZoneName):GetCoordinate() - --CAP_Wing:AddPatrolPointCAP(PatrolCoordinateKutaisi,self.capalt,UTILS.KnotsToAltKIAS(self.capspeed,self.capalt),self.capdir,self.capleg) - CAP_Wing:SetTakeoffHot() + + CAP_Wing:SetTakeoffType(self.defaulttakeofftype) CAP_Wing:SetLowFuelThreshold(0.3) CAP_Wing.RandomAssetScore = math.random(50,100) CAP_Wing:Start() @@ -606,6 +715,12 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local Intel = self.Intel local TankerInvisible = self.TankerInvisible + local engagerange = self.engagerange + local GoZoneSet = self.GoZoneSet + local NoGoZoneSet = self.NoGoZoneSet + local FuelLow = self.FuelLowThreshold or 25 + local FuelCritical = self.FuelCriticalThreshold or 10 + local EngageTypes = self.EngageTargetTypes or {"Air"} function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission) local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP @@ -617,10 +732,15 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) flightgroup:SetDestinationbase(AIRBASE:FindByName(Airbasename)) flightgroup:GetGroup():CommandEPLRS(true,5) flightgroup:GetGroup():SetOptionRadarUsingForContinousSearch() + flightgroup:GetGroup():SetOptionLandingOverheadBreak() if Mission.type ~= AUFTRAG.Type.TANKER and Mission.type ~= AUFTRAG.Type.AWACS and Mission.type ~= AUFTRAG.Type.RECON then flightgroup:SetDetection(true) - flightgroup:SetEngageDetectedOn(self.engagerange,{"Air"},self.GoZoneSet,self.NoGoZoneSet) + flightgroup:SetEngageDetectedOn(engagerange,EngageTypes,GoZoneSet,NoGoZoneSet) flightgroup:SetOutOfAAMRTB() + flightgroup:SetFuelLowRTB(true) + flightgroup:SetFuelLowThreshold(FuelLow) + flightgroup:SetFuelCriticalRTB(true) + flightgroup:SetFuelCriticalThreshold(FuelCritical) if CapFormation then flightgroup:GetGroup():SetOption(AI.Option.Air.id.FORMATION,CapFormation) end @@ -659,24 +779,30 @@ end --- Add a CAP patrol point to a Wing -- @param #EASYGCICAP self -- @param #string AirbaseName Name of the Wing's airbase --- @param Core.Point#COORDINATE Coordinate. +-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Defaults to 25000 feet ASL. -- @param #number Speed Defaults to 300 knots TAS. -- @param #number Heading Defaults to 90 degrees (East). -- @param #number LegLength Defaults to 15 NM. -- @return #EASYGCICAP self function EASYGCICAP:AddPatrolPointCAP(AirbaseName,Coordinate,Altitude,Speed,Heading,LegLength) - self:T(self.lid.."AddPatrolPointCAP "..Coordinate:ToStringLLDDM()) - local EntryCAP = {} -- #EASYGCICAP.CapPoint + self:T(self.lid.."AddPatrolPointCAP")--..Coordinate:ToStringLLDDM()) + local coordinate = Coordinate + local EntryCAP = {} -- #EASYGCICAP.CapPoint + if Coordinate:IsInstanceOf("ZONE_BASE") then + -- adjust coordinate and get the coordinate from the zone + coordinate = Coordinate:GetCoordinate() + EntryCAP.Zone = Coordinate + end EntryCAP.AirbaseName = AirbaseName - EntryCAP.Coordinate = Coordinate + EntryCAP.Coordinate = coordinate EntryCAP.Altitude = Altitude or 25000 EntryCAP.Speed = Speed or 300 EntryCAP.Heading = Heading or 90 EntryCAP.LegLength = LegLength or 15 self.ManagedCP[#self.ManagedCP+1] = EntryCAP if self.debug then - local mark = MARKER:New(Coordinate,self.lid.."Patrol Point"):ToAll() + local mark = MARKER:New(coordinate,self.lid.."Patrol Point"):ToAll() end return self end @@ -684,7 +810,7 @@ end --- Add a RECON patrol point to a Wing -- @param #EASYGCICAP self -- @param #string AirbaseName Name of the Wing's airbase --- @param Core.Point#COORDINATE Coordinate. +-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Defaults to 25000 feet. -- @param #number Speed Defaults to 300 knots. -- @param #number Heading Defaults to 90 degrees (East). @@ -709,7 +835,7 @@ end --- Add a TANKER patrol point to a Wing -- @param #EASYGCICAP self -- @param #string AirbaseName Name of the Wing's airbase --- @param Core.Point#COORDINATE Coordinate. +-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Defaults to 25000 feet. -- @param #number Speed Defaults to 300 knots. -- @param #number Heading Defaults to 90 degrees (East). @@ -734,7 +860,7 @@ end --- Add an AWACS patrol point to a Wing -- @param #EASYGCICAP self -- @param #string AirbaseName Name of the Wing's airbase --- @param Core.Point#COORDINATE Coordinate. +-- @param Core.Point#COORDINATE Coordinate. Can be handed as a Core.Zone#ZONE object (e.g. in case you want the point to align with a moving zone). -- @param #number Altitude Defaults to 25000 feet. -- @param #number Speed Defaults to 300 knots. -- @param #number Heading Defaults to 90 degrees (East). @@ -763,6 +889,11 @@ function EASYGCICAP:_SetTankerPatrolPoints() self:T(self.lid.."_SetTankerPatrolPoints") for _,_data in pairs(self.ManagedTK) do local data = _data --#EASYGCICAP.CapPoint + self:T("Airbasename = "..data.AirbaseName) + if not self.wings[data.AirbaseName] then + MESSAGE:New(self.lid.."You are trying to create a TANKER point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING local Coordinate = data.Coordinate local Altitude = data.Altitude @@ -782,6 +913,11 @@ function EASYGCICAP:_SetAwacsPatrolPoints() self:T(self.lid.."_SetAwacsPatrolPoints") for _,_data in pairs(self.ManagedEWR) do local data = _data --#EASYGCICAP.CapPoint + self:T("Airbasename = "..data.AirbaseName) + if not self.wings[data.AirbaseName] then + MESSAGE:New(self.lid.."You are trying to create an AWACS point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING local Coordinate = data.Coordinate local Altitude = data.Altitude @@ -801,13 +937,23 @@ function EASYGCICAP:_SetCAPPatrolPoints() self:T(self.lid.."_SetCAPPatrolPoints") for _,_data in pairs(self.ManagedCP) do local data = _data --#EASYGCICAP.CapPoint + self:T("Airbasename = "..data.AirbaseName) + if not self.wings[data.AirbaseName] then + MESSAGE:New(self.lid.."You are trying to create a CAP point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING local Coordinate = data.Coordinate local Altitude = data.Altitude local Speed = data.Speed local Heading = data.Heading local LegLength = data.LegLength - Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) + local Zone = _data.Zone + if Zone then + Wing:AddPatrolPointCAP(Zone,Altitude,Speed,Heading,LegLength) + else + Wing:AddPatrolPointCAP(Coordinate,Altitude,Speed,Heading,LegLength) + end end return self @@ -820,6 +966,11 @@ function EASYGCICAP:_SetReconPatrolPoints() self:T(self.lid.."_SetReconPatrolPoints") for _,_data in pairs(self.ManagedREC) do local data = _data --#EASYGCICAP.CapPoint + self:T("Airbasename = "..data.AirbaseName) + if not self.wings[data.AirbaseName] then + MESSAGE:New(self.lid.."You are trying to create a RECON point for which there is no wing! "..tostring(data.AirbaseName),30,"CHECK"):ToAllIf(self.debug):ToLog() + return + end local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING local Coordinate = data.Coordinate local Altitude = data.Altitude @@ -868,7 +1019,7 @@ end -- @param #string SquadName Squadron name - must be unique! -- @param #string AirbaseName Name of the airbase the airwing resides on, e.g. AIRBASE.Caucasus.Kutaisi -- @param #number AirFrames Number of available airframes, e.g. 20. --- @param #string Skill(optional) Skill level, e.g. AI.Skill.AVERAGE +-- @param #string Skill (optional) Skill level, e.g. AI.Skill.AVERAGE -- @param #string Modex (optional) Modex to be used,e.g. 402. -- @param #string Livery (optional) Livery name to be used. -- @return #EASYGCICAP self @@ -1073,7 +1224,9 @@ function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, Air Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE) Squadron_One:SetMissionRange(self.missionrange) Squadron_One:SetRadio(Frequency,Modulation) - Squadron_One:AddTacanChannel(TACAN,TACAN) + if TACAN then + Squadron_One:AddTacanChannel(TACAN,TACAN) + end local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING @@ -1157,19 +1310,19 @@ end -- @return #boolean assigned -- @return #number leftover function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group,WingSize) - self:I("_TryAssignIntercept for size "..WingSize or 1) + self:T("_TryAssignIntercept for size "..WingSize or 1) local assigned = false local wingsize = WingSize or 1 local mindist = 0 local disttable = {} if Group and Group:IsAlive() then local gcoord = Group:GetCoordinate() or COORDINATE:New(0,0,0) - self:I(self.lid..string.format("Assignment for %s",Group:GetName())) + self:T(self.lid..string.format("Assignment for %s",Group:GetName())) for _name,_FG in pairs(ReadyFlightGroups or {}) do local FG = _FG -- Ops.FlightGroup#FLIGHTGROUP local fcoord = FG:GetCoordinate() local dist = math.floor(UTILS.Round(fcoord:Get2DDistance(gcoord)/1000,1)) - self:I(self.lid..string.format("FG %s Distance %dkm",_name,dist)) + self:T(self.lid..string.format("FG %s Distance %dkm",_name,dist)) disttable[#disttable+1] = { FG=FG, dist=dist} if dist>mindist then mindist=dist end end @@ -1186,7 +1339,7 @@ function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group local cm = FG:GetMissionCurrent() if cm then cm:Cancel() end wingsize = wingsize - 1 - self:I(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) + self:T(self.lid..string.format("Assigned to FG %s Distance %dkm",FG:GetName(),_entry.dist)) if wingsize == 0 then assigned = true break @@ -1216,7 +1369,7 @@ function EASYGCICAP:_AssignIntercept(Cluster) local conflictzoneset = self.ConflictZoneSet local ReadyFlightGroups = self.ReadyFlightGroups - -- Aircraft? + -- Aircraft? if Cluster.ctype ~= INTEL.Ctype.AIRCRAFT then return end -- Threatlevel 0..10 local contact = self.Intel:GetHighestThreatContact(Cluster) @@ -1261,6 +1414,10 @@ function EASYGCICAP:_AssignIntercept(Cluster) local data = _data -- #EASYGCICAP.CapPoint local name = data.AirbaseName local zonecoord = data.Coordinate + if data.Zone then + -- refresh coordinate in case we have a (moving) zone + zonecoord = data.Zone:GetCoordinate() + end local airwing = wings[name][1] local coa = AIRBASE:FindByName(name):GetCoalition() local samecoalitionab = coa == self.coalition and true or false @@ -1362,7 +1519,7 @@ function EASYGCICAP:_StartIntel() end ------------------------------------------------------------------------- --- FSM Functions +-- TODO FSM Functions ------------------------------------------------------------------------- --- (Internal) FSM Function onafterStart @@ -1458,7 +1615,7 @@ function EASYGCICAP:onafterStatus(From,Event,To) local engage = FG:IsEngaging() local hasmissiles = FG:IsOutOfMissiles() == nil and true or false local ready = hasmissiles and FG:IsFuelGood() and FG:IsAirborne() - --self:I(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready))) + --self:T(string.format("Flightgroup %s Engaging = %s Ready = %s",tostring(name),tostring(engage),tostring(ready))) if ready then self.ReadyFlightGroups[name] = FG end @@ -1493,5 +1650,8 @@ end function EASYGCICAP:onafterStop(From,Event,To) self:T({From,Event,To}) self.Intel:Stop() + for _,_wing in pairs(self.wings or {}) do + _wing:Stop() + end return self end diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 656c87c2e..612fafb93 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -2587,7 +2587,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Payer Menu +-- Player Menu ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create player menu. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1d77397e0..269dae89e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -259,7 +259,7 @@ function FLIGHTGROUP:New(group) local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #FLIGHTGROUP -- Set some string id for output to DCS.log file. - self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) + self.lid=string.format("FLIGHTGROUP %s | ", self.groupname or "N/A") -- Defaults self:SetDefaultROE() @@ -779,6 +779,61 @@ function FLIGHTGROUP:SetJettisonWeapons(Switch) return self end +--- Set the aircraft to land straight in. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionLandingStraightIn() + self.OptionLandingStraightIn = true + if self:GetGroup():IsAlive() then + self:GetGroup():SetOptionLandingStraightIn() + end + return self +end + +--- Set the aircraft to land in pairs. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionLandingForcePair() + self.OptionLandingForcePair = true + if self:GetGroup():IsAlive() then + self:GetGroup():SetOptionLandingForcePair() + end + return self +end + +--- Set the aircraft to NOT land in pairs. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionLandingRestrictPair() + self.OptionLandingRestrictPair = true + if self:GetGroup():IsAlive() then + self:GetGroup():SetOptionLandingRestrictPair() + end + return self +end + +--- Set the aircraft to land after overhead break. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionLandingOverheadBreak() + self.OptionLandingOverheadBreak = true + if self:GetGroup():IsAlive() then + self:GetGroup():SetOptionLandingOverheadBreak() + end + return self +end + +--- [HELICOPTER] Set the aircraft to prefer takeoff and landing vertically. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionPreferVertical() + self.OptionPreferVertical = true + if self:GetGroup():IsAlive() then + self:GetGroup():OptionPreferVerticalLanding() + end + return self +end + --- Set if group is ready for taxi/takeoff if controlled by a `FLIGHTCONTROL`. -- @param #FLIGHTGROUP self -- @param #boolean ReadyTO If `true`, flight is ready for takeoff. @@ -2002,6 +2057,9 @@ function FLIGHTGROUP:onafterElementAirborne(From, Event, To, Element) -- Debug info. self:T2(self.lid..string.format("Element airborne %s", Element.name)) + + -- Set parking spot to free. Also for FC. This is usually done after taxiing but doing it here in case the group is teleported. + self:_SetElementParkingFree(Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.AIRBORNE) @@ -3076,7 +3134,7 @@ function FLIGHTGROUP:onbeforeLandAtAirbase(From, Event, To, airbase) local Tsuspend=nil if airbase==nil then - self:T(self.lid.."ERROR: Airbase is nil in LandAtAirase() call!") + self:T(self.lid.."ERROR: Airbase is nil in LandAtAirbase() call!") allowed=false end @@ -4494,6 +4552,11 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) -- Airbase. airbase=airbase or self:GetClosestAirbase() + if airbase == nil then + self:T(self.lid.."No airbase found for element "..element.name) + return nil + end + -- Parking table of airbase. local parking=airbase.parking --:GetParkingSpotsTable() @@ -4604,10 +4667,12 @@ function FLIGHTGROUP:GetParking(airbase) local coords={} for clientname, client in pairs(clients) do local template=_DATABASE:GetGroupTemplateFromUnitName(clientname) - local units=template.units - for i,unit in pairs(units) do - local coord=COORDINATE:New(unit.x, unit.alt, unit.y) - coords[unit.name]=coord + if template then + local units=template.units + for i,unit in pairs(units) do + local coord=COORDINATE:New(unit.x, unit.alt, unit.y) + coords[unit.name]=coord + end end end return coords @@ -4964,7 +5029,7 @@ function FLIGHTGROUP:_UpdateMenu(delay) -- Message to group. MESSAGE:New(text, 5):ToGroup(self.group) - self:I(self.lid..text) + self:T(self.lid..text) end -- Get current position of player. diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index f9996cf91..f582f0675 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -2324,7 +2324,7 @@ INTEL_DLINK = { verbose = 0, lid = nil, alias = nil, - cachetime = 300, + cachetime = 120, interval = 20, contacts = {}, clusters = {}, @@ -2333,7 +2333,7 @@ INTEL_DLINK = { --- Version string -- @field #string version -INTEL_DLINK.version = "0.0.1" +INTEL_DLINK.version = "0.0.2" --- Function to instantiate a new object -- @param #INTEL_DLINK self @@ -2384,15 +2384,15 @@ function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime) self.alias="SPECTRE" end - -- Cache time - self.cachetime = Cachetime or 300 - -- Interval self.interval = Interval or 20 -- Set some string id for output to DCS.log file. self.lid=string.format("INTEL_DLINK %s | ", self.alias) + -- Cache time + self:SetDLinkCacheTime(Cachetime or 120) + -- Start State. self:SetStartState("Stopped") @@ -2477,6 +2477,16 @@ function INTEL_DLINK:onafterStart(From, Event, To) return self end + --- Function to set how long INTEL DLINK remembers contacts. + -- @param #INTEL_DLINK self + -- @param #number seconds Remember this many seconds. Defaults to 180. + -- @return #INTEL_DLINK self + function INTEL_DLINK:SetDLinkCacheTime(seconds) + self.cachetime = math.abs(seconds or 120) + self:I(self.lid.."Caching for "..self.cachetime.." seconds.") + return self + end + --- Function to collect data from the various #INTEL -- @param #INTEL_DLINK self -- @param #string From The From state diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 075866209..8e3a3f10c 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -662,6 +662,15 @@ function LEGION:CheckMissionQueue() if mission:IsNotOver() and mission:IsReadyToCancel() then mission:Cancel() end + + -- Housekeeping + local TNow = timer.getTime() + if mission:IsOver() and mission:IsNotRepeatable() and mission.DeletionTimstamp == nil then + mission.DeletionTimstamp = TNow + end + if mission.DeletionTimstamp ~= nil and TNow - mission.DeletionTimstamp > 1800 then + mission = nil + end end -- Check that runway is operational and that carrier is not recovering. @@ -761,7 +770,7 @@ function LEGION:CheckMissionQueue() -- Reduce number of reinforcements. if reinforce then mission.reinforce=mission.reinforce-#assets - self:I(self.lid..string.format("Reinforced with N=%d Nreinforce=%d", #assets, mission.reinforce)) + self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d", #assets, mission.reinforce)) end return true @@ -1823,6 +1832,7 @@ function LEGION:_CreateFlightGroup(asset) --- opsgroup=ARMYGROUP:New(asset.spawngroupname) + opsgroup:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) elseif self:IsFleet() then @@ -2513,9 +2523,12 @@ function LEGION._GetCohorts(Legions, Cohorts, Operation, OpsQueue) for _,_legion in pairs(Legions or {}) do local legion=_legion --Ops.Legion#LEGION - -- Check that runway is operational. - local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true - + -- Check that runway is operational. + local Runway=true + if legion:IsAirwing() then + Runway=legion:IsRunwayOperational() and legion.airbase and legion.airbase:GetCoalition() == legion:GetCoalition() + end + -- Legion has to be running. if legion:IsRunning() and Runway then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 463f9bfa7..20b992bc9 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4637,7 +4637,12 @@ function OPSGROUP:_UpdateTask(Task, Mission) self:T(self.lid..string.format("Zone %s captured ==> Task DONE!", zoneCurr:GetName())) -- Task done. - self:TaskDone(Task) + if Task.StayInZoneTime then + local stay = Task.StayInZoneTime + self:__TaskDone(stay,Task) + else + self:TaskDone(Task) + end else -- Current zone NOT captured yet ==> Find Target @@ -5595,10 +5600,13 @@ function OPSGROUP:onafterUnpauseMission(From, Event, To) -- Debug info. self:T(self.lid..string.format("Unpausing mission %s [%s]", mission:GetName(), mission:GetType())) + -- Set state of mission, e.g. for not teleporting again + mission.unpaused=true + -- Start mission. self:MissionStart(mission) - -- Remove mission from + -- Remove mission from pausedmissions queue for i,mid in pairs(self.pausedmissions) do --self:T(self.lid..string.format("Checking paused mission", mid)) if mid==mission.auftragsnummer then @@ -5733,7 +5741,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) -- Decrease patrol data. if Mission.patroldata then Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 - AIRWING.UpdatePatrolPointMarker(Mission.patroldata) + AIRWING.UpdatePatrolPointMarker(self,Mission.patroldata) end -- Switch auto engage detected off. This IGNORES that engage detected had been activated for the group! @@ -6238,7 +6246,7 @@ function OPSGROUP:RouteToMission(mission, delay) end -- Check if group is mobile. Note that some immobile units report a speed of 1 m/s = 3.6 km/h. - if self.speedMax<=3.6 or mission.teleport then + if (self.speedMax<=3.6 or mission.teleport) and not mission.unpaused then -- Teleport to waypoint coordinate. Mission will not be paused. self:Teleport(waypointcoord, nil, true) @@ -7537,7 +7545,7 @@ end function OPSGROUP:onafterElementDead(From, Event, To, Element) -- Debug info. - self:I(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) + self:T(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) @@ -7850,8 +7858,13 @@ function OPSGROUP:_Spawn(Delay, Template) -- Debug output. self:T2({Template=Template}) + if self:IsArmygroup() and self.ValidateAndRepositionGroundUnits then + UTILS.ValidateAndRepositionGroundUnits(Template.units) + end + -- Spawn new group. self.group=_DATABASE:Spawn(Template) + self.group:SetValidateAndRepositionGroundUnits(self.ValidateAndRepositionGroundUnits) --local countryID=self.group:GetCountry() --local categoryID=self.group:GetCategory() --local dcsgroup=coalition.addGroup(countryID, categoryID, Template) @@ -8088,7 +8101,7 @@ function OPSGROUP:onafterStop(From, Event, To) _DATABASE.FLIGHTGROUPS[self.groupname]=nil -- Debug output. - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") + self:T(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") end --- On after "OutOfAmmo" event. @@ -13962,6 +13975,15 @@ function OPSGROUP:_GetDetectedTarget() return targetgroup, targetdist end +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +--- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #boolean Enabled Enable/disable the feature. +function OPSGROUP:SetValidateAndRepositionGroundUnits(Enabled) + self.ValidateAndRepositionGroundUnits = Enabled +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index bbcf4f97b..9a47b16ae 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -53,7 +53,8 @@ -- @field #number threatlevelCapture Threat level necessary to capture a zone. -- @field Core.Set#SET_UNIT ScanUnitSet Set of scanned units. -- @field Core.Set#SET_GROUP ScanGroupSet Set of scanned groups. --- @extends Core.Fsm#FSM +-- @field #number UpdateSeconds Run status every this many seconds. +-- @extends Core.Fsm#FSM --- *Gentlemen, when the enemy is committed to a mistake we must not interrupt him too soon.* --- Horation Nelson -- @@ -77,6 +78,7 @@ OPSZONE = { Tnut = 0, chiefs = {}, Missions = {}, + UpdateSeconds = 120, } --- OPSZONE.MISSION @@ -97,7 +99,7 @@ OPSZONE.ZoneType={ --- OPSZONE class version. -- @field #string version -OPSZONE.version="0.6.1" +OPSZONE.version="0.6.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -733,7 +735,8 @@ function OPSZONE:onafterStart(From, Event, To) self.timerStatus=self.timerStatus or TIMER:New(OPSZONE.Status, self) -- Status update. - self.timerStatus:Start(1, 120) + local EveryUpdateIn = self.UpdateSeconds or 120 + self.timerStatus:Start(1, EveryUpdateIn) -- Handle base captured event. if self.airbase then diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index e178b58f0..544c25034 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -1544,7 +1544,7 @@ end -- @param #PLAYERRECCE self -- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations! -- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies! --- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #string Gender (Optional) Defaults to "male" -- @param #string Culture (Optional) Defaults to "en-US" -- @param #number Port (Optional) Defaults to 5002 @@ -1556,7 +1556,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend) self:T(self.lid.."SetSRS") - self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- + self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- self.Gender = Gender or MSRS.gender or "male" -- self.Culture = Culture or MSRS.culture or "en-US" -- self.Port = Port or MSRS.port or 5002 -- diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 673c5095c..13cc66eba 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update Jan 2025 +-- @date Last Update May 2025 do @@ -98,7 +98,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.25" +PLAYERTASK.version="0.1.28" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -387,6 +387,14 @@ function PLAYERTASK:_CheckCaptureOpsZoneSuccess(OpsZone, CaptureSquadGroupNamePr return OpsZone:GetOwner() == Coalition and isClientInZone and isCaptureGroupInZone end +--- [User] Override this function in order to implement custom logic if a player can join a task or not. +-- @param #PLAYERTASK self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Client#CLIENT Client +-- @return #boolean Outcome True if player can join the task, false if not +function PLAYERTASK:CanJoinTask(Group, Client) + return true +end --- [Internal] Add a PLAYERTASKCONTROLLER for this task -- @param #PLAYERTASK self @@ -556,6 +564,7 @@ end -- @param #PLAYERTASK self -- @param #SET_BASE CaptureSquadGroupNamePrefix The prefix of the group name that needs to capture the zone. -- @param #number Coalition The coalition that needs to capture the zone. +-- @param #boolean CheckClientInZone If true, a CLIENT assigned to this task also needs to be in the zone for the task to be successful. -- @return #PLAYERTASK self -- @usage -- -- We can use either STATIC, SET_STATIC, SCENERY or SET_SCENERY as target objects. @@ -570,20 +579,20 @@ end -- -- -- We set CaptureSquadGroupNamePrefix the group name prefix as set in the ME or the spawn of the group that need to be present at the OpsZone like a capture squad, -- -- and set the capturing Coalition in order to trigger a successful task. --- mytask:AddOpsZoneCaptureSuccessCondition("capture-squad", coalition.side.BLUE) +-- mytask:AddOpsZoneCaptureSuccessCondition("capture-squad", coalition.side.BLUE, false) -- -- playerTaskManager:AddPlayerTaskToQueue(mytask) -function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix, Coalition) +function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone) local task = self task:AddConditionSuccess( function(target) if target:IsInstanceOf("OPSZONE") then - return task:_CheckCaptureOpsZoneSuccess(target, CaptureSquadGroupNamePrefix, Coalition, true) + return task:_CheckCaptureOpsZoneSuccess(target, CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone or true) elseif target:IsInstanceOf("SET_OPSZONE") then local successes = 0 local isClientInZone = false target:ForEachZone(function(opszone) - if task:_CheckCaptureOpsZoneSuccess(opszone, CaptureSquadGroupNamePrefix, Coalition) then + if task:_CheckCaptureOpsZoneSuccess(opszone, CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone or true) then successes = successes + 1 end @@ -979,6 +988,12 @@ function PLAYERTASK:onafterStatus(From, Event, To) if status == "Stopped" then return self end + -- update marker in case target is moving + if self.TargetMarker then + local coordinate = self.Target:GetCoordinate() + self.TargetMarker:UpdateCoordinate(coordinate,0.5) + end + -- Check Target status local targetdead = false @@ -1220,7 +1235,10 @@ function PLAYERTASK:onafterFailed(From, Event, To) self.TargetMarker:Remove() end self.FinalState = "Failed" - self:__Done(-1) + if self.TaskController then + self.TaskController:__TaskFailed(-1,self) + end + self:__Done(-1.5) end if self.TaskController.Scoring then local clients,count = self:GetClientObjects() @@ -1433,9 +1451,9 @@ do -- taskmanager:AddRejectZone(ZONE:FindByName("RejectZone")) -- -- -- Set up using SRS for messaging --- local hereSRSPath = "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- local hereSRSPath = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- local hereSRSPort = 5002 --- -- local hereSRSGoogle = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourkey.json" +-- -- local hereSRSGoogle = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourkey.json" -- taskmanager:SetSRS({130,255},{radio.modulation.AM,radio.modulation.AM},hereSRSPath,"female","en-GB",hereSRSPort,"Microsoft Hazel Desktop",0.7,hereSRSGoogle) -- -- -- Controller will announce itself under these broadcast frequencies, handy to use cold-start frequencies here of your aircraft @@ -1902,7 +1920,7 @@ PLAYERTASKCONTROLLER.Messages = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.69" +PLAYERTASKCONTROLLER.version="0.1.70" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -1944,7 +1962,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.taskinfomenu = false self.activehasinfomenu = false self.MenuName = nil - self.menuitemlimit = 5 + self.menuitemlimit = 6 self.holdmenutime = 30 self.MarkerReadOnly = false @@ -2415,7 +2433,7 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi end ) else - self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!") + self:E(self.lid.."No OPSGROUP/SET_OPSGROUP object passed or object is not alive!") end else self.autolase = nil @@ -2574,7 +2592,7 @@ function PLAYERTASKCONTROLLER:SetMenuOptions(InfoMenu,ItemLimit,HoldTime) if self.activehasinfomenu then self:EnableTaskInfoMenu() end - self.menuitemlimit = ItemLimit or 5 + self.menuitemlimit = ItemLimit+1 or 6 self.holdmenutime = HoldTime or 30 return self end @@ -3479,7 +3497,7 @@ end -- @param #PLAYERTASKCONTROLLER self -- @param Ops.PlayerTask#PLAYERTASK PlayerTask -- @param #boolean Silent If true, make no "has new task" announcement --- @param #boolen TaskFilter If true, apply the white/black-list task filters here, also +-- @param #boolean TaskFilter If true, apply the white/black-list task filters here, also -- @return #PLAYERTASKCONTROLLER self -- @usage -- Example to create a PLAYERTASK of type CTLD and give Players 10 minutes to complete: @@ -3523,6 +3541,16 @@ function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter) return self end +--- [User] Override this function in order to implement custom logic if a player can join a task or not. +-- @param #PLAYERTASKCONTROLLER self +-- @param Ops.PlayerTask#PLAYERTASK Task +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Client#CLIENT Client +-- @return #boolean Outcome True if player can join the task, false if not +function PLAYERTASKCONTROLLER:CanJoinTask(Task, Group, Client) + return true +end + --- [Internal] Join a player to a task -- @param #PLAYERTASKCONTROLLER self -- @param Ops.PlayerTask#PLAYERTASK Task @@ -3533,6 +3561,15 @@ end function PLAYERTASKCONTROLLER:_JoinTask(Task, Force, Group, Client) self:T({Force, Group, Client}) self:T(self.lid.."_JoinTask") + + if not self:CanJoinTask(Task, Group, Client) then + return self + end + + if not Task:CanJoinTask(Group, Client) then + return self + end + local force = false if type(Force) == "boolean" then force = Force @@ -3703,6 +3740,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) else CoordText = Coordinate:ToStringA2A(Client,nil,self.ShowMagnetic) end + --self:I("CoordText = "..CoordText) -- Threat Level local ThreatLevel = task.Target:GetThreatLevelMax() --local ThreatLevelText = "high" @@ -3837,7 +3875,8 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) Text = string.gsub(Text,"9","niner") CoordText = "MGRS;"..Text if self.PathToGoogleKey then - CoordText = string.format("%s",CoordText) + --CoordText = string.format("%s",CoordText) + --doesn't seem to work any longer end --self:I(self.lid.." | ".. CoordText) end @@ -3855,10 +3894,12 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) CoordText = string.gsub(ttstext," BR, "," Bee, Arr, ") end elseif task:HasFreetext() then + -- add tts freetext local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale) ttstext = ttstext .. string.format("; %s: ",brieftxt)..task:GetFreetextTTS() end + --self:I("**** TTS Text ****\n"..ttstext.."\n*****") self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,2) end else @@ -4357,7 +4398,7 @@ function PLAYERTASKCONTROLLER:SwitchDetectStatics(OnOff) return self end ---- [User] Add accept zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add an accept zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. -- @return #PLAYERTASKCONTROLLER self @@ -4371,7 +4412,7 @@ function PLAYERTASKCONTROLLER:AddAcceptZone(AcceptZone) return self end ---- [User] Add accept SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add an accept SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param Core.Set#SET_ZONE AcceptZoneSet Add a SET_ZONE to the accept zone set. -- @return #PLAYERTASKCONTROLLER self @@ -4385,7 +4426,7 @@ function PLAYERTASKCONTROLLER:AddAcceptZoneSet(AcceptZoneSet) return self end ---- [User] Add reject zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add a reject zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param Core.Zone#ZONE RejectZone Add a zone to the reject zone set. -- @return #PLAYERTASKCONTROLLER self @@ -4399,7 +4440,7 @@ function PLAYERTASKCONTROLLER:AddRejectZone(RejectZone) return self end ---- [User] Add reject SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add a reject SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param Core.Set#SET_ZONE RejectZoneSet Add a zone to the reject zone set. -- @return #PLAYERTASKCONTROLLER self @@ -4413,9 +4454,37 @@ function PLAYERTASKCONTROLLER:AddRejectZoneSet(RejectZoneSet) return self end ---- [User] Remove accept zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Add a conflict zone to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self --- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. +-- @param Core.Zone#ZONE ConflictZone Add a zone to the conflict zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddConflictZone(ConflictZone) + self:T(self.lid.."AddConflictZone") + if self.Intel then + self.Intel:AddConflictZone(ConflictZone) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + +--- [User] Add a conflict SET_ZONE to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Set#SET_ZONE ConflictZoneSet Add a zone to the conflict zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddConflictZoneSet(ConflictZoneSet) + self:T(self.lid.."AddConflictZoneSet") + if self.Intel then + self.Intel.conflictzoneset:AddSet(ConflictZoneSet) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + +--- [User] Remove an accept zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Zone#ZONE AcceptZone Remove this zone from the accept zone set. -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone) self:T(self.lid.."RemoveAcceptZone") @@ -4427,11 +4496,11 @@ function PLAYERTASKCONTROLLER:RemoveAcceptZone(AcceptZone) return self end ---- [User] Remove reject zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +--- [User] Remove a reject zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self --- @param Core.Zone#ZONE RejectZone Add a zone to the reject zone set. +-- @param Core.Zone#ZONE RejectZone Remove this zone from the reject zone set. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:RemoveRejectZoneSet(RejectZone) +function PLAYERTASKCONTROLLER:RemoveRejectZone(RejectZone) self:T(self.lid.."RemoveRejectZone") if self.Intel then self.Intel:RemoveRejectZone(RejectZone) @@ -4441,6 +4510,20 @@ function PLAYERTASKCONTROLLER:RemoveRejectZoneSet(RejectZone) return self end +--- [User] Remove a conflict zone from INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Zone#ZONE ConflictZone Remove this zone from the conflict zone set. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:RemoveConflictZone(ConflictZone) + self:T(self.lid.."RemoveConflictZone") + if self.Intel then + self.Intel:RemoveConflictZone(ConflictZone) + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + --- [User] Set the top menu name to a custom string. -- @param #PLAYERTASKCONTROLLER self -- @param #string Name The name to use as the top menu designation. @@ -4553,7 +4636,7 @@ end -- @param #PLAYERTASKCONTROLLER self -- @param #number Frequency Frequency to be used. Can also be given as a table of multiple frequencies, e.g. 271 or {127,251}. There needs to be exactly the same number of modulations! -- @param #number Modulation Modulation to be used. Can also be given as a table of multiple modulations, e.g. radio.modulation.AM or {radio.modulation.FM,radio.modulation.AM}. There needs to be exactly the same number of frequencies! --- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone" +-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- @param #string Gender (Optional) Defaults to "male" -- @param #string Culture (Optional) Defaults to "en-US" -- @param #number Port (Optional) Defaults to 5002 @@ -4567,7 +4650,7 @@ end -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend) self:T(self.lid.."SetSRS") - self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- + self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- self.Gender = Gender or MSRS.gender or "male" -- self.Culture = Culture or MSRS.culture or "en-US" -- self.Port = Port or MSRS.port or 5002 -- diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index fcc108087..4863f5202 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -1715,6 +1715,26 @@ function TARGET:GetAverageCoordinate() return nil end + +--- Get coordinates of all targets. (e.g. for a SET_STATIC) +-- @param #TARGET self +-- @return #table Table with coordinates of all targets. +function TARGET:GetCoordinates() + local coordinates={} + + for _,_target in pairs(self.targets) do + local target=_target --#TARGET.Object + + local coordinate=self:GetTargetCoordinate(target) + if coordinate then + table.insert(coordinates, coordinate) + end + + end + + return coordinates +end + --- Get heading of target. -- @param #TARGET self -- @return #number Heading of the target in degrees. @@ -1968,6 +1988,21 @@ function TARGET:GetObject(RefCoordinate, Coalitions) return nil end +--- Get all target objects. +-- @param #TARGET self +-- @return #table List of target objects. +function TARGET:GetObjects() + local objects={} + + for _,_target in pairs(self.targets) do + local target=_target --#TARGET.Object + + table.insert(objects, target.Object) + end + + return objects +end + --- Count alive objects. -- @param #TARGET self -- @param #TARGET.Object Target Target objective. diff --git a/Moose Development/Moose/Shapes/Circle.lua b/Moose Development/Moose/Shapes/Circle.lua index 39461c522..7bdbb31c1 100644 --- a/Moose Development/Moose/Shapes/Circle.lua +++ b/Moose Development/Moose/Shapes/Circle.lua @@ -72,7 +72,7 @@ end --- Checks if a point is contained within the circle. -- @param #table point The point to check --- @return #bool True if the point is contained, false otherwise +-- @return #boolean True if the point is contained, false otherwise function CIRCLE:ContainsPoint(point) if ((point.x - self.CenterVec2.x) ^ 2 + (point.y - self.CenterVec2.y) ^ 2) ^ 0.5 <= self.Radius then return true @@ -226,6 +226,11 @@ end --- Returns a random Vec2 within the circle. -- @return #table The random Vec2 function CIRCLE:GetRandomVec2() + + math.random() + math.random() + math.random() + local angle = math.random() * 2 * math.pi local rx = math.random(0, self.Radius) * math.cos(angle) + self.CenterVec2.x @@ -237,6 +242,11 @@ end --- Returns a random Vec2 on the border of the circle. -- @return #table The random Vec2 function CIRCLE:GetRandomVec2OnBorder() + + math.random() + math.random() + math.random() + local angle = math.random() * 2 * math.pi local rx = self.Radius * math.cos(angle) + self.CenterVec2.x diff --git a/Moose Development/Moose/Shapes/Polygon.lua b/Moose Development/Moose/Shapes/Polygon.lua index d0253fa04..e126a2002 100644 --- a/Moose Development/Moose/Shapes/Polygon.lua +++ b/Moose Development/Moose/Shapes/Polygon.lua @@ -352,6 +352,7 @@ end --- Returns a random Vec2 within the polygon. The Vec2 is weighted by the areas of the triangles that make up the polygon. -- @return #table The random Vec2 function POLYGON:GetRandomVec2() + local weights = {} for _, triangle in pairs(self.Triangles) do weights[triangle] = triangle.SurfaceArea / self.SurfaceArea diff --git a/Moose Development/Moose/Shapes/Triangle.lua b/Moose Development/Moose/Shapes/Triangle.lua index 747407d2e..e9a52e866 100644 --- a/Moose Development/Moose/Shapes/Triangle.lua +++ b/Moose Development/Moose/Shapes/Triangle.lua @@ -73,6 +73,11 @@ end -- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it -- @return #table The random Vec2 function TRIANGLE:GetRandomVec2(points) + + math.random() + math.random() + math.random() + points = points or self.Points local pt = {math.random(), math.random()} table.sort(pt) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index c2e9cf152..3432e0f0c 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -443,28 +443,32 @@ MSRS.Voices = { ["en_AU_Standard_B"] = 'en-AU-Standard-B', -- [2] MALE ["en_AU_Standard_C"] = 'en-AU-Standard-C', -- [3] FEMALE ["en_AU_Standard_D"] = 'en-AU-Standard-D', -- [4] MALE - ["en_IN_Standard_A"] = 'en-IN-Standard-A', -- [5] FEMALE - ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE - ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE - ["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE + -- IN + ["en_IN_Standard_A"] = 'en-IN-Standard-A', -- Female + ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- Male + ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- Male + ["en_IN_Standard_D"] = 'en-IN-Standard-D', -- Female + ["en_IN_Standard_E"] = 'en-IN-Standard-E', -- Female + ["en_IN_Standard_F"] = 'en-IN-Standard-F', -- Male -- 2025 changes - ["en_GB_Standard_A"] = 'en-GB-Standard-N', -- [9] FEMALE - ["en_GB_Standard_B"] = 'en-GB-Standard-O', -- [10] MALE - ["en_GB_Standard_C"] = 'en-GB-Standard-N', -- [11] FEMALE - ["en_GB_Standard_D"] = 'en-GB-Standard-O', -- [12] MALE - ["en_GB_Standard_F"] = 'en-GB-Standard-N', -- [13] FEMALE - ["en_GB_Standard_O"] = 'en-GB-Standard-O', -- [12] MALE - ["en_GB_Standard_N"] = 'en-GB-Standard-N', -- [13] FEMALE - ["en_US_Standard_A"] = 'en-US-Standard-A', -- [14] MALE - ["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE - ["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE - ["en_US_Standard_D"] = 'en-US-Standard-D', -- [17] MALE - ["en_US_Standard_E"] = 'en-US-Standard-E', -- [18] FEMALE - ["en_US_Standard_F"] = 'en-US-Standard-F', -- [19] FEMALE - ["en_US_Standard_G"] = 'en-US-Standard-G', -- [20] FEMALE - ["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE - ["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE - ["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE + ["en_GB_Standard_A"] = 'en-GB-Standard-A', -- Female + ["en_GB_Standard_B"] = 'en-GB-Standard-B', -- Male + ["en_GB_Standard_C"] = 'en-GB-Standard-C', -- Female + ["en_GB_Standard_D"] = 'en-GB-Standard-D', -- Male + ["en_GB_Standard_F"] = 'en-GB-Standard-F', -- Female + ["en_GB_Standard_N"] = 'en-GB-Standard-N', -- Female + ["en_GB_Standard_O"] = 'en-GB-Standard-O', -- Male + -- US + ["en_US_Standard_A"] = 'en-US-Standard-A', -- Male + ["en_US_Standard_B"] = 'en-US-Standard-B', -- Male + ["en_US_Standard_C"] = 'en-US-Standard-C', -- Female + ["en_US_Standard_D"] = 'en-US-Standard-D', -- Male + ["en_US_Standard_E"] = 'en-US-Standard-E', -- Female + ["en_US_Standard_F"] = 'en-US-Standard-F', -- Female + ["en_US_Standard_G"] = 'en-US-Standard-G', -- Female + ["en_US_Standard_H"] = 'en-US-Standard-H', -- Female + ["en_US_Standard_I"] = 'en-US-Standard-I', -- Male + ["en_US_Standard_J"] = 'en-US-Standard-J', -- Male -- 2025 catalog changes ["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female ["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male @@ -474,14 +478,15 @@ MSRS.Voices = { ["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male ["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female -- 2025 catalog changes - ["de_DE_Standard_A"] = "de-DE-Standard-G", -- Female - ["de_DE_Standard_B"] = "de-DE-Standard-H", -- Male - ["de_DE_Standard_C"] = "de-DE-Standard-G", -- Female - ["de_DE_Standard_D"] = "de-DE-Standard-H", -- Male - ["de_DE_Standard_E"] = "de-DE-Standard-H", -- Male - ["de_DE_Standard_F"] = "de-DE-Standard-G", -- Female - ["de_DE_Standard_H"] = "de-DE-Standard-H", -- Male - ["de_DE_Standard_G"] = "de-DE-Standard-G", -- Female + ["de_DE_Standard_A"] = 'de-DE-Standard-A', -- Female + ["de_DE_Standard_B"] = 'de-DE-Standard-B', -- Male + ["de_DE_Standard_C"] = 'de-DE-Standard-C', -- Female + ["de_DE_Standard_D"] = 'de-DE-Standard-D', -- Male + ["de_DE_Standard_E"] = 'de-DE-Standard-E', -- Male + ["de_DE_Standard_F"] = 'de-DE-Standard-F', -- Female + ["de_DE_Standard_G"] = 'de-DE-Standard-G', -- Female + ["de_DE_Standard_H"] = 'de-DE-Standard-H', -- Male + -- ES ["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female ["es_ES_Standard_B"] = "es-ES-Standard-F", -- Male ["es_ES_Standard_C"] = "es-ES-Standard-E", -- Female @@ -497,32 +502,36 @@ MSRS.Voices = { ["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male }, Wavenet = { - ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE - ["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- [2] MALE - ["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- [3] FEMALE - ["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- [4] MALE - ["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- [5] FEMALE - ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE - ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE - ["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE + ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- Female + ["en_AU_Wavenet_B"] = 'en-AU-Wavenet-B', -- Male + ["en_AU_Wavenet_C"] = 'en-AU-Wavenet-C', -- Female + ["en_AU_Wavenet_D"] = 'en-AU-Wavenet-D', -- Male + -- IN + ["en_IN_Wavenet_A"] = 'en-IN-Wavenet-A', -- Female + ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- Male + ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- Male + ["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- Female + ["en_IN_Wavenet_E"] = 'en-IN-Wavenet-E', -- Female + ["en_IN_Wavenet_F"] = 'en-IN-Wavenet-F', -- Male -- 2025 changes - ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-N', -- [9] FEMALE - ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-O', -- [10] MALE - ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-N', -- [11] FEMALE - ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-O', -- [12] MALE - ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-N', -- [13] FEMALE + ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE + ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE + ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE + ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE + ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE ["en_GB_Wavenet_O"] = 'en-GB-Wavenet-O', -- [12] MALE - ["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE - ["en_US_Wavenet_A"] = 'en-US-Wavenet-N', -- [14] MALE - ["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- [15] MALE - ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE - ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE - ["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- [18] FEMALE - ["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- [19] FEMALE - ["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- [20] FEMALE - ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE - ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE - ["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE + ["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE + -- US + ["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- Male + ["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- Male + ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- Female + ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- Male + ["en_US_Wavenet_E"] = 'en-US-Wavenet-E', -- Female + ["en_US_Wavenet_F"] = 'en-US-Wavenet-F', -- Female + ["en_US_Wavenet_G"] = 'en-US-Wavenet-G', -- Female + ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- Female + ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- Male + ["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- Male -- 2025 catalog changes ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male @@ -532,14 +541,15 @@ MSRS.Voices = { ["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male ["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female -- 2025 catalog changes - ["de_DE_Wavenet_A"] = "de-DE-Wavenet-G", -- Female - ["de_DE_Wavenet_B"] = "de-DE-Wavenet-H", -- Male - ["de_DE_Wavenet_C"] = "de-DE-Wavenet-G", -- Female - ["de_DE_Wavenet_D"] = "de-DE-Wavenet-H", -- Male - ["de_DE_Wavenet_E"] = "de-DE-Wavenet-H", -- Male - ["de_DE_Wavenet_F"] = "de-DE-Wavenet-G", -- Female - ["de_DE_Wavenet_H"] = "de-DE-Wavenet-H", -- Male - ["de_DE_Wavenet_G"] = "de-DE-Wavenet-G", -- Female + ["de_DE_Wavenet_A"] = 'de-DE-Wavenet-A', -- Female + ["de_DE_Wavenet_B"] = 'de-DE-Wavenet-B', -- Male + ["de_DE_Wavenet_C"] = 'de-DE-Wavenet-C', -- Female + ["de_DE_Wavenet_D"] = 'de-DE-Wavenet-D', -- Male + ["de_DE_Wavenet_E"] = 'de-DE-Wavenet-E', -- Male + ["de_DE_Wavenet_F"] = 'de-DE-Wavenet-F', -- Female + ["de_DE_Wavenet_G"] = 'de-DE-Wavenet-G', -- Female + ["de_DE_Wavenet_H"] = 'de-DE-Wavenet-H', -- Male + -- ES ["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male ["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female ["es_ES_Wavenet_D"] = "es-ES-Wavenet-E", -- Female @@ -553,6 +563,134 @@ MSRS.Voices = { ["it_IT_Wavenet_E"] = "it-IT-Wavenet-E", -- Female ["it_IT_Wavenet_F"] = "it-IT-Wavenet-F", -- Male } , + Chirp3HD = { + ["en_GB_Chirp3_HD_Aoede"] = 'en-GB-Chirp3-HD-Aoede', -- Female + ["en_GB_Chirp3_HD_Charon"] = 'en-GB-Chirp3-HD-Charon', -- Male + ["en_GB_Chirp3_HD_Fenrir"] = 'en-GB-Chirp3-HD-Fenrir', -- Male + ["en_GB_Chirp3_HD_Kore"] = 'en-GB-Chirp3-HD-Kore', -- Female + ["en_GB_Chirp3_HD_Leda"] = 'en-GB-Chirp3-HD-Leda', -- Female + ["en_GB_Chirp3_HD_Orus"] = 'en-GB-Chirp3-HD-Orus', -- Male + ["en_GB_Chirp3_HD_Puck"] = 'en-GB-Chirp3-HD-Puck', -- Male + ["en_GB_Chirp3_HD_Zephyr"] = 'en-GB-Chirp3-HD-Zephyr', -- Female + --["de_DE_Chirp3_HD_Aoede"] = 'de-DE-Chirp3-HD-Aoede', -- Female (Datenfehler im Original) + ["en_US_Chirp3_HD_Charon"] = 'en-US-Chirp3-HD-Charon', -- Male + ["en_US_Chirp3_HD_Fenrir"] = 'en-US-Chirp3-HD-Fenrir', -- Male + ["en_US_Chirp3_HD_Kore"] = 'en-US-Chirp3-HD-Kore', -- Female + ["en_US_Chirp3_HD_Leda"] = 'en-US-Chirp3-HD-Leda', -- Female + ["en_US_Chirp3_HD_Orus"] = 'en-US-Chirp3-HD-Orus', -- Male + ["en_US_Chirp3_HD_Puck"] = 'en-US-Chirp3-HD-Puck', -- Male + --["de_DE_Chirp3_HD_Zephyr"] = 'de-DE-Chirp3-HD-Zephyr', -- Female (Datenfehler im Original) + -- DE + ["de_DE_Chirp3_HD_Aoede"] = 'de-DE-Chirp3-HD-Aoede', -- Female + ["de_DE_Chirp3_HD_Charon"] = 'de-DE-Chirp3-HD-Charon', -- Male + ["de_DE_Chirp3_HD_Fenrir"] = 'de-DE-Chirp3-HD-Fenrir', -- Male + ["de_DE_Chirp3_HD_Kore"] = 'de-DE-Chirp3-HD-Kore', -- Female + ["de_DE_Chirp3_HD_Leda"] = 'de-DE-Chirp3-HD-Leda', -- Female + ["de_DE_Chirp3_HD_Orus"] = 'de-DE-Chirp3-HD-Orus', -- Male + ["de_DE_Chirp3_HD_Puck"] = 'de-DE-Chirp3-HD-Puck', -- Male + ["de_DE_Chirp3_HD_Zephyr"] = 'de-DE-Chirp3-HD-Zephyr', -- Female + -- AU + ["en_AU_Chirp3_HD_Aoede"] = 'en-AU-Chirp3-HD-Aoede', -- Female + ["en_AU_Chirp3_HD_Charon"] = 'en-AU-Chirp3-HD-Charon', -- Male + ["en_AU_Chirp3_HD_Fenrir"] = 'en-AU-Chirp3-HD-Fenrir', -- Male + ["en_AU_Chirp3_HD_Kore"] = 'en-AU-Chirp3-HD-Kore', -- Female + ["en_AU_Chirp3_HD_Leda"] = 'en-AU-Chirp3-HD-Leda', -- Female + ["en_AU_Chirp3_HD_Orus"] = 'en-AU-Chirp3-HD-Orus', -- Male + ["en_AU_Chirp3_HD_Puck"] = 'en-AU-Chirp3-HD-Puck', -- Male + ["en_AU_Chirp3_HD_Zephyr"] = 'en-AU-Chirp3-HD-Zephyr', -- Female + -- IN + ["en_IN_Chirp3_HD_Aoede"] = 'en-IN-Chirp3-HD-Aoede', -- Female + ["en_IN_Chirp3_HD_Charon"] = 'en-IN-Chirp3-HD-Charon', -- Male + ["en_IN_Chirp3_HD_Fenrir"] = 'en-IN-Chirp3-HD-Fenrir', -- Male + ["en_IN_Chirp3_HD_Kore"] = 'en-IN-Chirp3-HD-Kore', -- Female + ["en_IN_Chirp3_HD_Leda"] = 'en-IN-Chirp3-HD-Leda', -- Female + ["en_IN_Chirp3_HD_Orus"] = 'en-IN-Chirp3-HD-Orus', -- Male + }, + ChirpHD = { + ["en_US_Chirp_HD_D"] = 'en-US-Chirp-HD-D', -- Male + ["en_US_Chirp_HD_F"] = 'en-US-Chirp-HD-F', -- Female + ["en_US_Chirp_HD_O"] = 'en-US-Chirp-HD-O', -- Female + -- DE + ["de_DE_Chirp_HD_D"] = 'de-DE-Chirp-HD-D', -- Male + ["de_DE_Chirp_HD_F"] = 'de-DE-Chirp-HD-F', -- Female + ["de_DE_Chirp_HD_O"] = 'de-DE-Chirp-HD-O', -- Female + -- AU + ["en_AU_Chirp_HD_D"] = 'en-AU-Chirp-HD-D', -- Male + ["en_AU_Chirp_HD_F"] = 'en-AU-Chirp-HD-F', -- Female + ["en_AU_Chirp_HD_O"] = 'en-AU-Chirp-HD-O', -- Female + -- IN + ["en_IN_Chirp_HD_D"] = 'en-IN-Chirp-HD-D', -- Male + ["en_IN_Chirp_HD_F"] = 'en-IN-Chirp-HD-F', -- Female + ["en_IN_Chirp_HD_O"] = 'en-IN-Chirp-HD-O', -- Female + }, + }, + Neural2 = { + ["en_GB_Neural2_A"] = 'en-GB-Neural2-A', -- Female + ["en_GB_Neural2_B"] = 'en-GB-Neural2-B', -- Male + ["en_GB_Neural2_C"] = 'en-GB-Neural2-C', -- Female + ["en_GB_Neural2_D"] = 'en-GB-Neural2-D', -- Male + ["en_GB_Neural2_F"] = 'en-GB-Neural2-F', -- Female + ["en_GB_Neural2_N"] = 'en-GB-Neural2-N', -- Female + ["en_GB_Neural2_O"] = 'en-GB-Neural2-O', -- Male + -- US + ["en_US_Neural2_A"] = 'en-US-Neural2-A', -- Male + ["en_US_Neural2_C"] = 'en-US-Neural2-C', -- Female + ["en_US_Neural2_D"] = 'en-US-Neural2-D', -- Male + ["en_US_Neural2_E"] = 'en-US-Neural2-E', -- Female + ["en_US_Neural2_F"] = 'en-US-Neural2-F', -- Female + ["en_US_Neural2_G"] = 'en-US-Neural2-G', -- Female + ["en_US_Neural2_H"] = 'en-US-Neural2-H', -- Female + ["en_US_Neural2_I"] = 'en-US-Neural2-I', -- Male + ["en_US_Neural2_J"] = 'en-US-Neural2-J', -- Male + -- DE + ["de_DE_Neural2_G"] = 'de-DE-Neural2-G', -- Female + ["de_DE_Neural2_H"] = 'de-DE-Neural2-H', -- Male + -- AU + ["en_AU_Neural2_A"] = 'en-AU-Neural2-A', -- Female + ["en_AU_Neural2_B"] = 'en-AU-Neural2-B', -- Male + ["en_AU_Neural2_C"] = 'en-AU-Neural2-C', -- Female + ["en_AU_Neural2_D"] = 'en-AU-Neural2-D', -- Male + -- IN + ["en_IN_Neural2_A"] = 'en-IN-Neural2-A', -- Female + ["en_IN_Neural2_B"] = 'en-IN-Neural2-B', -- Male + ["en_IN_Neural2_C"] = 'en-IN-Neural2-C', -- Male + ["en_IN_Neural2_D"] = 'en-IN-Neural2-D', -- Female + }, + News = { + ["en_GB_News_G"] = 'en-GB-News-G', -- Female + ["en_GB_News_H"] = 'en-GB-News-H', -- Female + ["en_GB_News_I"] = 'en-GB-News-I', -- Female + ["en_GB_News_J"] = 'en-GB-News-J', -- Male + ["en_GB_News_K"] = 'en-GB-News-K', -- Male + ["en_GB_News_L"] = 'en-GB-News-L', -- Male + ["en_GB_News_M"] = 'en-GB-News-M', -- Male + -- US + ["en_US_News_K"] = 'en-US-News-K', -- Female + ["en_US_News_L"] = 'en-US-News-L', -- Female + ["en_US_News_N"] = 'en-US-News-N', -- Male + -- AU + ["en_AU_News_E"] = 'en-AU-News-E', -- Female + ["en_AU_News_F"] = 'en-AU-News-F', -- Female + ["en_AU_News_G"] = 'en-AU-News-G', -- Male + }, + Casual = { + ["en_US_Casual_K"] = 'en-US-Casual-K', -- Male + }, + Polyglot = { + ["en_US_Polyglot_1"] = 'en-US-Polyglot-1', -- Male + ["de_DE_Polyglot_1"] = 'de-DE-Polyglot-1', -- Male + ["en_AU_Polyglot_1"] = 'en-AU-Polyglot-1', -- Male + }, + Studio = { + -- Englisch (UK) - Studio + ["en_GB_Studio_B"] = 'en-GB-Studio-B', -- Male + ["en_GB_Studio_C"] = 'en-GB-Studio-C', -- Female + -- Englisch (USA) - Studio + ["en_US_Studio_O"] = 'en-US-Studio-O', -- Female + ["en_US_Studio_Q"] = 'en-US-Studio-Q', -- Male + -- DE + ["de_DE_Studio_B"] = 'de-DE-Studio-B', -- Male + ["de_DE_Studio_C"] = 'de-DE-Studio-C', -- Female }, } @@ -632,7 +770,7 @@ end -- set the path to the exe file via @{#MSRS.SetPath}. -- -- @param #MSRS self --- @param #string Path Path to SRS directory. Default `C:\\Program Files\\DCS-SimpleRadio-Standalone`. +-- @param #string Path Path to SRS directory. Default `C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio`. -- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies. -- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations. -- @param #string Backend Backend used: `MSRS.Backend.SRSEXE` (default) or `MSRS.Backend.GRPC`. @@ -767,13 +905,13 @@ end --- Set path to SRS install directory. More precisely, path to where the `DCS-SR-ExternalAudio.exe` is located. -- @param #MSRS self --- @param #string Path Path to the directory, where the sound file is located. Default is `C:\\Program Files\\DCS-SimpleRadio-Standalone`. +-- @param #string Path Path to the directory, where the sound file is located. Default is `C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio`. -- @return #MSRS self function MSRS:SetPath(Path) self:F( {Path=Path} ) -- Set path. - self.path=Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.path=Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" -- Remove (back)slashes. local n=1 ; local nmax=1000 @@ -1817,7 +1955,7 @@ end -- -- -- Moose MSRS default Config -- MSRS_Config = { --- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone", -- Path to SRS install directory. +-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio", -- Path to SRS install directory. -- Port = 5002, -- Port of SRS server. Default 5002. -- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc". -- Frequency = {127, 243}, -- Default frequences. Must be a table 1..n entries! @@ -1837,7 +1975,7 @@ end -- -- Google Cloud -- gcloud = { -- voice = "en-GB-Standard-A", -- The Google Cloud voice to use (see https://cloud.google.com/text-to-speech/docs/voices). --- credentials="C:\\Program Files\\DCS-SimpleRadio-Standalone\\yourfilename.json", -- Full path to credentials JSON file (only for SRS-TTS.exe backend) +-- credentials="C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio\\yourfilename.json", -- Full path to credentials JSON file (only for SRS-TTS.exe backend) -- key="Your access Key", -- Google API access key (only for DCS-gRPC backend) -- }, -- -- Amazon Web Service @@ -1905,7 +2043,7 @@ function MSRS:LoadConfigFile(Path,Filename) local Self = self or MSRS --#MSRS - Self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + Self.path = MSRS_Config.Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio" Self.port = MSRS_Config.Port or 5002 Self.backend = MSRS_Config.Backend or MSRS.Backend.SRSEXE Self.frequencies = MSRS_Config.Frequency or {127,243} diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 59ab36a3c..68da47300 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -29,6 +29,8 @@ --- Governs multiple missions, the tasking and the reporting. +-- +-- ![Banner Image](..\Images\deprecated.png) -- -- Command centers govern missions, communicates the task assignments between human players of the coalition, and manages the menu flow. -- It can assign a random task to a player when requested. diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 81dd1f882..16025282a 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -5,6 +5,8 @@ -- The @{#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. -- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- 1.1) DETECTION_MANAGER constructor: -- ----------------------------------- -- * @{#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua index 6fa3da747..8cd08f087 100644 --- a/Moose Development/Moose/Tasking/Mission.lua +++ b/Moose Development/Moose/Tasking/Mission.lua @@ -28,6 +28,8 @@ --- Models goals to be achieved and can contain multiple tasks to be executed to achieve the goals. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- A mission contains multiple tasks and can be of different task types. -- These tasks need to be assigned to human players to be executed. -- diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index 452f88242..04d6a4598 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -12,6 +12,8 @@ -- -- === -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- # 1) Tasking from a player perspective. -- -- Tasking can be controlled by using the "other" menu in the radio menu of the player group. diff --git a/Moose Development/Moose/Tasking/TaskInfo.lua b/Moose Development/Moose/Tasking/TaskInfo.lua index 428e629c9..203b94e01 100644 --- a/Moose Development/Moose/Tasking/TaskInfo.lua +++ b/Moose Development/Moose/Tasking/TaskInfo.lua @@ -18,6 +18,8 @@ --- -- # TASKINFO class, extends @{Core.Base#BASE} -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- ## The TASKINFO class implements the methods to contain information and display information of a task. -- -- # Developer Note diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua index 9309526bb..43d6e1f5a 100644 --- a/Moose Development/Moose/Tasking/Task_A2A.lua +++ b/Moose Development/Moose/Tasking/Task_A2A.lua @@ -20,6 +20,9 @@ do -- TASK_A2A --- Defines Air To Air tasks for a @{Core.Set} of Target Units, -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. + -- + -- ![Banner Image](..\Images\deprecated.png) + -- -- The TASK_A2A is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua index d592ad4a6..b3c4b0568 100644 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua @@ -30,6 +30,8 @@ do -- TASK_A2A_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER --- Orchestrates the dynamic dispatching of tasks upon groups of detected units determined a @{Core.Set} of EWR installation groups. + -- + -- ![Banner Image](..\Images\deprecated.png) -- -- ![Banner Image](..\Presentations\TASK_A2A_DISPATCHER\Dia3.JPG) -- diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua index 84bdcf360..f39c4968c 100644 --- a/Moose Development/Moose/Tasking/Task_A2G.lua +++ b/Moose Development/Moose/Tasking/Task_A2G.lua @@ -20,6 +20,9 @@ do -- TASK_A2G --- The TASK_A2G class defines Air To Ground tasks for a @{Core.Set} of Target Units, -- based on the tasking capabilities defined in @{Tasking.Task#TASK}. + -- + -- ![Banner Image](..\Images\deprecated.png) + -- -- The TASK_A2G is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index a98477d86..9fc410de1 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -33,6 +33,8 @@ do -- TASK_A2G_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER --- Orchestrates dynamic **A2G Task Dispatching** based on the detection results of a linked @{Functional.Detection} object. + -- + -- ![Banner Image](..\Images\deprecated.png) -- -- It uses the Tasking System within the MOOSE framework, which is a multi-player Tasking Orchestration system. -- It provides a truly dynamic battle environment for pilots and ground commanders to engage upon, diff --git a/Moose Development/Moose/Tasking/Task_CARGO.lua b/Moose Development/Moose/Tasking/Task_CARGO.lua index be894d805..6bfcfffa1 100644 --- a/Moose Development/Moose/Tasking/Task_CARGO.lua +++ b/Moose Development/Moose/Tasking/Task_CARGO.lua @@ -10,6 +10,8 @@ -- -- === -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- ## Test Missions: -- -- Test missions can be located on the main GITHUB site. @@ -1176,7 +1178,7 @@ do -- TASK_CARGO end - ---@param Color Might be SMOKECOLOR.Blue, SMOKECOLOR.Red SMOKECOLOR.Orange, SMOKECOLOR.White or SMOKECOLOR.Green + --@param Color Might be SMOKECOLOR.Blue, SMOKECOLOR.Red SMOKECOLOR.Orange, SMOKECOLOR.White or SMOKECOLOR.Green function TASK_CARGO:SetSmokeColor(SmokeColor) -- Makes sure Coloe is set if SmokeColor == nil then diff --git a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua index c9c476efa..fa334dcf4 100644 --- a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua @@ -76,6 +76,8 @@ do -- TASK_CAPTURE_DISPATCHER --- Implements the dynamic dispatching of capture zone tasks. -- + -- ![Banner Image](..\Images\deprecated.png) + -- -- The **TASK_CAPTURE_DISPATCHER** allows you to setup various tasks for let human -- players capture zones in a co-operation effort. -- diff --git a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua b/Moose Development/Moose/Tasking/Task_Capture_Zone.lua index 33b464a35..4c2372e8d 100644 --- a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua +++ b/Moose Development/Moose/Tasking/Task_Capture_Zone.lua @@ -20,6 +20,8 @@ do -- TASK_ZONE_GOAL --- # TASK_ZONE_GOAL class, extends @{Tasking.Task#TASK} -- + -- ![Banner Image](..\Images\deprecated.png) + -- -- The TASK_ZONE_GOAL class defines the task to protect or capture a protection zone. -- The TASK_ZONE_GOAL is implemented using a @{Core.Fsm#FSM_TASK}, and has the following statuses: -- diff --git a/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua b/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua index e27a225c0..ca71d64ef 100644 --- a/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua +++ b/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua @@ -44,6 +44,8 @@ -- -- === -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- Please read through the @{Tasking.Task_CARGO} process to understand the mechanisms of tasking and cargo tasking and handling. -- -- The cargo will be a downed pilot, which is located somwhere on the battlefield. Use the menus system and facilities to diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua index f78b2d5cd..b79a098bd 100644 --- a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua @@ -2,6 +2,8 @@ -- -- The **TASK_CARGO_DISPATCHER** allows you to setup various tasks for let human -- players transport cargo as part of a task. +-- +-- ![Banner Image](..\Images\deprecated.png) -- -- The cargo dispatcher will implement for you mechanisms to create cargo transportation tasks: -- diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua b/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua index 6293f03fa..3a30eaf22 100644 --- a/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua +++ b/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua @@ -1,5 +1,7 @@ --- **Tasking** - Models tasks for players to transport cargo. -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- **Specific features:** -- -- * Creates a task to transport #Cargo.Cargo to and between deployment zones. diff --git a/Moose Development/Moose/Tasking/Task_Manager.lua b/Moose Development/Moose/Tasking/Task_Manager.lua index 127b455ad..7cd71c485 100644 --- a/Moose Development/Moose/Tasking/Task_Manager.lua +++ b/Moose Development/Moose/Tasking/Task_Manager.lua @@ -2,6 +2,8 @@ -- -- === -- +-- ![Banner Image](..\Images\deprecated.png) +-- -- 1) @{Tasking.Task_Manager#TASK_MANAGER} class, extends @{Core.Fsm#FSM} -- === -- The @{Tasking.Task_Manager#TASK_MANAGER} class defines the core functions to report tasks to groups. diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index d4bbf158d..ce6fe4e2e 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -101,7 +101,7 @@ ENUMS.WeaponFlag={ AntiRadarMissile2 = 1073741824, -- Air-To-Air Missiles SRAM = 4194304, - MRAAM = 8388608, + MRAAM = 8388608, LRAAM = 16777216, IR_AAM = 33554432, SAR_AAM = 67108864, @@ -126,19 +126,19 @@ ENUMS.WeaponFlag={ --- Air-To-Air Missiles AnyAAM = 264241152, -- IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM AnyAutonomousMissile = 36012032, -- IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile - AnyMissile = 268402688, -- AnyASM + AnyAAM + AnyMissile = 268402688, -- AnyASM + AnyAAM --- Guns Cannons = 805306368, -- GUN_POD + BuiltInCannon --- Torpedo Torpedo = 4294967296, --- - -- Even More Genral + -- Even More Genral Auto = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons) AutoDCS = 1073741822, -- Something if often see AnyAG = 2956984318, -- Any Air-To-Ground Weapon AnyAA = 264241152, -- Any Air-To-Air Weapon AnyUnguided = 2952822768, -- Any Unguided Weapon - AnyGuided = 268402702, -- Any Guided Weapon + AnyGuided = 268402702, -- Any Guided Weapon } --- Weapon types by category. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on hoggit wiki. @@ -167,7 +167,7 @@ ENUMS.WeaponType.Bomb={ -- Combinations GuidedBomb = 14, -- (LGB + TvGB + SNSGB) AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb) - AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb) + AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb) } ENUMS.WeaponType.Rocket={ -- Rockets @@ -201,12 +201,12 @@ ENUMS.WeaponType.Missile={ AnyASM = 4161536, -- (AntiRadarMissile + AntiShipMissile + AntiTankMissile + FireAndForgetASM + GuidedASM + CruiseMissile) AnyASM2 = 1077903360, -- 4161536+1073741824, AnyAutonomousMissile = 36012032, -- IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile - AnyMissile = 268402688, -- AnyASM + AnyAAM + AnyMissile = 268402688, -- AnyASM + AnyAAM } ENUMS.WeaponType.AAM={ -- Air-To-Air Missiles SRAM = 4194304, - MRAAM = 8388608, + MRAAM = 8388608, LRAAM = 16777216, IR_AAM = 33554432, SAR_AAM = 67108864, @@ -219,12 +219,12 @@ ENUMS.WeaponType.Torpedo={ Torpedo = 4294967296, } ENUMS.WeaponType.Any={ - -- General combinations + -- General combinations Weapon = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons) AG = 2956984318, -- Any Air-To-Ground Weapon AA = 264241152, -- Any Air-To-Air Weapon Unguided = 2952822768, -- Any Unguided Weapon - Guided = 268402702, -- Any Guided Weapon + Guided = 268402702, -- Any Guided Weapon } @@ -409,7 +409,7 @@ ENUMS.Morse[" "]=" " --- ISO (639-1) 2-letter Language Codes. See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). -- -- @type ENUMS.ISOLang -ENUMS.ISOLang = +ENUMS.ISOLang = { Arabic = 'AR', Chinese = 'ZH', @@ -515,7 +515,7 @@ ENUMS.ReportingName = Atlas = "A400", Lancer = "B1-B", Stratofortress = "B-52H", - Hercules = "C-130", + Hercules = "C-130", Super_Hercules = "Hercules", Globemaster = "C-17", Greyhound = "C-2A", @@ -539,7 +539,7 @@ ENUMS.ReportingName = Curl = "An-26", Candid = "IL-76", Midas = "IL-78", - Mainstay = "A-50", + Mainstay = "A-50", Mainring = "KJ-2000", -- A-50 China Yak = "Yak-52", -- Helos @@ -606,565 +606,566 @@ ENUMS.Storage = { OH58 = {}, -- Kiowa specifics UH1H = {}, -- Huey specifics AH64D = {}, -- Huey specifics + UH60L = {}, -- Blackhawk specifics } } -ENUMS.Storage.weapons.nurs.SNEB_TYPE253_F1B = "weapons.nurs.SNEB_TYPE253_F1B" -ENUMS.Storage.weapons.missiles.P_24T = "weapons.missiles.P_24T" -ENUMS.Storage.weapons.bombs.BLU_3B_OLD = "weapons.bombs.BLU-3B_OLD" -ENUMS.Storage.weapons.missiles.AGM_154 = "weapons.missiles.AGM_154" -ENUMS.Storage.weapons.nurs.HYDRA_70_M151_M433 = "weapons.nurs.HYDRA_70_M151_M433" -ENUMS.Storage.weapons.bombs.SAM_Avenger_M1097_Skid_7090lb = "weapons.bombs.SAM Avenger M1097 Skid [7090lb]" -ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk5 = "weapons.bombs.British_GP_250LB_Bomb_Mk5" -ENUMS.Storage.weapons.containers.OV10_SMOKE = "weapons.containers.{OV10_SMOKE}" -ENUMS.Storage.weapons.bombs.BLU_4B_OLD = "weapons.bombs.BLU-4B_OLD" -ENUMS.Storage.weapons.bombs.FAB_500M54 = "weapons.bombs.FAB-500M54" -ENUMS.Storage.weapons.bombs.GBU_38 = "weapons.bombs.GBU_38" -ENUMS.Storage.weapons.containers.F_15E_AXQ_14_DATALINK = "weapons.containers.F-15E_AXQ-14_DATALINK" -ENUMS.Storage.weapons.bombs.BEER_BOMB = "weapons.bombs.BEER_BOMB" -ENUMS.Storage.weapons.bombs.P_50T = "weapons.bombs.P-50T" -ENUMS.Storage.weapons.nurs.C_8CM_GN = "weapons.nurs.C_8CM_GN" -ENUMS.Storage.weapons.bombs.FAB_500SL = "weapons.bombs.FAB-500SL" -ENUMS.Storage.weapons.bombs.KAB_1500Kr = "weapons.bombs.KAB_1500Kr" -ENUMS.Storage.weapons.bombs.two50_2 = "weapons.bombs.250-2" -ENUMS.Storage.weapons.droptanks.Spitfire_tank_1 = "weapons.droptanks.Spitfire_tank_1" -ENUMS.Storage.weapons.missiles.AGM_65G = "weapons.missiles.AGM_65G" -ENUMS.Storage.weapons.missiles.AGM_65A = "weapons.missiles.AGM_65A" -ENUMS.Storage.weapons.containers.Hercules_JATO = "weapons.containers.Hercules_JATO" -ENUMS.Storage.weapons.nurs.HYDRA_70_M259 = "weapons.nurs.HYDRA_70_M259" -ENUMS.Storage.weapons.missiles.AGM_84E = "weapons.missiles.AGM_84E" -ENUMS.Storage.weapons.bombs.AN_M30A1 = "weapons.bombs.AN_M30A1" -ENUMS.Storage.weapons.nurs.C_25 = "weapons.nurs.C_25" -ENUMS.Storage.weapons.containers.AV8BNA_ALQ164 = "weapons.containers.AV8BNA_ALQ164" -ENUMS.Storage.weapons.containers.lav_25 = "weapons.containers.lav-25" -ENUMS.Storage.weapons.missiles.P_60 = "weapons.missiles.P_60" -ENUMS.Storage.weapons.bombs.FAB_1500 = "weapons.bombs.FAB_1500" -ENUMS.Storage.weapons.droptanks.FuelTank_350L = "weapons.droptanks.FuelTank_350L" -ENUMS.Storage.weapons.bombs.AAA_Vulcan_M163_Skid_21577lb = "weapons.bombs.AAA Vulcan M163 Skid [21577lb]" -ENUMS.Storage.weapons.missiles.Kormoran = "weapons.missiles.Kormoran" -ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY = "weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY" -ENUMS.Storage.weapons.droptanks.FuelTank_150L = "weapons.droptanks.FuelTank_150L" -ENUMS.Storage.weapons.missiles.Rb_15F_for_A_I = "weapons.missiles.Rb 15F (for A.I.)" -ENUMS.Storage.weapons.missiles.RB75T = "weapons.missiles.RB75T" -ENUMS.Storage.weapons.missiles.Vikhr_M = "weapons.missiles.Vikhr_M" -ENUMS.Storage.weapons.nurs.FFAR_M156_WP = "weapons.nurs.FFAR M156 WP" -ENUMS.Storage.weapons.nurs.British_HE_60LBSAPNo2_3INCHNo1 = "weapons.nurs.British_HE_60LBSAPNo2_3INCHNo1" -ENUMS.Storage.weapons.missiles.DWS39_MJ2 = "weapons.missiles.DWS39_MJ2" -ENUMS.Storage.weapons.bombs.HEBOMBD = "weapons.bombs.HEBOMBD" -ENUMS.Storage.weapons.missiles.CATM_9M = "weapons.missiles.CATM_9M" -ENUMS.Storage.weapons.bombs.Mk_81 = "weapons.bombs.Mk_81" -ENUMS.Storage.weapons.droptanks.Drop_Tank_300_Liter = "weapons.droptanks.Drop_Tank_300_Liter" -ENUMS.Storage.weapons.containers.HMMWV_M1025 = "weapons.containers.HMMWV_M1025" -ENUMS.Storage.weapons.bombs.SAM_CHAPARRAL_Air_21624lb = "weapons.bombs.SAM CHAPARRAL Air [21624lb]" -ENUMS.Storage.weapons.missiles.AGM_154A = "weapons.missiles.AGM_154A" -ENUMS.Storage.weapons.bombs.Mk_84AIR_TP = "weapons.bombs.Mk_84AIR_TP" -ENUMS.Storage.weapons.bombs.GBU_31_V_3B = "weapons.bombs.GBU_31_V_3B" -ENUMS.Storage.weapons.nurs.C_8CM_WH = "weapons.nurs.C_8CM_WH" -ENUMS.Storage.weapons.missiles.Matra_Super_530D = "weapons.missiles.Matra Super 530D" -ENUMS.Storage.weapons.nurs.ARF8M3TPSM = "weapons.nurs.ARF8M3TPSM" -ENUMS.Storage.weapons.missiles.TGM_65H = "weapons.missiles.TGM_65H" -ENUMS.Storage.weapons.nurs.M8rocket = "weapons.nurs.M8rocket" -ENUMS.Storage.weapons.bombs.GBU_27 = "weapons.bombs.GBU_27" -ENUMS.Storage.weapons.missiles.AGR_20A = "weapons.missiles.AGR_20A" -ENUMS.Storage.weapons.missiles.LS_6_250 = "weapons.missiles.LS-6-250" -ENUMS.Storage.weapons.droptanks.M2KC_RPL_522_EMPTY = "weapons.droptanks.M2KC_RPL_522_EMPTY" -ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541 = "weapons.droptanks.M2KC_02_RPL541" -ENUMS.Storage.weapons.missiles.AGM_45 = "weapons.missiles.AGM_45" -ENUMS.Storage.weapons.missiles.AGM_84A = "weapons.missiles.AGM_84A" -ENUMS.Storage.weapons.bombs.APC_BTR_80_Air_23936lb = "weapons.bombs.APC BTR-80 Air [23936lb]" -ENUMS.Storage.weapons.missiles.P_33E = "weapons.missiles.P_33E" -ENUMS.Storage.weapons.missiles.Ataka_9M120 = "weapons.missiles.Ataka_9M120" -ENUMS.Storage.weapons.bombs.MK76 = "weapons.bombs.MK76" -ENUMS.Storage.weapons.bombs.AB_250_2_SD_2 = "weapons.bombs.AB_250_2_SD_2" -ENUMS.Storage.weapons.missiles.Rb_05A = "weapons.missiles.Rb 05A" -ENUMS.Storage.weapons.bombs.ART_GVOZDIKA_34720lb = "weapons.bombs.ART GVOZDIKA [34720lb]" -ENUMS.Storage.weapons.bombs.Generic_Crate_20000lb = "weapons.bombs.Generic Crate [20000lb]" -ENUMS.Storage.weapons.bombs.FAB_100SV = "weapons.bombs.FAB_100SV" -ENUMS.Storage.weapons.bombs.BetAB_500 = "weapons.bombs.BetAB_500" -ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541_EMPTY = "weapons.droptanks.M2KC_02_RPL541_EMPTY" -ENUMS.Storage.weapons.droptanks.PTB600_MIG15 = "weapons.droptanks.PTB600_MIG15" -ENUMS.Storage.weapons.missiles.Rb_24J = "weapons.missiles.Rb 24J" -ENUMS.Storage.weapons.nurs.C_8CM_BU = "weapons.nurs.C_8CM_BU" -ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_F1B = "weapons.nurs.SNEB_TYPE259E_F1B" -ENUMS.Storage.weapons.nurs.WGr21 = "weapons.nurs.WGr21" -ENUMS.Storage.weapons.bombs.SAMP250HD = "weapons.bombs.SAMP250HD" -ENUMS.Storage.weapons.containers.alq_184long = "weapons.containers.alq-184long" -ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_H1 = "weapons.nurs.SNEB_TYPE259E_H1" -ENUMS.Storage.weapons.bombs.British_SAP_250LB_Bomb_Mk5 = "weapons.bombs.British_SAP_250LB_Bomb_Mk5" -ENUMS.Storage.weapons.bombs.Transport_UAZ_469_Air_3747lb = "weapons.bombs.Transport UAZ-469 Air [3747lb]" -ENUMS.Storage.weapons.bombs.Mk_83CT = "weapons.bombs.Mk_83CT" -ENUMS.Storage.weapons.missiles.AIM_7P = "weapons.missiles.AIM-7P" -ENUMS.Storage.weapons.missiles.AT_6 = "weapons.missiles.AT_6" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_GREEN = "weapons.nurs.SNEB_TYPE254_H1_GREEN" -ENUMS.Storage.weapons.nurs.SNEB_TYPE250_F1B = "weapons.nurs.SNEB_TYPE250_F1B" -ENUMS.Storage.weapons.containers.U22A = "weapons.containers.U22A" -ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk1 = "weapons.bombs.British_GP_250LB_Bomb_Mk1" -ENUMS.Storage.weapons.bombs.CBU_105 = "weapons.bombs.CBU_105" -ENUMS.Storage.weapons.droptanks.FW_190_Fuel_Tank = "weapons.droptanks.FW-190_Fuel-Tank" -ENUMS.Storage.weapons.missiles.X_58 = "weapons.missiles.X_58" -ENUMS.Storage.weapons.missiles.BK90_MJ1_MJ2 = "weapons.missiles.BK90_MJ1_MJ2" -ENUMS.Storage.weapons.missiles.TGM_65D = "weapons.missiles.TGM_65D" -ENUMS.Storage.weapons.containers.BRD_4_250 = "weapons.containers.BRD-4-250" -ENUMS.Storage.weapons.missiles.P_73 = "weapons.missiles.P_73" -ENUMS.Storage.weapons.bombs.AN_M66 = "weapons.bombs.AN_M66" -ENUMS.Storage.weapons.bombs.APC_LAV_25_Air_22520lb = "weapons.bombs.APC LAV-25 Air [22520lb]" -ENUMS.Storage.weapons.missiles.AIM_7MH = "weapons.missiles.AIM-7MH" -ENUMS.Storage.weapons.containers.MB339_TravelPod = "weapons.containers.MB339_TravelPod" -ENUMS.Storage.weapons.bombs.GBU_12 = "weapons.bombs.GBU_12" -ENUMS.Storage.weapons.bombs.SC_250_T3_J = "weapons.bombs.SC_250_T3_J" -ENUMS.Storage.weapons.missiles.KD_20 = "weapons.missiles.KD-20" -ENUMS.Storage.weapons.missiles.AGM_86C = "weapons.missiles.AGM_86C" -ENUMS.Storage.weapons.missiles.X_35 = "weapons.missiles.X_35" -ENUMS.Storage.weapons.bombs.MK106 = "weapons.bombs.MK106" -ENUMS.Storage.weapons.bombs.BETAB_500S = "weapons.bombs.BETAB-500S" -ENUMS.Storage.weapons.nurs.C_5 = "weapons.nurs.C_5" -ENUMS.Storage.weapons.nurs.S_24B = "weapons.nurs.S-24B" -ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk2 = "weapons.bombs.British_MC_500LB_Bomb_Mk2" -ENUMS.Storage.weapons.containers.ANAWW_13 = "weapons.containers.ANAWW_13" -ENUMS.Storage.weapons.droptanks.droptank_108_gal = "weapons.droptanks.droptank_108_gal" -ENUMS.Storage.weapons.droptanks.DFT_300_GAL_A4E_LR = "weapons.droptanks.DFT_300_GAL_A4E_LR" -ENUMS.Storage.weapons.bombs.CBU_87 = "weapons.bombs.CBU_87" -ENUMS.Storage.weapons.missiles.GAR_8 = "weapons.missiles.GAR-8" -ENUMS.Storage.weapons.bombs.BELOUGA = "weapons.bombs.BELOUGA" -ENUMS.Storage.weapons.containers.EclairM_33 = "weapons.containers.{EclairM_33}" -ENUMS.Storage.weapons.bombs.ART_2S9_NONA_Air_19140lb = "weapons.bombs.ART 2S9 NONA Air [19140lb]" -ENUMS.Storage.weapons.bombs.BR_250 = "weapons.bombs.BR_250" -ENUMS.Storage.weapons.bombs.IAB_500 = "weapons.bombs.IAB-500" -ENUMS.Storage.weapons.containers.AN_ASQ_228 = "weapons.containers.AN_ASQ_228" -ENUMS.Storage.weapons.missiles.P_27P = "weapons.missiles.P_27P" -ENUMS.Storage.weapons.bombs.SD_250_Stg = "weapons.bombs.SD_250_Stg" -ENUMS.Storage.weapons.missiles.R_530F_IR = "weapons.missiles.R_530F_IR" -ENUMS.Storage.weapons.bombs.British_SAP_500LB_Bomb_Mk5 = "weapons.bombs.British_SAP_500LB_Bomb_Mk5" -ENUMS.Storage.weapons.bombs.FAB_250M54 = "weapons.bombs.FAB-250M54" -ENUMS.Storage.weapons.containers.M2KC_AAF = "weapons.containers.{M2KC_AAF}" -ENUMS.Storage.weapons.missiles.CM_802AKG_AI = "weapons.missiles.CM-802AKG_AI" -ENUMS.Storage.weapons.bombs.CBU_103 = "weapons.bombs.CBU_103" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_RED = "weapons.containers.{US_M10_SMOKE_TANK_RED}" -ENUMS.Storage.weapons.missiles.X_29T = "weapons.missiles.X_29T" -ENUMS.Storage.weapons.bombs.HEMTT_TFFT_34400lb = "weapons.bombs.HEMTT TFFT [34400lb]" -ENUMS.Storage.weapons.missiles.C_701IR = "weapons.missiles.C-701IR" -ENUMS.Storage.weapons.containers.fullCargoSeats = "weapons.containers.fullCargoSeats" -ENUMS.Storage.weapons.bombs.GBU_15_V_31_B = "weapons.bombs.GBU_15_V_31_B" -ENUMS.Storage.weapons.bombs.APC_M1043_HMMWV_Armament_Air_7023lb = "weapons.bombs.APC M1043 HMMWV Armament Air [7023lb]" -ENUMS.Storage.weapons.missiles.PL_5EII = "weapons.missiles.PL-5EII" -ENUMS.Storage.weapons.bombs.SC_250_T1_L2 = "weapons.bombs.SC_250_T1_L2" -ENUMS.Storage.weapons.torpedoes.mk46torp_name = "weapons.torpedoes.mk46torp_name" -ENUMS.Storage.weapons.containers.F_15E_AAQ_33_XR_ATP_SE = "weapons.containers.F-15E_AAQ-33_XR_ATP-SE" -ENUMS.Storage.weapons.missiles.AIM_7 = "weapons.missiles.AIM_7" -ENUMS.Storage.weapons.missiles.AGM_122 = "weapons.missiles.AGM_122" -ENUMS.Storage.weapons.bombs.HEBOMB = "weapons.bombs.HEBOMB" -ENUMS.Storage.weapons.bombs.CBU_97 = "weapons.bombs.CBU_97" -ENUMS.Storage.weapons.bombs.MK_81SE = "weapons.bombs.MK-81SE" -ENUMS.Storage.weapons.nurs.Zuni_127 = "weapons.nurs.Zuni_127" -ENUMS.Storage.weapons.containers.M2KC_AGF = "weapons.containers.{M2KC_AGF}" -ENUMS.Storage.weapons.droptanks.Hercules_ExtFuelTank = "weapons.droptanks.Hercules_ExtFuelTank" -ENUMS.Storage.weapons.containers.SMOKE_WHITE = "weapons.containers.{SMOKE_WHITE}" -ENUMS.Storage.weapons.droptanks.droptank_150_gal = "weapons.droptanks.droptank_150_gal" -ENUMS.Storage.weapons.nurs.HYDRA_70_WTU1B = "weapons.nurs.HYDRA_70_WTU1B" -ENUMS.Storage.weapons.missiles.GB_6_SFW = "weapons.missiles.GB-6-SFW" -ENUMS.Storage.weapons.missiles.KD_63 = "weapons.missiles.KD-63" -ENUMS.Storage.weapons.bombs.GBU_28 = "weapons.bombs.GBU_28" -ENUMS.Storage.weapons.nurs.C_8CM_YE = "weapons.nurs.C_8CM_YE" -ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK = "weapons.droptanks.HB_F14_EXT_DROPTANK" -ENUMS.Storage.weapons.missiles.Super_530F = "weapons.missiles.Super_530F" -ENUMS.Storage.weapons.missiles.Ataka_9M220 = "weapons.missiles.Ataka_9M220" -ENUMS.Storage.weapons.bombs.BDU_33 = "weapons.bombs.BDU_33" -ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk4 = "weapons.bombs.British_GP_250LB_Bomb_Mk4" -ENUMS.Storage.weapons.missiles.TOW = "weapons.missiles.TOW" -ENUMS.Storage.weapons.bombs.ATGM_M1045_HMMWV_TOW_Air_7183lb = "weapons.bombs.ATGM M1045 HMMWV TOW Air [7183lb]" -ENUMS.Storage.weapons.missiles.X_25MR = "weapons.missiles.X_25MR" -ENUMS.Storage.weapons.droptanks.fueltank230 = "weapons.droptanks.fueltank230" -ENUMS.Storage.weapons.droptanks.PTB_490C_MIG21 = "weapons.droptanks.PTB-490C-MIG21" -ENUMS.Storage.weapons.bombs.M1025_HMMWV_Air_6160lb = "weapons.bombs.M1025 HMMWV Air [6160lb]" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_GREEN = "weapons.nurs.SNEB_TYPE254_F1B_GREEN" -ENUMS.Storage.weapons.missiles.R_550 = "weapons.missiles.R_550" -ENUMS.Storage.weapons.bombs.KAB_1500LG = "weapons.bombs.KAB_1500LG" -ENUMS.Storage.weapons.missiles.AGM_84D = "weapons.missiles.AGM_84D" -ENUMS.Storage.weapons.missiles.YJ_83K = "weapons.missiles.YJ-83K" -ENUMS.Storage.weapons.missiles.AIM_54C_Mk47 = "weapons.missiles.AIM_54C_Mk47" -ENUMS.Storage.weapons.missiles.BRM_1_90MM = "weapons.missiles.BRM-1_90MM" -ENUMS.Storage.weapons.missiles.Ataka_9M120F = "weapons.missiles.Ataka_9M120F" -ENUMS.Storage.weapons.droptanks.Eleven00L_Tank = "weapons.droptanks.1100L Tank" -ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP_100" -ENUMS.Storage.weapons.adapters.lau_88 = "weapons.adapters.lau-88" -ENUMS.Storage.weapons.missiles.P_40T = "weapons.missiles.P_40T" -ENUMS.Storage.weapons.missiles.GB_6 = "weapons.missiles.GB-6" -ENUMS.Storage.weapons.bombs.FAB_250M54TU = "weapons.bombs.FAB-250M54TU" -ENUMS.Storage.weapons.missiles.DWS39_MJ1 = "weapons.missiles.DWS39_MJ1" -ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG" -ENUMS.Storage.weapons.bombs.FAB_250 = "weapons.bombs.FAB_250" -ENUMS.Storage.weapons.missiles.C_802AK = "weapons.missiles.C_802AK" -ENUMS.Storage.weapons.bombs.SD_500_A = "weapons.bombs.SD_500_A" -ENUMS.Storage.weapons.bombs.GBU_32_V_2B = "weapons.bombs.GBU_32_V_2B" -ENUMS.Storage.weapons.containers.marder = "weapons.containers.marder" -ENUMS.Storage.weapons.missiles.ADM_141B = "weapons.missiles.ADM_141B" -ENUMS.Storage.weapons.bombs.ROCKEYE = "weapons.bombs.ROCKEYE" -ENUMS.Storage.weapons.missiles.BK90_MJ1 = "weapons.missiles.BK90_MJ1" -ENUMS.Storage.weapons.containers.BTR_80 = "weapons.containers.BTR-80" -ENUMS.Storage.weapons.bombs.SAM_ROLAND_ADS_34720lb = "weapons.bombs.SAM ROLAND ADS [34720lb]" -ENUMS.Storage.weapons.containers.wmd7 = "weapons.containers.wmd7" -ENUMS.Storage.weapons.missiles.C_701T = "weapons.missiles.C-701T" -ENUMS.Storage.weapons.missiles.AIM_7E_2 = "weapons.missiles.AIM-7E-2" -ENUMS.Storage.weapons.nurs.HVAR = "weapons.nurs.HVAR" -ENUMS.Storage.weapons.containers.HMMWV_M1043 = "weapons.containers.HMMWV_M1043" -ENUMS.Storage.weapons.droptanks.PTB_800_MIG21 = "weapons.droptanks.PTB-800-MIG21" -ENUMS.Storage.weapons.missiles.AGM_114 = "weapons.missiles.AGM_114" -ENUMS.Storage.weapons.bombs.APC_M1126_Stryker_ICV_29542lb = "weapons.bombs.APC M1126 Stryker ICV [29542lb]" -ENUMS.Storage.weapons.bombs.APC_M113_Air_21624lb = "weapons.bombs.APC M113 Air [21624lb]" -ENUMS.Storage.weapons.bombs.M_117 = "weapons.bombs.M_117" -ENUMS.Storage.weapons.missiles.AGM_65D = "weapons.missiles.AGM_65D" -ENUMS.Storage.weapons.droptanks.MB339_TT320_L = "weapons.droptanks.MB339_TT320_L" -ENUMS.Storage.weapons.missiles.AGM_86 = "weapons.missiles.AGM_86" -ENUMS.Storage.weapons.bombs.BDU_45LGB = "weapons.bombs.BDU_45LGB" -ENUMS.Storage.weapons.missiles.AGM_65H = "weapons.missiles.AGM_65H" -ENUMS.Storage.weapons.nurs.RS_82 = "weapons.nurs.RS-82" -ENUMS.Storage.weapons.nurs.SNEB_TYPE252_F1B = "weapons.nurs.SNEB_TYPE252_F1B" -ENUMS.Storage.weapons.bombs.BL_755 = "weapons.bombs.BL_755" -ENUMS.Storage.weapons.containers.F_15E_AAQ_28_LITENING = "weapons.containers.F-15E_AAQ-28_LITENING" -ENUMS.Storage.weapons.nurs.SNEB_TYPE256_F1B = "weapons.nurs.SNEB_TYPE256_F1B" -ENUMS.Storage.weapons.missiles.AGM_84H = "weapons.missiles.AGM_84H" -ENUMS.Storage.weapons.missiles.AIM_54 = "weapons.missiles.AIM_54" -ENUMS.Storage.weapons.missiles.X_31A = "weapons.missiles.X_31A" -ENUMS.Storage.weapons.bombs.KAB_500Kr = "weapons.bombs.KAB_500Kr" -ENUMS.Storage.weapons.containers.SPS_141_100 = "weapons.containers.SPS-141-100" -ENUMS.Storage.weapons.missiles.BK90_MJ2 = "weapons.missiles.BK90_MJ2" -ENUMS.Storage.weapons.missiles.Super_530D = "weapons.missiles.Super_530D" -ENUMS.Storage.weapons.bombs.CBU_52B = "weapons.bombs.CBU_52B" -ENUMS.Storage.weapons.droptanks.PTB_450 = "weapons.droptanks.PTB-450" -ENUMS.Storage.weapons.bombs.IFV_MCV_80_34720lb = "weapons.bombs.IFV MCV-80 [34720lb]" -ENUMS.Storage.weapons.containers.Two_c9 = "weapons.containers.2-c9" -ENUMS.Storage.weapons.missiles.AIM_9JULI = "weapons.missiles.AIM-9JULI" -ENUMS.Storage.weapons.droptanks.MB339_TT500_R = "weapons.droptanks.MB339_TT500_R" -ENUMS.Storage.weapons.nurs.C_8CM = "weapons.nurs.C_8CM" -ENUMS.Storage.weapons.containers.BARAX = "weapons.containers.BARAX" -ENUMS.Storage.weapons.missiles.P_40R = "weapons.missiles.P_40R" -ENUMS.Storage.weapons.missiles.YJ_12 = "weapons.missiles.YJ-12" -ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_YELLOW = "weapons.nurs.SNEB_TYPE254_H1_YELLOW" -ENUMS.Storage.weapons.bombs.Durandal = "weapons.bombs.Durandal" -ENUMS.Storage.weapons.droptanks.i16_eft = "weapons.droptanks.i16_eft" -ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D_EMPTY = "weapons.droptanks.AV8BNA_AERO1D_EMPTY" -ENUMS.Storage.weapons.containers.Hercules_Battle_Station_TGP = "weapons.containers.Hercules_Battle_Station_TGP" -ENUMS.Storage.weapons.nurs.C_8CM_VT = "weapons.nurs.C_8CM_VT" -ENUMS.Storage.weapons.missiles.PL_12 = "weapons.missiles.PL-12" -ENUMS.Storage.weapons.missiles.R_3R = "weapons.missiles.R-3R" -ENUMS.Storage.weapons.bombs.GBU_54_V_1B = "weapons.bombs.GBU_54_V_1B" -ENUMS.Storage.weapons.droptanks.MB339_TT320_R = "weapons.droptanks.MB339_TT320_R" -ENUMS.Storage.weapons.bombs.RN_24 = "weapons.bombs.RN-24" -ENUMS.Storage.weapons.containers.Twoc6m = "weapons.containers.2c6m" -ENUMS.Storage.weapons.bombs.ARV_BRDM_2_Air_12320lb = "weapons.bombs.ARV BRDM-2 Air [12320lb]" -ENUMS.Storage.weapons.bombs.ARV_BRDM_2_Skid_12210lb = "weapons.bombs.ARV BRDM-2 Skid [12210lb]" -ENUMS.Storage.weapons.nurs.SNEB_TYPE251_F1B = "weapons.nurs.SNEB_TYPE251_F1B" -ENUMS.Storage.weapons.missiles.X_41 = "weapons.missiles.X_41" -ENUMS.Storage.weapons.containers.MIG21_SMOKE_WHITE = "weapons.containers.{MIG21_SMOKE_WHITE}" -ENUMS.Storage.weapons.bombs.MK_82AIR = "weapons.bombs.MK_82AIR" -ENUMS.Storage.weapons.missiles.R_530F_EM = "weapons.missiles.R_530F_EM" -ENUMS.Storage.weapons.bombs.SAMP400LD = "weapons.bombs.SAMP400LD" -ENUMS.Storage.weapons.bombs.FAB_50 = "weapons.bombs.FAB_50" -ENUMS.Storage.weapons.bombs.AB_250_2_SD_10A = "weapons.bombs.AB_250_2_SD_10A" -ENUMS.Storage.weapons.missiles.ADM_141A = "weapons.missiles.ADM_141A" -ENUMS.Storage.weapons.containers.KBpod = "weapons.containers.KBpod" -ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4 = "weapons.bombs.British_GP_500LB_Bomb_Mk4" -ENUMS.Storage.weapons.missiles.AGM_65E = "weapons.missiles.AGM_65E" -ENUMS.Storage.weapons.containers.sa342_dipole_antenna = "weapons.containers.sa342_dipole_antenna" -ENUMS.Storage.weapons.bombs.OFAB_100_Jupiter = "weapons.bombs.OFAB-100 Jupiter" -ENUMS.Storage.weapons.nurs.SNEB_TYPE257_F1B = "weapons.nurs.SNEB_TYPE257_F1B" -ENUMS.Storage.weapons.missiles.Rb_04E_for_A_I = "weapons.missiles.Rb 04E (for A.I.)" -ENUMS.Storage.weapons.bombs.AN_M66A2 = "weapons.bombs.AN-M66A2" -ENUMS.Storage.weapons.missiles.P_27T = "weapons.missiles.P_27T" -ENUMS.Storage.weapons.droptanks.LNS_VIG_XTANK = "weapons.droptanks.LNS_VIG_XTANK" -ENUMS.Storage.weapons.missiles.R_55 = "weapons.missiles.R-55" -ENUMS.Storage.weapons.torpedoes.YU_6 = "weapons.torpedoes.YU-6" -ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk2 = "weapons.bombs.British_MC_250LB_Bomb_Mk2" -ENUMS.Storage.weapons.droptanks.PTB_120_F86F35 = "weapons.droptanks.PTB_120_F86F35" -ENUMS.Storage.weapons.missiles.PL_8B = "weapons.missiles.PL-8B" -ENUMS.Storage.weapons.droptanks.F_15E_Drop_Tank_Empty = "weapons.droptanks.F-15E_Drop_Tank_Empty" -ENUMS.Storage.weapons.nurs.British_HE_60LBFNo1_3INCHNo1 = "weapons.nurs.British_HE_60LBFNo1_3INCHNo1" -ENUMS.Storage.weapons.missiles.P_77 = "weapons.missiles.P_77" -ENUMS.Storage.weapons.torpedoes.LTF_5B = "weapons.torpedoes.LTF_5B" -ENUMS.Storage.weapons.missiles.R_3S = "weapons.missiles.R-3S" -ENUMS.Storage.weapons.nurs.SNEB_TYPE253_H1 = "weapons.nurs.SNEB_TYPE253_H1" -ENUMS.Storage.weapons.missiles.PL_8A = "weapons.missiles.PL-8A" -ENUMS.Storage.weapons.bombs.APC_BTR_82A_Skid_24888lb = "weapons.bombs.APC BTR-82A Skid [24888lb]" -ENUMS.Storage.weapons.containers.Sborka = "weapons.containers.Sborka" -ENUMS.Storage.weapons.missiles.AGM_65L = "weapons.missiles.AGM_65L" -ENUMS.Storage.weapons.missiles.X_28 = "weapons.missiles.X_28" -ENUMS.Storage.weapons.missiles.TGM_65G = "weapons.missiles.TGM_65G" -ENUMS.Storage.weapons.nurs.SNEB_TYPE257_H1 = "weapons.nurs.SNEB_TYPE257_H1" -ENUMS.Storage.weapons.missiles.RB75B = "weapons.missiles.RB75B" -ENUMS.Storage.weapons.missiles.X_25ML = "weapons.missiles.X_25ML" -ENUMS.Storage.weapons.droptanks.FPU_8A = "weapons.droptanks.FPU_8A" -ENUMS.Storage.weapons.bombs.BLG66 = "weapons.bombs.BLG66" -ENUMS.Storage.weapons.nurs.C_8CM_RD = "weapons.nurs.C_8CM_RD" -ENUMS.Storage.weapons.containers.EclairM_06 = "weapons.containers.{EclairM_06}" -ENUMS.Storage.weapons.bombs.RBK_500AO = "weapons.bombs.RBK_500AO" -ENUMS.Storage.weapons.missiles.AIM_9P = "weapons.missiles.AIM-9P" -ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4_Short = "weapons.bombs.British_GP_500LB_Bomb_Mk4_Short" -ENUMS.Storage.weapons.containers.MB339_Vinten = "weapons.containers.MB339_Vinten" -ENUMS.Storage.weapons.missiles.Rb_15F = "weapons.missiles.Rb 15F" -ENUMS.Storage.weapons.nurs.ARAKM70BHE = "weapons.nurs.ARAKM70BHE" -ENUMS.Storage.weapons.bombs.AAA_Vulcan_M163_Air_21666lb = "weapons.bombs.AAA Vulcan M163 Air [21666lb]" -ENUMS.Storage.weapons.missiles.X_29L = "weapons.missiles.X_29L" -ENUMS.Storage.weapons.containers.F14_LANTIRN_TP = "weapons.containers.{F14-LANTIRN-TP}" -ENUMS.Storage.weapons.bombs.FAB_250_M62 = "weapons.bombs.FAB-250-M62" -ENUMS.Storage.weapons.missiles.AIM_120C = "weapons.missiles.AIM_120C" -ENUMS.Storage.weapons.bombs.EWR_SBORKA_Air_21624lb = "weapons.bombs.EWR SBORKA Air [21624lb]" -ENUMS.Storage.weapons.bombs.SAMP250LD = "weapons.bombs.SAMP250LD" -ENUMS.Storage.weapons.droptanks.Spitfire_slipper_tank = "weapons.droptanks.Spitfire_slipper_tank" -ENUMS.Storage.weapons.missiles.LS_6_500 = "weapons.missiles.LS-6-500" -ENUMS.Storage.weapons.bombs.GBU_31_V_4B = "weapons.bombs.GBU_31_V_4B" -ENUMS.Storage.weapons.droptanks.PTB400_MIG15 = "weapons.droptanks.PTB400_MIG15" -ENUMS.Storage.weapons.containers.m_113 = "weapons.containers.m-113" -ENUMS.Storage.weapons.bombs.SPG_M1128_Stryker_MGS_33036lb = "weapons.bombs.SPG M1128 Stryker MGS [33036lb]" -ENUMS.Storage.weapons.missiles.AIM_9L = "weapons.missiles.AIM-9L" -ENUMS.Storage.weapons.missiles.AIM_9X = "weapons.missiles.AIM_9X" -ENUMS.Storage.weapons.nurs.C_8 = "weapons.nurs.C_8" -ENUMS.Storage.weapons.bombs.SAM_CHAPARRAL_Skid_21516lb = "weapons.bombs.SAM CHAPARRAL Skid [21516lb]" -ENUMS.Storage.weapons.missiles.P_27TE = "weapons.missiles.P_27TE" -ENUMS.Storage.weapons.bombs.ODAB_500PM = "weapons.bombs.ODAB-500PM" -ENUMS.Storage.weapons.bombs.MK77mod1_WPN = "weapons.bombs.MK77mod1-WPN" -ENUMS.Storage.weapons.droptanks.PTB400_MIG19 = "weapons.droptanks.PTB400_MIG19" -ENUMS.Storage.weapons.torpedoes.Mark_46 = "weapons.torpedoes.Mark_46" -ENUMS.Storage.weapons.containers.rightSeat = "weapons.containers.rightSeat" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_ORANGE = "weapons.containers.{US_M10_SMOKE_TANK_ORANGE}" -ENUMS.Storage.weapons.bombs.SAB_100MN = "weapons.bombs.SAB_100MN" -ENUMS.Storage.weapons.nurs.FFAR_Mk5_HEAT = "weapons.nurs.FFAR Mk5 HEAT" -ENUMS.Storage.weapons.bombs.IFV_TPZ_FUCH_33440lb = "weapons.bombs.IFV TPZ FUCH [33440lb]" -ENUMS.Storage.weapons.bombs.IFV_M2A2_Bradley_34720lb = "weapons.bombs.IFV M2A2 Bradley [34720lb]" -ENUMS.Storage.weapons.bombs.MK77mod0_WPN = "weapons.bombs.MK77mod0-WPN" -ENUMS.Storage.weapons.containers.ASO_2 = "weapons.containers.ASO-2" -ENUMS.Storage.weapons.bombs.Mk_84AIR_GP = "weapons.bombs.Mk_84AIR_GP" -ENUMS.Storage.weapons.nurs.S_24A = "weapons.nurs.S-24A" -ENUMS.Storage.weapons.bombs.RBK_250_275_AO_1SCH = "weapons.bombs.RBK_250_275_AO_1SCH" -ENUMS.Storage.weapons.bombs.Transport_Tigr_Skid_15730lb = "weapons.bombs.Transport Tigr Skid [15730lb]" -ENUMS.Storage.weapons.missiles.AIM_7F = "weapons.missiles.AIM-7F" -ENUMS.Storage.weapons.bombs.CBU_99 = "weapons.bombs.CBU_99" -ENUMS.Storage.weapons.bombs.LUU_2B = "weapons.bombs.LUU_2B" -ENUMS.Storage.weapons.bombs.FAB_500TA = "weapons.bombs.FAB-500TA" -ENUMS.Storage.weapons.missiles.AGR_20_M282 = "weapons.missiles.AGR_20_M282" -ENUMS.Storage.weapons.droptanks.MB339_FT330 = "weapons.droptanks.MB339_FT330" -ENUMS.Storage.weapons.bombs.SAMP125LD = "weapons.bombs.SAMP125LD" -ENUMS.Storage.weapons.missiles.X_25MP = "weapons.missiles.X_25MP" -ENUMS.Storage.weapons.nurs.SNEB_TYPE252_H1 = "weapons.nurs.SNEB_TYPE252_H1" -ENUMS.Storage.weapons.missiles.AGM_65F = "weapons.missiles.AGM_65F" -ENUMS.Storage.weapons.missiles.AIM_9P5 = "weapons.missiles.AIM-9P5" -ENUMS.Storage.weapons.bombs.Transport_Tigr_Air_15900lb = "weapons.bombs.Transport Tigr Air [15900lb]" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_RED = "weapons.nurs.SNEB_TYPE254_H1_RED" -ENUMS.Storage.weapons.nurs.FFAR_Mk1_HE = "weapons.nurs.FFAR Mk1 HE" -ENUMS.Storage.weapons.nurs.SPRD_99 = "weapons.nurs.SPRD-99" -ENUMS.Storage.weapons.bombs.BIN_200 = "weapons.bombs.BIN_200" -ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU_4B_GROUP" -ENUMS.Storage.weapons.bombs.GBU_24 = "weapons.bombs.GBU_24" -ENUMS.Storage.weapons.missiles.Rb_04E = "weapons.missiles.Rb 04E" -ENUMS.Storage.weapons.missiles.Rb_74 = "weapons.missiles.Rb 74" -ENUMS.Storage.weapons.containers.leftSeat = "weapons.containers.leftSeat" -ENUMS.Storage.weapons.bombs.LS_6_100 = "weapons.bombs.LS-6-100" -ENUMS.Storage.weapons.bombs.Transport_URAL_375_14815lb = "weapons.bombs.Transport URAL-375 [14815lb]" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_GREEN = "weapons.containers.{US_M10_SMOKE_TANK_GREEN}" -ENUMS.Storage.weapons.missiles.X_22 = "weapons.missiles.X_22" -ENUMS.Storage.weapons.containers.FAS = "weapons.containers.FAS" -ENUMS.Storage.weapons.nurs.S_25_O = "weapons.nurs.S-25-O" -ENUMS.Storage.weapons.droptanks.para = "weapons.droptanks.para" -ENUMS.Storage.weapons.droptanks.F_15E_Drop_Tank = "weapons.droptanks.F-15E_Drop_Tank" -ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541_EMPTY = "weapons.droptanks.M2KC_08_RPL541_EMPTY" -ENUMS.Storage.weapons.missiles.X_31P = "weapons.missiles.X_31P" -ENUMS.Storage.weapons.bombs.RBK_500U = "weapons.bombs.RBK_500U" -ENUMS.Storage.weapons.missiles.AIM_54A_Mk47 = "weapons.missiles.AIM_54A_Mk47" -ENUMS.Storage.weapons.droptanks.oiltank = "weapons.droptanks.oiltank" -ENUMS.Storage.weapons.missiles.AGM_154B = "weapons.missiles.AGM_154B" -ENUMS.Storage.weapons.containers.MB339_SMOKE_POD = "weapons.containers.MB339_SMOKE-POD" -ENUMS.Storage.weapons.containers.ECM_POD_L_175V = "weapons.containers.{ECM_POD_L_175V}" -ENUMS.Storage.weapons.droptanks.PTB_580G_F1 = "weapons.droptanks.PTB_580G_F1" -ENUMS.Storage.weapons.containers.EclairM_15 = "weapons.containers.{EclairM_15}" -ENUMS.Storage.weapons.containers.F_15E_AAQ_13_LANTIRN = "weapons.containers.F-15E_AAQ-13_LANTIRN" -ENUMS.Storage.weapons.droptanks.Eight00L_Tank_Empty = "weapons.droptanks.800L Tank Empty" -ENUMS.Storage.weapons.containers.One6c_hts_pod = "weapons.containers.16c_hts_pod" -ENUMS.Storage.weapons.bombs.AN_M81 = "weapons.bombs.AN-M81" -ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_100gal = "weapons.droptanks.Mosquito_Drop_Tank_100gal" -ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_50gal = "weapons.droptanks.Mosquito_Drop_Tank_50gal" -ENUMS.Storage.weapons.droptanks.DFT_150_GAL_A4E = "weapons.droptanks.DFT_150_GAL_A4E" -ENUMS.Storage.weapons.missiles.AIM_9 = "weapons.missiles.AIM_9" -ENUMS.Storage.weapons.bombs.IFV_BTR_D_Air_18040lb = "weapons.bombs.IFV BTR-D Air [18040lb]" -ENUMS.Storage.weapons.containers.EclairM_42 = "weapons.containers.{EclairM_42}" -ENUMS.Storage.weapons.bombs.KAB_1500T = "weapons.bombs.KAB_1500T" -ENUMS.Storage.weapons.droptanks.PTB_490_MIG21 = "weapons.droptanks.PTB-490-MIG21" -ENUMS.Storage.weapons.droptanks.PTB_200_F86F35 = "weapons.droptanks.PTB_200_F86F35" -ENUMS.Storage.weapons.droptanks.PTB760_MIG19 = "weapons.droptanks.PTB760_MIG19" -ENUMS.Storage.weapons.bombs.GBU_43_B_MOAB = "weapons.bombs.GBU-43/B(MOAB)" -ENUMS.Storage.weapons.torpedoes.G7A_T1 = "weapons.torpedoes.G7A_T1" -ENUMS.Storage.weapons.bombs.IFV_BMD_1_Air_18040lb = "weapons.bombs.IFV BMD-1 Air [18040lb]" -ENUMS.Storage.weapons.bombs.SAM_LINEBACKER_34720lb = "weapons.bombs.SAM LINEBACKER [34720lb]" -ENUMS.Storage.weapons.containers.ais_pod_t50_r = "weapons.containers.ais-pod-t50_r" -ENUMS.Storage.weapons.containers.CE2_SMOKE_WHITE = "weapons.containers.{CE2_SMOKE_WHITE}" -ENUMS.Storage.weapons.droptanks.fuel_tank_230 = "weapons.droptanks.fuel_tank_230" -ENUMS.Storage.weapons.droptanks.M2KC_RPL_522 = "weapons.droptanks.M2KC_RPL_522" -ENUMS.Storage.weapons.missiles.AGM_130 = "weapons.missiles.AGM_130" -ENUMS.Storage.weapons.droptanks.Eight00L_Tank = "weapons.droptanks.800L Tank" -ENUMS.Storage.weapons.bombs.IFV_BTR_D_Skid_17930lb = "weapons.bombs.IFV BTR-D Skid [17930lb]" -ENUMS.Storage.weapons.containers.bmp_1 = "weapons.containers.bmp-1" -ENUMS.Storage.weapons.bombs.GBU_31 = "weapons.bombs.GBU_31" -ENUMS.Storage.weapons.containers.aaq_28LEFT_litening = "weapons.containers.aaq-28LEFT litening" -ENUMS.Storage.weapons.missiles.Kh_66_Grom = "weapons.missiles.Kh-66_Grom" -ENUMS.Storage.weapons.containers.MIG21_SMOKE_RED = "weapons.containers.{MIG21_SMOKE_RED}" -ENUMS.Storage.weapons.containers.U22 = "weapons.containers.U22" -ENUMS.Storage.weapons.bombs.IFV_BMD_1_Skid_17930lb = "weapons.bombs.IFV BMD-1 Skid [17930lb]" -ENUMS.Storage.weapons.droptanks.Bidon = "weapons.droptanks.Bidon" -ENUMS.Storage.weapons.bombs.GBU_31_V_2B = "weapons.bombs.GBU_31_V_2B" -ENUMS.Storage.weapons.bombs.Mk_82Y = "weapons.bombs.Mk_82Y" -ENUMS.Storage.weapons.containers.pl5eii = "weapons.containers.pl5eii" -ENUMS.Storage.weapons.bombs.RBK_500U_OAB_2_5RT = "weapons.bombs.RBK_500U_OAB_2_5RT" -ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk5 = "weapons.bombs.British_GP_500LB_Bomb_Mk5" -ENUMS.Storage.weapons.containers.Eclair = "weapons.containers.{Eclair}" -ENUMS.Storage.weapons.nurs.S5MO_HEFRAG_FFAR = "weapons.nurs.S5MO_HEFRAG_FFAR" -ENUMS.Storage.weapons.bombs.BETAB_500M = "weapons.bombs.BETAB-500M" -ENUMS.Storage.weapons.bombs.Transport_M818_16000lb = "weapons.bombs.Transport M818 [16000lb]" -ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk1 = "weapons.bombs.British_MC_250LB_Bomb_Mk1" -ENUMS.Storage.weapons.nurs.SNEB_TYPE251_H1 = "weapons.nurs.SNEB_TYPE251_H1" -ENUMS.Storage.weapons.bombs.TYPE_200A = "weapons.bombs.TYPE-200A" -ENUMS.Storage.weapons.nurs.HYDRA_70_M151 = "weapons.nurs.HYDRA_70_M151" -ENUMS.Storage.weapons.bombs.IFV_BMP_3_32912lb = "weapons.bombs.IFV BMP-3 [32912lb]" -ENUMS.Storage.weapons.bombs.APC_MTLB_Air_26400lb = "weapons.bombs.APC MTLB Air [26400lb]" -ENUMS.Storage.weapons.nurs.HYDRA_70_M229 = "weapons.nurs.HYDRA_70_M229" -ENUMS.Storage.weapons.bombs.BDU_45 = "weapons.bombs.BDU_45" -ENUMS.Storage.weapons.bombs.OFAB_100_120TU = "weapons.bombs.OFAB-100-120TU" -ENUMS.Storage.weapons.missiles.AIM_9J = "weapons.missiles.AIM-9J" -ENUMS.Storage.weapons.nurs.ARF8M3API = "weapons.nurs.ARF8M3API" -ENUMS.Storage.weapons.bombs.BetAB_500ShP = "weapons.bombs.BetAB_500ShP" -ENUMS.Storage.weapons.nurs.C_8OFP2 = "weapons.nurs.C_8OFP2" -ENUMS.Storage.weapons.bombs.GBU_10 = "weapons.bombs.GBU_10" -ENUMS.Storage.weapons.bombs.APC_MTLB_Skid_26290lb = "weapons.bombs.APC MTLB Skid [26290lb]" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_RED = "weapons.nurs.SNEB_TYPE254_F1B_RED" -ENUMS.Storage.weapons.missiles.X_65 = "weapons.missiles.X_65" -ENUMS.Storage.weapons.missiles.R_550_M1 = "weapons.missiles.R_550_M1" -ENUMS.Storage.weapons.missiles.AGM_65K = "weapons.missiles.AGM_65K" -ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_YELLOW = "weapons.nurs.SNEB_TYPE254_F1B_YELLOW" -ENUMS.Storage.weapons.missiles.AGM_88 = "weapons.missiles.AGM_88" -ENUMS.Storage.weapons.nurs.C_8OM = "weapons.nurs.C_8OM" -ENUMS.Storage.weapons.bombs.SAM_ROLAND_LN_34720b = "weapons.bombs.SAM ROLAND LN [34720b]" -ENUMS.Storage.weapons.missiles.AIM_120 = "weapons.missiles.AIM_120" -ENUMS.Storage.weapons.missiles.HOT3_MBDA = "weapons.missiles.HOT3_MBDA" -ENUMS.Storage.weapons.missiles.R_13M = "weapons.missiles.R-13M" -ENUMS.Storage.weapons.missiles.AIM_54C_Mk60 = "weapons.missiles.AIM_54C_Mk60" -ENUMS.Storage.weapons.bombs.AAA_GEPARD_34720lb = "weapons.bombs.AAA GEPARD [34720lb]" -ENUMS.Storage.weapons.missiles.R_13M1 = "weapons.missiles.R-13M1" -ENUMS.Storage.weapons.bombs.APC_Cobra_Air_10912lb = "weapons.bombs.APC Cobra Air [10912lb]" -ENUMS.Storage.weapons.bombs.RBK_250 = "weapons.bombs.RBK_250" -ENUMS.Storage.weapons.bombs.SC_500_J = "weapons.bombs.SC_500_J" -ENUMS.Storage.weapons.missiles.AGM_114K = "weapons.missiles.AGM_114K" -ENUMS.Storage.weapons.missiles.ALARM = "weapons.missiles.ALARM" -ENUMS.Storage.weapons.bombs.Mk_83 = "weapons.bombs.Mk_83" -ENUMS.Storage.weapons.missiles.AGM_65B = "weapons.missiles.AGM_65B" -ENUMS.Storage.weapons.bombs.MK_82SNAKEYE = "weapons.bombs.MK_82SNAKEYE" -ENUMS.Storage.weapons.nurs.HYDRA_70_MK1 = "weapons.nurs.HYDRA_70_MK1" -ENUMS.Storage.weapons.bombs.BLG66_BELOUGA = "weapons.bombs.BLG66_BELOUGA" -ENUMS.Storage.weapons.containers.EclairM_51 = "weapons.containers.{EclairM_51}" -ENUMS.Storage.weapons.missiles.AIM_54A_Mk60 = "weapons.missiles.AIM_54A_Mk60" -ENUMS.Storage.weapons.droptanks.DFT_300_GAL_A4E = "weapons.droptanks.DFT_300_GAL_A4E" -ENUMS.Storage.weapons.bombs.ATGM_M1134_Stryker_30337lb = "weapons.bombs.ATGM M1134 Stryker [30337lb]" -ENUMS.Storage.weapons.bombs.BAT_120 = "weapons.bombs.BAT-120" -ENUMS.Storage.weapons.missiles.DWS39_MJ1_MJ2 = "weapons.missiles.DWS39_MJ1_MJ2" -ENUMS.Storage.weapons.containers.SPRD = "weapons.containers.SPRD" -ENUMS.Storage.weapons.bombs.BR_500 = "weapons.bombs.BR_500" -ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk1 = "weapons.bombs.British_GP_500LB_Bomb_Mk1" -ENUMS.Storage.weapons.bombs.BDU_50HD = "weapons.bombs.BDU_50HD" -ENUMS.Storage.weapons.missiles.RS2US = "weapons.missiles.RS2US" -ENUMS.Storage.weapons.bombs.IFV_BMP_2_25168lb = "weapons.bombs.IFV BMP-2 [25168lb]" -ENUMS.Storage.weapons.bombs.SAMP400HD = "weapons.bombs.SAMP400HD" -ENUMS.Storage.weapons.containers.Hercules_Battle_Station = "weapons.containers.Hercules_Battle_Station" -ENUMS.Storage.weapons.bombs.AN_M64 = "weapons.bombs.AN_M64" -ENUMS.Storage.weapons.containers.rearCargoSeats = "weapons.containers.rearCargoSeats" -ENUMS.Storage.weapons.bombs.Mk_82 = "weapons.bombs.Mk_82" -ENUMS.Storage.weapons.missiles.AKD_10 = "weapons.missiles.AKD-10" -ENUMS.Storage.weapons.bombs.BDU_50LGB = "weapons.bombs.BDU_50LGB" -ENUMS.Storage.weapons.missiles.SD_10 = "weapons.missiles.SD-10" -ENUMS.Storage.weapons.containers.IRDeflector = "weapons.containers.IRDeflector" -ENUMS.Storage.weapons.bombs.FAB_500 = "weapons.bombs.FAB_500" -ENUMS.Storage.weapons.bombs.KAB_500 = "weapons.bombs.KAB_500" -ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S-5M" -ENUMS.Storage.weapons.missiles.MICA_R = "weapons.missiles.MICA_R" -ENUMS.Storage.weapons.missiles.X_59M = "weapons.missiles.X_59M" -ENUMS.Storage.weapons.nurs.UG_90MM = "weapons.nurs.UG_90MM" -ENUMS.Storage.weapons.bombs.LYSBOMB = "weapons.bombs.LYSBOMB" -ENUMS.Storage.weapons.nurs.R4M = "weapons.nurs.R4M" -ENUMS.Storage.weapons.containers.dlpod_akg = "weapons.containers.dlpod_akg" -ENUMS.Storage.weapons.missiles.LD_10 = "weapons.missiles.LD-10" -ENUMS.Storage.weapons.bombs.SC_50 = "weapons.bombs.SC_50" -ENUMS.Storage.weapons.nurs.HYDRA_70_MK5 = "weapons.nurs.HYDRA_70_MK5" -ENUMS.Storage.weapons.bombs.FAB_100M = "weapons.bombs.FAB_100M" -ENUMS.Storage.weapons.missiles.Rb_24 = "weapons.missiles.Rb 24" -ENUMS.Storage.weapons.bombs.BDU_45B = "weapons.bombs.BDU_45B" -ENUMS.Storage.weapons.missiles.GB_6_HE = "weapons.missiles.GB-6-HE" -ENUMS.Storage.weapons.missiles.KD_63B = "weapons.missiles.KD-63B" -ENUMS.Storage.weapons.missiles.P_27PE = "weapons.missiles.P_27PE" -ENUMS.Storage.weapons.droptanks.PTB300_MIG15 = "weapons.droptanks.PTB300_MIG15" -ENUMS.Storage.weapons.bombs.Two50_3 = "weapons.bombs.250-3" -ENUMS.Storage.weapons.bombs.SC_500_L2 = "weapons.bombs.SC_500_L2" -ENUMS.Storage.weapons.containers.HMMWV_M1045 = "weapons.containers.HMMWV_M1045" -ENUMS.Storage.weapons.bombs.FAB_500M54TU = "weapons.bombs.FAB-500M54TU" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_YELLOW = "weapons.containers.{US_M10_SMOKE_TANK_YELLOW}" -ENUMS.Storage.weapons.containers.EclairM_60 = "weapons.containers.{EclairM_60}" -ENUMS.Storage.weapons.bombs.SAB_250_200 = "weapons.bombs.SAB_250_200" -ENUMS.Storage.weapons.bombs.FAB_100 = "weapons.bombs.FAB_100" -ENUMS.Storage.weapons.bombs.KAB_500S = "weapons.bombs.KAB_500S" -ENUMS.Storage.weapons.missiles.AGM_45A = "weapons.missiles.AGM_45A" -ENUMS.Storage.weapons.missiles.Kh25MP_PRGS1VP = "weapons.missiles.Kh25MP_PRGS1VP" -ENUMS.Storage.weapons.nurs.S5M1_HEFRAG_FFAR = "weapons.nurs.S5M1_HEFRAG_FFAR" -ENUMS.Storage.weapons.containers.kg600 = "weapons.containers.kg600" -ENUMS.Storage.weapons.bombs.AN_M65 = "weapons.bombs.AN_M65" -ENUMS.Storage.weapons.bombs.AN_M57 = "weapons.bombs.AN_M57" -ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP" -ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100" -ENUMS.Storage.weapons.containers.HEMTT = "weapons.containers.HEMTT" -ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk1_Short = "weapons.bombs.British_MC_500LB_Bomb_Mk1_Short" -ENUMS.Storage.weapons.nurs.ARAKM70BAP = "weapons.nurs.ARAKM70BAP" -ENUMS.Storage.weapons.missiles.AGM_119 = "weapons.missiles.AGM_119" -ENUMS.Storage.weapons.missiles.MMagicII = "weapons.missiles.MMagicII" -ENUMS.Storage.weapons.bombs.AB_500_1_SD_10A = "weapons.bombs.AB_500_1_SD_10A" -ENUMS.Storage.weapons.nurs.HYDRA_70_M282 = "weapons.nurs.HYDRA_70_M282" -ENUMS.Storage.weapons.droptanks.DFT_400_GAL_A4E = "weapons.droptanks.DFT_400_GAL_A4E" -ENUMS.Storage.weapons.nurs.HYDRA_70_M257 = "weapons.nurs.HYDRA_70_M257" -ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D = "weapons.droptanks.AV8BNA_AERO1D" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_BLUE = "weapons.containers.{US_M10_SMOKE_TANK_BLUE}" -ENUMS.Storage.weapons.nurs.ARF8M3HEI = "weapons.nurs.ARF8M3HEI" -ENUMS.Storage.weapons.bombs.RN_28 = "weapons.bombs.RN-28" -ENUMS.Storage.weapons.bombs.Squad_30_x_Soldier_7950lb = "weapons.bombs.Squad 30 x Soldier [7950lb]" -ENUMS.Storage.weapons.containers.uaz_469 = "weapons.containers.uaz-469" -ENUMS.Storage.weapons.containers.Otokar_Cobra = "weapons.containers.Otokar_Cobra" -ENUMS.Storage.weapons.bombs.APC_BTR_82A_Air_24998lb = "weapons.bombs.APC BTR-82A Air [24998lb]" -ENUMS.Storage.weapons.nurs.HYDRA_70_M274 = "weapons.nurs.HYDRA_70_M274" -ENUMS.Storage.weapons.missiles.P_24R = "weapons.missiles.P_24R" -ENUMS.Storage.weapons.nurs.HYDRA_70_MK61 = "weapons.nurs.HYDRA_70_MK61" -ENUMS.Storage.weapons.missiles.Igla_1E = "weapons.missiles.Igla_1E" -ENUMS.Storage.weapons.missiles.C_802AK = "weapons.missiles.C-802AK" -ENUMS.Storage.weapons.nurs.C_24 = "weapons.nurs.C_24" -ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541 = "weapons.droptanks.M2KC_08_RPL541" -ENUMS.Storage.weapons.nurs.C_13 = "weapons.nurs.C_13" -ENUMS.Storage.weapons.droptanks.droptank_110_gal = "weapons.droptanks.droptank_110_gal" -ENUMS.Storage.weapons.bombs.Mk_84 = "weapons.bombs.Mk_84" -ENUMS.Storage.weapons.missiles.Sea_Eagle = "weapons.missiles.Sea_Eagle" -ENUMS.Storage.weapons.droptanks.PTB_1200_F1 = "weapons.droptanks.PTB_1200_F1" -ENUMS.Storage.weapons.nurs.SNEB_TYPE256_H1 = "weapons.nurs.SNEB_TYPE256_H1" -ENUMS.Storage.weapons.containers.MATRA_PHIMAT = "weapons.containers.MATRA-PHIMAT" -ENUMS.Storage.weapons.containers.smoke_pod = "weapons.containers.smoke_pod" -ENUMS.Storage.weapons.containers.F_15E_AAQ_14_LANTIRN = "weapons.containers.F-15E_AAQ-14_LANTIRN" -ENUMS.Storage.weapons.containers.EclairM_24 = "weapons.containers.{EclairM_24}" -ENUMS.Storage.weapons.bombs.GBU_16 = "weapons.bombs.GBU_16" -ENUMS.Storage.weapons.nurs.HYDRA_70_M156 = "weapons.nurs.HYDRA_70_M156" -ENUMS.Storage.weapons.missiles.R_60 = "weapons.missiles.R-60" -ENUMS.Storage.weapons.containers.zsu_23_4 = "weapons.containers.zsu-23-4" -ENUMS.Storage.weapons.missiles.RB75 = "weapons.missiles.RB75" -ENUMS.Storage.weapons.missiles.Mistral = "weapons.missiles.Mistral" -ENUMS.Storage.weapons.droptanks.MB339_TT500_L = "weapons.droptanks.MB339_TT500_L" -ENUMS.Storage.weapons.bombs.SAM_SA_13_STRELA_21624lb = "weapons.bombs.SAM SA-13 STRELA [21624lb]" -ENUMS.Storage.weapons.bombs.SAM_Avenger_M1097_Air_7200lb = "weapons.bombs.SAM Avenger M1097 Air [7200lb]" -ENUMS.Storage.weapons.droptanks.Eleven00L_Tank_Empty = "weapons.droptanks.1100L Tank Empty" -ENUMS.Storage.weapons.bombs.AN_M88 = "weapons.bombs.AN-M88" -ENUMS.Storage.weapons.missiles.S_25L = "weapons.missiles.S_25L" -ENUMS.Storage.weapons.nurs.British_AP_25LBNo1_3INCHNo1 = "weapons.nurs.British_AP_25LBNo1_3INCHNo1" +ENUMS.Storage.weapons.nurs.SNEB_TYPE253_F1B = "weapons.nurs.SNEB_TYPE253_F1B" +ENUMS.Storage.weapons.missiles.P_24T = "weapons.missiles.P_24T" +ENUMS.Storage.weapons.bombs.BLU_3B_OLD = "weapons.bombs.BLU-3B_OLD" +ENUMS.Storage.weapons.missiles.AGM_154 = "weapons.missiles.AGM_154" +ENUMS.Storage.weapons.nurs.HYDRA_70_M151_M433 = "weapons.nurs.HYDRA_70_M151_M433" +ENUMS.Storage.weapons.bombs.SAM_Avenger_M1097_Skid_7090lb = "weapons.bombs.SAM Avenger M1097 Skid [7090lb]" +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk5 = "weapons.bombs.British_GP_250LB_Bomb_Mk5" +ENUMS.Storage.weapons.containers.OV10_SMOKE = "weapons.containers.{OV10_SMOKE}" +ENUMS.Storage.weapons.bombs.BLU_4B_OLD = "weapons.bombs.BLU-4B_OLD" +ENUMS.Storage.weapons.bombs.FAB_500M54 = "weapons.bombs.FAB-500M54" +ENUMS.Storage.weapons.bombs.GBU_38 = "weapons.bombs.GBU_38" +ENUMS.Storage.weapons.containers.F_15E_AXQ_14_DATALINK = "weapons.containers.F-15E_AXQ-14_DATALINK" +ENUMS.Storage.weapons.bombs.BEER_BOMB = "weapons.bombs.BEER_BOMB" +ENUMS.Storage.weapons.bombs.P_50T = "weapons.bombs.P-50T" +ENUMS.Storage.weapons.nurs.C_8CM_GN = "weapons.nurs.C_8CM_GN" +ENUMS.Storage.weapons.bombs.FAB_500SL = "weapons.bombs.FAB-500SL" +ENUMS.Storage.weapons.bombs.KAB_1500Kr = "weapons.bombs.KAB_1500Kr" +ENUMS.Storage.weapons.bombs.two50_2 = "weapons.bombs.250-2" +ENUMS.Storage.weapons.droptanks.Spitfire_tank_1 = "weapons.droptanks.Spitfire_tank_1" +ENUMS.Storage.weapons.missiles.AGM_65G = "weapons.missiles.AGM_65G" +ENUMS.Storage.weapons.missiles.AGM_65A = "weapons.missiles.AGM_65A" +ENUMS.Storage.weapons.containers.Hercules_JATO = "weapons.containers.Hercules_JATO" +ENUMS.Storage.weapons.nurs.HYDRA_70_M259 = "weapons.nurs.HYDRA_70_M259" +ENUMS.Storage.weapons.missiles.AGM_84E = "weapons.missiles.AGM_84E" +ENUMS.Storage.weapons.bombs.AN_M30A1 = "weapons.bombs.AN_M30A1" +ENUMS.Storage.weapons.nurs.C_25 = "weapons.nurs.C_25" +ENUMS.Storage.weapons.containers.AV8BNA_ALQ164 = "weapons.containers.AV8BNA_ALQ164" +ENUMS.Storage.weapons.containers.lav_25 = "weapons.containers.lav-25" +ENUMS.Storage.weapons.missiles.P_60 = "weapons.missiles.P_60" +ENUMS.Storage.weapons.bombs.FAB_1500 = "weapons.bombs.FAB_1500" +ENUMS.Storage.weapons.droptanks.FuelTank_350L = "weapons.droptanks.FuelTank_350L" +ENUMS.Storage.weapons.bombs.AAA_Vulcan_M163_Skid_21577lb = "weapons.bombs.AAA Vulcan M163 Skid [21577lb]" +ENUMS.Storage.weapons.missiles.Kormoran = "weapons.missiles.Kormoran" +ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY = "weapons.droptanks.HB_F14_EXT_DROPTANK_EMPTY" +ENUMS.Storage.weapons.droptanks.FuelTank_150L = "weapons.droptanks.FuelTank_150L" +ENUMS.Storage.weapons.missiles.Rb_15F_for_A_I = "weapons.missiles.Rb 15F (for A.I.)" +ENUMS.Storage.weapons.missiles.RB75T = "weapons.missiles.RB75T" +ENUMS.Storage.weapons.missiles.Vikhr_M = "weapons.missiles.Vikhr_M" +ENUMS.Storage.weapons.nurs.FFAR_M156_WP = "weapons.nurs.FFAR M156 WP" +ENUMS.Storage.weapons.nurs.British_HE_60LBSAPNo2_3INCHNo1 = "weapons.nurs.British_HE_60LBSAPNo2_3INCHNo1" +ENUMS.Storage.weapons.missiles.DWS39_MJ2 = "weapons.missiles.DWS39_MJ2" +ENUMS.Storage.weapons.bombs.HEBOMBD = "weapons.bombs.HEBOMBD" +ENUMS.Storage.weapons.missiles.CATM_9M = "weapons.missiles.CATM_9M" +ENUMS.Storage.weapons.bombs.Mk_81 = "weapons.bombs.Mk_81" +ENUMS.Storage.weapons.droptanks.Drop_Tank_300_Liter = "weapons.droptanks.Drop_Tank_300_Liter" +ENUMS.Storage.weapons.containers.HMMWV_M1025 = "weapons.containers.HMMWV_M1025" +ENUMS.Storage.weapons.bombs.SAM_CHAPARRAL_Air_21624lb = "weapons.bombs.SAM CHAPARRAL Air [21624lb]" +ENUMS.Storage.weapons.missiles.AGM_154A = "weapons.missiles.AGM_154A" +ENUMS.Storage.weapons.bombs.Mk_84AIR_TP = "weapons.bombs.Mk_84AIR_TP" +ENUMS.Storage.weapons.bombs.GBU_31_V_3B = "weapons.bombs.GBU_31_V_3B" +ENUMS.Storage.weapons.nurs.C_8CM_WH = "weapons.nurs.C_8CM_WH" +ENUMS.Storage.weapons.missiles.Matra_Super_530D = "weapons.missiles.Matra Super 530D" +ENUMS.Storage.weapons.nurs.ARF8M3TPSM = "weapons.nurs.ARF8M3TPSM" +ENUMS.Storage.weapons.missiles.TGM_65H = "weapons.missiles.TGM_65H" +ENUMS.Storage.weapons.nurs.M8rocket = "weapons.nurs.M8rocket" +ENUMS.Storage.weapons.bombs.GBU_27 = "weapons.bombs.GBU_27" +ENUMS.Storage.weapons.missiles.AGR_20A = "weapons.missiles.AGR_20A" +ENUMS.Storage.weapons.missiles.LS_6_250 = "weapons.missiles.LS-6-250" +ENUMS.Storage.weapons.droptanks.M2KC_RPL_522_EMPTY = "weapons.droptanks.M2KC_RPL_522_EMPTY" +ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541 = "weapons.droptanks.M2KC_02_RPL541" +ENUMS.Storage.weapons.missiles.AGM_45 = "weapons.missiles.AGM_45" +ENUMS.Storage.weapons.missiles.AGM_84A = "weapons.missiles.AGM_84A" +ENUMS.Storage.weapons.bombs.APC_BTR_80_Air_23936lb = "weapons.bombs.APC BTR-80 Air [23936lb]" +ENUMS.Storage.weapons.missiles.P_33E = "weapons.missiles.P_33E" +ENUMS.Storage.weapons.missiles.Ataka_9M120 = "weapons.missiles.Ataka_9M120" +ENUMS.Storage.weapons.bombs.MK76 = "weapons.bombs.MK76" +ENUMS.Storage.weapons.bombs.AB_250_2_SD_2 = "weapons.bombs.AB_250_2_SD_2" +ENUMS.Storage.weapons.missiles.Rb_05A = "weapons.missiles.Rb 05A" +ENUMS.Storage.weapons.bombs.ART_GVOZDIKA_34720lb = "weapons.bombs.ART GVOZDIKA [34720lb]" +ENUMS.Storage.weapons.bombs.Generic_Crate_20000lb = "weapons.bombs.Generic Crate [20000lb]" +ENUMS.Storage.weapons.bombs.FAB_100SV = "weapons.bombs.FAB_100SV" +ENUMS.Storage.weapons.bombs.BetAB_500 = "weapons.bombs.BetAB_500" +ENUMS.Storage.weapons.droptanks.M2KC_02_RPL541_EMPTY = "weapons.droptanks.M2KC_02_RPL541_EMPTY" +ENUMS.Storage.weapons.droptanks.PTB600_MIG15 = "weapons.droptanks.PTB600_MIG15" +ENUMS.Storage.weapons.missiles.Rb_24J = "weapons.missiles.Rb 24J" +ENUMS.Storage.weapons.nurs.C_8CM_BU = "weapons.nurs.C_8CM_BU" +ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_F1B = "weapons.nurs.SNEB_TYPE259E_F1B" +ENUMS.Storage.weapons.nurs.WGr21 = "weapons.nurs.WGr21" +ENUMS.Storage.weapons.bombs.SAMP250HD = "weapons.bombs.SAMP250HD" +ENUMS.Storage.weapons.containers.alq_184long = "weapons.containers.alq-184long" +ENUMS.Storage.weapons.nurs.SNEB_TYPE259E_H1 = "weapons.nurs.SNEB_TYPE259E_H1" +ENUMS.Storage.weapons.bombs.British_SAP_250LB_Bomb_Mk5 = "weapons.bombs.British_SAP_250LB_Bomb_Mk5" +ENUMS.Storage.weapons.bombs.Transport_UAZ_469_Air_3747lb = "weapons.bombs.Transport UAZ-469 Air [3747lb]" +ENUMS.Storage.weapons.bombs.Mk_83CT = "weapons.bombs.Mk_83CT" +ENUMS.Storage.weapons.missiles.AIM_7P = "weapons.missiles.AIM-7P" +ENUMS.Storage.weapons.missiles.AT_6 = "weapons.missiles.AT_6" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_GREEN = "weapons.nurs.SNEB_TYPE254_H1_GREEN" +ENUMS.Storage.weapons.nurs.SNEB_TYPE250_F1B = "weapons.nurs.SNEB_TYPE250_F1B" +ENUMS.Storage.weapons.containers.U22A = "weapons.containers.U22A" +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk1 = "weapons.bombs.British_GP_250LB_Bomb_Mk1" +ENUMS.Storage.weapons.bombs.CBU_105 = "weapons.bombs.CBU_105" +ENUMS.Storage.weapons.droptanks.FW_190_Fuel_Tank = "weapons.droptanks.FW-190_Fuel-Tank" +ENUMS.Storage.weapons.missiles.X_58 = "weapons.missiles.X_58" +ENUMS.Storage.weapons.missiles.BK90_MJ1_MJ2 = "weapons.missiles.BK90_MJ1_MJ2" +ENUMS.Storage.weapons.missiles.TGM_65D = "weapons.missiles.TGM_65D" +ENUMS.Storage.weapons.containers.BRD_4_250 = "weapons.containers.BRD-4-250" +ENUMS.Storage.weapons.missiles.P_73 = "weapons.missiles.P_73" +ENUMS.Storage.weapons.bombs.AN_M66 = "weapons.bombs.AN_M66" +ENUMS.Storage.weapons.bombs.APC_LAV_25_Air_22520lb = "weapons.bombs.APC LAV-25 Air [22520lb]" +ENUMS.Storage.weapons.missiles.AIM_7MH = "weapons.missiles.AIM-7MH" +ENUMS.Storage.weapons.containers.MB339_TravelPod = "weapons.containers.MB339_TravelPod" +ENUMS.Storage.weapons.bombs.GBU_12 = "weapons.bombs.GBU_12" +ENUMS.Storage.weapons.bombs.SC_250_T3_J = "weapons.bombs.SC_250_T3_J" +ENUMS.Storage.weapons.missiles.KD_20 = "weapons.missiles.KD-20" +ENUMS.Storage.weapons.missiles.AGM_86C = "weapons.missiles.AGM_86C" +ENUMS.Storage.weapons.missiles.X_35 = "weapons.missiles.X_35" +ENUMS.Storage.weapons.bombs.MK106 = "weapons.bombs.MK106" +ENUMS.Storage.weapons.bombs.BETAB_500S = "weapons.bombs.BETAB-500S" +ENUMS.Storage.weapons.nurs.C_5 = "weapons.nurs.C_5" +ENUMS.Storage.weapons.nurs.S_24B = "weapons.nurs.S-24B" +ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk2 = "weapons.bombs.British_MC_500LB_Bomb_Mk2" +ENUMS.Storage.weapons.containers.ANAWW_13 = "weapons.containers.ANAWW_13" +ENUMS.Storage.weapons.droptanks.droptank_108_gal = "weapons.droptanks.droptank_108_gal" +ENUMS.Storage.weapons.droptanks.DFT_300_GAL_A4E_LR = "weapons.droptanks.DFT_300_GAL_A4E_LR" +ENUMS.Storage.weapons.bombs.CBU_87 = "weapons.bombs.CBU_87" +ENUMS.Storage.weapons.missiles.GAR_8 = "weapons.missiles.GAR-8" +ENUMS.Storage.weapons.bombs.BELOUGA = "weapons.bombs.BELOUGA" +ENUMS.Storage.weapons.containers.EclairM_33 = "weapons.containers.{EclairM_33}" +ENUMS.Storage.weapons.bombs.ART_2S9_NONA_Air_19140lb = "weapons.bombs.ART 2S9 NONA Air [19140lb]" +ENUMS.Storage.weapons.bombs.BR_250 = "weapons.bombs.BR_250" +ENUMS.Storage.weapons.bombs.IAB_500 = "weapons.bombs.IAB-500" +ENUMS.Storage.weapons.containers.AN_ASQ_228 = "weapons.containers.AN_ASQ_228" +ENUMS.Storage.weapons.missiles.P_27P = "weapons.missiles.P_27P" +ENUMS.Storage.weapons.bombs.SD_250_Stg = "weapons.bombs.SD_250_Stg" +ENUMS.Storage.weapons.missiles.R_530F_IR = "weapons.missiles.R_530F_IR" +ENUMS.Storage.weapons.bombs.British_SAP_500LB_Bomb_Mk5 = "weapons.bombs.British_SAP_500LB_Bomb_Mk5" +ENUMS.Storage.weapons.bombs.FAB_250M54 = "weapons.bombs.FAB-250M54" +ENUMS.Storage.weapons.containers.M2KC_AAF = "weapons.containers.{M2KC_AAF}" +ENUMS.Storage.weapons.missiles.CM_802AKG_AI = "weapons.missiles.CM-802AKG_AI" +ENUMS.Storage.weapons.bombs.CBU_103 = "weapons.bombs.CBU_103" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_RED = "weapons.containers.{US_M10_SMOKE_TANK_RED}" +ENUMS.Storage.weapons.missiles.X_29T = "weapons.missiles.X_29T" +ENUMS.Storage.weapons.bombs.HEMTT_TFFT_34400lb = "weapons.bombs.HEMTT TFFT [34400lb]" +ENUMS.Storage.weapons.missiles.C_701IR = "weapons.missiles.C-701IR" +ENUMS.Storage.weapons.containers.fullCargoSeats = "weapons.containers.fullCargoSeats" +ENUMS.Storage.weapons.bombs.GBU_15_V_31_B = "weapons.bombs.GBU_15_V_31_B" +ENUMS.Storage.weapons.bombs.APC_M1043_HMMWV_Armament_Air_7023lb = "weapons.bombs.APC M1043 HMMWV Armament Air [7023lb]" +ENUMS.Storage.weapons.missiles.PL_5EII = "weapons.missiles.PL-5EII" +ENUMS.Storage.weapons.bombs.SC_250_T1_L2 = "weapons.bombs.SC_250_T1_L2" +ENUMS.Storage.weapons.torpedoes.mk46torp_name = "weapons.torpedoes.mk46torp_name" +ENUMS.Storage.weapons.containers.F_15E_AAQ_33_XR_ATP_SE = "weapons.containers.F-15E_AAQ-33_XR_ATP-SE" +ENUMS.Storage.weapons.missiles.AIM_7 = "weapons.missiles.AIM_7" +ENUMS.Storage.weapons.missiles.AGM_122 = "weapons.missiles.AGM_122" +ENUMS.Storage.weapons.bombs.HEBOMB = "weapons.bombs.HEBOMB" +ENUMS.Storage.weapons.bombs.CBU_97 = "weapons.bombs.CBU_97" +ENUMS.Storage.weapons.bombs.MK_81SE = "weapons.bombs.MK-81SE" +ENUMS.Storage.weapons.nurs.Zuni_127 = "weapons.nurs.Zuni_127" +ENUMS.Storage.weapons.containers.M2KC_AGF = "weapons.containers.{M2KC_AGF}" +ENUMS.Storage.weapons.droptanks.Hercules_ExtFuelTank = "weapons.droptanks.Hercules_ExtFuelTank" +ENUMS.Storage.weapons.containers.SMOKE_WHITE = "weapons.containers.{SMOKE_WHITE}" +ENUMS.Storage.weapons.droptanks.droptank_150_gal = "weapons.droptanks.droptank_150_gal" +ENUMS.Storage.weapons.nurs.HYDRA_70_WTU1B = "weapons.nurs.HYDRA_70_WTU1B" +ENUMS.Storage.weapons.missiles.GB_6_SFW = "weapons.missiles.GB-6-SFW" +ENUMS.Storage.weapons.missiles.KD_63 = "weapons.missiles.KD-63" +ENUMS.Storage.weapons.bombs.GBU_28 = "weapons.bombs.GBU_28" +ENUMS.Storage.weapons.nurs.C_8CM_YE = "weapons.nurs.C_8CM_YE" +ENUMS.Storage.weapons.droptanks.HB_F14_EXT_DROPTANK = "weapons.droptanks.HB_F14_EXT_DROPTANK" +ENUMS.Storage.weapons.missiles.Super_530F = "weapons.missiles.Super_530F" +ENUMS.Storage.weapons.missiles.Ataka_9M220 = "weapons.missiles.Ataka_9M220" +ENUMS.Storage.weapons.bombs.BDU_33 = "weapons.bombs.BDU_33" +ENUMS.Storage.weapons.bombs.British_GP_250LB_Bomb_Mk4 = "weapons.bombs.British_GP_250LB_Bomb_Mk4" +ENUMS.Storage.weapons.missiles.TOW = "weapons.missiles.TOW" +ENUMS.Storage.weapons.bombs.ATGM_M1045_HMMWV_TOW_Air_7183lb = "weapons.bombs.ATGM M1045 HMMWV TOW Air [7183lb]" +ENUMS.Storage.weapons.missiles.X_25MR = "weapons.missiles.X_25MR" +ENUMS.Storage.weapons.droptanks.fueltank230 = "weapons.droptanks.fueltank230" +ENUMS.Storage.weapons.droptanks.PTB_490C_MIG21 = "weapons.droptanks.PTB-490C-MIG21" +ENUMS.Storage.weapons.bombs.M1025_HMMWV_Air_6160lb = "weapons.bombs.M1025 HMMWV Air [6160lb]" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_GREEN = "weapons.nurs.SNEB_TYPE254_F1B_GREEN" +ENUMS.Storage.weapons.missiles.R_550 = "weapons.missiles.R_550" +ENUMS.Storage.weapons.bombs.KAB_1500LG = "weapons.bombs.KAB_1500LG" +ENUMS.Storage.weapons.missiles.AGM_84D = "weapons.missiles.AGM_84D" +ENUMS.Storage.weapons.missiles.YJ_83K = "weapons.missiles.YJ-83K" +ENUMS.Storage.weapons.missiles.AIM_54C_Mk47 = "weapons.missiles.AIM_54C_Mk47" +ENUMS.Storage.weapons.missiles.BRM_1_90MM = "weapons.missiles.BRM-1_90MM" +ENUMS.Storage.weapons.missiles.Ataka_9M120F = "weapons.missiles.Ataka_9M120F" +ENUMS.Storage.weapons.droptanks.Eleven00L_Tank = "weapons.droptanks.1100L Tank" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP_100" +ENUMS.Storage.weapons.adapters.lau_88 = "weapons.adapters.lau-88" +ENUMS.Storage.weapons.missiles.P_40T = "weapons.missiles.P_40T" +ENUMS.Storage.weapons.missiles.GB_6 = "weapons.missiles.GB-6" +ENUMS.Storage.weapons.bombs.FAB_250M54TU = "weapons.bombs.FAB-250M54TU" +ENUMS.Storage.weapons.missiles.DWS39_MJ1 = "weapons.missiles.DWS39_MJ1" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.bombs.FAB_250 = "weapons.bombs.FAB_250" +ENUMS.Storage.weapons.missiles.C_802AK = "weapons.missiles.C_802AK" +ENUMS.Storage.weapons.bombs.SD_500_A = "weapons.bombs.SD_500_A" +ENUMS.Storage.weapons.bombs.GBU_32_V_2B = "weapons.bombs.GBU_32_V_2B" +ENUMS.Storage.weapons.containers.marder = "weapons.containers.marder" +ENUMS.Storage.weapons.missiles.ADM_141B = "weapons.missiles.ADM_141B" +ENUMS.Storage.weapons.bombs.ROCKEYE = "weapons.bombs.ROCKEYE" +ENUMS.Storage.weapons.missiles.BK90_MJ1 = "weapons.missiles.BK90_MJ1" +ENUMS.Storage.weapons.containers.BTR_80 = "weapons.containers.BTR-80" +ENUMS.Storage.weapons.bombs.SAM_ROLAND_ADS_34720lb = "weapons.bombs.SAM ROLAND ADS [34720lb]" +ENUMS.Storage.weapons.containers.wmd7 = "weapons.containers.wmd7" +ENUMS.Storage.weapons.missiles.C_701T = "weapons.missiles.C-701T" +ENUMS.Storage.weapons.missiles.AIM_7E_2 = "weapons.missiles.AIM-7E-2" +ENUMS.Storage.weapons.nurs.HVAR = "weapons.nurs.HVAR" +ENUMS.Storage.weapons.containers.HMMWV_M1043 = "weapons.containers.HMMWV_M1043" +ENUMS.Storage.weapons.droptanks.PTB_800_MIG21 = "weapons.droptanks.PTB-800-MIG21" +ENUMS.Storage.weapons.missiles.AGM_114 = "weapons.missiles.AGM_114" +ENUMS.Storage.weapons.bombs.APC_M1126_Stryker_ICV_29542lb = "weapons.bombs.APC M1126 Stryker ICV [29542lb]" +ENUMS.Storage.weapons.bombs.APC_M113_Air_21624lb = "weapons.bombs.APC M113 Air [21624lb]" +ENUMS.Storage.weapons.bombs.M_117 = "weapons.bombs.M_117" +ENUMS.Storage.weapons.missiles.AGM_65D = "weapons.missiles.AGM_65D" +ENUMS.Storage.weapons.droptanks.MB339_TT320_L = "weapons.droptanks.MB339_TT320_L" +ENUMS.Storage.weapons.missiles.AGM_86 = "weapons.missiles.AGM_86" +ENUMS.Storage.weapons.bombs.BDU_45LGB = "weapons.bombs.BDU_45LGB" +ENUMS.Storage.weapons.missiles.AGM_65H = "weapons.missiles.AGM_65H" +ENUMS.Storage.weapons.nurs.RS_82 = "weapons.nurs.RS-82" +ENUMS.Storage.weapons.nurs.SNEB_TYPE252_F1B = "weapons.nurs.SNEB_TYPE252_F1B" +ENUMS.Storage.weapons.bombs.BL_755 = "weapons.bombs.BL_755" +ENUMS.Storage.weapons.containers.F_15E_AAQ_28_LITENING = "weapons.containers.F-15E_AAQ-28_LITENING" +ENUMS.Storage.weapons.nurs.SNEB_TYPE256_F1B = "weapons.nurs.SNEB_TYPE256_F1B" +ENUMS.Storage.weapons.missiles.AGM_84H = "weapons.missiles.AGM_84H" +ENUMS.Storage.weapons.missiles.AIM_54 = "weapons.missiles.AIM_54" +ENUMS.Storage.weapons.missiles.X_31A = "weapons.missiles.X_31A" +ENUMS.Storage.weapons.bombs.KAB_500Kr = "weapons.bombs.KAB_500Kr" +ENUMS.Storage.weapons.containers.SPS_141_100 = "weapons.containers.SPS-141-100" +ENUMS.Storage.weapons.missiles.BK90_MJ2 = "weapons.missiles.BK90_MJ2" +ENUMS.Storage.weapons.missiles.Super_530D = "weapons.missiles.Super_530D" +ENUMS.Storage.weapons.bombs.CBU_52B = "weapons.bombs.CBU_52B" +ENUMS.Storage.weapons.droptanks.PTB_450 = "weapons.droptanks.PTB-450" +ENUMS.Storage.weapons.bombs.IFV_MCV_80_34720lb = "weapons.bombs.IFV MCV-80 [34720lb]" +ENUMS.Storage.weapons.containers.Two_c9 = "weapons.containers.2-c9" +ENUMS.Storage.weapons.missiles.AIM_9JULI = "weapons.missiles.AIM-9JULI" +ENUMS.Storage.weapons.droptanks.MB339_TT500_R = "weapons.droptanks.MB339_TT500_R" +ENUMS.Storage.weapons.nurs.C_8CM = "weapons.nurs.C_8CM" +ENUMS.Storage.weapons.containers.BARAX = "weapons.containers.BARAX" +ENUMS.Storage.weapons.missiles.P_40R = "weapons.missiles.P_40R" +ENUMS.Storage.weapons.missiles.YJ_12 = "weapons.missiles.YJ-12" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_YELLOW = "weapons.nurs.SNEB_TYPE254_H1_YELLOW" +ENUMS.Storage.weapons.bombs.Durandal = "weapons.bombs.Durandal" +ENUMS.Storage.weapons.droptanks.i16_eft = "weapons.droptanks.i16_eft" +ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D_EMPTY = "weapons.droptanks.AV8BNA_AERO1D_EMPTY" +ENUMS.Storage.weapons.containers.Hercules_Battle_Station_TGP = "weapons.containers.Hercules_Battle_Station_TGP" +ENUMS.Storage.weapons.nurs.C_8CM_VT = "weapons.nurs.C_8CM_VT" +ENUMS.Storage.weapons.missiles.PL_12 = "weapons.missiles.PL-12" +ENUMS.Storage.weapons.missiles.R_3R = "weapons.missiles.R-3R" +ENUMS.Storage.weapons.bombs.GBU_54_V_1B = "weapons.bombs.GBU_54_V_1B" +ENUMS.Storage.weapons.droptanks.MB339_TT320_R = "weapons.droptanks.MB339_TT320_R" +ENUMS.Storage.weapons.bombs.RN_24 = "weapons.bombs.RN-24" +ENUMS.Storage.weapons.containers.Twoc6m = "weapons.containers.2c6m" +ENUMS.Storage.weapons.bombs.ARV_BRDM_2_Air_12320lb = "weapons.bombs.ARV BRDM-2 Air [12320lb]" +ENUMS.Storage.weapons.bombs.ARV_BRDM_2_Skid_12210lb = "weapons.bombs.ARV BRDM-2 Skid [12210lb]" +ENUMS.Storage.weapons.nurs.SNEB_TYPE251_F1B = "weapons.nurs.SNEB_TYPE251_F1B" +ENUMS.Storage.weapons.missiles.X_41 = "weapons.missiles.X_41" +ENUMS.Storage.weapons.containers.MIG21_SMOKE_WHITE = "weapons.containers.{MIG21_SMOKE_WHITE}" +ENUMS.Storage.weapons.bombs.MK_82AIR = "weapons.bombs.MK_82AIR" +ENUMS.Storage.weapons.missiles.R_530F_EM = "weapons.missiles.R_530F_EM" +ENUMS.Storage.weapons.bombs.SAMP400LD = "weapons.bombs.SAMP400LD" +ENUMS.Storage.weapons.bombs.FAB_50 = "weapons.bombs.FAB_50" +ENUMS.Storage.weapons.bombs.AB_250_2_SD_10A = "weapons.bombs.AB_250_2_SD_10A" +ENUMS.Storage.weapons.missiles.ADM_141A = "weapons.missiles.ADM_141A" +ENUMS.Storage.weapons.containers.KBpod = "weapons.containers.KBpod" +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4 = "weapons.bombs.British_GP_500LB_Bomb_Mk4" +ENUMS.Storage.weapons.missiles.AGM_65E = "weapons.missiles.AGM_65E" +ENUMS.Storage.weapons.containers.sa342_dipole_antenna = "weapons.containers.sa342_dipole_antenna" +ENUMS.Storage.weapons.bombs.OFAB_100_Jupiter = "weapons.bombs.OFAB-100 Jupiter" +ENUMS.Storage.weapons.nurs.SNEB_TYPE257_F1B = "weapons.nurs.SNEB_TYPE257_F1B" +ENUMS.Storage.weapons.missiles.Rb_04E_for_A_I = "weapons.missiles.Rb 04E (for A.I.)" +ENUMS.Storage.weapons.bombs.AN_M66A2 = "weapons.bombs.AN-M66A2" +ENUMS.Storage.weapons.missiles.P_27T = "weapons.missiles.P_27T" +ENUMS.Storage.weapons.droptanks.LNS_VIG_XTANK = "weapons.droptanks.LNS_VIG_XTANK" +ENUMS.Storage.weapons.missiles.R_55 = "weapons.missiles.R-55" +ENUMS.Storage.weapons.torpedoes.YU_6 = "weapons.torpedoes.YU-6" +ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk2 = "weapons.bombs.British_MC_250LB_Bomb_Mk2" +ENUMS.Storage.weapons.droptanks.PTB_120_F86F35 = "weapons.droptanks.PTB_120_F86F35" +ENUMS.Storage.weapons.missiles.PL_8B = "weapons.missiles.PL-8B" +ENUMS.Storage.weapons.droptanks.F_15E_Drop_Tank_Empty = "weapons.droptanks.F-15E_Drop_Tank_Empty" +ENUMS.Storage.weapons.nurs.British_HE_60LBFNo1_3INCHNo1 = "weapons.nurs.British_HE_60LBFNo1_3INCHNo1" +ENUMS.Storage.weapons.missiles.P_77 = "weapons.missiles.P_77" +ENUMS.Storage.weapons.torpedoes.LTF_5B = "weapons.torpedoes.LTF_5B" +ENUMS.Storage.weapons.missiles.R_3S = "weapons.missiles.R-3S" +ENUMS.Storage.weapons.nurs.SNEB_TYPE253_H1 = "weapons.nurs.SNEB_TYPE253_H1" +ENUMS.Storage.weapons.missiles.PL_8A = "weapons.missiles.PL-8A" +ENUMS.Storage.weapons.bombs.APC_BTR_82A_Skid_24888lb = "weapons.bombs.APC BTR-82A Skid [24888lb]" +ENUMS.Storage.weapons.containers.Sborka = "weapons.containers.Sborka" +ENUMS.Storage.weapons.missiles.AGM_65L = "weapons.missiles.AGM_65L" +ENUMS.Storage.weapons.missiles.X_28 = "weapons.missiles.X_28" +ENUMS.Storage.weapons.missiles.TGM_65G = "weapons.missiles.TGM_65G" +ENUMS.Storage.weapons.nurs.SNEB_TYPE257_H1 = "weapons.nurs.SNEB_TYPE257_H1" +ENUMS.Storage.weapons.missiles.RB75B = "weapons.missiles.RB75B" +ENUMS.Storage.weapons.missiles.X_25ML = "weapons.missiles.X_25ML" +ENUMS.Storage.weapons.droptanks.FPU_8A = "weapons.droptanks.FPU_8A" +ENUMS.Storage.weapons.bombs.BLG66 = "weapons.bombs.BLG66" +ENUMS.Storage.weapons.nurs.C_8CM_RD = "weapons.nurs.C_8CM_RD" +ENUMS.Storage.weapons.containers.EclairM_06 = "weapons.containers.{EclairM_06}" +ENUMS.Storage.weapons.bombs.RBK_500AO = "weapons.bombs.RBK_500AO" +ENUMS.Storage.weapons.missiles.AIM_9P = "weapons.missiles.AIM-9P" +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk4_Short = "weapons.bombs.British_GP_500LB_Bomb_Mk4_Short" +ENUMS.Storage.weapons.containers.MB339_Vinten = "weapons.containers.MB339_Vinten" +ENUMS.Storage.weapons.missiles.Rb_15F = "weapons.missiles.Rb 15F" +ENUMS.Storage.weapons.nurs.ARAKM70BHE = "weapons.nurs.ARAKM70BHE" +ENUMS.Storage.weapons.bombs.AAA_Vulcan_M163_Air_21666lb = "weapons.bombs.AAA Vulcan M163 Air [21666lb]" +ENUMS.Storage.weapons.missiles.X_29L = "weapons.missiles.X_29L" +ENUMS.Storage.weapons.containers.F14_LANTIRN_TP = "weapons.containers.{F14-LANTIRN-TP}" +ENUMS.Storage.weapons.bombs.FAB_250_M62 = "weapons.bombs.FAB-250-M62" +ENUMS.Storage.weapons.missiles.AIM_120C = "weapons.missiles.AIM_120C" +ENUMS.Storage.weapons.bombs.EWR_SBORKA_Air_21624lb = "weapons.bombs.EWR SBORKA Air [21624lb]" +ENUMS.Storage.weapons.bombs.SAMP250LD = "weapons.bombs.SAMP250LD" +ENUMS.Storage.weapons.droptanks.Spitfire_slipper_tank = "weapons.droptanks.Spitfire_slipper_tank" +ENUMS.Storage.weapons.missiles.LS_6_500 = "weapons.missiles.LS-6-500" +ENUMS.Storage.weapons.bombs.GBU_31_V_4B = "weapons.bombs.GBU_31_V_4B" +ENUMS.Storage.weapons.droptanks.PTB400_MIG15 = "weapons.droptanks.PTB400_MIG15" +ENUMS.Storage.weapons.containers.m_113 = "weapons.containers.m-113" +ENUMS.Storage.weapons.bombs.SPG_M1128_Stryker_MGS_33036lb = "weapons.bombs.SPG M1128 Stryker MGS [33036lb]" +ENUMS.Storage.weapons.missiles.AIM_9L = "weapons.missiles.AIM-9L" +ENUMS.Storage.weapons.missiles.AIM_9X = "weapons.missiles.AIM_9X" +ENUMS.Storage.weapons.nurs.C_8 = "weapons.nurs.C_8" +ENUMS.Storage.weapons.bombs.SAM_CHAPARRAL_Skid_21516lb = "weapons.bombs.SAM CHAPARRAL Skid [21516lb]" +ENUMS.Storage.weapons.missiles.P_27TE = "weapons.missiles.P_27TE" +ENUMS.Storage.weapons.bombs.ODAB_500PM = "weapons.bombs.ODAB-500PM" +ENUMS.Storage.weapons.bombs.MK77mod1_WPN = "weapons.bombs.MK77mod1-WPN" +ENUMS.Storage.weapons.droptanks.PTB400_MIG19 = "weapons.droptanks.PTB400_MIG19" +ENUMS.Storage.weapons.torpedoes.Mark_46 = "weapons.torpedoes.Mark_46" +ENUMS.Storage.weapons.containers.rightSeat = "weapons.containers.rightSeat" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_ORANGE = "weapons.containers.{US_M10_SMOKE_TANK_ORANGE}" +ENUMS.Storage.weapons.bombs.SAB_100MN = "weapons.bombs.SAB_100MN" +ENUMS.Storage.weapons.nurs.FFAR_Mk5_HEAT = "weapons.nurs.FFAR Mk5 HEAT" +ENUMS.Storage.weapons.bombs.IFV_TPZ_FUCH_33440lb = "weapons.bombs.IFV TPZ FUCH [33440lb]" +ENUMS.Storage.weapons.bombs.IFV_M2A2_Bradley_34720lb = "weapons.bombs.IFV M2A2 Bradley [34720lb]" +ENUMS.Storage.weapons.bombs.MK77mod0_WPN = "weapons.bombs.MK77mod0-WPN" +ENUMS.Storage.weapons.containers.ASO_2 = "weapons.containers.ASO-2" +ENUMS.Storage.weapons.bombs.Mk_84AIR_GP = "weapons.bombs.Mk_84AIR_GP" +ENUMS.Storage.weapons.nurs.S_24A = "weapons.nurs.S-24A" +ENUMS.Storage.weapons.bombs.RBK_250_275_AO_1SCH = "weapons.bombs.RBK_250_275_AO_1SCH" +ENUMS.Storage.weapons.bombs.Transport_Tigr_Skid_15730lb = "weapons.bombs.Transport Tigr Skid [15730lb]" +ENUMS.Storage.weapons.missiles.AIM_7F = "weapons.missiles.AIM-7F" +ENUMS.Storage.weapons.bombs.CBU_99 = "weapons.bombs.CBU_99" +ENUMS.Storage.weapons.bombs.LUU_2B = "weapons.bombs.LUU_2B" +ENUMS.Storage.weapons.bombs.FAB_500TA = "weapons.bombs.FAB-500TA" +ENUMS.Storage.weapons.missiles.AGR_20_M282 = "weapons.missiles.AGR_20_M282" +ENUMS.Storage.weapons.droptanks.MB339_FT330 = "weapons.droptanks.MB339_FT330" +ENUMS.Storage.weapons.bombs.SAMP125LD = "weapons.bombs.SAMP125LD" +ENUMS.Storage.weapons.missiles.X_25MP = "weapons.missiles.X_25MP" +ENUMS.Storage.weapons.nurs.SNEB_TYPE252_H1 = "weapons.nurs.SNEB_TYPE252_H1" +ENUMS.Storage.weapons.missiles.AGM_65F = "weapons.missiles.AGM_65F" +ENUMS.Storage.weapons.missiles.AIM_9P5 = "weapons.missiles.AIM-9P5" +ENUMS.Storage.weapons.bombs.Transport_Tigr_Air_15900lb = "weapons.bombs.Transport Tigr Air [15900lb]" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_H1_RED = "weapons.nurs.SNEB_TYPE254_H1_RED" +ENUMS.Storage.weapons.nurs.FFAR_Mk1_HE = "weapons.nurs.FFAR Mk1 HE" +ENUMS.Storage.weapons.nurs.SPRD_99 = "weapons.nurs.SPRD-99" +ENUMS.Storage.weapons.bombs.BIN_200 = "weapons.bombs.BIN_200" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU_4B_GROUP" +ENUMS.Storage.weapons.bombs.GBU_24 = "weapons.bombs.GBU_24" +ENUMS.Storage.weapons.missiles.Rb_04E = "weapons.missiles.Rb 04E" +ENUMS.Storage.weapons.missiles.Rb_74 = "weapons.missiles.Rb 74" +ENUMS.Storage.weapons.containers.leftSeat = "weapons.containers.leftSeat" +ENUMS.Storage.weapons.bombs.LS_6_100 = "weapons.bombs.LS-6-100" +ENUMS.Storage.weapons.bombs.Transport_URAL_375_14815lb = "weapons.bombs.Transport URAL-375 [14815lb]" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_GREEN = "weapons.containers.{US_M10_SMOKE_TANK_GREEN}" +ENUMS.Storage.weapons.missiles.X_22 = "weapons.missiles.X_22" +ENUMS.Storage.weapons.containers.FAS = "weapons.containers.FAS" +ENUMS.Storage.weapons.nurs.S_25_O = "weapons.nurs.S-25-O" +ENUMS.Storage.weapons.droptanks.para = "weapons.droptanks.para" +ENUMS.Storage.weapons.droptanks.F_15E_Drop_Tank = "weapons.droptanks.F-15E_Drop_Tank" +ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541_EMPTY = "weapons.droptanks.M2KC_08_RPL541_EMPTY" +ENUMS.Storage.weapons.missiles.X_31P = "weapons.missiles.X_31P" +ENUMS.Storage.weapons.bombs.RBK_500U = "weapons.bombs.RBK_500U" +ENUMS.Storage.weapons.missiles.AIM_54A_Mk47 = "weapons.missiles.AIM_54A_Mk47" +ENUMS.Storage.weapons.droptanks.oiltank = "weapons.droptanks.oiltank" +ENUMS.Storage.weapons.missiles.AGM_154B = "weapons.missiles.AGM_154B" +ENUMS.Storage.weapons.containers.MB339_SMOKE_POD = "weapons.containers.MB339_SMOKE-POD" +ENUMS.Storage.weapons.containers.ECM_POD_L_175V = "weapons.containers.{ECM_POD_L_175V}" +ENUMS.Storage.weapons.droptanks.PTB_580G_F1 = "weapons.droptanks.PTB_580G_F1" +ENUMS.Storage.weapons.containers.EclairM_15 = "weapons.containers.{EclairM_15}" +ENUMS.Storage.weapons.containers.F_15E_AAQ_13_LANTIRN = "weapons.containers.F-15E_AAQ-13_LANTIRN" +ENUMS.Storage.weapons.droptanks.Eight00L_Tank_Empty = "weapons.droptanks.800L Tank Empty" +ENUMS.Storage.weapons.containers.One6c_hts_pod = "weapons.containers.16c_hts_pod" +ENUMS.Storage.weapons.bombs.AN_M81 = "weapons.bombs.AN-M81" +ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_100gal = "weapons.droptanks.Mosquito_Drop_Tank_100gal" +ENUMS.Storage.weapons.droptanks.Mosquito_Drop_Tank_50gal = "weapons.droptanks.Mosquito_Drop_Tank_50gal" +ENUMS.Storage.weapons.droptanks.DFT_150_GAL_A4E = "weapons.droptanks.DFT_150_GAL_A4E" +ENUMS.Storage.weapons.missiles.AIM_9 = "weapons.missiles.AIM_9" +ENUMS.Storage.weapons.bombs.IFV_BTR_D_Air_18040lb = "weapons.bombs.IFV BTR-D Air [18040lb]" +ENUMS.Storage.weapons.containers.EclairM_42 = "weapons.containers.{EclairM_42}" +ENUMS.Storage.weapons.bombs.KAB_1500T = "weapons.bombs.KAB_1500T" +ENUMS.Storage.weapons.droptanks.PTB_490_MIG21 = "weapons.droptanks.PTB-490-MIG21" +ENUMS.Storage.weapons.droptanks.PTB_200_F86F35 = "weapons.droptanks.PTB_200_F86F35" +ENUMS.Storage.weapons.droptanks.PTB760_MIG19 = "weapons.droptanks.PTB760_MIG19" +ENUMS.Storage.weapons.bombs.GBU_43_B_MOAB = "weapons.bombs.GBU-43/B(MOAB)" +ENUMS.Storage.weapons.torpedoes.G7A_T1 = "weapons.torpedoes.G7A_T1" +ENUMS.Storage.weapons.bombs.IFV_BMD_1_Air_18040lb = "weapons.bombs.IFV BMD-1 Air [18040lb]" +ENUMS.Storage.weapons.bombs.SAM_LINEBACKER_34720lb = "weapons.bombs.SAM LINEBACKER [34720lb]" +ENUMS.Storage.weapons.containers.ais_pod_t50_r = "weapons.containers.ais-pod-t50_r" +ENUMS.Storage.weapons.containers.CE2_SMOKE_WHITE = "weapons.containers.{CE2_SMOKE_WHITE}" +ENUMS.Storage.weapons.droptanks.fuel_tank_230 = "weapons.droptanks.fuel_tank_230" +ENUMS.Storage.weapons.droptanks.M2KC_RPL_522 = "weapons.droptanks.M2KC_RPL_522" +ENUMS.Storage.weapons.missiles.AGM_130 = "weapons.missiles.AGM_130" +ENUMS.Storage.weapons.droptanks.Eight00L_Tank = "weapons.droptanks.800L Tank" +ENUMS.Storage.weapons.bombs.IFV_BTR_D_Skid_17930lb = "weapons.bombs.IFV BTR-D Skid [17930lb]" +ENUMS.Storage.weapons.containers.bmp_1 = "weapons.containers.bmp-1" +ENUMS.Storage.weapons.bombs.GBU_31 = "weapons.bombs.GBU_31" +ENUMS.Storage.weapons.containers.aaq_28LEFT_litening = "weapons.containers.aaq-28LEFT litening" +ENUMS.Storage.weapons.missiles.Kh_66_Grom = "weapons.missiles.Kh-66_Grom" +ENUMS.Storage.weapons.containers.MIG21_SMOKE_RED = "weapons.containers.{MIG21_SMOKE_RED}" +ENUMS.Storage.weapons.containers.U22 = "weapons.containers.U22" +ENUMS.Storage.weapons.bombs.IFV_BMD_1_Skid_17930lb = "weapons.bombs.IFV BMD-1 Skid [17930lb]" +ENUMS.Storage.weapons.droptanks.Bidon = "weapons.droptanks.Bidon" +ENUMS.Storage.weapons.bombs.GBU_31_V_2B = "weapons.bombs.GBU_31_V_2B" +ENUMS.Storage.weapons.bombs.Mk_82Y = "weapons.bombs.Mk_82Y" +ENUMS.Storage.weapons.containers.pl5eii = "weapons.containers.pl5eii" +ENUMS.Storage.weapons.bombs.RBK_500U_OAB_2_5RT = "weapons.bombs.RBK_500U_OAB_2_5RT" +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk5 = "weapons.bombs.British_GP_500LB_Bomb_Mk5" +ENUMS.Storage.weapons.containers.Eclair = "weapons.containers.{Eclair}" +ENUMS.Storage.weapons.nurs.S5MO_HEFRAG_FFAR = "weapons.nurs.S5MO_HEFRAG_FFAR" +ENUMS.Storage.weapons.bombs.BETAB_500M = "weapons.bombs.BETAB-500M" +ENUMS.Storage.weapons.bombs.Transport_M818_16000lb = "weapons.bombs.Transport M818 [16000lb]" +ENUMS.Storage.weapons.bombs.British_MC_250LB_Bomb_Mk1 = "weapons.bombs.British_MC_250LB_Bomb_Mk1" +ENUMS.Storage.weapons.nurs.SNEB_TYPE251_H1 = "weapons.nurs.SNEB_TYPE251_H1" +ENUMS.Storage.weapons.bombs.TYPE_200A = "weapons.bombs.TYPE-200A" +ENUMS.Storage.weapons.nurs.HYDRA_70_M151 = "weapons.nurs.HYDRA_70_M151" +ENUMS.Storage.weapons.bombs.IFV_BMP_3_32912lb = "weapons.bombs.IFV BMP-3 [32912lb]" +ENUMS.Storage.weapons.bombs.APC_MTLB_Air_26400lb = "weapons.bombs.APC MTLB Air [26400lb]" +ENUMS.Storage.weapons.nurs.HYDRA_70_M229 = "weapons.nurs.HYDRA_70_M229" +ENUMS.Storage.weapons.bombs.BDU_45 = "weapons.bombs.BDU_45" +ENUMS.Storage.weapons.bombs.OFAB_100_120TU = "weapons.bombs.OFAB-100-120TU" +ENUMS.Storage.weapons.missiles.AIM_9J = "weapons.missiles.AIM-9J" +ENUMS.Storage.weapons.nurs.ARF8M3API = "weapons.nurs.ARF8M3API" +ENUMS.Storage.weapons.bombs.BetAB_500ShP = "weapons.bombs.BetAB_500ShP" +ENUMS.Storage.weapons.nurs.C_8OFP2 = "weapons.nurs.C_8OFP2" +ENUMS.Storage.weapons.bombs.GBU_10 = "weapons.bombs.GBU_10" +ENUMS.Storage.weapons.bombs.APC_MTLB_Skid_26290lb = "weapons.bombs.APC MTLB Skid [26290lb]" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_RED = "weapons.nurs.SNEB_TYPE254_F1B_RED" +ENUMS.Storage.weapons.missiles.X_65 = "weapons.missiles.X_65" +ENUMS.Storage.weapons.missiles.R_550_M1 = "weapons.missiles.R_550_M1" +ENUMS.Storage.weapons.missiles.AGM_65K = "weapons.missiles.AGM_65K" +ENUMS.Storage.weapons.nurs.SNEB_TYPE254_F1B_YELLOW = "weapons.nurs.SNEB_TYPE254_F1B_YELLOW" +ENUMS.Storage.weapons.missiles.AGM_88 = "weapons.missiles.AGM_88" +ENUMS.Storage.weapons.nurs.C_8OM = "weapons.nurs.C_8OM" +ENUMS.Storage.weapons.bombs.SAM_ROLAND_LN_34720b = "weapons.bombs.SAM ROLAND LN [34720b]" +ENUMS.Storage.weapons.missiles.AIM_120 = "weapons.missiles.AIM_120" +ENUMS.Storage.weapons.missiles.HOT3_MBDA = "weapons.missiles.HOT3_MBDA" +ENUMS.Storage.weapons.missiles.R_13M = "weapons.missiles.R-13M" +ENUMS.Storage.weapons.missiles.AIM_54C_Mk60 = "weapons.missiles.AIM_54C_Mk60" +ENUMS.Storage.weapons.bombs.AAA_GEPARD_34720lb = "weapons.bombs.AAA GEPARD [34720lb]" +ENUMS.Storage.weapons.missiles.R_13M1 = "weapons.missiles.R-13M1" +ENUMS.Storage.weapons.bombs.APC_Cobra_Air_10912lb = "weapons.bombs.APC Cobra Air [10912lb]" +ENUMS.Storage.weapons.bombs.RBK_250 = "weapons.bombs.RBK_250" +ENUMS.Storage.weapons.bombs.SC_500_J = "weapons.bombs.SC_500_J" +ENUMS.Storage.weapons.missiles.AGM_114K = "weapons.missiles.AGM_114K" +ENUMS.Storage.weapons.missiles.ALARM = "weapons.missiles.ALARM" +ENUMS.Storage.weapons.bombs.Mk_83 = "weapons.bombs.Mk_83" +ENUMS.Storage.weapons.missiles.AGM_65B = "weapons.missiles.AGM_65B" +ENUMS.Storage.weapons.bombs.MK_82SNAKEYE = "weapons.bombs.MK_82SNAKEYE" +ENUMS.Storage.weapons.nurs.HYDRA_70_MK1 = "weapons.nurs.HYDRA_70_MK1" +ENUMS.Storage.weapons.bombs.BLG66_BELOUGA = "weapons.bombs.BLG66_BELOUGA" +ENUMS.Storage.weapons.containers.EclairM_51 = "weapons.containers.{EclairM_51}" +ENUMS.Storage.weapons.missiles.AIM_54A_Mk60 = "weapons.missiles.AIM_54A_Mk60" +ENUMS.Storage.weapons.droptanks.DFT_300_GAL_A4E = "weapons.droptanks.DFT_300_GAL_A4E" +ENUMS.Storage.weapons.bombs.ATGM_M1134_Stryker_30337lb = "weapons.bombs.ATGM M1134 Stryker [30337lb]" +ENUMS.Storage.weapons.bombs.BAT_120 = "weapons.bombs.BAT-120" +ENUMS.Storage.weapons.missiles.DWS39_MJ1_MJ2 = "weapons.missiles.DWS39_MJ1_MJ2" +ENUMS.Storage.weapons.containers.SPRD = "weapons.containers.SPRD" +ENUMS.Storage.weapons.bombs.BR_500 = "weapons.bombs.BR_500" +ENUMS.Storage.weapons.bombs.British_GP_500LB_Bomb_Mk1 = "weapons.bombs.British_GP_500LB_Bomb_Mk1" +ENUMS.Storage.weapons.bombs.BDU_50HD = "weapons.bombs.BDU_50HD" +ENUMS.Storage.weapons.missiles.RS2US = "weapons.missiles.RS2US" +ENUMS.Storage.weapons.bombs.IFV_BMP_2_25168lb = "weapons.bombs.IFV BMP-2 [25168lb]" +ENUMS.Storage.weapons.bombs.SAMP400HD = "weapons.bombs.SAMP400HD" +ENUMS.Storage.weapons.containers.Hercules_Battle_Station = "weapons.containers.Hercules_Battle_Station" +ENUMS.Storage.weapons.bombs.AN_M64 = "weapons.bombs.AN_M64" +ENUMS.Storage.weapons.containers.rearCargoSeats = "weapons.containers.rearCargoSeats" +ENUMS.Storage.weapons.bombs.Mk_82 = "weapons.bombs.Mk_82" +ENUMS.Storage.weapons.missiles.AKD_10 = "weapons.missiles.AKD-10" +ENUMS.Storage.weapons.bombs.BDU_50LGB = "weapons.bombs.BDU_50LGB" +ENUMS.Storage.weapons.missiles.SD_10 = "weapons.missiles.SD-10" +ENUMS.Storage.weapons.containers.IRDeflector = "weapons.containers.IRDeflector" +ENUMS.Storage.weapons.bombs.FAB_500 = "weapons.bombs.FAB_500" +ENUMS.Storage.weapons.bombs.KAB_500 = "weapons.bombs.KAB_500" +ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S-5M" +ENUMS.Storage.weapons.missiles.MICA_R = "weapons.missiles.MICA_R" +ENUMS.Storage.weapons.missiles.X_59M = "weapons.missiles.X_59M" +ENUMS.Storage.weapons.nurs.UG_90MM = "weapons.nurs.UG_90MM" +ENUMS.Storage.weapons.bombs.LYSBOMB = "weapons.bombs.LYSBOMB" +ENUMS.Storage.weapons.nurs.R4M = "weapons.nurs.R4M" +ENUMS.Storage.weapons.containers.dlpod_akg = "weapons.containers.dlpod_akg" +ENUMS.Storage.weapons.missiles.LD_10 = "weapons.missiles.LD-10" +ENUMS.Storage.weapons.bombs.SC_50 = "weapons.bombs.SC_50" +ENUMS.Storage.weapons.nurs.HYDRA_70_MK5 = "weapons.nurs.HYDRA_70_MK5" +ENUMS.Storage.weapons.bombs.FAB_100M = "weapons.bombs.FAB_100M" +ENUMS.Storage.weapons.missiles.Rb_24 = "weapons.missiles.Rb 24" +ENUMS.Storage.weapons.bombs.BDU_45B = "weapons.bombs.BDU_45B" +ENUMS.Storage.weapons.missiles.GB_6_HE = "weapons.missiles.GB-6-HE" +ENUMS.Storage.weapons.missiles.KD_63B = "weapons.missiles.KD-63B" +ENUMS.Storage.weapons.missiles.P_27PE = "weapons.missiles.P_27PE" +ENUMS.Storage.weapons.droptanks.PTB300_MIG15 = "weapons.droptanks.PTB300_MIG15" +ENUMS.Storage.weapons.bombs.Two50_3 = "weapons.bombs.250-3" +ENUMS.Storage.weapons.bombs.SC_500_L2 = "weapons.bombs.SC_500_L2" +ENUMS.Storage.weapons.containers.HMMWV_M1045 = "weapons.containers.HMMWV_M1045" +ENUMS.Storage.weapons.bombs.FAB_500M54TU = "weapons.bombs.FAB-500M54TU" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_YELLOW = "weapons.containers.{US_M10_SMOKE_TANK_YELLOW}" +ENUMS.Storage.weapons.containers.EclairM_60 = "weapons.containers.{EclairM_60}" +ENUMS.Storage.weapons.bombs.SAB_250_200 = "weapons.bombs.SAB_250_200" +ENUMS.Storage.weapons.bombs.FAB_100 = "weapons.bombs.FAB_100" +ENUMS.Storage.weapons.bombs.KAB_500S = "weapons.bombs.KAB_500S" +ENUMS.Storage.weapons.missiles.AGM_45A = "weapons.missiles.AGM_45A" +ENUMS.Storage.weapons.missiles.Kh25MP_PRGS1VP = "weapons.missiles.Kh25MP_PRGS1VP" +ENUMS.Storage.weapons.nurs.S5M1_HEFRAG_FFAR = "weapons.nurs.S5M1_HEFRAG_FFAR" +ENUMS.Storage.weapons.containers.kg600 = "weapons.containers.kg600" +ENUMS.Storage.weapons.bombs.AN_M65 = "weapons.bombs.AN_M65" +ENUMS.Storage.weapons.bombs.AN_M57 = "weapons.bombs.AN_M57" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100" +ENUMS.Storage.weapons.containers.HEMTT = "weapons.containers.HEMTT" +ENUMS.Storage.weapons.bombs.British_MC_500LB_Bomb_Mk1_Short = "weapons.bombs.British_MC_500LB_Bomb_Mk1_Short" +ENUMS.Storage.weapons.nurs.ARAKM70BAP = "weapons.nurs.ARAKM70BAP" +ENUMS.Storage.weapons.missiles.AGM_119 = "weapons.missiles.AGM_119" +ENUMS.Storage.weapons.missiles.MMagicII = "weapons.missiles.MMagicII" +ENUMS.Storage.weapons.bombs.AB_500_1_SD_10A = "weapons.bombs.AB_500_1_SD_10A" +ENUMS.Storage.weapons.nurs.HYDRA_70_M282 = "weapons.nurs.HYDRA_70_M282" +ENUMS.Storage.weapons.droptanks.DFT_400_GAL_A4E = "weapons.droptanks.DFT_400_GAL_A4E" +ENUMS.Storage.weapons.nurs.HYDRA_70_M257 = "weapons.nurs.HYDRA_70_M257" +ENUMS.Storage.weapons.droptanks.AV8BNA_AERO1D = "weapons.droptanks.AV8BNA_AERO1D" +ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_BLUE = "weapons.containers.{US_M10_SMOKE_TANK_BLUE}" +ENUMS.Storage.weapons.nurs.ARF8M3HEI = "weapons.nurs.ARF8M3HEI" +ENUMS.Storage.weapons.bombs.RN_28 = "weapons.bombs.RN-28" +ENUMS.Storage.weapons.bombs.Squad_30_x_Soldier_7950lb = "weapons.bombs.Squad 30 x Soldier [7950lb]" +ENUMS.Storage.weapons.containers.uaz_469 = "weapons.containers.uaz-469" +ENUMS.Storage.weapons.containers.Otokar_Cobra = "weapons.containers.Otokar_Cobra" +ENUMS.Storage.weapons.bombs.APC_BTR_82A_Air_24998lb = "weapons.bombs.APC BTR-82A Air [24998lb]" +ENUMS.Storage.weapons.nurs.HYDRA_70_M274 = "weapons.nurs.HYDRA_70_M274" +ENUMS.Storage.weapons.missiles.P_24R = "weapons.missiles.P_24R" +ENUMS.Storage.weapons.nurs.HYDRA_70_MK61 = "weapons.nurs.HYDRA_70_MK61" +ENUMS.Storage.weapons.missiles.Igla_1E = "weapons.missiles.Igla_1E" +ENUMS.Storage.weapons.missiles.C_802AK = "weapons.missiles.C-802AK" +ENUMS.Storage.weapons.nurs.C_24 = "weapons.nurs.C_24" +ENUMS.Storage.weapons.droptanks.M2KC_08_RPL541 = "weapons.droptanks.M2KC_08_RPL541" +ENUMS.Storage.weapons.nurs.C_13 = "weapons.nurs.C_13" +ENUMS.Storage.weapons.droptanks.droptank_110_gal = "weapons.droptanks.droptank_110_gal" +ENUMS.Storage.weapons.bombs.Mk_84 = "weapons.bombs.Mk_84" +ENUMS.Storage.weapons.missiles.Sea_Eagle = "weapons.missiles.Sea_Eagle" +ENUMS.Storage.weapons.droptanks.PTB_1200_F1 = "weapons.droptanks.PTB_1200_F1" +ENUMS.Storage.weapons.nurs.SNEB_TYPE256_H1 = "weapons.nurs.SNEB_TYPE256_H1" +ENUMS.Storage.weapons.containers.MATRA_PHIMAT = "weapons.containers.MATRA-PHIMAT" +ENUMS.Storage.weapons.containers.smoke_pod = "weapons.containers.smoke_pod" +ENUMS.Storage.weapons.containers.F_15E_AAQ_14_LANTIRN = "weapons.containers.F-15E_AAQ-14_LANTIRN" +ENUMS.Storage.weapons.containers.EclairM_24 = "weapons.containers.{EclairM_24}" +ENUMS.Storage.weapons.bombs.GBU_16 = "weapons.bombs.GBU_16" +ENUMS.Storage.weapons.nurs.HYDRA_70_M156 = "weapons.nurs.HYDRA_70_M156" +ENUMS.Storage.weapons.missiles.R_60 = "weapons.missiles.R-60" +ENUMS.Storage.weapons.containers.zsu_23_4 = "weapons.containers.zsu-23-4" +ENUMS.Storage.weapons.missiles.RB75 = "weapons.missiles.RB75" +ENUMS.Storage.weapons.missiles.Mistral = "weapons.missiles.Mistral" +ENUMS.Storage.weapons.droptanks.MB339_TT500_L = "weapons.droptanks.MB339_TT500_L" +ENUMS.Storage.weapons.bombs.SAM_SA_13_STRELA_21624lb = "weapons.bombs.SAM SA-13 STRELA [21624lb]" +ENUMS.Storage.weapons.bombs.SAM_Avenger_M1097_Air_7200lb = "weapons.bombs.SAM Avenger M1097 Air [7200lb]" +ENUMS.Storage.weapons.droptanks.Eleven00L_Tank_Empty = "weapons.droptanks.1100L Tank Empty" +ENUMS.Storage.weapons.bombs.AN_M88 = "weapons.bombs.AN-M88" +ENUMS.Storage.weapons.missiles.S_25L = "weapons.missiles.S_25L" +ENUMS.Storage.weapons.nurs.British_AP_25LBNo1_3INCHNo1 = "weapons.nurs.British_AP_25LBNo1_3INCHNo1" ENUMS.Storage.weapons.bombs.BDU_50LD = "weapons.bombs.BDU_50LD" ENUMS.Storage.weapons.bombs.AGM_62 = "weapons.bombs.AGM_62" -ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE = "weapons.containers.{US_M10_SMOKE_TANK_WHITE}" -ENUMS.Storage.weapons.missiles.MICA_T = "weapons.missiles.MICA_T" +ENUMS.Storage.weapons.containers.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" @@ -1315,6 +1316,26 @@ ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right_Door = {4,15,46,175} ENUMS.Storage.weapons.UH1H.M60_MG_Right_Door = {4,15,46,177} ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left_Door = {4,15,46,174} ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door = {4,15,46,176} +-- UH-60L +ENUMS.Storage.weapons.UH60L.M151_HYDRA = {4, 7, 33, 147} -- 2.75" Hydra, UnGd Rkts M151, HE +ENUMS.Storage.weapons.UH60L.M156_HYDRA = {4, 7, 33, 148} -- 2.75" Hydra, UnGd Rkts M156, Wht Phos +ENUMS.Storage.weapons.UH60L.M229_HYDRA = {4, 7, 33, 148} -- 2.75" Hydra, UnGd Rkts M229, HE +ENUMS.Storage.weapons.UH60L.M257_HYDRA = {4, 7, 33, 151} -- 2.75" Hydra, UnGd Rkts M257, Para Illum +ENUMS.Storage.weapons.UH60L.M259_HYDRA = {4, 7, 33, 151} -- 2.75" Hydra, UnGd Rkts M259, Smoke Marker +ENUMS.Storage.weapons.UH60L.M274_HYDRA = {4, 7, 33, 150} -- 2.75" Hydra, UnGd Rkts M274, Practice Smk +ENUMS.Storage.weapons.UH60L.M134_DOOR_GUN = {4, 15, 46, 3031} +ENUMS.Storage.weapons.UH60L.M3M = {4, 15, 46, 2496} +ENUMS.Storage.weapons.UH60L.M3M_DOOR_GUN = {4, 15, 46, 3032} +ENUMS.Storage.weapons.UH60L.M60_DOOR_GUN = {4, 15, 46, 3033} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_200 = {1,3,43,3023} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_230 = {1,3,43,3024} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_450 = {1,3,43,3025} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_DUAL_AUX = {1,3,43,3026} +ENUMS.Storage.weapons.UH60L.CARGO_SEAT_REAR_ROW = {1,3,43,3030} +ENUMS.Storage.weapons.UH60L.CARGO_SEAT_THREE_ROWS = {1,3,43,3029} +ENUMS.Storage.weapons.UH60L.EMPTY_GUNNER_SEAT_1 = {1,3,43,3027} +ENUMS.Storage.weapons.UH60L.EMPTY_GUNNER_SEAT_2 = {1,3,43,3028} + -- Kiowa ENUMS.Storage.weapons.OH58.FIM92 = {4,4,7,449} ENUMS.Storage.weapons.OH58.MG_M3P100 = {4,15,46,2611} @@ -1348,7 +1369,7 @@ ENUMS.FARPType = { FARP = "FARP", INVISIBLE = "INVISIBLE", HELIPADSINGLE = "HELIPADSINGLE", - PADSINGLE = "PADSINGLE", + PADSINGLE = "PADSINGLE", } diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 84fef4f6d..7763ba1d1 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -12,27 +12,35 @@ -- @module Utilities.Utils -- @image MOOSE.JPG ---- +--- Smoke color enum `trigger.smokeColor`. -- @type SMOKECOLOR --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue +-- @field #number Green Green smoke (0) +-- @field #number Red Red smoke (1) +-- @field #number White White smoke (2) +-- @field #number Orange Orange smoke (3) +-- @field #number Blue Blue smoke (4) SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR ---- +--- Flare colur enum `trigger.flareColor`. -- @type FLARECOLOR --- @field Green --- @field Red --- @field White --- @field Yellow +-- @field #number Green (0) +-- @field #number Red Red flare (1) +-- @field #number White White flare (2) +-- @field #number Yellow Yellow flare (3) FLARECOLOR = trigger.flareColor -- #FLARECOLOR --- Big smoke preset enum. -- @type BIGSMOKEPRESET +-- @field #number SmallSmokeAndFire Small moke and fire (1) +-- @field #number MediumSmokeAndFire Medium smoke and fire (2) +-- @field #number LargeSmokeAndFire Large smoke and fire (3) +-- @field #number HugeSmokeAndFire Huge smoke and fire (4) +-- @field #number SmallSmoke Small smoke (5) +-- @field #number MediumSmoke Medium smoke (6) +-- @field #number LargeSmoke Large smoke (7) +-- @field #number HugeSmoke Huge smoke (8) BIGSMOKEPRESET = { SmallSmokeAndFire=1, MediumSmokeAndFire=2, @@ -490,7 +498,7 @@ end --- Counts the number of elements in a table. -- @param #table T Table to count --- @return #int Number of elements in the table +-- @return #number Number of elements in the table function UTILS.TableLength(T) local count = 0 for _ in pairs(T or {}) do count = count + 1 end @@ -1653,12 +1661,12 @@ function UTILS.HdgDiff(h1, h2) return math.abs(delta) end ---- Returns the heading from one vec3 to another vec3. --- @param DCS#Vec3 a From vec3. --- @param DCS#Vec3 b To vec3. +--- Returns the heading from one vec2/vec3 to another vec2/vec3. +-- @param DCS#Vec3 a From Vec2 or Vec3. +-- @param DCS#Vec3 b To Vec2 or Vec3. -- @return #number Heading in degrees. function UTILS.HdgTo(a, b) - local dz=b.z-a.z + local dz=(b.z or b.y) - (a.z or a.y) local dx=b.x-a.x local heading=math.deg(math.atan2(dz, dx)) if heading < 0 then @@ -1980,6 +1988,13 @@ end function UTILS.GetReportingName(Typename) local typename = string.lower(Typename) + + -- special cases - Shark and Manstay have "A-50" in the name + if string.find(typename,"ka-50",1,true) then + return "Shark" + elseif string.find(typename,"a-50",1,true) then + return "Mainstay" + end for name, value in pairs(ENUMS.ReportingName.NATO) do local svalue = string.lower(value) @@ -2212,9 +2227,9 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal) local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude)) if rising and cosH > 1 then - return "N/S" -- The sun never rises on this location on the specified date + return "N/R" -- The sun never rises on this location on the specified date elseif cosH < -1 then - return "N/R" -- The sun never sets on this location on the specified date + return "N/S" -- The sun never sets on this location on the specified date end -- Finish calculating H and convert into hours @@ -2390,6 +2405,16 @@ function UTILS.IsLoadingDoorOpen( unit_name ) return true end + if type_name == "UH-60L_DAP" and (unit:getDrawArgumentValue(401) == 1 or unit:getDrawArgumentValue(402) == 1) then + BASE:T(unit_name .. " cargo door is open") + return true + end + + if type_name == "UH-60L_DAP" and (unit:getDrawArgumentValue(38) > 0 or unit:getDrawArgumentValue(400) == 1 ) then + BASE:T(unit_name .. " front door(s) are open") + return true + end + if type_name == "AH-64D_BLK_II" then BASE:T(unit_name .. " front door(s) are open") return true -- no doors on this one ;) @@ -4243,6 +4268,45 @@ function UTILS.LCGRandom() return UTILS.lcg.seed / UTILS.lcg.m end +--- Create a table of grid-points for n points. +-- @param #number startVec2 Starting DCS#Vec2 map coordinate, e.g. `{x=63598575,y=-63598575}` +-- @param #number n Number of points to generate. +-- @param #number spacingX Horizonzal spacing (meters). +-- @param #number spacingY Vertical spacing (meters). +-- @return #table Grid Table of DCS#Vec2 entries. +function UTILS.GenerateGridPoints(startVec2, n, spacingX, spacingY) + local points = {} + local gridSize = math.ceil(math.sqrt(n)) + local count = 0 + local n = n or 1 + local spacingX = spacingX or 100 + local spacingY = spacingY or 100 + local startX = startVec2.x or 100 + local startY = startVec2.y or 100 + + for row = 0, gridSize - 1 do + for col = 0, gridSize - 1 do + if count >= n then + break + end + + local point = { + x = startX + (col * spacingX), + y = startY + (row * spacingY) + } + + table.insert(points, point) + count = count + 1 + end + + if count >= n then + break + end + end + + return points +end + --- Spawns a new FARP of a defined type and coalition and functional statics (fuel depot, ammo storage, tent, windsock) around that FARP to make it operational. -- Adds vehicles from template if given. Fills the FARP warehouse with liquids and known materiels. -- References: [DCS Forum Topic](https://forum.dcs.world/topic/282989-farp-equipment-to-run-it) @@ -4259,9 +4323,42 @@ end -- @param #string VehicleTemplate, template name for additional vehicles. Can be nil for no additional vehicles. -- @param #number Liquids Tons of fuel to be added initially to the FARP. Defaults to 10 (tons). Set to 0 for no fill. -- @param #number Equipment Number of equipment items per known item to be added initially to the FARP. Defaults to 10 (items). Set to 0 for no fill. +-- @param #number Airframes Number of helicopter airframes per known type in Ops.CSAR#CSAR.AircraftType to be added initially to the FARP. Set to 0 for no airframes. +-- @param #string F10Text Text to display on F10 map if given. Handy to post things like the ADF beacon Frequency, Callsign and ATC Frequency. +-- @param #boolean DynamicSpawns If true, allow Dynamic Spawns from this FARP. +-- @param #boolean HotStart If true and DynamicSpawns is true, allow hot starts for Dynamic Spawns from this FARP. +-- @param #number NumberPads If given, spawn this number of pads. +-- @param #number SpacingX For NumberPads > 1, space this many meters horizontally. Defaults to 100. +-- @param #number SpacingY For NumberPads > 1, space this many meters vertically. Defaults to 100. -- @return #list Table of spawned objects and vehicle object (if given). -- @return #string ADFBeaconName Name of the ADF beacon, to be able to remove/stop it later. -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment) +-- @return #number MarkerID ID of the F10 Text, to be able to remove it later. +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) + + local function PopulateStorage(Name,liquids,equip,airframes) + local newWH = STORAGE:New(Name) + if liquids and liquids > 0 then + -- Storage fill-up + newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons + newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) + newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) + newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) + end + + if equip and equip > 0 then + for cat,nitem in pairs(ENUMS.Storage.weapons) do + for name,item in pairs(nitem) do + newWH:SetItem(item,equip) + end + end + end + + if airframes and airframes > 0 then + for typename in pairs (CSAR.AircraftType) do + newWH:SetItem(typename,airframes) + end + end + end -- Set Defaults local farplocation = Coordinate @@ -4275,18 +4372,43 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, local liquids = Liquids or 10 liquids = liquids * 1000 -- tons to kg local equip = Equipment or 10 + local airframes = Airframes or 10 local statictypes = ENUMS.FARPObjectTypeNamesAndShape[farptype] or {TypeName="FARP", ShapeName="FARPS"} local STypeName = statictypes.TypeName local SShapeName = statictypes.ShapeName local Country = Country or (Coalition == coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects = {} - -- Spawn FARP - local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" - newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" - newfarp:InitFARP(callsign,freq,mod) - local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) - table.insert(ReturnObjects,spawnedfarp) + -- many FARPs + local NumberPads = NumberPads or 1 + local SpacingX = SpacingX or 100 + local SpacingY = SpacingY or 100 + local FarpVec2 = Coordinate:GetVec2() + + if NumberPads > 1 then + local Grid = UTILS.GenerateGridPoints(FarpVec2, NumberPads, SpacingX, SpacingY) + for id,gridpoint in ipairs(Grid) do + -- Spawn FARP + local location = COORDINATE:NewFromVec2(gridpoint) + local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" + newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" + newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) + local spawnedfarp = newfarp:SpawnFromCoordinate(location,0,Name.."-"..id) + table.insert(ReturnObjects,spawnedfarp) + + PopulateStorage(Name.."-"..id,liquids,equip,airframes) + end + else + -- Spawn FARP + local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" + newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" + newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) + local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) + table.insert(ReturnObjects,spawnedfarp) + + PopulateStorage(Name,liquids,equip,airframes) + end + -- Spawn Objects local FARPStaticObjectsNato = { ["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"}, @@ -4320,23 +4442,6 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, table.insert(ReturnObjects,spawnedvehicle) end - local newWH = STORAGE:New(Name) - if liquids and liquids > 0 then - -- Storage fill-up - newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons - newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) - newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) - newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) - end - - if equip and equip > 0 then - for cat,nitem in pairs(ENUMS.Storage.weapons) do - for name,item in pairs(nitem) do - newWH:SetItem(item,equip) - end - end - end - local ADFName if ADF and type(ADF) == "number" then local ADFFreq = ADF*1000 -- KHz to Hz @@ -4347,7 +4452,150 @@ function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition, trigger.action.radioTransmission(Sound, vec3, 0, true, ADFFreq, 250, ADFName) end - return ReturnObjects, ADFName + local MarkerID = nil + if F10Text then + local Color = {0,0,1} + if Coalition == coalition.side.RED then + Color = {1,0,0} + elseif Coalition == coalition.side.NEUTRAL then + Color = {0,1,0} + end + local Alpha = 0.75 + local coordinate = Coordinate:Translate(600,0) + MarkerID = coordinate:TextToAll(F10Text,Coalition,Color,1,{1,1,1},Alpha,14,true) + end + + return ReturnObjects, ADFName, MarkerID +end + +--- Spawn a MASH at a given coordinate, optionally, add an ADF Beacon. +-- @param #string Name Unique Name of the Mash. +-- @param Core.Point#COORDINATE Coordinate Coordinate where to spawn the MASH. Can be given as a Core.Zone#ZONE object, in this case we take the center coordinate. +-- @param #number Country Country ID the MASH belongs to, e.g. country.id.USA or country.id.RUSSIA. +-- @param #number ADF (Optional) ADF Frequency in kHz (Kilohertz), if given activate an ADF Beacon at the location of the MASH. +-- @param #string Livery (Optional) The livery of the static CH-47, defaults to dark green. +-- @param #boolean DeployHelo (Optional) If true, deploy the helicopter static. +-- @param #number MASHRadio MASH Radio Frequency, defaults to 127.5. +-- @param #number MASHRadioModulation MASH Radio Modulation, defaults to radio.modulation.AM. +-- @param #number MASHCallsign Defaults to CALLSIGN.FARP.Berlin. +-- @param #table Templates (Optional) You can hand in your own template table of numbered(!) entries. Each entry consist of a relative(!) x,y position and data of a +-- static, shape_name is optional. Also, livery_id is optional, but is applied to the helicopter static only. +-- @return #table Table of Wrapper.Static#STATIC objects that were spawned. +-- @return #string ADFName Name of the ADF Beacon to remove it later. +-- @usage +-- -- MASH Template example, this one is the built in one used in the function: +-- MASHTemplates = { +-- [1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,}, +-- [2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,}, +-- [3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,}, +-- [4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id = "us army dark green",}, +-- [5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,}, +-- [6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,}, +-- [7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,}, +-- [8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,}, +-- [9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,}, +-- [10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,}, +-- [11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,}, +-- [12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,}, +-- [13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,}, +-- [14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,}, +-- [15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,}, +-- [16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,}, +-- [17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,}, +-- [18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,}, +-- } +-- +function UTILS.SpawnMASHStatics(Name,Coordinate,Country,ADF,Livery,DeployHelo,MASHRadio,MASHRadioModulation,MASHCallsign,Templates) + + -- Basic objects table + + local MASHTemplates = { + [1]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.000000,y=0.000000,}, + [2]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=0.313533,y=8.778935,}, + [3]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=16.303737,y=20.379671,}, + [4]={category='Helicopters',type='CH-47Fbl1',shape_name='none',heading=0,x=-20.047735,y=-63.166179,livery_id = "us army dark green",}, + [5]={category='Infantry',type='Soldier M4',shape_name='none',heading=0,x=26.650339,y=20.066138,}, + [6]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.432292,y=9.077099,}, + [7]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=-3.216114,}, + [8]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-25.439281,y=-3.216114,}, + [9]={category='Heliports',type='FARP_SINGLE_01',shape_name='FARP_SINGLE_01',heading=0,x=-12.717421,y=9.155603,}, + [10]={category='Fortifications',type='TACAN_beacon',shape_name='none',heading=0,x=-2.329847,y=-16.579903,}, + [11]={category='Fortifications',type='FARP Fuel Depot',shape_name='GSM Rus',heading=0,x=2.222011,y=4.487030,}, + [12]={category='Fortifications',type='APFC fuel',shape_name='M92_APFCfuel',heading=0,x=3.614927,y=0.367838,}, + [13]={category='Fortifications',type='Camouflage03',shape_name='M92_Camouflage03',heading=0,x=21.544148,y=21.998879,}, + [14]={category='Fortifications',type='Container_generator',shape_name='M92_Container_generator',heading=0,x=20.989192,y=37.314334,}, + [15]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=3.988003,y=8.362333,}, + [16]={category='Fortifications',type='FireExtinguisher02',shape_name='M92_FireExtinguisher02',heading=0,x=-3.953195,y=12.945844,}, + [17]={category='Fortifications',type='Windsock',shape_name='H-Windsock_RW',heading=0,x=-18.944173,y=-33.042196,}, + [18]={category='Fortifications',type='Tent04',shape_name='M92_Tent04',heading=0,x=21.220671,y=30.247529,}, + } + + if Templates then MASHTemplates=Templates end + + -- locals + local name = Name or "Florence Nightingale" + local positionVec2 + local positionVec3 + local ReturnStatics = {} + local CountryID = Country or country.id.USA + local livery = "us army dark green" + local MASHRadio = MASHRadio or 127.5 + local MASHRadioModulation = MASHRadioModulation or radio.modulation.AM + local MASHCallsign = MASHCallsign or CALLSIGN.FARP.Berlin + + -- check for coordinate or zone + if type(Coordinate) == "table" then + if Coordinate:IsInstanceOf("COORDINATE") or Coordinate:IsInstanceOf("ZONE_BASE") then + positionVec2 = Coordinate:GetVec2() + positionVec3 = Coordinate:GetVec3() + end + else + BASE:E("Spawn MASH - no ZONE or COORDINATE handed!") + return + end + + -- position + local BaseX = positionVec2.x + local BaseY = positionVec2.y + + -- Statics + for id,object in pairs(MASHTemplates) do + local NewName = string.format("%s#%3d",name,id) + local vec2 = {x=BaseX+object.x,y=BaseY+object.y} + local Coordinate=COORDINATE:NewFromVec2(vec2) + local static = SPAWNSTATIC:NewFromType(object.type,object.category,CountryID) + if object.shape_name and object.shape_name ~= "none" then + static:InitShape(object.shape_name) + end + if object.category == "Helicopters" and DeployHelo == true then + if object.livery_id ~= nil then + livery = object.livery_id + end + static:InitLivery(livery) + local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName) + table.insert(ReturnStatics,newstatic) + elseif object.category == "Heliports" then + static:InitFARP(MASHCallsign,MASHRadio,MASHRadioModulation,false,false) + local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName) + table.insert(ReturnStatics,newstatic) + elseif object.category ~= "Helicopters" and object.category ~= "Heliports" then + local newstatic = static:SpawnFromCoordinate(Coordinate,object.heading,NewName) + table.insert(ReturnStatics,newstatic) + end + + end + + -- Beacon + local ADFName + if ADF and type(ADF) == "number" then + local ADFFreq = ADF*1000 -- KHz to Hz + local Sound = "l10n/DEFAULT/beacon.ogg" + ADFName = Name .. " ADF "..tostring(ADF).."KHz" + --BASE:I(string.format("Adding MASH Beacon %d KHz Name %s",ADF,ADFName)) + trigger.action.radioTransmission(Sound, positionVec3, 0, true, ADFFreq, 250, ADFName) + end + + return ReturnStatics, ADFName end --- Converts a Vec2 to a Vec3. @@ -4544,3 +4792,449 @@ end function UTILS.Weather.StopFogAnimation() return world.weather.setFogAnimation({}) end + +--- Find a ME created zone by its name +function UTILS.GetEnvZone(name) + for _,v in ipairs(env.mission.triggers.zones) do + if v.name == name then + return v + end + end +end + +--- net.dostring_in +function UTILS.DoStringIn(State,DoString) + return net.dostring_in(State,DoString) +end + +--- Show a picture on the screen to all +-- @param #string FileName File name of the picture +-- @param #number Duration Duration in seconds, defaults to 10 +-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false +-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToAll(FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + net.dostring_in("mission", string.format("a_out_picture(\"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +end + +--- Show a picture on the screen to Coalition +-- @param #number Coalition Coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL +-- @param #string FileName File name of the picture +-- @param #number Duration Duration in seconds, defaults to 10 +-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false +-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToCoalition(Coalition, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + local coalName = string.lower(UTILS.GetCoalitionName(Coalition)) + + net.dostring_in("mission", string.format("a_out_picture_s(\"%s\", \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", coalName, FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +end + +--- Show a picture on the screen to Country +-- @param #number Country Country ID, can be country.id.USA, country.id.RUSSIA, etc. +-- @param #string FileName File name of the picture +-- @param #number Duration Duration in seconds, defaults to 10 +-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false +-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToCountry(Country, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + net.dostring_in("mission", string.format("a_out_picture_c(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Country, FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +end + +--- Show a picture on the screen to Group +-- @param Wrapper.Group#GROUP Group Group to show the picture to +-- @param #string FileName File name of the picture +-- @param #number Duration Duration in seconds, defaults to 10 +-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false +-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToGroup(Group, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + net.dostring_in("mission", string.format("a_out_picture_g(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Group:GetID(), FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +end + +--- Show a picture on the screen to Unit +-- @param Wrapper.Unit#UNIT Unit Unit to show the picture to +-- @param #string FileName File name of the picture +-- @param #number Duration Duration in seconds, defaults to 10 +-- @param #boolean ClearView If true, clears the view before showing the picture, defaults to false +-- @param #number StartDelay Delay in seconds before showing the picture, defaults to 0 +-- @param #number HorizontalAlign Horizontal alignment of the picture, 0: Left, 1: Center, 2: Right +-- @param #number VerticalAlign Vertical alignment of the picture, 0: Top, 1: Center, 2: Bottom +-- @param #number Size Size of the picture in percent, defaults to 100 +-- @param #number SizeUnits Size units, 0 for % of original picture size, and 1 for % of window size +function UTILS.ShowPictureToUnit(Unit, FilePath, Duration, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits) + ClearView = ClearView or false + StartDelay = StartDelay or 0 + HorizontalAlign = HorizontalAlign or 1 + VerticalAlign = VerticalAlign or 1 + Size = Size or 100 + SizeUnits = SizeUnits or 0 + + if ClearView then ClearView = "true" else ClearView = "false" end + + net.dostring_in("mission", string.format("a_out_picture_u(%d, \"%s\", %d, %s, %d, \"%d\", \"%d\", %d, \"%d\")", Unit:GetID(), FilePath, Duration or 10, ClearView, StartDelay, HorizontalAlign, VerticalAlign, Size, SizeUnits)) +end + +--- Load a mission file. This will replace the current mission with the one given carrying along the online clients. +-- @param #string FileName Mission filename +function UTILS.LoadMission(FileName) + net.dostring_in("mission", string.format("a_load_mission(\"%s\")", FileName)) +end + +--- Set the mission briefing for a coalition. +-- @param #number Coalition Briefing coalition ID, can be coalition.side.BLUE, coalition.side.RED or coalition.side.NEUTRAL +-- @param #string Text Briefing text, can contain newlines, will be converted formatted properly for DCS +-- @param #string Picture Picture file path, can be a file in the DEFAULT folder inside the .miz +function UTILS.SetMissionBriefing(Coalition, Text, Picture) + Text = Text or "" + Text = Text:gsub("\n", "\\n") + Picture = Picture or "" + local coalName = string.lower(UTILS.GetCoalitionName(Coalition)) + net.dostring_in("mission", string.format("a_set_briefing(\"%s\", \"%s\", \"%s\")", coalName, Picture, Text)) +end + +--- Show a helper gate at a DCS#Vec3 position +-- @param DCS#Vec3 pos The position +-- @param #number heading Heading in degrees, can be 0..359 degrees +function UTILS.ShowHelperGate(pos, heading) + net.dostring_in("mission",string.format("a_show_helper_gate(%s, %s, %s, %f)", pos.x, pos.y, pos.z, math.rad(heading))) +end + +--- Show a helper gate for a unit. +-- @param Wrapper.Unit#UNIT Unit The unit to show the gate for +-- @param #number Flag Helper gate flag +function UTILS.ShowHelperGateForUnit(Unit, Flag) + net.dostring_in("mission",string.format("a_show_route_gates_for_unit(%d, \"%d\")", Unit:GetID(), Flag)) +end + +--- Set the carrier illumination mode. -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +-- @param #number UnitID Carrier unit ID ( UNIT:GetID() ) +-- @param #number Mode Illumination mode, can be -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +function UTILS.SetCarrierIlluminationMode(UnitID, Mode) + net.dostring_in("mission",string.format("a_set_carrier_illumination_mode(%d, %d)", UnitID, Mode)) +end + +--- Shell a zone, zone must ME created +-- @param #string name The name of the ME created zone +-- @param #number power Equals kg of TNT, e.g. 75 +-- @param #count Number of shells simulated +function UTILS.ShellZone(name, power, count) + local z = UTILS.GetEnvZone(name) + if z then + net.dostring_in("mission",string.format("a_shelling_zone(%d, %d, %d)", z.zoneId, power, count)) + end +end + +--- Remove objects from a zone, zone must ME created +-- @param #string name The name of the ME created zone +-- @param #number type Type of objects to remove can be 0:all, 1: trees, 2:objects +function UTILS.RemoveObjects(name, type) + local z = UTILS.GetEnvZone(name) + if z then + net.dostring_in("mission",string.format("a_remove_scene_objects(%d, %d)", z.zoneId, type)) + end +end + +--- Remove scenery objects from a zone, zone must ME created +-- @param #string name The name of the ME created zone +-- @param #number level Level of removal +function UTILS.DestroyScenery(name, level) + local z = UTILS.GetEnvZone(name) + if z then + net.dostring_in("mission",string.format("a_scenery_destruction_zone(%d, %d)", z.zoneId, level)) + end +end + +--- Search for clear zones in a given area. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param DCS##Vec3 Center position vector for the search area. +-- @param #number SearchRadius Radius of the search area. +-- @param #number PosRadius Required clear radius around each position. +-- @param #number NumPositions Number of positions to find. +-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. +function UTILS.GetSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions) + return Disposition.getSimpleZones(Vec3, SearchRadius, PosRadius, NumPositions) +end + +--- Search for clear ground spawn zones within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param Core.Zone#ZONE Zone to search. +-- @param #number (Optional) PosRadius Required clear radius around each position. (Default is math.min(Radius/10, 200)) +-- @param #number (Optional) NumPositions Number of positions to find. (Default 50) +-- @return #table A table of DCS#Vec2 positions that are clear of map objects within the given PosRadius. nil if no clear positions are found. +function UTILS.GetClearZonePositions(Zone, PosRadius, NumPositions) + local radius = PosRadius or math.min(Zone:GetRadius()/10, 200) + local clearPositions = UTILS.GetSimpleZones(Zone:GetVec3(), Zone:GetRadius(), radius, NumPositions or 50) + if clearPositions and #clearPositions > 0 then + local validZones = {} + for _, vec2 in pairs(clearPositions) do + if Zone:IsVec2InZone(vec2) then + table.insert(validZones, vec2) + end + end + if #validZones > 0 then + return validZones, radius + end + end + return nil +end + + +--- Search for a random clear ground spawn coordinate within this zone. A powerful and efficient function using Disposition to find clear areas for spawning ground units avoiding trees, water and map scenery. +-- @param Core.Zone#ZONE Zone to search. +-- @param #number PosRadius (Optional) Required clear radius around each position. (Default is math.min(Radius/10, 200)) +-- @param #number NumPositions (Optional) Number of positions to find. (Default 50) +-- @return Core.Point#COORDINATE A random coordinate for a clear zone. nil if no clear positions are found. +-- @return #number Assigned radius for the found zones. nil if no clear positions are found. +function UTILS.GetRandomClearZoneCoordinate(Zone, PosRadius, NumPositions) + local clearPositions = UTILS.GetClearZonePositions(Zone, PosRadius, NumPositions) + if clearPositions and #clearPositions > 0 then + local randomPosition, radius = clearPositions[math.random(1, #clearPositions)] + return COORDINATE:NewFromVec2(randomPosition), radius + end + + return nil +end + +--- Find the point on the radius of a circle closest to a point outside of the radius. +-- @param DCS#Vec2 Vec1 Simple Vec2 marking the middle of the circle. +-- @param #number Radius The radius of the circle. +-- @param DCS#Vec2 Vec2 Simple Vec2 marking the point outside of the circle. +-- @return DCS#Vec2 Vec2 point on the radius. +function UTILS.FindNearestPointOnCircle(Vec1,Radius,Vec2) + local r = Radius + local cx = Vec1.x or 1 + local cy = Vec1.y or 1 + local px = Vec2.x or 1 + local py = Vec2.y or 1 + + -- Berechne den Vektor vom Mittelpunkt zum externen Punkt + local dx = px - cx + local dy = py - cy + + -- Berechne die Länge des Vektors + local dist = math.sqrt(dx * dx + dy * dy) + + -- Wenn der Punkt im Mittelpunkt liegt, wähle einen Punkt auf der X-Achse + if dist == 0 then + return {x=cx + r, y=cy} + end + + -- Normalisiere den Vektor (richtungsweise Vektor mit Länge 1) + local norm_dx = dx / dist + local norm_dy = dy / dist + + -- Berechne den Punkt auf dem Rand des Kreises + local qx = cx + r * norm_dx + local qy = cy + r * norm_dy + + local shift_factor = 1 + qx = qx + shift_factor * norm_dx + qy = qy + shift_factor * norm_dy + + return {x=qx, y=qy} +end + +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +-- @param #table Positions A table of DCS#Vec2 or DCS#Vec3, can be a units table from the group template. +-- @param DCS#Vec2 Anchor (Optional) DCS#Vec2 or DCS#Vec3 as anchor point to calculate offset of the units. +-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the units. +-- @param #number Spacing (Optional) Minimum spacing between units in meters. Default is 5% of the search radius or 5 meters, whichever is larger. +function UTILS.ValidateAndRepositionGroundUnits(Positions, Anchor, MaxRadius, Spacing) + local units = Positions + Anchor = Anchor or UTILS.GetCenterPoint(units) + local gPos = { x = Anchor.x, y = Anchor.z or Anchor.y } + local maxRadius = 0 + local unitCount = 0 + for _, unit in pairs(units) do + local pos = { x = unit.x, y = unit.z or unit.y } + local dist = UTILS.VecDist2D(pos, gPos) + if dist > maxRadius then + maxRadius = dist + end + unitCount = unitCount + 1 + end + maxRadius = MaxRadius or math.max(maxRadius * 2, 10) + local spacing = Spacing or math.max(maxRadius * 0.05, 5) + if unitCount > 0 and maxRadius > 5 then + local spots = UTILS.GetSimpleZones(UTILS.Vec2toVec3(gPos), maxRadius, spacing, 1000) + if spots and #spots > 0 then + local validSpots = {} + for _, spot in pairs(spots) do -- Disposition sometimes returns points on roads, hence this filter. + if land.getSurfaceType(spot) == land.SurfaceType.LAND then + table.insert(validSpots, spot) + end + end + spots = validSpots + end + + local step = spacing + for _, unit in pairs(units) do + local pos = { x = unit.x, y = unit.z or unit.y } + local isOnLand = land.getSurfaceType(pos) == land.SurfaceType.LAND + local isValid = false + if spots and #spots > 0 then + local si = 1 + local sid = 0 + local closestDist = 100000000 + local closestSpot + for _, spot in pairs(spots) do + local dist = UTILS.VecDist2D(pos, spot) + if dist < closestDist then + closestDist = dist + closestSpot = spot + sid = si + end + si = si + 1 + end + if closestSpot then + if closestDist >= spacing then + pos = closestSpot + end + isValid = true + table.remove(spots, sid) + end + end + + -- Failsafe calculation + if not isValid and not isOnLand then + + local h = UTILS.HdgTo(pos, gPos) + local retries = 0 + while not isValid and retries < 500 do + + local dist = UTILS.VecDist2D(pos, gPos) + pos = UTILS.Vec2Translate(pos, step, h) + + local skip = false + for _, unit2 in pairs(units) do + if unit ~= unit2 then + local pos2 = { x = unit2.x, y = unit2.z or unit2.y } + local dist2 = UTILS.VecDist2D(pos, pos2) + if dist2 < 12 then + isValid = false + skip = true + break + end + end + end + + if not skip and dist > step and land.getSurfaceType(pos) == land.SurfaceType.LAND then + isValid = true + break + elseif dist <= step then + break + end + + retries = retries + 1 + end + end + + if isValid then + unit.x = pos.x + if unit.z then + unit.z = pos.y + else + unit.y = pos.y + end + end + end + end +end + +--- This function uses Disposition and other fallback logic to find better ground positions for statics. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +-- @param #table Positions A table of DCS#Vec2 or DCS#Vec3, can be a units table from the group template. +-- @param DCS#Vec2 Position DCS#Vec2 or DCS#Vec3 initial spawn location. +-- @param #number MaxRadius (Optional) Max radius to search for valid ground locations in meters. Default is double the max radius of the static. +-- @return DCS#Vec2 Initial Position if it's valid, else a valid spawn position. nil if no valid position found. +function UTILS.ValidateAndRepositionStatic(Country, Category, Type, Position, ShapeName, MaxRadius) + local coord = COORDINATE:NewFromVec2(Position) + local st = SPAWNSTATIC:NewFromType(Type, Category, Country) + if ShapeName then + st:InitShape(ShapeName) + end + local sName = "s-"..timer.getTime().."-"..math.random(1,10000) + local tempStatic = st:SpawnFromCoordinate(coord, 0, sName) + if tempStatic then + local sRadius = tempStatic:GetBoundingRadius(2) or 3 + tempStatic:Destroy() + sRadius = sRadius * 0.5 + MaxRadius = MaxRadius or math.max(sRadius * 10, 100) + local positions = UTILS.GetSimpleZones(coord:GetVec3(), MaxRadius, sRadius, 20) + if positions and #positions > 0 then + local closestSpot + local closestDist = math.huge + for _, spot in pairs(positions) do -- Disposition sometimes returns points on roads, hence this filter. + if land.getSurfaceType(spot) == land.SurfaceType.LAND then + local dist = UTILS.VecDist2D(Position, spot) + if dist < closestDist then + closestDist = dist + closestSpot = spot + end + end + end + + if closestSpot then + if closestDist >= sRadius then + return closestSpot + else + return Position + end + end + end + end + + return nil +end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 9c0928486..502b37dfc 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -451,7 +451,6 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Al_Dumayr -- * AIRBASE.Syria.Al_Qusayr -- * AIRBASE.Syria.Aleppo --- * AIRBASE.Syria.Amman -- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.At_Tanf -- * AIRBASE.Syria.Bassel_Al_Assad @@ -513,8 +512,9 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Wujah_Al_Hajar -- * AIRBASE.Syria.Ben_Gurion -- * AIRBASE.Syria.Hatzor --- * AIRBASE.Syria.Palmashim +-- * AIRBASE.Syria.Palmachim -- * AIRBASE.Syria.Tel_Nof +-- * AIRBASE.Syria.Marka -- --@field Syria AIRBASE.Syria={ @@ -524,7 +524,6 @@ AIRBASE.Syria={ ["Al_Dumayr"] = "Al-Dumayr", ["Al_Qusayr"] = "Al Qusayr", ["Aleppo"] = "Aleppo", - ["Amman"] = "Amman", ["An_Nasiriyah"] = "An Nasiriyah", ["At_Tanf"] = "At Tanf", ["Bassel_Al_Assad"] = "Bassel Al-Assad", @@ -556,6 +555,7 @@ AIRBASE.Syria={ ["Kuweires"] = "Kuweires", ["Lakatamia"] = "Lakatamia", ["Larnaca"] = "Larnaca", + ["Marka"] = "Marka", ["Marj_Ruhayyil"] = "Marj Ruhayyil", ["Marj_as_Sultan_North"] = "Marj as Sultan North", ["Marj_as_Sultan_South"] = "Marj as Sultan South", @@ -586,7 +586,7 @@ AIRBASE.Syria={ ["Wujah_Al_Hajar"] = "Wujah Al Hajar", ["Ben_Gurion"] = "Ben Gurion", ["Hatzor"] = "Hatzor", - ["Palmashim"] = "Palmashim", + ["Palmachim"] = "Palmachim", ["Tel_Nof"] = "Tel Nof", } @@ -613,6 +613,35 @@ AIRBASE.MarianaIslands = { ["Tinian_Intl"] = "Tinian Intl", } +--- Airbase of the Marianas WWII map +-- +-- * AIRBASE.MarianaIslandsWWII.Agana +-- * AIRBASE.MarianaIslandsWWII.Airfield_3 +-- * AIRBASE.MarianaIslandsWWII.Charon_Kanoa +-- * AIRBASE.MarianaIslandsWWII.Gurguan_Point +-- * AIRBASE.MarianaIslandsWWII.Isley +-- * AIRBASE.MarianaIslandsWWII.Kagman +-- * AIRBASE.MarianaIslandsWWII.Marpi +-- * AIRBASE.MarianaIslandsWWII.Orote +-- * AIRBASE.MarianaIslandsWWII.Pagan +-- * AIRBASE.MarianaIslandsWWII.Rota +-- * AIRBASE.MarianaIslandsWWII.Ushi +-- @field AIRBASE.MarianaIslandsWWII +AIRBASE.MarianaIslandsWWII = +{ + ["Agana"] = "Agana", + ["Airfield_3"] = "Airfield 3", + ["Charon_Kanoa"] = "Charon Kanoa", + ["Gurguan_Point"] = "Gurguan Point", + ["Isley"] = "Isley", + ["Kagman"] = "Kagman", + ["Marpi"] = "Marpi", + ["Orote"] = "Orote", + ["Pagan"] = "Pagan", + ["Rota"] = "Rota", + ["Ushi"] = "Ushi", +} + --- Airbases of the South Atlantic map: -- -- * AIRBASE.SouthAtlantic.Almirante_Schroeders @@ -676,51 +705,56 @@ AIRBASE.SouthAtlantic={ --- Airbases of the Sinai map: -- --- * AIRBASE.Sinai.Abu_Rudeis --- * AIRBASE.Sinai.Abu_Suwayr --- * AIRBASE.Sinai.Al_Bahr_al_Ahmar --- * AIRBASE.Sinai.Al_Ismailiyah --- * AIRBASE.Sinai.Al_Khatatbah --- * AIRBASE.Sinai.Al_Mansurah --- * AIRBASE.Sinai.Al_Rahmaniyah_Air_Base --- * AIRBASE.Sinai.As_Salihiyah --- * AIRBASE.Sinai.AzZaqaziq --- * AIRBASE.Sinai.Baluza --- * AIRBASE.Sinai.Ben_Gurion --- * AIRBASE.Sinai.Beni_Suef --- * AIRBASE.Sinai.Bilbeis_Air_Base --- * AIRBASE.Sinai.Bir_Hasanah --- * AIRBASE.Sinai.Birma_Air_Base --- * AIRBASE.Sinai.Borj_El_Arab_International_Airport --- * AIRBASE.Sinai.Cairo_International_Airport --- * AIRBASE.Sinai.Cairo_West --- * AIRBASE.Sinai.Difarsuwar_Airfield --- * AIRBASE.Sinai.El_Arish --- * AIRBASE.Sinai.El_Gora --- * AIRBASE.Sinai.El_Minya --- * AIRBASE.Sinai.Fayed --- * AIRBASE.Sinai.Gebel_El_Basur_Air_Base --- * AIRBASE.Sinai.Hatzerim --- * AIRBASE.Sinai.Hatzor --- * AIRBASE.Sinai.Hurghada_International_Airport --- * AIRBASE.Sinai.Inshas_Airbase --- * AIRBASE.Sinai.Jiyanklis_Air_Base --- * AIRBASE.Sinai.Kedem --- * AIRBASE.Sinai.Kibrit_Air_Base --- * AIRBASE.Sinai.Kom_Awshim --- * AIRBASE.Sinai.Melez --- * AIRBASE.Sinai.Nevatim --- * AIRBASE.Sinai.Ovda --- * AIRBASE.Sinai.Palmachim --- * AIRBASE.Sinai.Quwaysina --- * AIRBASE.Sinai.Ramon_Airbase --- * AIRBASE.Sinai.Ramon_International_Airport --- * AIRBASE.Sinai.Sde_Dov --- * AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport --- * AIRBASE.Sinai.St_Catherine --- * AIRBASE.Sinai.Tel_Nof --- * AIRBASE.Sinai.Wadi_Abu_Rish --- * AIRBASE.Sinai.Wadi_al_Jandali +-- * AIRBASE.SinaiMap.Abu_Rudeis +-- * AIRBASE.SinaiMap.Abu_Suwayr +-- * AIRBASE.SinaiMap.Al_Bahr_al_Ahmar +-- * AIRBASE.SinaiMap.Al_Ismailiyah +-- * AIRBASE.SinaiMap.Al_Khatatbah +-- * AIRBASE.SinaiMap.Al_Mansurah +-- * AIRBASE.SinaiMap.Al_Rahmaniyah_Air_Base +-- * AIRBASE.SinaiMap.As_Salihiyah +-- * AIRBASE.SinaiMap.AzZaqaziq +-- * AIRBASE.SinaiMap.Baluza +-- * AIRBASE.SinaiMap.Ben_Gurion +-- * AIRBASE.SinaiMap.Beni_Suef +-- * AIRBASE.SinaiMap.Bilbeis_Air_Base +-- * AIRBASE.SinaiMap.Bir_Hasanah +-- * AIRBASE.SinaiMap.Birma_Air_Base +-- * AIRBASE.SinaiMap.Borg_El_Arab_International_Airport +-- * AIRBASE.SinaiMap.Cairo_International_Airport +-- * AIRBASE.SinaiMap.Cairo_West +-- * AIRBASE.SinaiMap.Damascus_Intl +-- * AIRBASE.SinaiMap.Difarsuwar_Airfield +-- * AIRBASE.SinaiMap.El_Arish +-- * AIRBASE.SinaiMap.El_Gora +-- * AIRBASE.SinaiMap.El_Minya +-- * AIRBASE.SinaiMap.Fayed +-- * AIRBASE.SinaiMap.Gebel_El_Basur_Air_Base +-- * AIRBASE.SinaiMap.Hatzerim +-- * AIRBASE.SinaiMap.Hatzor +-- * AIRBASE.SinaiMap.Hurghada_International_Airport +-- * AIRBASE.SinaiMap.Inshas_Airbase +-- * AIRBASE.SinaiMap.Jiyanklis_Air_Base +-- * AIRBASE.SinaiMap.Kedem +-- * AIRBASE.SinaiMap.Kibrit_Air_Base +-- * AIRBASE.SinaiMap.Kom_Awshim +-- * AIRBASE.SinaiMap.Melez +-- * AIRBASE.SinaiMap.Mezzeh_Air_Base +-- * AIRBASE.SinaiMap.Nevatim +-- * AIRBASE.SinaiMap.Ovda +-- * AIRBASE.SinaiMap.Palmachim +-- * AIRBASE.SinaiMap.Quwaysina +-- * AIRBASE.SinaiMap.Rafic_Hariri_Intl +-- * AIRBASE.SinaiMap.Ramat_David +-- * AIRBASE.SinaiMap.Ramon_Airbase +-- * AIRBASE.SinaiMap.Ramon_International_Airport +-- * AIRBASE.SinaiMap.Sde_Dov +-- * AIRBASE.SinaiMap.Sharm_El_Sheikh_International_Airport +-- * AIRBASE.SinaiMap.St_Catherine +-- * AIRBASE.SinaiMap.Tabuk +-- * AIRBASE.SinaiMap.Tel_Nof +-- * AIRBASE.SinaiMap.Wadi_Abu_Rish +-- * AIRBASE.SinaiMap.Wadi_al_Jandali -- -- @field Sinai AIRBASE.Sinai = { @@ -739,9 +773,10 @@ AIRBASE.Sinai = { ["Bilbeis_Air_Base"] = "Bilbeis Air Base", ["Bir_Hasanah"] = "Bir Hasanah", ["Birma_Air_Base"] = "Birma Air Base", - ["Borj_El_Arab_International_Airport"] = "Borj El Arab International Airport", + ["Borg_El_Arab_International_Airport"] = "Borg El Arab International Airport", ["Cairo_International_Airport"] = "Cairo International Airport", ["Cairo_West"] = "Cairo West", + ["Damascus_Intl"] = "Damascus Intl", ["Difarsuwar_Airfield"] = "Difarsuwar Airfield", ["El_Arish"] = "El Arish", ["El_Gora"] = "El Gora", @@ -757,15 +792,19 @@ AIRBASE.Sinai = { ["Kibrit_Air_Base"] = "Kibrit Air Base", ["Kom_Awshim"] = "Kom Awshim", ["Melez"] = "Melez", + ["Mezzeh_Air_Base"] = "Mezzeh Air Base", ["Nevatim"] = "Nevatim", ["Ovda"] = "Ovda", ["Palmachim"] = "Palmachim", ["Quwaysina"] = "Quwaysina", + ["Rafic_Hariri_Intl"] = "Rafic Hariri Intl", + ["Ramat_David"] = "Ramat David", ["Ramon_Airbase"] = "Ramon Airbase", ["Ramon_International_Airport"] = "Ramon International Airport", ["Sde_Dov"] = "Sde Dov", ["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport", ["St_Catherine"] = "St Catherine", + ["Tabuk"] = "Tabuk", ["Tel_Nof"] = "Tel Nof", ["Wadi_Abu_Rish"] = "Wadi Abu Rish", ["Wadi_al_Jandali"] = "Wadi al Jandali", @@ -792,9 +831,14 @@ AIRBASE.Sinai = { -- * AIRBASE.Kola.Vidsel -- * AIRBASE.Kola.Vuojarvi -- * AIRBASE.Kola.Andoya --- * AIRBASE.Kola.Alakourtti +-- * AIRBASE.Kola.Alakurtti -- * AIRBASE.Kola.Kittila -- * AIRBASE.Kola.Bardufoss +-- * AIRBASE.Kola.Alta +-- * AIRBASE.Kola.Sodankyla +-- * AIRBASE.Kola.Enontekio +-- * AIRBASE.Kola.Evenes +-- * AIRBASE.Kola.Hosio -- -- @field Kola AIRBASE.Kola = { @@ -817,9 +861,20 @@ AIRBASE.Kola = { ["Vidsel"] = "Vidsel", ["Vuojarvi"] = "Vuojarvi", ["Andoya"] = "Andoya", - ["Alakourtti"] = "Alakourtti", + ["Alakurtti"] = "Alakurtti", ["Kittila"] = "Kittila", ["Bardufoss"] = "Bardufoss", + ["Alta"] = "Alta", + ["Sodankyla"] = "Sodankyla", + ["Enontekio"] = "Enontekio", + ["Evenes"] = "Evenes", + ["Hosio"] = "Hosio", + ["Kilpyavr"] = "Kilpyavr", + ["Afrikanda"] = "Afrikanda", + ["Kalevala"] = "Kalevala", + ["Koshka_Yavr"] = "Koshka Yavr", + ["Poduzhemye"] = "Poduzhemye", + ["Luostari_Pechenga"] = "Luostari Pechenga", } --- Airbases of the Afghanistan map @@ -880,43 +935,57 @@ AIRBASE.Afghanistan = { --- Airbases of the Iraq map -- --- * AIRBASE.Iraq.Baghdad_International_Airport --- * AIRBASE.Iraq.Sulaimaniyah_International_Airport --- * AIRBASE.Iraq.Al_Sahra_Airport --- * AIRBASE.Iraq.Erbil_International_Airpor --- * AIRBASE.Iraq.Al_Taji_Airport -- * AIRBASE.Iraq.Al_Asad_Airbase +-- * AIRBASE.Iraq.Al_Kut_Airbase +-- * AIRBASE.Iraq.Al_Sahra_Airport -- * AIRBASE.Iraq.Al_Salam_Airbase --- * AIRBASE.Iraq.Balad_Airbase --- * AIRBASE.Iraq.Kirkuk_International_Airport --- * AIRBASE.Iraq.Bashur_Airport +-- * AIRBASE.Iraq.Al_Taji_Airport -- * AIRBASE.Iraq.Al_Taquddum_Airport --- * AIRBASE.Iraq.Qayyarah_Airfield_West +-- * AIRBASE.Iraq.Baghdad_International_Airport +-- * AIRBASE.Iraq.Balad_Airbase +-- * AIRBASE.Iraq.Bashur_Airport +-- * AIRBASE.Iraq.Erbil_International_Airport +-- * AIRBASE.Iraq.Sulaimaniyah_International_Airport +-- * AIRBASE.Iraq.H2_Airbase +-- * AIRBASE.Iraq.H3_Main_Airbase +-- * AIRBASE.Iraq.H3_Northwest_Airbase +-- * AIRBASE.Iraq.H3_Southwest_Airbase -- * AIRBASE.Iraq.K1_Base +-- * AIRBASE.Iraq.Kirkuk_International_Airport +-- * AIRBASE.Iraq.Mosul_International_Airport +-- * AIRBASE.Iraq.Qayyarah_Airfield_West +-- * AIRBASE.Iraq.Sulaimaniyah_International_Airport -- -- @field Iraq AIRBASE.Iraq = { - ["Baghdad_International_Airport"] = "Baghdad International Airport", - ["Sulaimaniyah_International_Airport"] = "Sulaimaniyah International Airport", - ["Al_Sahra_Airport"] = "Al-Sahra Airport", - ["Erbil_International_Airport"] = "Erbil International Airport", - ["Al_Taji_Airport"] = "Al-Taji Airport", ["Al_Asad_Airbase"] = "Al-Asad Airbase", + ["Al_Kut_Airport"] = "Al-Kut Airport", + ["Al_Sahra_Airport"] = "Al-Sahra Airport", ["Al_Salam_Airbase"] = "Al-Salam Airbase", - ["Balad_Airbase"] = "Balad Airbase", - ["Kirkuk_International_Airport"] = "Kirkuk International Airport", - ["Bashur_Airport"] = "Bashur Airport", + ["Al_Taji_Airport"] = "Al-Taji Airport", ["Al_Taquddum_Airport"] = "Al-Taquddum Airport", - ["Qayyarah_Airfield_West"] = "Qayyarah Airfield West", + ["Baghdad_International_Airport"] = "Baghdad International Airport", + ["Balad_Airbase"] = "Balad Airbase", + ["Bashur_Airport"] = "Bashur Airport", + ["Erbil_International_Airport"] = "Erbil International Airport", + ["H2_Airbase"] = "H-2 Airbase", + ["H3_Main_Airbase"] = "H-3 Main Airbase", + ["H3_Northwest_Airbase"] = "H-3 Northwest Airbase", + ["H3_Southwest_Airbase"] = "H-3 Southwest Airbase", ["K1_Base"] = "K1 Base", + ["Kirkuk_International_Airport"] = "Kirkuk International Airport", + ["Mosul_International_Airport"] = "Mosul International Airport", + ["Qayyarah_Airfield_West"] = "Qayyarah Airfield West", + ["Sulaimaniyah_International_Airport"] = "Sulaimaniyah International Airport", } + --- Airbases of the Germany Cold War map -- * AIRBASE.GermanyCW.Airracing_Frankfurt +-- * AIRBASE.GermanyCW.Airracing_Frankfurt -- * AIRBASE.GermanyCW.Airracing_Koblenz -- * AIRBASE.GermanyCW.Airracing_Luebeck -- * AIRBASE.GermanyCW.Allstedt --- * AIRBASE.GermanyCW.Alt_Daber -- * AIRBASE.GermanyCW.Altes_Lager -- * AIRBASE.GermanyCW.Bad_Duerkheim -- * AIRBASE.GermanyCW.Barth @@ -939,14 +1008,13 @@ AIRBASE.Iraq = { -- * AIRBASE.GermanyCW.Fritzlar -- * AIRBASE.GermanyCW.Fulda -- * AIRBASE.GermanyCW.Gardelegen +-- * AIRBASE.GermanyCW.Garz -- * AIRBASE.GermanyCW.Gatow -- * AIRBASE.GermanyCW.Gelnhausen -- * AIRBASE.GermanyCW.Giebelstadt --- * AIRBASE.GermanyCW.Glindbruchkippe --- * AIRBASE.GermanyCW.Gross_Doelln +-- * AIRBASE.GermanyCW.Glindbruchkippe -- * AIRBASE.GermanyCW.Gross_Mohrdorf -- * AIRBASE.GermanyCW.Grosse_Wiese --- * AIRBASE.GermanyCW.Gaerz -- * AIRBASE.GermanyCW.Guetersloh -- * AIRBASE.GermanyCW.H_FRG_01 -- * AIRBASE.GermanyCW.H_FRG_02 @@ -996,7 +1064,7 @@ AIRBASE.Iraq = { -- * AIRBASE.GermanyCW.H_GDR_04 -- * AIRBASE.GermanyCW.H_GDR_05 -- * AIRBASE.GermanyCW.H_GDR_06 --- * AIRBASE.GermanyCW.H_GDR_07 +-- * AIRBASE.GermanyCW.H_GDR_07 -- * AIRBASE.GermanyCW.H_GDR_08 -- * AIRBASE.GermanyCW.H_GDR_09 -- * AIRBASE.GermanyCW.H_GDR_10 @@ -1018,10 +1086,11 @@ AIRBASE.Iraq = { -- * AIRBASE.GermanyCW.H_GDR_31 -- * AIRBASE.GermanyCW.H_GDR_32 -- * AIRBASE.GermanyCW.H_GDR_33 +-- * AIRBASE.GermanyCW.H_GDR_34 +-- * AIRBASE.GermanyCW.H_Med_FRG_01 -- * AIRBASE.GermanyCW.H_Med_FRG_02 -- * AIRBASE.GermanyCW.H_Med_FRG_04 -- * AIRBASE.GermanyCW.H_Med_FRG_06 --- * AIRBASE.GermanyCW.H_Med_FRG_09 -- * AIRBASE.GermanyCW.H_Med_FRG_11 -- * AIRBASE.GermanyCW.H_Med_FRG_12 -- * AIRBASE.GermanyCW.H_Med_FRG_13 @@ -1069,9 +1138,9 @@ AIRBASE.Iraq = { -- * AIRBASE.GermanyCW.Koethen -- * AIRBASE.GermanyCW.Laage -- * AIRBASE.GermanyCW.Langenselbold +-- * AIRBASE.GermanyCW.Laerz -- * AIRBASE.GermanyCW.Leipzig_Halle -- * AIRBASE.GermanyCW.Leipzig_Mockau --- * AIRBASE.GermanyCW.Laerz -- * AIRBASE.GermanyCW.Luebeck -- * AIRBASE.GermanyCW.Lueneburg -- * AIRBASE.GermanyCW.Mahlwinkel @@ -1089,14 +1158,15 @@ AIRBASE.Iraq = { -- * AIRBASE.GermanyCW.Pottschutthoehe -- * AIRBASE.GermanyCW.Ramstein -- * AIRBASE.GermanyCW.Rinteln --- * AIRBASE.GermanyCW.Schweinfurt -- * AIRBASE.GermanyCW.Schoenefeld +-- * AIRBASE.GermanyCW.Schweinfurt -- * AIRBASE.GermanyCW.Sembach -- * AIRBASE.GermanyCW.Spangdahlem -- * AIRBASE.GermanyCW.Sperenberg -- * AIRBASE.GermanyCW.Stendal -- * AIRBASE.GermanyCW.Tegel -- * AIRBASE.GermanyCW.Tempelhof +-- * AIRBASE.GermanyCW.Templin -- * AIRBASE.GermanyCW.Tutow -- * AIRBASE.GermanyCW.Uelzen -- * AIRBASE.GermanyCW.Uetersen @@ -1108,6 +1178,7 @@ AIRBASE.Iraq = { -- * AIRBASE.GermanyCW.Weser_Wuemme -- * AIRBASE.GermanyCW.Wiesbaden -- * AIRBASE.GermanyCW.Wismar +-- * AIRBASE.GermanyCW.Wittstock -- * AIRBASE.GermanyCW.Worms -- * AIRBASE.GermanyCW.Wunstorf -- * AIRBASE.GermanyCW.Zerbst @@ -1119,7 +1190,6 @@ AIRBASE.GermanyCW = { ["Airracing_Koblenz"] = "Airracing Koblenz", ["Airracing_Luebeck"] = "Airracing Lubeck", ["Allstedt"] = "Allstedt", - ["Alt_Daber"] = "Alt Daber", ["Altes_Lager"] = "Altes Lager", ["Bad_Duerkheim"] = "Bad Durkheim", ["Barth"] = "Barth", @@ -1142,14 +1212,13 @@ AIRBASE.GermanyCW = { ["Fritzlar"] = "Fritzlar", ["Fulda"] = "Fulda", ["Gardelegen"] = "Gardelegen", + ["Garz"] = "Garz", ["Gatow"] = "Gatow", ["Gelnhausen"] = "Gelnhausen", ["Giebelstadt"] = "Giebelstadt", - ["Glindbruchkippe_"] = "Glindbruchkippe ", - ["Gross_Doelln"] = "Gross Dolln", + ["Glindbruchkippe"] = "Glindbruchkippe ", ["Gross_Mohrdorf"] = "Gross Mohrdorf", ["Grosse_Wiese"] = "Grosse Wiese", - ["Gaerz"] = "Garz", ["Guetersloh"] = "Gutersloh", ["H_FRG_01"] = "H FRG 01", ["H_FRG_02"] = "H FRG 02", @@ -1221,10 +1290,11 @@ AIRBASE.GermanyCW = { ["H_GDR_31"] = "H GDR 31", ["H_GDR_32"] = "H GDR 32", ["H_GDR_33"] = "H GDR 33", + ["H_GDR_34"] = "H GDR 34", + ["H_Med_FRG_01"] = "H Med FRG 01", ["H_Med_FRG_02"] = "H Med FRG 02", ["H_Med_FRG_04"] = "H Med FRG 04", ["H_Med_FRG_06"] = "H Med FRG 06", - ["H_Med_FRG_09"] = "H Med FRG 09", ["H_Med_FRG_11"] = "H Med FRG 11", ["H_Med_FRG_12"] = "H Med FRG 12", ["H_Med_FRG_13"] = "H Med FRG 13", @@ -1272,9 +1342,9 @@ AIRBASE.GermanyCW = { ["Koethen"] = "Kothen", ["Laage"] = "Laage", ["Langenselbold"] = "Langenselbold", + ["Laerz"] = "Larz", ["Leipzig_Halle"] = "Leipzig Halle", ["Leipzig_Mockau"] = "Leipzig Mockau", - ["Laerz"] = "Larz", ["Luebeck"] = "Lubeck", ["Lueneburg"] = "Luneburg", ["Mahlwinkel"] = "Mahlwinkel", @@ -1292,14 +1362,15 @@ AIRBASE.GermanyCW = { ["Pottschutthoehe"] = "Pottschutthohe", ["Ramstein"] = "Ramstein", ["Rinteln"] = "Rinteln", - ["Schweinfurt"] = "Schweinfurt", ["Schoenefeld"] = "Schonefeld", + ["Schweinfurt"] = "Schweinfurt", ["Sembach"] = "Sembach", ["Spangdahlem"] = "Spangdahlem", ["Sperenberg"] = "Sperenberg", ["Stendal"] = "Stendal", ["Tegel"] = "Tegel", ["Tempelhof"] = "Tempelhof", + ["Templin"] = "Templin", ["Tutow"] = "Tutow", ["Uelzen"] = "Uelzen", ["Uetersen"] = "Uetersen", @@ -1311,6 +1382,7 @@ AIRBASE.GermanyCW = { ["Weser_Wuemme"] = "Weser Wumme", ["Wiesbaden"] = "Wiesbaden", ["Wismar"] = "Wismar", + ["Wittstock"] = "Wittstock", ["Worms"] = "Worms", ["Wunstorf"] = "Wunstorf", ["Zerbst"] = "Zerbst", @@ -1447,7 +1519,7 @@ function AIRBASE:Register(AirbaseName) self.descriptors=self:GetDesc() -- Debug info. - --self:I({airbase=AirbaseName, descriptors=self.descriptors}) + --self:T({airbase=AirbaseName, descriptors=self.descriptors}) -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME @@ -1530,6 +1602,17 @@ end return self end + +--- Get the true airbase center as seen in the ME. The position returned by the dcs object is is wrong and often at the start of the runway. +-- @return DCS#Vec2 The center of the true center of the airbase if it contains runways, otherwise the default DCS object position. +function AIRBASE:GetVec2() + local runways = self:GetRunways() + if runways and #runways > 0 then + return runways[1].center:GetVec2() + end + return self:GetCoordinate():GetVec2() +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() @@ -2765,11 +2848,12 @@ function AIRBASE:_InitRunways(IncludeInverse) runway.name=string.format("%02d", tonumber(namefromheading)) else runway.name=string.format("%02d", tonumber(name)) - self:I("RunwayName: "..runway.name) + --self:I("RunwayName: "..runway.name) end --runway.name=string.format("%02d", tonumber(name)) runway.magheading=tonumber(runway.name)*10 + runway.idx=runway.magheading runway.heading=heading runway.width=width or 0 runway.length=length or 0 @@ -3082,6 +3166,7 @@ function AIRBASE:GetRunwayData(magvar, mark) local runway={} --#AIRBASE.Runway runway.heading=hdg runway.idx=idx + runway.magheading=idx runway.length=c1:Get2DDistance(c2) runway.position=c1 runway.endpoint=c2 @@ -3097,6 +3182,57 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Add runway. table.insert(runways, runway) + end + + -- Look for identical (parallel) runways, e.g. 03L and 03R at Nellis. + local rpairs={} + for i,_ri in pairs(runways) do + local ri=_ri --#AIRBASE.Runway + for j,_rj in pairs(runways) do + local rj=_rj --#AIRBASE.Runway + if i 0 + return ((b.z - a.z)*(c.x - a.x) - (b.x - a.x)*(c.z - a.z)) > 0 + end + + for i,j in pairs(rpairs) do + local ri=runways[i] --#AIRBASE.Runway + local rj=runways[j] --#AIRBASE.Runway + + -- Draw arrow. + --ri.center:ArrowToAll(rj.center) + + local c0=ri.position + + -- Vector in the direction of the runway. + local a=UTILS.VecTranslate(c0, 1000, ri.heading) + + -- Vector from runway i to runway j. + local b=UTILS.VecSubstract(rj.position, ri.position) + b=UTILS.VecAdd(ri.position, b) + + -- Check if rj is left of ri. + local left=isLeft(c0, a, b) + + --env.info(string.format("Found pair %s: i=%d, j=%d, left==%s", ri.name, i, j, tostring(left))) + + if left then + ri.isLeft=false + rj.isLeft=true + else + ri.isLeft=true + rj.isLeft=false + end + + --break end return runways diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index b9f5a0d86..780dc31c8 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -168,16 +168,25 @@ -- * @{#CONTROLLABLE.OptionAlarmStateGreen} -- * @{#CONTROLLABLE.OptionAlarmStateRed} -- --- ## 5.4) Jettison weapons: +-- ## 5.4) [AIR] Jettison weapons: -- -- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat} -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- --- ## 5.5) Air-2-Air missile attack range: +-- ## 5.5) [AIR] Air-2-Air missile attack range: -- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. -- -- # 6) [GROUND] IR Maker Beacons for GROUPs and UNITs -- * @{#CONTROLLABLE:NewIRMarker}(): Create a blinking IR Marker on a GROUP or UNIT. +-- +-- # 7) [HELICOPTER] Units prefer vertical landing and takeoffs: +-- * @{#CONTROLLABLE.OptionPreferVerticalLanding}(): Set aircraft to prefer vertical landing and takeoff. +-- +-- # 8) [AIRCRAFT] Landing approach options +-- * @{#CONTROLLABLE.SetOptionLandingStraightIn}(): Landing approach straight in. +-- * @{#CONTROLLABLE.SetOptionLandingForcePair}(): Landing approach in pairs for groups > 1 unit. +-- * @{#CONTROLLABLE.SetOptionLandingRestrictPair}(): Landing approach single. +-- * @{#CONTROLLABLE.SetOptionLandingOverheadBreak}(): Landing approach overhead break. -- -- @field #CONTROLLABLE CONTROLLABLE = { @@ -1432,7 +1441,7 @@ end -- @param #number Speed The speed [m/s] flying when holding the position. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) - self:F2( { self.ControllableName, Point, Altitude, Speed } ) + --self:F2( { self.ControllableName, Point, Altitude, Speed } ) local DCSTask = { id = 'Orbit', @@ -3629,6 +3638,26 @@ function CONTROLLABLE:OptionROTPassiveDefense() return nil end +--- Helicopter - prefer vertical landing. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionPreferVerticalLanding() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.PREFER_VERTICAL, true ) + end + + return self + end + + return nil +end + --- Can the CONTROLLABLE evade on enemy fire? -- @param #CONTROLLABLE self -- @return #boolean @@ -4134,7 +4163,7 @@ function CONTROLLABLE:OptionRestrictBurner( RestrictBurner ) end ---- Sets Controllable Option for A2A attack range for AIR FIGHTER units. +--- [AIR] Sets Controllable Option for A2A attack range for AIR FIGHTER units. -- @param #CONTROLLABLE self -- @param #number range Defines the range -- @return #CONTROLLABLE self @@ -4159,6 +4188,66 @@ function CONTROLLABLE:OptionAAAttackRange( range ) return nil end +--- [GROUND/AAA] Sets Controllable Option for Ground AAA minimum firing height. +-- @param #CONTROLLABLE self +-- @param #number meters The minimum height in meters. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) + self:F2( { self.ControllableName } ) + local meters = meters or 20 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsGround() then + self:SetOption(27, meters) + end + end + return self + end + return nil +end + +--- [GROUND/AAA] Sets Controllable Option for Ground AAA maximum firing height. +-- @param #CONTROLLABLE self +-- @param #number meters The maximum height in meters. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) + self:F2( { self.ControllableName } ) + local meters = meters or 1000 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsGround() then + self:SetOption(29, meters) + end + end + return self + end + return nil +end + +--- [GROUND/AAA] Sets Controllable Option for Ground AAA minimum firing height. +-- @param #CONTROLLABLE self +-- @param #number feet The minimum height in feet. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) + self:F2( { self.ControllableName } ) + local feet = feet or 60 + return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end + +--- [GROUND/AAA] Sets Controllable Option for Ground AAA maximum firing height. +-- @param #CONTROLLABLE self +-- @param #number feet The maximum height in feet. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) + self:F2( { self.ControllableName } ) + local feet = feet or 3000 + return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end + --- Defines the range at which a GROUND unit/group is allowed to use its weapons automatically. -- @param #CONTROLLABLE self -- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. @@ -4183,6 +4272,50 @@ function CONTROLLABLE:OptionEngageRange( EngageRange ) return nil end +--- [AIR] Set how the AI lands on an airfield. Here: Straight in. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetOptionLandingStraightIn() + self:F2( { self.ControllableName } ) + if self:IsAir() then + self:SetOption("36","0") + end + return self +end + +--- [AIR] Set how the AI lands on an airfield. Here: In pairs (if > 1 aircraft in group) +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetOptionLandingForcePair() + self:F2( { self.ControllableName } ) + if self:IsAir() then + self:SetOption("36","1") + end + return self +end + +--- [AIR] Set how the AI lands on an airfield. Here: No landing in pairs. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetOptionLandingRestrictPair() + self:F2( { self.ControllableName } ) + if self:IsAir() then + self:SetOption("36","2") + end + return self +end + +--- [AIR] Set how the AI lands on an airfield. Here: Overhead break. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetOptionLandingOverheadBreak() + self:F2( { self.ControllableName } ) + if self:IsAir() then + self:SetOption("36","3") + end + return self +end + --- [AIR] Set how the AI uses the onboard radar. -- @param #CONTROLLABLE self -- @param #number Option Options are: `NEVER = 0, FOR_ATTACK_ONLY = 1,FOR_SEARCH_IF_REQUIRED = 2, FOR_CONTINUOUS_SEARCH = 3` diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua index db0d79834..eb033972e 100644 --- a/Moose Development/Moose/Wrapper/DynamicCargo.lua +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -108,6 +108,8 @@ DYNAMICCARGO.State = { -- @type DYNAMICCARGO.AircraftTypes DYNAMICCARGO.AircraftTypes = { ["CH-47Fbl1"] = "CH-47Fbl1", + ["Mi-8MTV2"] = "CH-47Fbl1", + ["Mi-8MT"] = "CH-47Fbl1", } --- Helo types possible. @@ -120,17 +122,30 @@ DYNAMICCARGO.AircraftDimensions = { ["length"] = 11, ["ropelength"] = 30, }, + ["Mi-8MTV2"] = { + ["width"] = 6, + ["height"] = 6, + ["length"] = 15, + ["ropelength"] = 30, + }, + ["Mi-8MT"] = { + ["width"] = 6, + ["height"] = 6, + ["length"] = 15, + ["ropelength"] = 30, + }, } --- DYNAMICCARGO class version. -- @field #string version -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.0.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: A lot... +-- DONE: Added Mi-8 type and dimensions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index fa597e461..1c38db672 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -230,6 +230,8 @@ GROUP.Attribute = { GROUND_EWR="Ground_EWR", GROUND_AAA="Ground_AAA", GROUND_SAM="Ground_SAM", + GROUND_SHORAD="Ground_SHORAD", + GROUND_BALLISTICMISSILE="Ground_BallisticMissile", GROUND_OTHER="Ground_OtherGround", NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", NAVAL_WARSHIP="Naval_WarShip", @@ -912,15 +914,18 @@ function GROUP:GetVelocityVec3() if DCSGroup and DCSGroup:isExist() then local GroupUnits = DCSGroup:getUnits() - local GroupCount = #GroupUnits + local GroupCount = 0 local VelocityVec3 = { x = 0, y = 0, z = 0 } for _, DCSUnit in pairs( GroupUnits ) do - local UnitVelocityVec3 = DCSUnit:getVelocity() - VelocityVec3.x = VelocityVec3.x + UnitVelocityVec3.x - VelocityVec3.y = VelocityVec3.y + UnitVelocityVec3.y - VelocityVec3.z = VelocityVec3.z + UnitVelocityVec3.z + if DCSUnit:isExist() and DCSUnit:isActive() then + local UnitVelocityVec3 = DCSUnit:getVelocity() + VelocityVec3.x = VelocityVec3.x + UnitVelocityVec3.x + VelocityVec3.y = VelocityVec3.y + UnitVelocityVec3.y + VelocityVec3.z = VelocityVec3.z + UnitVelocityVec3.z + GroupCount = GroupCount + 1 + end end VelocityVec3.x = VelocityVec3.x / GroupCount @@ -1754,11 +1759,13 @@ function GROUP:GetMaxVelocity() for Index, UnitData in pairs( DCSGroup:getUnits() ) do - local UnitVelocityVec3 = UnitData:getVelocity() - local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z ) + if UnitData:isExist() and UnitData:isActive() then + local UnitVelocityVec3 = UnitData:getVelocity() + local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z ) - if UnitVelocity > GroupVelocityMax then - GroupVelocityMax = UnitVelocity + if UnitVelocity > GroupVelocityMax then + GroupVelocityMax = UnitVelocity + end end end @@ -2225,6 +2232,10 @@ function GROUP:Respawn( Template, Reset ) --UTILS.PrintTableToLog(Template) + if self.ValidateAndRepositionGroundUnits then + UTILS.ValidateAndRepositionGroundUnits(Template.units) + end + -- Spawn new group. self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template) --_DATABASE:Spawn(Template) @@ -2633,6 +2644,8 @@ function GROUP:GetAttribute() local artillery=self:HasAttribute("Artillery") local tank=self:HasAttribute("Old Tanks") or self:HasAttribute("Modern Tanks") or self:HasAttribute("Tanks") local aaa=self:HasAttribute("AAA") and (not self:HasAttribute("SAM elements")) + local ballisticMissile=artillery and self:HasAttribute("SS_missile") + local shorad=self:HasAttribute("SR SAM") local ewr=self:HasAttribute("EWR") local ifv=self:HasAttribute("IFV") local sam=self:HasAttribute("SAM elements") or self:HasAttribute("Optical Tracker") @@ -2674,6 +2687,8 @@ function GROUP:GetAttribute() attribute=GROUP.Attribute.GROUND_SAM elseif aaa then attribute=GROUP.Attribute.GROUND_AAA + elseif artillery and ballisticMissile then + attribute=GROUP.Attribute.GROUND_BALLISTICMISSILE elseif artillery then attribute=GROUP.Attribute.GROUND_ARTILLERY elseif tank then @@ -3186,3 +3201,60 @@ function GROUP:IsAAA() end return isAAA end + +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +--- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #GROUP self +-- @param #boolean Enabled Enable/disable the feature. +function GROUP:SetValidateAndRepositionGroundUnits(Enabled) + self.ValidateAndRepositionGroundUnits = Enabled +end + + +--- Get the bounding box of the group combining UNIT:GetBoundingBox() units. +-- @param #GROUP self +-- @return DCS#Box3 The bounding box of the GROUP. +-- @return #nil The GROUP does not have any alive units. +function GROUP:GetBoundingBox() + local bbox = { min = { x = math.huge, y = math.huge, z = math.huge }, + max = { x = -math.huge, y = -math.huge, z = -math.huge } + } + + local Units = self:GetUnits() or {} + if #Units == 0 then + return nil + end + + for _, unit in pairs(Units) do + if unit and unit:IsAlive() then + local ubox = unit:GetBoundingBox() + + if ubox then + if ubox.min.x < bbox.min.x then + bbox.min.x = ubox.min.x + end + if ubox.min.y < bbox.min.y then + bbox.min.y = ubox.min.y + end + if ubox.min.z < bbox.min.z then + bbox.min.z = ubox.min.z + end + + if ubox.max.x > bbox.max.x then + bbox.max.x = ubox.max.x + end + if ubox.max.y > bbox.max.y then + bbox.max.y = ubox.max.y + end + if ubox.max.z > bbox.max.z then + bbox.max.z = ubox.max.z + end + end + end + end + + return bbox +end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 41857a215..038eefe56 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -246,18 +246,20 @@ end function POSITIONABLE:GetVec3() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - --local status, vec3 = pcall( - -- function() - -- local vec3 = DCSPositionable:getPoint() - -- return vec3 - --end - --) + local vec3 = DCSPositionable:getPoint() - --if status then - return vec3 - --else - --self:E( { "Cannot get Vec3 from DCS Object", Positionable = self, Alive = self:IsAlive() } ) - --end + + if not vec3 then + local pos = DCSPositionable:getPosition() + if pos and pos.p then + vec3 = pos.p + else + self:E( { "Cannot get the position from DCS Object for GetVec3", Positionable = self, Alive = self:IsAlive() } ) + end + end + + return vec3 + end -- ERROR! self:E( { "Cannot get the Positionable DCS Object for GetVec3", Positionable = self, Alive = self:IsAlive() } ) @@ -359,15 +361,17 @@ function POSITIONABLE:GetCoord() -- Get the current position. local PositionableVec3 = self:GetVec3() - if self.coordinate then - -- Update COORDINATE from 3D vector. - self.coordinate:UpdateFromVec3( PositionableVec3 ) - else - -- New COORDINATE. - self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 ) - end + if PositionableVec3 then + if self.coordinate then + -- Update COORDINATE from 3D vector. + self.coordinate:UpdateFromVec3( PositionableVec3 ) + else + -- New COORDINATE. + self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 ) + end - return self.coordinate + return self.coordinate + end end -- Error message. @@ -388,13 +392,13 @@ function POSITIONABLE:GetCoordinate() -- Get the current position. local PositionableVec3 = self:GetVec3() - - local coord=COORDINATE:NewFromVec3(PositionableVec3) - local heading = self:GetHeading() - coord.Heading = heading - -- Return a new coordiante object. - return coord - + if PositionableVec3 then + local coord=COORDINATE:NewFromVec3(PositionableVec3) + local heading = self:GetHeading() + coord.Heading = heading + -- Return a new coordiante object. + return coord + end end -- Error message. diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 4b445613c..5b5c23b3d 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -753,7 +753,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) + self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") end end @@ -773,7 +773,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) + self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") end end @@ -805,7 +805,7 @@ function STORAGE:LoadFromFile(Path,Filename) end end else - self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) + self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") end end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 10d59ed81..9d2ab5cfd 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -377,6 +377,10 @@ function UNIT:ReSpawnAt(Coordinate, Heading) --self:T( SpawnGroupTemplate ) + if self.ValidateAndRepositionGroundUnits then + UTILS.ValidateAndRepositionGroundUnits(SpawnGroupTemplate.units) + end + _DATABASE:Spawn(SpawnGroupTemplate) end @@ -897,7 +901,7 @@ function UNIT:GetAmmunition() nAPshells = nAPshells + Nammo end - if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName, "_HE", 1, true) then + if ammotable[w].desc.typeName and (string.find(ammotable[w].desc.typeName, "_HE", 1, true) or string.find(ammotable[w].desc.typeName, "HESH", 1, true)) then nHEshells = nHEshells + Nammo end @@ -1107,7 +1111,6 @@ function UNIT:GetUnits() if DCSUnit then Units[1] = UNIT:Find(DCSUnit) - - self:T3(Units) return Units end @@ -1925,3 +1928,28 @@ function UNIT:IsAAA() end return false end + +--- Set the relative life points of a UNIT object +-- @param #UNIT self +-- @param #number Percent Percent to set, can be 0..100. +function UNIT:SetLife(Percent) + net.dostring_in("mission",string.format("a_unit_set_life_percentage(%d, %f)", self:GetID(), Percent)) +end + +--- Set the carrier illumination mode. -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +-- @param #UNIT self +-- @param #number Mode Illumination mode, can be -2: OFF, -1: AUTO, 0: NAVIGATION, 1: AC LAUNCH, 2: AC RECOVERY +function UNIT:SetCarrierIlluminationMode(Mode) + UTILS.SetCarrierIlluminationMode(self:GetID(), Mode) +end + +--- This function uses Disposition and other fallback logic to find better ground positions for ground units. +--- NOTE: This is not a spawn randomizer. +--- It will try to find clear ground locations avoiding trees, water, roads, runways, map scenery, statics and other units in the area and modifies the provided positions table. +--- Maintains the original layout and unit positions as close as possible by searching for the next closest valid position to each unit. +--- Uses UTILS.ValidateAndRepositionGroundUnits. +-- @param #UNIT self +-- @param #boolean Enabled Enable/disable the feature. +function UNIT:SetValidateAndRepositionGroundUnits(Enabled) + self.ValidateAndRepositionGroundUnits = Enabled +end