Compare commits

..

62 Commits

Author SHA1 Message Date
Applevangelist d13ceb847e * Added SetDefaultINTERCEPTAlt 2026-06-28 09:30:14 +02:00
Applevangelist 0f9921f16b #MSRS #HOUND - fixed frequency not formatted in a way allowing frequencys smaller the 10th after the decimal dot. 2026-06-27 10:51:01 +02:00
Applevangelist 82282ad2e4 Merge remote-tracking branch 'origin/develop' into branch 2026-06-27 10:13:33 +02:00
Applevangelist b33f17d3df Merge remote-tracking branch 'origin/master-ng' into branch 2026-06-27 10:13:22 +02:00
Applevangelist d4b0afa635 xx 2026-06-27 10:12:50 +02:00
Applevangelist 9820c26d4b Merge remote-tracking branch 'origin/develop' into branch 2026-06-21 12:55:52 +02:00
Applevangelist d1d94a99dc Merge remote-tracking branch 'origin/master-ng' into branch 2026-06-21 12:55:41 +02:00
Applevangelist 4767ad45cd xx 2026-06-21 12:55:09 +02:00
Applevangelist 7d4432fa47 #AIRBASE - Add Syria Airbases 2026-06-14 13:43:42 +02:00
Applevangelist 4b6e860512 Merge remote-tracking branch 'origin/develop' into branch 2026-06-14 12:08:23 +02:00
Applevangelist 2332440f81 Merge remote-tracking branch 'origin/master-ng' into branch 2026-06-14 12:08:16 +02:00
Applevangelist 5f64ac30f0 Merge remote-tracking branch 'origin/develop' into branch 2026-06-11 17:35:41 +02:00
Applevangelist b5e4846fb1 Merge remote-tracking branch 'origin/master-ng' into branch 2026-06-11 17:35:32 +02:00
Applevangelist 2146e92154 Merge remote-tracking branch 'origin/master' into branch
# Conflicts:
#	Moose Development/Moose/Utilities/Enums.lua
2026-06-05 14:26:43 +02:00
Applevangelist 6b1903466e Merge remote-tracking branch 'origin/master-ng' into branch 2026-06-05 14:26:22 +02:00
Applevangelist b430a3450e xx 2026-06-05 14:24:14 +02:00
Applevangelist 22ae3cf796 Merge remote-tracking branch 'origin/develop' into branch 2026-05-14 10:48:42 +02:00
Applevangelist 62cdcf5536 Merge remote-tracking branch 'origin/master-ng' into branch 2026-05-14 10:48:36 +02:00
Applevangelist ff55fd5a99 Merge remote-tracking branch 'origin/develop' into branch 2026-05-10 13:05:40 +02:00
Applevangelist 8ee86d3128 Merge remote-tracking branch 'origin/master-ng' into branch 2026-05-10 13:05:30 +02:00
Applevangelist 2ab3d7150d #SET - Added function to set Regex and Dash Replacement options on name filter (FilterPrefixes)
- Added pseudo-docu for some functions inherited from SET_BASE to show functions inherited from SET_BASE properly in intellisense
2026-05-01 12:45:59 +02:00
Applevangelist 957b72ab67 Merge remote-tracking branch 'origin/develop' into branch 2026-04-25 18:15:20 +02:00
Applevangelist d4a00e30e1 Merge remote-tracking branch 'origin/master-ng' into branch
# Conflicts:
#	Moose Development/Moose/Ops/TARS.lua
2026-04-25 18:15:09 +02:00
Applevangelist 74174906f3 xx 2026-04-25 16:50:15 +02:00
Applevangelist 4e8efc19ac Merge remote-tracking branch 'origin/develop' into branch 2026-04-21 18:29:05 +02:00
Applevangelist 21920f9d2e Merge remote-tracking branch 'origin/master-ng' into branch
# Conflicts:
#	Moose Development/Moose/Ops/TARS.lua
2026-04-21 18:28:56 +02:00
Applevangelist 9a938080b8 #TARS - Added some docu commentary, removed log noise, etc 2026-04-21 18:27:52 +02:00
Applevangelist dae4dc2d40 x 2026-04-21 18:19:22 +02:00
Applevangelist 85bb9a0566 #TARS
- Correct optical view projection implemented (ellipse bc of the angle of view)
- Duration now describes the number of expositions your camera has
- Interval of shots is dependent on the speed of the TARS unit
- Added platform parameters overlap (of shots, usually 25% for faster movers, 50% for slower ones), overlap is clamped by minimum interval — at high speeds the interval floor is reached and overlap effectively increases (and you go through shots really fast)
- Fixed zone draw for debug
2026-04-19 15:29:48 +02:00
Applevangelist 338d38d2a2 #TARS added option TARS_SESSION.debugunitsearch 2026-04-16 12:37:35 +02:00
Applevangelist dde08f2bbe Merge remote-tracking branch 'origin/develop' into branch 2026-04-16 11:20:51 +02:00
Applevangelist 55224c96ab Merge remote-tracking branch 'origin/master-ng' into branch
# Conflicts:
#	Moose Development/Moose/Ops/TARS.lua
#	Moose Setup/Moose.files
2026-04-16 11:20:43 +02:00
Applevangelist 257801edca xx 2026-04-16 11:19:21 +02:00
Applevangelist de71c1d903 #TARS Release 2026-04-12 12:52:07 +02:00
Applevangelist 6863398d52 Merge remote-tracking branch 'origin/develop' into branch 2026-04-08 15:19:04 +02:00
Applevangelist a019a511b1 Merge remote-tracking branch 'origin/master-ng' into branch 2026-04-08 15:18:58 +02:00
Applevangelist 85ee0ae38e Merge remote-tracking branch 'origin/develop' into branch 2026-04-08 14:44:56 +02:00
Applevangelist f26cbc5b13 Merge remote-tracking branch 'origin/master-ng' into branch 2026-04-08 14:44:49 +02:00
Applevangelist 23d142a4c9 xx 2026-04-08 14:40:58 +02:00
Applevangelist 4956809c3f xx 2026-04-07 12:19:18 +02:00
Applevangelist 04befd1b61 xx 2026-04-06 19:55:58 +02:00
Thomas 5facdd9363 Merge pull request #2572 from FlightControl-Master/Applevangelist-patch-1
Delete Moose Development/Moose/Functional/REDGCI.lua
2026-04-06 19:05:27 +02:00
Thomas 04bea41400 Create RedGCI.lua 2026-04-06 19:04:57 +02:00
Thomas a4c4913ead Delete Moose Development/Moose/Functional/REDGCI.lua 2026-04-06 19:04:11 +02:00
Applevangelist 1405e10257 xx 2026-04-06 19:01:11 +02:00
Applevangelist d042c778f4 xx 2026-04-06 18:53:30 +02:00
Applevangelist d136470149 xx 2026-04-06 18:47:29 +02:00
Applevangelist 29dfbbf314 Merge remote-tracking branch 'origin/develop' into branch 2026-04-04 17:18:38 +02:00
Applevangelist c54bd793f2 Merge remote-tracking branch 'origin/master-ng' into branch 2026-04-04 17:18:29 +02:00
Applevangelist fddac77c79 xx 2026-04-04 17:17:04 +02:00
Applevangelist 044a49a61a xx 2026-03-27 17:39:45 +01:00
Applevangelist 44aeed0cbd xx 2026-03-27 17:24:59 +01:00
Applevangelist 84e20dae29 #REDGCI 2026-03-27 15:10:23 +01:00
Applevangelist 0806d822ff xx 2026-03-26 11:08:05 +01:00
Applevangelist b577b3db29 Merge remote-tracking branch 'origin/master-ng' into branchxx 2026-03-26 09:20:27 +01:00
Applevangelist 64433394f5 Merge remote-tracking branch 'origin/Apple/Develop' into branch 2026-03-26 09:11:15 +01:00
Applevangelist 9fb9b6378e xx 2026-03-26 09:11:03 +01:00
Thomas a66290ab63 Merge pull request #2558 from FlightControl-Master/master-ng
Merge from master
2026-03-24 10:13:47 +01:00
Applevangelist 58eeabe3fa xx 2026-03-22 16:31:37 +01:00
Applevangelist 2cc7356520 xx 2026-03-21 15:43:46 +01:00
Thomas c335881b2d Merge pull request #2505 from shaji-Dev/master
[ADDED] 268 Missing Warehouse EQP ENUMS
2026-02-09 18:09:24 +01:00
smiki 993fc45e83 [ADDED] 268 Missing Warehouse EQP ENUMS 2026-02-09 16:48:32 +01:00
38 changed files with 5248 additions and 7474 deletions
-22
View File
@@ -735,12 +735,6 @@ do -- Event Handling
-- @function [parent=#BASE] OnEventPlayerEnterAircraft -- @function [parent=#BASE] OnEventPlayerEnterAircraft
-- @param #BASE self -- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure. -- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an option is changed for an AI group.
-- The event identifies the initiating group, but does not provide the option ID or value.
-- @function [parent=#BASE] OnEventGroupChangeOption
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a player creates a dynamic cargo object from the F8 ground crew menu. --- Occurs when a player creates a dynamic cargo object from the F8 ground crew menu.
-- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024. -- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024.
@@ -886,22 +880,6 @@ end
world.onEvent(Event) world.onEvent(Event)
end end
--- Creation of a `S_EVENT_GROUP_CHANGE_OPTION` event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Group Initiator The initiating group of the event.
function BASE:CreateEventGroupChangeOption( EventTime, Initiator )
self:F( { EventTime, Initiator } )
local Event = {
id = EVENTS.GroupChangeOption,
time = EventTime,
initiator = Initiator,
}
world.onEvent( Event )
end
--- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event. --- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event.
-- @param #BASE self -- @param #BASE self
-36
View File
@@ -92,8 +92,6 @@ DATABASE = {
ZONES_GOAL = {}, ZONES_GOAL = {},
WAREHOUSES = {}, WAREHOUSES = {},
FLIGHTGROUPS = {}, FLIGHTGROUPS = {},
COHORTS={},
LEGIONS={},
FLIGHTCONTROLS = {}, FLIGHTCONTROLS = {},
OPSZONES = {}, OPSZONES = {},
PATHLINES = {}, PATHLINES = {},
@@ -2089,40 +2087,6 @@ function DATABASE:FindOpsGroupFromUnit(unitname)
end end
end end
--
--- Add an OPS COHORT (SQUADRON, PLATOON, FLOTILLA) to the data base.
-- @param #DATABASE self
-- @param Ops.Cohort#COHORT cohort The cohort added to the DB.
function DATABASE:AddCohort(cohort)
self.COHORTS[cohort.name]=cohort
end
--- Find an OPS COHORT (SQUADRON, PLATOON, FLOTILLA) in the data base.
-- @param #DATABASE self
-- @param #string cohortname Name of the cohort.
-- @return Ops.Cohort#COHORT Cohort object.
function DATABASE:FindCohort(cohortname)
return self.COHORTS[cohortname]
end
--- Add an OPS LEGION (AIRWING, BRIGADE, FLEET) to the data base.
-- @param #DATABASE self
-- @param Ops.Legion#LEGION legion The legion added to the DB.
function DATABASE:AddLegion(legion)
self.LEGIONS[legion.alias]=legion
end
--- Find an OPS LEGION (AIRWING, BRIGADE, FLEET) in the data base.
-- @param #DATABASE self
-- @param #string legionname Name of the legion.
-- @return Ops.Legion#LEGION Legion object.
function DATABASE:FindLegion(legionname)
return self.LEGIONS[legionname]
end
--
--- Add a flight control to the data base. --- Add a flight control to the data base.
-- @param #DATABASE self -- @param #DATABASE self
-- @param OPS.FlightControl#FLIGHTCONTROL flightcontrol -- @param OPS.FlightControl#FLIGHTCONTROL flightcontrol
+7 -25
View File
@@ -278,7 +278,6 @@ EVENTS = {
SimulationUnfreeze = world.event.S_EVENT_SIMULATION_UNFREEZE or -1, SimulationUnfreeze = world.event.S_EVENT_SIMULATION_UNFREEZE or -1,
HumanAircraftRepairStart = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or -1, HumanAircraftRepairStart = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or -1,
HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1, HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1,
GroupChangeOption = world.event.S_EVENT_GROUP_CHANGE_OPTION or -1,
-- dynamic cargo -- dynamic cargo
NewDynamicCargo = world.event.S_EVENT_NEW_DYNAMIC_CARGO or -1, NewDynamicCargo = world.event.S_EVENT_NEW_DYNAMIC_CARGO or -1,
DynamicCargoLoaded = world.event.S_EVENT_DYNAMIC_CARGO_LOADED or -1, DynamicCargoLoaded = world.event.S_EVENT_DYNAMIC_CARGO_LOADED or -1,
@@ -292,25 +291,23 @@ EVENTS = {
-- --
-- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event. -- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event.
-- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event. -- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.
-- * A GROUP : A DCS Group object is involved in the Event.
-- --
-- @type EVENTDATA -- @type EVENTDATA
-- @field #number id The identifier of the event. -- @field #number id The identifier of the event.
-- --
-- @field DCS#Unit initiator (UNIT/STATIC/SCENERY/GROUP) The initiating @{DCS#Unit} or @{DCS#StaticObject}. For `S_EVENT_GROUP_CHANGE_OPTION`, this is a DCS Group. -- @field DCS#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
-- @field DCS#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ). -- @field DCS#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ).
-- @field DCS#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCS#Unit} or @{DCS#StaticObject}. -- @field DCS#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
-- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name. -- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name.
-- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Wrapper.Unit#UNIT} of the initiator Unit object. -- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Wrapper.Unit#UNIT} of the initiator Unit object.
-- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName). -- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName).
-- @field DCS#Group IniDCSGroup (UNIT/GROUP) The initiating @{DCS#Group}. -- @field DCS#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}.
-- @field #string IniDCSGroupName (UNIT/GROUP) The initiating Group name. -- @field #string IniDCSGroupName (UNIT) The initiating Group name.
-- @field Wrapper.Group#GROUP IniGroup (UNIT/GROUP) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object. -- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object.
-- @field #string IniGroupName (UNIT/GROUP) The initiating GROUP name (same as IniDCSGroupName). -- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName).
-- @field DCS#Group.Category IniGroupCategory (GROUP) The category of the initiating group.
-- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot. -- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot.
-- @field #string IniPlayerUCID (UNIT) The UCID of the initiating player in case the Unit is a client or player slot and on a multi-player server. -- @field #string IniPlayerUCID (UNIT) The UCID of the initiating player in case the Unit is a client or player slot and on a multi-player server.
-- @field DCS#coalition.side IniCoalition (UNIT/GROUP) The coalition of the initiator. -- @field DCS#coalition.side IniCoalition (UNIT) The coalition of the initiator.
-- @field DCS#Unit.Category IniCategory (UNIT) The category of the initiator. -- @field DCS#Unit.Category IniCategory (UNIT) The category of the initiator.
-- @field #string IniTypeName (UNIT) The type name of the initiator. -- @field #string IniTypeName (UNIT) The type name of the initiator.
-- --
@@ -737,12 +734,6 @@ local _EVENTMETA = {
Side = "I", Side = "I",
Event = "OnEventHumanAircraftRepairFinish", Event = "OnEventHumanAircraftRepairFinish",
Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH" Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH"
},
[EVENTS.GroupChangeOption] = {
Order = 1,
Side = "I",
Event = "OnEventGroupChangeOption",
Text = "S_EVENT_GROUP_CHANGE_OPTION"
}, },
-- dynamic cargo -- dynamic cargo
[EVENTS.NewDynamicCargo] = { [EVENTS.NewDynamicCargo] = {
@@ -1243,16 +1234,7 @@ function EVENT:onEvent( Event )
if Event.initiator then if Event.initiator then
if Event.id == EVENTS.GroupChangeOption then Event.IniObjectCategory = Object.getCategory(Event.initiator)
Event.IniDCSGroup = Event.initiator
Event.IniDCSGroupName = Group.getName(Event.initiator)
Event.IniGroupName = Event.IniDCSGroupName
Event.IniGroup = GROUP:FindByName(Event.IniDCSGroupName)
Event.IniCoalition = Group.getCoalition(Event.initiator)
Event.IniGroupCategory = Group.getCategory(Event.initiator)
else
Event.IniObjectCategory = Object.getCategory(Event.initiator)
end
if Event.IniObjectCategory == Object.Category.STATIC then if Event.IniObjectCategory == Object.Category.STATIC then
--- ---
+2 -2
View File
@@ -87,8 +87,7 @@ do -- world
-- @field S_EVENT_SIMULATION_UNFREEZE = 58 -- @field S_EVENT_SIMULATION_UNFREEZE = 58
-- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_START = 59 -- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_START = 59
-- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH = 60 -- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH = 60
-- @field S_EVENT_GROUP_CHANGE_OPTION = 61 -- @field S_EVENT_MAX = 61
-- @field S_EVENT_MAX = 62
--- The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events. --- The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events.
-- @type world.BirthPlace -- @type world.BirthPlace
@@ -615,6 +614,7 @@ do -- Object
-- @field SCENERY -- @field SCENERY
-- @field CARGO -- @field CARGO
---
-- @type Object.Desc -- @type Object.Desc
-- @extends #Desc -- @extends #Desc
-- @field #number life initial life level -- @field #number life initial life level
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,760 @@
--- **Functional** - Enhanced Warsaw Pact GCI Controller.
--
-- ## Main Features:
--
-- * Guide AI and human pilots in Warsaw Pact Style. GCI Kernel Functions.
-- * Advanced Tactics for Groups.
-- * Many additional events that the mission designer can hook into.
--
-- ===
--
-- ### Author: **Applevangelist**
--
-- ===
-- @module Functional.REDGCI_KERNEL
-- @image Func_RedGCI.png
-- @version 1.0.0
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- REDGCI_KERNEL class
-- @type REDGCI_KERNEL
---
-- # RedGCI — Soviet GCI Doctrine & Player Guide
--
-- ## Philosophy: Централизованное управление (Centralized Control)
--
-- The fundamental difference between Soviet and NATO GCI is **who makes the tactical decision**.
--
-- In NATO doctrine, the GCI controller provides situational awareness — bearing, range, altitude, aspect — and the pilot decides how to prosecute the intercept. The pilot is an autonomous tactician. GCI is an advisor.
--
-- In Soviet doctrine, the GCI controller **directs**. The pilot executes. The controller selects the intercept geometry, assigns the heading, manages the radar, calls weapons free, and coordinates multi-ship tactics. The pilot's job is to fly the numbers and shoot when told. This is not a flaw — it is the system working as designed. Soviet fighter pilots were trained to be precise executors of GCI instructions, not independent tacticians. The ground radar network (PVO) was the brain; the aircraft was the weapon.
--
-- RedGCI models this philosophy faithfully.
--
-- ---
--
-- ## What to Expect as a Player
--
-- ### You will not be asked what you want to do.
--
-- There are no "recommend a vector" calls, no "at your discretion" callouts. The controller tells you your heading, your altitude, and your task. Your acknowledgement is assumed.
--
-- ### The controller manages your radar.
--
-- You do not decide when to turn your radar on. The GCI will tell you when to switch on (`локатор` / `Radar on`). Before that call, you fly cold and silent. This preserves your emissions discipline and prevents the target from getting an early RWR spike.
--
-- ### Weapons free is a controlled event.
--
-- You do not engage until the controller clears you (`цель разрешена` / `WEAPONS FREE`). The controller determines when geometry, range, and aspect are favorable. Shooting early breaks the coordinated intercept and may compromise your wingman's attack.
--
-- ### Radio calls are short and military.
--
-- Soviet GCI brevity is terse by design. Expect calls like:
--
-- - `"Сокол, курс 170, высота 4500."` — vector, altitude
-- - `"Сокол, цель, пара, истребитель. Локатор."` — picture call on commit: count, type, radar on
-- - `"Сокол, захват. Дальность 20. Цель разрешена."` — lock confirmed, range, weapons free
-- - `"Сокол, молодец. Домой."` — good kill, RTB
--
-- There are no "BOGEY DOPE" requests, no "BRAA" calls, no "DECLARE" queries. The controller has already done that work. You fly the vector.
--
-- ---
--
-- ## State Flow — What the GCI is Doing Behind the Scenes
--
-- RedGCI manages a state machine that progresses through six phases. Understanding these phases helps you anticipate what call is coming next.
--
-- ```
-- VECTOR → COMMIT → RADAR_CONTACT → VISUAL → MERGE → (SPLASH / ABORT / RTB)
-- ```
--
-- ### VECTOR
-- The controller has a track. You are being vectored onto an intercept geometry. Your radar is off. The controller is solving a collision course and updating your heading every tick. Altitude calls reflect the intercept geometry — you may be sent below the target (classic Soviet shoot-up doctrine for radar-limited types) or level/above (MiG-29/Su-27 lookdown geometry). Expect heading updates every 1015 seconds.
--
-- **What you should do:** Fly the heading. Don't deviate. Don't turn your radar on yet. Speed is expected at 900kph TAS (depending on airframe)
--
-- ### COMMIT
-- Range has closed to approximately 30km. The controller calls the picture: count and type. Your radar comes on. You are now committed to the intercept — turning away is no longer the default option. The controller is building your radar geometry toward a lock.
--
-- **What you should do:** Activate your radar. Acquire the target. Do not fire yet.
--
-- ### RADAR_CONTACT
-- You have radar lock (or the AI has achieved it). The controller confirms lock and calls range. If geometry and range are favorable, weapons free follows immediately. If not — for example if aspect angle is unfavorable for a stern conversion — the controller holds fire and waits for better geometry.
--
-- **What you should do:** Maintain lock. Track the target. Wait for the weapons free call.
--
-- ### VISUAL
-- Range has closed to approximately 5km — visual conditions. Weapons free is automatic at this point. You are now in the merge envelope.
--
-- **What you should do:** Engage.
--
-- ### MERGE
-- Inside 2km. The GCI transitions to merge control: bearing to target, overshoot calls, separation instructions, reattack vectors. At this range the controller cannot see fine-grained geometry — merge calls are based on relative bearing and closure.
--
-- **What you should do:** Fight. Listen for overshoot, separation, and reattack calls.
--
-- ### SPLASH / ABORT / RTB
-- - `SPLASH` — kill confirmed, RTB
-- - `ABORT (THREAT)` — your RWR is spiked or a threat geometry has developed; break off immediately on the given heading
-- - `ABORT (BINGO)` — fuel state critical; break off and return
--
-- ---
--
-- ## Multi-Ship (2v2) Tactics (REDGCI2v2)
--
-- When two fighters are dispatched against a threat, the GCI selects a tactic automatically based on the tactical situation. The tactic is applied at COMMIT — until then, both fighters are vectored together toward the intercept midpoint.
--
-- | Tactic | Description |
-- |--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
-- | **PINCER** | Classic bracket. F1 and F2 split left and right, attacking from opposite angles simultaneously. Forces the target to choose which threat to react to. |
-- | **HIGH-LOW** | Vertical split. One fighter attacks from below (radar up, clean sky background), one from above. Degrades the target's ability to acquire both simultaneously. |
-- | **STAGGER** | BVR timing offset. F1 fires first at long range, F2 follows 813km behind to engage a maneuvering or defending target. |
-- | **TRAIL** | Close trail. F1 is the shooter, F2 is support — ready to engage if F1 overshoots or is defeated. |
-- | **GIRAFFE** | *(Historical — Iraq/Iran War, Mirage F1 vs F-14A)* F1 attacks at normal altitude, binding the AWG-9 radar. F2 flies nap-of-earth |
-- | | (300600m AGL) using ground clutter to degrade radar detection, then pulls up and fires from close range. |
--
-- During a tactic split, you may receive a heading that seems unusual — a large lateral offset or an unexpected altitude change. **Trust the vector.** The controller is positioning you for the tactic geometry. The merge point will bring you back onto the target.
--
-- ---
--
-- ## Dispatcher & CAP Flow (REDGCI_DISPATCHER)
--
-- When using the dispatcher layer, the full operational flow is:
--
-- ```
-- Spawn at homeplate
-- → Taxi and takeoff (template-controlled)
-- → Transit to CAP zone
-- → Orbit in assigned zone (radar cold, weapons safe)
-- ↓ INTEL detects threat cluster
-- → "Attention, radar contact. Pair, fighter, 45 kilometers." (all CAP fighters)
-- ↓ Dispatcher assigns pair
-- → "101 102, intercept. Pair, fighter." (dispatched pair)
-- → VECTOR → COMMIT → RADAR_CONTACT → VISUAL → MERGE → SPLASH
-- ↓ Engagement complete
-- → AI: RTB waypoint → land → despawn → respawn after delay
-- → Human: "101, mission complete. RTB, refuel and rearm."
-- ↓ After RespawnDelay
-- → New AI pair spawns into same CAP zone
-- ```
--
-- Human players are dispatched first when available. If a human and AI are both in the CAP pool, the human is always assigned to the next intercept. AI fills gaps. The dispatcher does not send a single fighter if a pair is available — pairing is always preferred.
--
-- ---
--
-- ## Key Differences from NATO GCI at a Glance
--
-- | | Soviet (RedGCI) | NATO |
-- |--------------------|--------------------------------------|------------------------------------|
-- | Tactical decision | Controller | Pilot |
-- | Radar management | Controller-commanded | Pilot-initiated |
-- | Weapons free | Controller-called | Pilot-discretion (after WF) |
-- | Heading calls | Prescriptive | Advisory |
-- | Brevity style | Terse, military, positional | Standardized (BRAA, DECLARE, etc.) |
-- | Multi-ship tactics | Centrally planned, applied at COMMIT | Mutually briefed, pilot-executed |
-- | Pilot autonomy | Low (by design) | High |
--
-- **The Soviet system is not inferior** — it is optimized for a different kind of pilot and a different operational context. Mass interception of large NATO strike packages over defended Soviet airspace demanded centralized, efficient, high-throughput GCI control. RedGCI brings that experience to DCS.
--
-- @field #REDGCI_KERNEL
REDGCI_KERNEL = {}
REDGCI_KERNEL.version = "1.0.0"
-- ─────────────────────────────────────────────────────────────
-- Konstanten (aus gci_types.h)
-- ─────────────────────────────────────────────────────────────
REDGCI_KERNEL.C = {
RANGE_VECTOR_START = 60000.0,
RANGE_COMMIT = 30000.0,
RANGE_RADAR_FLOOR = 20000.0,
RANGE_VISUAL = 5000.0,
RANGE_MERGE = 2000.0,
WF_RANGE_MAX = 25000.0,
ALT_OFFSET_LOOKDOWN = 0.0,
ASPECT_NOTCH_MIN = 80.0,
ASPECT_NOTCH_MAX = 100.0,
ASPECT_REAR_ATTACK = 120.0,
MAX_TTI = 600.0,
TICK_INTERVAL = 15.0,
FUEL_BINGO = 0.25,
DELAY_MIN = 3.0,
DELAY_MAX = 8.0,
DELAY_MERGE_MIN = 2.0,
DELAY_MERGE_MAX = 4.0,
TACTIC_PINCER = 0,
TACTIC_HIGH_LOW = 1,
TACTIC_STAGGER = 2,
TACTIC_TRAIL = 3,
TACTIC_GIRAFFE = 4, -- Irak/Iran-Doktrin: F1=Decoy normal, F2=Nap-of-Earth im Groundclutter
PURSUIT_COLLISION = 0,
PURSUIT_LEAD = 1,
PURSUIT_PURE = 2,
PURSUIT_NO_SOLUTION = 3,
}
-- ─────────────────────────────────────────────────────────────
-- Hilfsfunktionen
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.clamp(v, lo, hi)
if v < lo then return lo end
if v > hi then return hi end
return v
end
function REDGCI_KERNEL.vec2len(x, z)
return math.sqrt(x*x + z*z)
end
function REDGCI_KERNEL.bearing(dx, dz)
local b = math.deg(math.atan2(dx, dz))
return b < 0.0 and b + 360.0 or b
end
function REDGCI_KERNEL.randDelay(lo, hi)
return lo + UTILS.LCGRandom() * (hi - lo)
end
-- ─────────────────────────────────────────────────────────────
-- Aspect Angle (0=Nose-on, 90=Beam, 180=Tail)
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.aspectAngle(target, observer)
local dx = observer.x - target.x
local dz = observer.z - target.z
local range = REDGCI_KERNEL.vec2len(dx, dz)
if range < 1.0 then return 0.0 end
local nx = dx / range
local nz = dz / range
local spd = (target.speed or 1.0) + 1e-6
local tvx = (target.vx or 0.0) / spd
local tvz = (target.vz or 0.0) / spd
local dot = REDGCI_KERNEL.clamp(tvx*nx + tvz*nz, -1.0, 1.0)
return math.deg(math.acos(dot))
end
-- ─────────────────────────────────────────────────────────────
-- Closure Rate (positiv = Annäherung)
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.closureRate(f, t)
local dx = t.x - f.x
local dz = t.z - f.z
local range = REDGCI_KERNEL.vec2len(dx, dz)
if range < 1.0 then return 0.0 end
local nx = dx / range
local nz = dz / range
local dvx = (t.vx or 0.0) - (f.vx or 0.0)
local dvz = (t.vz or 0.0) - (f.vz or 0.0)
return -(dvx*nx + dvz*nz)
end
-- ─────────────────────────────────────────────────────────────
-- _solveCollision (intern, GCI-Koordinaten)
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL._solveCollision(f, t)
local C = REDGCI_KERNEL.C
local dx = t.x - f.x
local dz = t.z - f.z
local vtx = t.vx or 0.0
local vtz = t.vz or 0.0
local vf = f.speed or 1.0
local a = vtx*vtx + vtz*vtz - vf*vf
local b = 2.0 * (dx*vtx + dz*vtz)
local c = dx*dx + dz*dz
local sol_t = -1.0
if math.abs(a) < 1.0 then
if math.abs(b) > 0.01 then
sol_t = -c / b
else
return false
end
else
local disc = b*b - 4.0*a*c
if disc < 0.0 then return false end
local sq = math.sqrt(disc)
local t1 = (-b - sq) / (2.0 * a)
local t2 = (-b + sq) / (2.0 * a)
if t1 > 0.0 and t2 > 0.0 then sol_t = math.min(t1, t2)
elseif t1 > 0.0 then sol_t = t1
elseif t2 > 0.0 then sol_t = t2
else return false
end
end
if sol_t < 0.0 or sol_t > C.MAX_TTI then return false end
local ip = {
x = t.x + vtx * sol_t,
z = t.z + vtz * sol_t,
y = math.max(t.y + C.ALT_OFFSET_LOOKDOWN, 300.0),
}
local hdg = REDGCI_KERNEL.bearing(ip.x - f.x, ip.z - f.z)
return true, hdg, sol_t, ip
end
-- ─────────────────────────────────────────────────────────────
-- _solveLead (intern, GCI-Koordinaten)
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL._solveLead(f, t)
local C = REDGCI_KERNEL.C
local dx = t.x - f.x
local dz = t.z - f.z
local range = REDGCI_KERNEL.vec2len(dx, dz)
if range < 1.0 then return 0.0, 0.0 end
local base_bearing = REDGCI_KERNEL.bearing(dx, dz)
local aspect_rad = math.rad(REDGCI_KERNEL.aspectAngle(t, f))
local speed_ratio = (t.speed or 1.0) / ((f.speed or 1.0) + 1e-6)
local sin_lead = REDGCI_KERNEL.clamp(speed_ratio * math.sin(aspect_rad), -1.0, 1.0)
local lead_deg = math.deg(math.asin(sin_lead))
local hdg
if math.abs(lead_deg) < 45.0 then
hdg = (base_bearing + lead_deg + 360.0) % 360.0
else
hdg = base_bearing
end
local closing = REDGCI_KERNEL.closureRate(f, t)
if closing < 50.0 then closing = 50.0 end
local tti = math.min(range / closing, C.MAX_TTI)
return hdg, tti
end
-- ─────────────────────────────────────────────────────────────
-- computeIntercept (GCI-Koordinaten intern)
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.computeIntercept(fighter, target)
local C = REDGCI_KERNEL.C
local dx = target.x - fighter.x
local dz = target.z - fighter.z
local sol = {
heading_deg = 0.0,
time_to_intercept = C.MAX_TTI,
intercept_point = { x=target.x, z=target.z, y=target.y },
target_alt = target.y,
solution_found = false,
aspect_angle = REDGCI_KERNEL.aspectAngle(target, fighter),
range = REDGCI_KERNEL.vec2len(dx, dz),
mode = C.PURSUIT_NO_SOLUTION,
weapons_free = false,
}
local closure = REDGCI_KERNEL.closureRate(fighter, target)
local projected_range = sol.range - closure * C.TICK_INTERVAL
sol.weapons_free = (sol.range < C.WF_RANGE_MAX) or
(closure > 0.0 and projected_range < C.WF_RANGE_MAX)
local ok, hdg, tti, ip = REDGCI_KERNEL._solveCollision(fighter, target)
if ok then
sol.heading_deg = hdg
sol.time_to_intercept = tti
sol.intercept_point = ip
sol.solution_found = true
sol.mode = C.PURSUIT_COLLISION
return sol
end
local lhdg, ltti = REDGCI_KERNEL._solveLead(fighter, target)
sol.heading_deg = lhdg
sol.time_to_intercept = ltti
sol.solution_found = true
sol.mode = C.PURSUIT_LEAD
sol.intercept_point = {
x = target.x + (target.vx or 0.0) * ltti,
z = target.z + (target.vz or 0.0) * ltti,
y = math.max(target.y + C.ALT_OFFSET_LOOKDOWN, 300.0),
}
return sol
end
-- ─────────────────────────────────────────────────────────────
-- computeInterceptDCS (DCS-Koordinaten Ein/Aus)
-- Flat-Rückgabe kompatibel mit alter C-API
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.computeInterceptDCS(f, t)
local f_gci = { x=f.z, z=f.x, y=f.y, vx=f.vz, vz=f.vx, vy=f.vy, speed=f.spd }
local t_gci = { x=t.z, z=t.x, y=t.y, vx=t.vz, vz=t.vx, vy=t.vy, speed=t.spd }
local sol = REDGCI_KERNEL.computeIntercept(f_gci, t_gci)
-- GCI→DCS: ip.z(Nord)→DCS.x, ip.x(Ost)→DCS.z
return sol.heading_deg,
sol.time_to_intercept,
sol.mode,
sol.weapons_free,
sol.range,
sol.aspect_angle,
sol.intercept_point.z,
sol.intercept_point.x,
sol.intercept_point.y,
sol.target_alt
end
-- ─────────────────────────────────────────────────────────────
-- computeSplit (GCI-Koordinaten intern)
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.computeSplit(f1, f2, tgt, tactic, variation)
local C = REDGCI_KERNEL.C
variation = REDGCI_KERNEL.clamp(variation or 0.5, 0.0, 1.0)
tactic = tactic or C.TACTIC_PINCER
local mid_x = (f1.x + f2.x) * 0.5
local mid_z = (f1.z + f2.z) * 0.5
local dx = tgt.x - mid_x
local dz = tgt.z - mid_z
local rng = REDGCI_KERNEL.vec2len(dx, dz)
if rng < 1.0 then rng = 1.0 end
local ax = dx / rng
local az = dz / rng
local px = -az
local pz = ax
local plan = {
tactic = tactic,
wp_f1 = { x=0, z=0, y=0 },
wp_f2 = { x=0, z=0, y=0 },
merge_f1 = { x=0, z=0, y=0 },
merge_f2 = { x=0, z=0, y=0 },
}
local function clamp_alt(pt)
if pt.y < 300.0 then pt.y = 300.0 end
end
local function clamp_alt_noe(pt)
if pt.y < 150.0 then pt.y = 150.0 end
end
-- GIRAFFE F2 fliegt NOE — separater clamp mit niedrigerer Mindesthöhe
local function clamp_alt_noe(pt)
if pt.y < 150.0 then pt.y = 150.0 end
end
if tactic == C.TACTIC_PINCER then
local spread = 12000.0 + variation * 5000.0
local approach = rng * 0.45
local merge_off = spread * 0.30
plan.wp_f1 = { x=mid_x+ax*approach+px*spread, z=mid_z+az*approach+pz*spread, y=tgt.y }
plan.wp_f2 = { x=mid_x+ax*approach-px*spread, z=mid_z+az*approach-pz*spread, y=tgt.y }
plan.merge_f1 = { x=tgt.x-ax*3000+px*merge_off, z=tgt.z-az*3000+pz*merge_off, y=tgt.y }
plan.merge_f2 = { x=tgt.x-ax*3000-px*merge_off, z=tgt.z-az*3000-pz*merge_off, y=tgt.y }
elseif tactic == C.TACTIC_HIGH_LOW then
local vert = 3000.0 + variation * 1500.0
local approach = rng * 0.50
local side_off = 2000.0
plan.wp_f1 = { x=mid_x+ax*approach+px*side_off, z=mid_z+az*approach+pz*side_off, y=tgt.y-500.0 }
plan.wp_f2 = { x=mid_x+ax*approach-px*side_off, z=mid_z+az*approach-pz*side_off, y=tgt.y+vert }
plan.merge_f1 = { x=tgt.x-ax*3000+px*side_off, z=tgt.z-az*3000+pz*side_off, y=plan.wp_f1.y }
plan.merge_f2 = { x=tgt.x-ax*3000-px*side_off, z=tgt.z-az*3000-pz*side_off, y=plan.wp_f2.y }
elseif tactic == C.TACTIC_STAGGER then
local lag = 8000.0 + variation * 3000.0
local lead_dist = rng * 0.85
plan.wp_f1 = { x=mid_x+ax*lead_dist, z=mid_z+az*lead_dist, y=tgt.y }
plan.wp_f2 = { x=mid_x+ax*(lead_dist-lag), z=mid_z+az*(lead_dist-lag), y=tgt.y }
plan.merge_f1 = { x=plan.wp_f1.x, z=plan.wp_f1.z, y=plan.wp_f1.y }
plan.merge_f2 = { x=plan.wp_f2.x, z=plan.wp_f2.z, y=plan.wp_f2.y }
elseif tactic == C.TACTIC_TRAIL then
local lag = 3000.0 + variation * 2000.0
local lead_dist = rng * 0.85
local side_off = 500.0
plan.wp_f1 = { x=mid_x+ax*lead_dist, z=mid_z+az*lead_dist, y=tgt.y }
plan.wp_f2 = { x=mid_x+ax*(lead_dist-lag)+px*side_off, z=mid_z+az*(lead_dist-lag)+pz*side_off, y=tgt.y }
plan.merge_f1 = { x=plan.wp_f1.x, z=plan.wp_f1.z, y=plan.wp_f1.y }
plan.merge_f2 = { x=plan.wp_f2.x, z=plan.wp_f2.z, y=plan.wp_f2.y }
elseif tactic == C.TACTIC_GIRAFFE then
-- ── GIRAFFE: Irak/Iran-Doktrin (Mirage F1 vs F-14A) ──────────────
--
-- F1 = Decoy: normale Höhe, direkter Anflug, bindet AWG-9 Radar.
-- Zieht Phoenixe auf sich — gibt F2 Zeit für Pull-up.
--
-- F2 = Killer: Nap-of-Earth (~300-600m AGL/MSL) weit seitlich versetzt.
-- Nutzt Groundclutter um AWG-9 Look-Down zu degradieren.
-- Nähert sich bis 10km vor Ziel, dann Pull-up und Schuss.
-- Seitlicher Versatz ~8-12km damit F2 nicht im Radarsektor
-- des AWG-9 liegt während F1 den Lock hält.
--
-- Historisch: Iran-Irak Krieg 1982-88. Irakische Mirage F1EQ nutzten
-- dieses Profil gegen F-14A/AIM-54 Phoenix — der AWG-9 hatte trotz
-- Look-Down-Fähigkeit bei sehr tiefen Zielen über unebenen Terrain
-- (Zagros-Berge, Khuzestan-Ebene) erhöhte Clutter-Probleme.
--
-- Höhen: neutral (0.0) — Lua addiert AltOffset + terrain-awareness.
-- Mindesthöhe 300m MSL wird am Ende geclampt.
local noe_alt = 300.0 + variation * 300.0 -- 300-600m MSL (Nap-of-Earth)
local side_off = 8000.0 + variation * 4000.0 -- 8-12km seitlich (aus AWG-9 Sektor)
local approach = rng * 0.60 -- F1 direkter Anflug, 60% des Weges
local noe_dist = rng * 0.75 -- F2 nähert sich weiter ran vor Pull-up
plan.wp_f1 = { x=mid_x+ax*approach+px*2000, z=mid_z+az*approach+pz*2000, y=tgt.y }
plan.merge_f1 = { x=tgt.x-ax*5000+px*2000, z=tgt.z-az*5000+pz*2000, y=tgt.y }
plan.wp_f2 = { x=mid_x+ax*noe_dist-px*side_off, z=mid_z+az*noe_dist-pz*side_off, y=noe_alt, absolute_alt=true }
plan.merge_f2 = { x=tgt.x-ax*10000-px*side_off*0.3, z=tgt.z-az*10000-pz*side_off*0.3, y=tgt.y }
else
plan.wp_f1 = { x=tgt.x, z=tgt.z, y=tgt.y }
plan.wp_f2 = { x=tgt.x, z=tgt.z, y=tgt.y }
plan.merge_f1 = { x=tgt.x, z=tgt.z, y=tgt.y }
plan.merge_f2 = { x=tgt.x, z=tgt.z, y=tgt.y }
end
clamp_alt(plan.wp_f1)
if tactic == C.TACTIC_GIRAFFE then
clamp_alt_noe(plan.wp_f2)
else
clamp_alt(plan.wp_f2)
end
clamp_alt(plan.merge_f1)
clamp_alt(plan.merge_f2)
return plan
end
-- ─────────────────────────────────────────────────────────────
-- computeSplitDCS (DCS-Koordinaten Ein/Aus)
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.computeSplitDCS(f1, f2, tgt, tactic, variation)
local function dcs2gci(u)
return { x=u.z, z=u.x, y=u.y, vx=u.vz or 0, vz=u.vx or 0, speed=u.spd or 0 }
end
local function gci2dcs(pt)
return { x=pt.z, z=pt.x, y=pt.y, absolute_alt=pt.absolute_alt }
end
local tgt_gci = { x=tgt.z, z=tgt.x, y=tgt.y }
local plan = REDGCI_KERNEL.computeSplit(
dcs2gci(f1), dcs2gci(f2), tgt_gci, tactic, variation)
plan.wp_f1 = gci2dcs(plan.wp_f1)
plan.wp_f2 = gci2dcs(plan.wp_f2)
plan.merge_f1 = gci2dcs(plan.merge_f1)
plan.merge_f2 = gci2dcs(plan.merge_f2)
return plan
end
-- ─────────────────────────────────────────────────────────────
-- buildTransmission (Port von gci_build_transmission)
--
-- ctx/prev Felder: state, prev_state, ticks_in_state, range,
-- aspect_angle, closure_rate, altitude_delta, fuel_fraction
-- sol Felder: heading_deg, time_to_intercept, target_alt,
-- range, aspect_angle, weapons_free
-- Rückgabe: { token_str, delay_sec, weapons_free, silence }
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.buildTransmission(ctx, prev, sol)
local C = REDGCI_KERNEL.C
local tx = {
token_str = "",
delay_sec = REDGCI_KERNEL.clamp(
REDGCI_KERNEL.randDelay(C.DELAY_MIN, C.DELAY_MAX), 3.0, 8.0),
weapons_free = sol.weapons_free,
silence = false,
priority = 50,
}
local hdg_i = math.floor((sol.heading_deg or 0) + 0.5)
local alt_i = math.floor((sol.target_alt or 0) / 100.0 + 0.5) * 100
local rng_km = math.floor((ctx.range or 0) / 1000.0 + 0.5)
local aspect_i = math.floor((ctx.aspect_angle or 0) + 0.5)
local delay = tx.delay_sec
local state = ctx.state or "VECTOR"
local prev_state = prev.state or "VECTOR"
local ticks = ctx.ticks_in_state or 0
local function emit(fmt, ...)
tx.token_str = string.format(fmt, ...)
end
if state == "VECTOR" then
if prev_state ~= "VECTOR" then
local tti_m = math.floor((sol.time_to_intercept or 0) / 60.0)
local tti_s = math.floor(sol.time_to_intercept or 0) % 60
if tti_m > 0 then
emit("VECTOR_WITH_TTI|hdg=%d|alt=%d|rng=%d|tti_m=%d|tti_s=%d|delay=%.1f",
hdg_i, alt_i, rng_km, tti_m, tti_s, delay)
else
emit("VECTOR|hdg=%d|alt=%d|rng=%d|delay=%.1f",
hdg_i, alt_i, rng_km, delay)
end
else
local hdg_delta = math.abs((sol.heading_deg or 0) - (ctx.aspect_angle or 0))
if hdg_delta < 5.0 and ticks > 3 then
tx.silence = true
else
emit("VECTOR|hdg=%d|alt=%d|rng=%d|delay=%.1f",
hdg_i, alt_i, rng_km, delay)
end
end
elseif state == "COMMIT" then
if prev_state ~= "COMMIT" then
tx.priority = 100
emit("COMMIT_FIRST|hdg=%d|alt=%d|rng=%d|aspect=%d|delay=%.1f",
hdg_i, alt_i, rng_km, aspect_i, delay)
elseif ticks == 6 then
emit("COMMIT_NO_LOCK|hdg=%d|rng=%d|aspect=%d|delay=%.1f",
hdg_i, rng_km, aspect_i, delay)
elseif ticks > 8 then
emit("COMMIT_NUDGE|hdg=%d|aspect=%d|delay=%.1f",
hdg_i, aspect_i, delay)
else
tx.silence = true
end
elseif state == "RADAR_CONTACT" then
if prev_state ~= "RADAR_CONTACT" then
if sol.weapons_free then
tx.priority = 100
emit("RADAR_LOCK_WF|rng=%d|delay=%.1f", rng_km, delay)
tx.weapons_free = true
else
emit("RADAR_LOCK_HOLD|rng=%d|delay=%.1f", rng_km, delay)
end
elseif not sol.weapons_free
and (ctx.range or 0) < C.WF_RANGE_MAX
and (ctx.aspect_angle or 0) > C.ASPECT_REAR_ATTACK
and prev_state == "RADAR_CONTACT" then
emit("RADAR_WF_NOW|rng=%d|delay=%.1f", rng_km, delay)
tx.priority = 100
tx.weapons_free = true
else
tx.silence = true
end
elseif state == "VISUAL" then
if prev_state ~= "VISUAL" then
emit("VISUAL_CONFIRM|rng=%d|delay=%.1f", rng_km, delay)
tx.weapons_free = true
else
tx.silence = true
end
elseif state == "NOTCH" then
if prev_state ~= "NOTCH" then
emit("NOTCH_ENTRY|delay=1.5")
elseif ticks % 8 == 0 then
emit("NOTCH_UPDATE|rng=%d|aspect=%d|delay=%.1f",
rng_km, aspect_i, delay)
else
tx.silence = true
end
elseif state == "ABORT" then
tx.delay_sec = 1.5
if (ctx.fuel_fraction or 1.0) < C.FUEL_BINGO then
emit("ABORT_BINGO|hdg=%d|delay=1.5", hdg_i)
else
emit("ABORT_THREAT|hdg=%d|delay=1.5", hdg_i)
end
else
tx.silence = true
end
return tx
end
-- ─────────────────────────────────────────────────────────────
-- mergeTransition (Port von gci_merge_transition)
--
-- ctx Felder: phase, ticks_in_phase, range, bearing_to_target,
-- closure_rate, altitude_delta, pass_count, radar_lost
-- Rückgabe: phase string
-- "ENTRY"|"OVERSHOOT"|"SEPARATION"|"REATTACK"|"LOST"|"SPLASH"
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.mergeTransition(ctx, prev)
if ctx.radar_lost then return "LOST" end
if (ctx.closure_rate or 0) < -30.0 and (ctx.range or 0) > 3000.0 then
return "SEPARATION"
end
local brg = ctx.bearing_to_target or 0
if brg > 100.0 and brg < 260.0 and (ctx.range or 0) < 5000.0 then
return "OVERSHOOT"
end
if ctx.phase == "SEPARATION" and (ctx.pass_count or 0) < 3 then
return "REATTACK"
end
return ctx.phase
end
-- ─────────────────────────────────────────────────────────────
-- buildMergeTransmission (Port von gci_build_merge_transmission)
-- ─────────────────────────────────────────────────────────────
function REDGCI_KERNEL.buildMergeTransmission(ctx, prev)
local C = REDGCI_KERNEL.C
local tx = {
token_str = "",
delay_sec = REDGCI_KERNEL.clamp(
REDGCI_KERNEL.randDelay(C.DELAY_MERGE_MIN, C.DELAY_MERGE_MAX), 2.0, 5.0),
weapons_free = false,
silence = false,
priority = 50,
}
local brg = math.floor((ctx.bearing_to_target or 0) + 0.5)
local rng_km = math.floor((ctx.range or 0) / 1000.0 + 0.5)
local delay = tx.delay_sec
local phase = ctx.phase or "ENTRY"
local prev_phase = prev.phase or "ENTRY"
local ticks = ctx.ticks_in_phase or 0
local dir_rl = ((ctx.bearing_to_target or 0) < 180.0) and "right" or "left"
local alt_d = ctx.altitude_delta or 0
local alt_rel = ""
if alt_d > 400.0 then alt_rel = "low"
elseif alt_d < -400.0 then alt_rel = "high"
end
local function emit(fmt, ...)
tx.token_str = string.format(fmt, ...)
end
if phase == "ENTRY" then
tx.priority = 100
emit("MERGE_ENTRY|brg=%d|dir_rl=%s|delay=%.1f", brg, dir_rl, delay)
elseif phase == "OVERSHOOT" then
emit("MERGE_OVERSHOOT|brg=%d|dir_rl=%s|alt_rel=%s|delay=%.1f",
brg, dir_rl, alt_rel, delay)
elseif phase == "SEPARATION" then
if (ctx.pass_count or 0) < 3 then
emit("MERGE_REATTACK|brg=%d|rng=%d|delay=%.1f", brg, rng_km, delay)
else
emit("ABORT_THREAT|hdg=%d|delay=%.1f", brg, delay)
end
elseif phase == "REATTACK" then
if prev_phase ~= "REATTACK" or ticks % 3 == 0 then
emit("MERGE_REATTACK|brg=%d|rng=%d|delay=%.1f", brg, rng_km, delay)
else
tx.silence = true
end
elseif phase == "LOST" then
if prev_phase ~= "LOST" or ticks == 4 then
emit("MERGE_LOST|brg=%d|rng=%d|delay=%.1f", brg, rng_km, delay)
else
tx.silence = true
end
elseif phase == "SPLASH" then
tx.delay_sec = 1.5
tx.priority = 100
emit("MERGE_SPLASH|delay=1.5")
else
tx.silence = true
end
return tx
end
-------------------------------------------------------------------------------
-- END of Class
-------------------------------------------------------------------------------
@@ -1,292 +0,0 @@
--- **Ops** - Passive strategic territory.
--
-- **Main Features:**
--
-- * Associate a static MOOSE zone with a coalition
-- * Keep strategic territory geometry separate from OPSZONE scanning
-- * Register territories in the MOOSE database
-- * Delegate coordinate checks and F10 drawing to the underlying zone
--
-- A TERRITORY is deliberately passive. It does not scan DCS objects, evaluate
-- ownership, run a scheduler, or make tactical or strategic decisions.
--
-- ===
--
-- @module Ops.Territory
--- TERRITORY class.
-- @type TERRITORY
-- @field #string ClassName Name of the class.
-- @field #string version Class version.
-- @field #number verbose Verbosity level.
-- @field #string lid Log ID string.
-- @field #string name Unique territory name.
-- @field #string zoneName Name of the underlying MOOSE zone.
-- @field Core.Zone#ZONE_BASE zone Underlying MOOSE zone.
-- @field #number coalition Coalition associated with the territory.
-- @extends Core.Base#BASE
--- A passive strategic area defined by an existing MOOSE zone.
--
-- TERRITORY contains geometry and declarative ownership only. Unlike
-- @{Ops.OpsZone#OPSZONE}, it performs no periodic object scans and has no FSM.
--
-- @field #TERRITORY
TERRITORY = {
ClassName = "TERRITORY",
verbose = 0,
}
--- TERRITORY class version.
-- @field #string version
TERRITORY.version = "0.1.0"
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- DATABASE extension
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Finds a TERRITORY based on its name.
-- @param Core.Database#DATABASE self
-- @param #string TerritoryName Name of the territory.
-- @return #TERRITORY The found territory or `nil`.
function DATABASE:FindTerritory(TerritoryName)
local territories = self.TERRITORIES or {}
return territories[TerritoryName]
end
--- Adds a TERRITORY to the database.
-- @param Core.Database#DATABASE self
-- @param #TERRITORY Territory Territory to add.
-- @return #TERRITORY The registered territory or `nil`.
function DATABASE:AddTerritory(Territory)
if not Territory then
return nil
end
self.TERRITORIES = self.TERRITORIES or {}
local territoryName = Territory:GetName()
if not self.TERRITORIES[territoryName] then
self.TERRITORIES[territoryName] = Territory
end
return self.TERRITORIES[territoryName]
end
--- Deletes a TERRITORY from the database.
-- @param Core.Database#DATABASE self
-- @param #string TerritoryName Name of the territory.
-- @return Core.Database#DATABASE self
function DATABASE:DeleteTerritory(TerritoryName)
if self.TERRITORIES then
self.TERRITORIES[TerritoryName] = nil
end
return self
end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new TERRITORY class object.
-- @param #TERRITORY self
-- @param Core.Zone#ZONE_BASE Zone The underlying MOOSE zone or its Mission Editor name.
-- @param #number Coalition (Optional) Associated coalition. Default `coalition.side.NEUTRAL`.
-- @param #string Name (Optional) Unique territory name. Default is the zone name.
-- @return #TERRITORY self
-- @usage
-- local north = TERRITORY:New("Territory North", coalition.side.BLUE)
-- local southZone = ZONE:FindByName("Territory South")
-- local south = TERRITORY:New(southZone, coalition.side.RED, "Southern Territory")
function TERRITORY:New(Zone, Coalition, Name)
-- Inherit everything from BASE class.
local self = BASE:Inherit(self, BASE:New()) -- #TERRITORY
-- Resolve a Mission Editor zone name.
if type(Zone) == "string" then
local zoneName = Zone
Zone = ZONE:FindByName(zoneName)
if not Zone then
self:E(string.format("ERROR: No ZONE found for name: %s", tostring(zoneName)))
return nil
end
elseif not Zone then
self:E("ERROR: First parameter Zone is nil in TERRITORY:New(Zone) call!")
return nil
end
-- A territory relies only on the common ZONE_BASE interface.
if type(Zone.GetName) ~= "function"
or type(Zone.GetCoordinate) ~= "function"
or type(Zone.IsCoordinateInZone) ~= "function" then
self:E("ERROR: TERRITORY requires a ZONE_BASE derived object!")
return nil
end
local zoneName = Zone:GetName()
local territoryName = Name or zoneName
if type(territoryName) ~= "string" or territoryName == "" then
self:E("ERROR: TERRITORY requires a non-empty name!")
return nil
end
self.zone = Zone
self.zoneName = zoneName
self.name = territoryName
self.lid = string.format("TERRITORY %s | ", territoryName)
if not self:SetCoalition(Coalition or coalition.side.NEUTRAL) then
return nil
end
-- Register in the MOOSE database.
_DATABASE:AddTerritory(self)
return self
end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Set functions
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set the coalition associated with this territory.
-- This method only changes declarative ownership. It does not evaluate DCS
-- units or trigger any capture logic.
-- @param #TERRITORY self
-- @param #number Coalition Coalition side number.
-- @return #TERRITORY self or `nil` if the coalition is invalid.
function TERRITORY:SetCoalition(Coalition)
if Coalition ~= coalition.side.NEUTRAL
and Coalition ~= coalition.side.RED
and Coalition ~= coalition.side.BLUE then
self:E(self.lid .. string.format("ERROR: Invalid coalition: %s", tostring(Coalition)))
return nil
end
self.coalition = Coalition
return self
end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Get functions
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Find a TERRITORY by name.
-- @param #TERRITORY self
-- @param #string Name Name of the territory.
-- @return #TERRITORY The found territory or `nil`.
function TERRITORY:FindByName(Name)
return _DATABASE:FindTerritory(Name)
end
--- Get the territory name.
-- @param #TERRITORY self
-- @return #string Territory name.
function TERRITORY:GetName()
return self.name
end
--- Get the underlying zone name.
-- @param #TERRITORY self
-- @return #string Zone name.
function TERRITORY:GetZoneName()
return self.zoneName
end
--- Get the underlying MOOSE zone.
-- @param #TERRITORY self
-- @return Core.Zone#ZONE_BASE The underlying zone.
function TERRITORY:GetZone()
return self.zone
end
--- Get the territory center coordinate.
-- @param #TERRITORY self
-- @return Core.Point#COORDINATE Territory center coordinate.
function TERRITORY:GetCoordinate()
return self.zone:GetCoordinate()
end
--- Get the coalition associated with the territory.
-- @param #TERRITORY self
-- @return #number Coalition side number.
function TERRITORY:GetCoalition()
return self.coalition
end
--- Get the name of the coalition associated with the territory.
-- @param #TERRITORY self
-- @return #string Coalition name.
function TERRITORY:GetCoalitionName()
return UTILS.GetCoalitionName(self.coalition)
end
--- Get the owner of the territory.
-- This is an alias for @{#TERRITORY.GetCoalition}.
-- @param #TERRITORY self
-- @return #number Coalition side number.
function TERRITORY:GetOwner()
return self:GetCoalition()
end
--- Get the owner coalition name.
-- This is an alias for @{#TERRITORY.GetCoalitionName}.
-- @param #TERRITORY self
-- @return #string Coalition name.
function TERRITORY:GetOwnerName()
return self:GetCoalitionName()
end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Zone functions
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Test whether a coordinate lies inside the territory.
-- @param #TERRITORY self
-- @param Core.Point#COORDINATE Coordinate Coordinate to test.
-- @return #boolean `true` if the coordinate lies inside the territory.
function TERRITORY:ContainsCoordinate(Coordinate)
return self.zone:IsCoordinateInZone(Coordinate)
end
--- Test whether a Vec2 lies inside the territory.
-- @param #TERRITORY self
-- @param DCS#Vec2 Vec2 Vec2 to test.
-- @return #boolean `true` if the Vec2 lies inside the territory.
function TERRITORY:ContainsVec2(Vec2)
return self.zone:IsVec2InZone(Vec2)
end
--- Draw the territory on the F10 map.
-- Drawing is delegated to the underlying MOOSE zone.
-- @param #TERRITORY self
-- @param #number Coalition (Optional) Coalition visibility. Default `-1` for all.
-- @param #table Color (Optional) RGB line color.
-- @param #number Alpha (Optional) Line alpha.
-- @param #table FillColor (Optional) RGB fill color.
-- @param #number FillAlpha (Optional) Fill alpha.
-- @param #number LineType (Optional) DCS line type.
-- @return #TERRITORY self
function TERRITORY:Draw(Coalition, Color, Alpha, FillColor, FillAlpha, LineType)
self.zone:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType)
return self
end
--- Remove the territory drawing from the F10 map.
-- @param #TERRITORY self
-- @return #TERRITORY self
function TERRITORY:Undraw()
self.zone:UndrawZone()
return self
end
--- Remove the territory from the MOOSE database.
-- The underlying zone is not deleted.
-- @param #TERRITORY self
-- @return #TERRITORY self
function TERRITORY:Remove()
_DATABASE:DeleteTerritory(self.name)
return self
end
+5 -10
View File
@@ -79,7 +79,11 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Tiresias.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua') __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Formation.lua') __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Formation.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Territory.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCIKernel.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCI.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCI2v2.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/RedGCIDispatcher.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' )
@@ -138,13 +142,4 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Beacons.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Radios.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Radios.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Towns.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Navigation/Towns.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Python/MooseBridgeJson.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Python/MooseBridge.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Python/MooseBridgeSocketTuningExtension.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Python/MooseBridgeDcsEventsExtension.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Python/MooseBridgeAuftragExecutionExtension.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Python/MooseBridgeAuftragTraceExtension.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Python/MooseBridgeIntelExtension.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Python/MooseBridgePayloadExtension.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Globals.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Globals.lua' )
+29 -54
View File
@@ -290,9 +290,6 @@ function AIRWING:New(warehousename, airwingname)
-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission. -- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self return self
end end
@@ -434,15 +431,7 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance)
capability.MissionType=AUFTRAG.Type.RELOCATECOHORT capability.MissionType=AUFTRAG.Type.RELOCATECOHORT
capability.Performance=50 capability.Performance=50
table.insert(payload.capabilities, capability) table.insert(payload.capabilities, capability)
end end
-- Add OPSTRANSPORT for all.
if not AUFTRAG.CheckMissionType(AUFTRAG.Type.OPSTRANSPORT, MissionTypes) then
local capability={} --Ops.Auftrag#AUFTRAG.Capability
capability.MissionType=AUFTRAG.Type.OPSTRANSPORT
capability.Performance=50
table.insert(payload.capabilities, capability)
end
-- Info -- Info
self:T(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s", self:T(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s",
@@ -552,47 +541,6 @@ function AIRWING:AddPayloadCapability(Payload, MissionTypes, Performance)
return self return self
end end
--- Filter available payloads for a given aircraft type and mission type.
-- @param #AIRWING self
-- @param #string UnitType The type of the unit.
-- @param #string MissionType The mission type.
-- @param #table Payloads Specific payloads only to be considered.
-- @return #AIRWING.Payload Payload table or *nil*.
function AIRWING:_FilterPlayloads(UnitType, MissionType, Payloads)
local function _checkPayloads(payload)
if Payloads then
for _,Payload in pairs(Payloads) do
if Payload.uid==payload.uid then
return true
end
end
else
-- Payload was not specified.
return nil
end
return false
end
-- Pre-selection: filter out only those payloads that are valid for the airframe and mission type and are available.
local payloads={}
for _,_payload in pairs(self.payloads) do
local payload=_payload --#AIRWING.Payload
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities)
local goforit = specialpayload or (specialpayload==nil and compatible)
if payload.aircrafttype==UnitType and payload.navail>0 and goforit then
table.insert(payloads, payload)
end
end
return payloads
end
--- Fetch a payload from the airwing resources for a given unit and mission type. --- Fetch a payload from the airwing resources for a given unit and mission type.
-- The payload with the highest priority is preferred. -- The payload with the highest priority is preferred.
-- @param #AIRWING self -- @param #AIRWING self
@@ -641,7 +589,34 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads)
end end
end end
local payloads=self:_FilterPlayloads(UnitType, MissionType, Payloads) local function _checkPayloads(payload)
if Payloads then
for _,Payload in pairs(Payloads) do
if Payload.uid==payload.uid then
return true
end
end
else
-- Payload was not specified.
return nil
end
return false
end
-- Pre-selection: filter out only those payloads that are valid for the airframe and mission type and are available.
local payloads={}
for _,_payload in pairs(self.payloads) do
local payload=_payload --#AIRWING.Payload
local specialpayload=_checkPayloads(payload)
local compatible=AUFTRAG.CheckMissionCapability(MissionType, payload.capabilities)
local goforit = specialpayload or (specialpayload==nil and compatible)
if payload.aircrafttype==UnitType and payload.navail>0 and goforit then
table.insert(payloads, payload)
end
end
-- Debug. -- Debug.
if self.verbose>=4 then if self.verbose>=4 then
+22 -56
View File
@@ -45,7 +45,7 @@
-- **Supported Aircraft:** -- **Supported Aircraft:**
-- --
-- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI) -- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI)
-- * [F-14A/B/B(U)/A Early Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [F-14A/B/A Early Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) -- * [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) -- * [T-45C Goshawk](https://forum.dcs.world/topic/203816-vnao-t-45-goshawk/) (VNAO mod) (Player & AI)
@@ -1276,7 +1276,6 @@ AIRBOSS = {
-- @field #string A4EC A-4E Community mod. -- @field #string A4EC A-4E Community mod.
-- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics. -- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics.
-- @field #string F14A F-14A by Heatblur. -- @field #string F14A F-14A by Heatblur.
-- @field #string F14BU F-14BU by Heatblur.
-- @field #string F14A_Early F-14A-135-GR-Early by Heatblur. -- @field #string F14A_Early F-14A-135-GR-Early by Heatblur.
-- @field #string F14B F-14B by Heatblur. -- @field #string F14B F-14B by Heatblur.
-- @field #string F14A_AI F-14A Tomcat (AI). -- @field #string F14A_AI F-14A Tomcat (AI).
@@ -1298,7 +1297,6 @@ AIRBOSS.AircraftCarrier={
F14A="F-14A-135-GR", F14A="F-14A-135-GR",
F14A_Early="F-14A-135-GR-Early", F14A_Early="F-14A-135-GR-Early",
F14B="F-14B", F14B="F-14B",
F14BU="F-14BU",
F14A_AI="F-14A", F14A_AI="F-14A",
FA18C="F/A-18C", FA18C="F/A-18C",
T45C="T-45", T45C="T-45",
@@ -5510,7 +5508,7 @@ function AIRBOSS:_GetAircraftAoA( playerData )
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
local tomcat = self:_IsTomcat( playerData.actype ) local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early
local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW
-- Table with AoA values. -- Table with AoA values.
@@ -5573,18 +5571,6 @@ function AIRBOSS:_GetAircraftAoA( playerData )
aoa.OnSpeedMin = 9.5 aoa.OnSpeedMin = 9.5
aoa.Fast = 8.0 aoa.Fast = 8.0
aoa.FAST = 7.5 aoa.FAST = 7.5
else
-- Unknown carrier-capable type: _IsCarrierAircraft() is enum-driven, so a type can
-- reach here with no AoA branch. Returning an empty table nil-propagates into
-- _GetAircraftParameters() and the AoA waveoff check. Fall back to Hornet values.
self:E( self.lid .. string.format( "ERROR: No AoA parameters defined for aircraft type %s! Falling back to F/A-18C values.", tostring( playerData.actype ) ) )
aoa.SLOW = 9.8
aoa.Slow = 9.3
aoa.OnSpeedMax = 8.8
aoa.OnSpeed = 8.1
aoa.OnSpeedMin = 7.4
aoa.Fast = 6.9
aoa.FAST = 6.3
end end
return aoa return aoa
@@ -5601,7 +5587,7 @@ function AIRBOSS:_AoAUnit2Deg( playerData, aoaunits )
local degrees = aoaunits local degrees = aoaunits
-- Check aircraft type of player. -- Check aircraft type of player.
if self:_IsTomcat( playerData.actype ) then if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
------------- -------------
-- F-14A/B -- -- F-14A/B --
@@ -5644,7 +5630,7 @@ function AIRBOSS:_AoADeg2Units( playerData, degrees )
local aoaunits = degrees local aoaunits = degrees
-- Check aircraft type of player. -- Check aircraft type of player.
if self:_IsTomcat( playerData.actype ) then if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
------------- -------------
-- F-14A/B -- -- F-14A/B --
@@ -5694,7 +5680,7 @@ function AIRBOSS:_GetAircraftParameters( playerData, step )
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
local tomcat = self:_IsTomcat( playerData.actype ) local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW
@@ -6627,7 +6613,9 @@ function AIRBOSS:_RefuelAI( flight )
local refuelac=false local refuelac=false
local actype=flight.group:GetTypeName() local actype=flight.group:GetTypeName()
if actype==AIRBOSS.AircraftCarrier.AV8B or if actype==AIRBOSS.AircraftCarrier.AV8B or
self:_IsTomcat( actype, true ) or actype==AIRBOSS.AircraftCarrier.F14A or
actype==AIRBOSS.AircraftCarrier.F14B or
actype==AIRBOSS.AircraftCarrier.F14A_AI or
actype==AIRBOSS.AircraftCarrier.HORNET or actype==AIRBOSS.AircraftCarrier.HORNET or
actype==AIRBOSS.AircraftCarrier.RHINOE or actype==AIRBOSS.AircraftCarrier.RHINOE or
actype==AIRBOSS.AircraftCarrier.RHINOF or actype==AIRBOSS.AircraftCarrier.RHINOF or
@@ -6737,7 +6725,7 @@ function AIRBOSS:_LandAI( flight )
Speed = UTILS.KnotsToKmph( 200 ) Speed = UTILS.KnotsToKmph( 200 )
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D or flight.actype == AIRBOSS.AircraftCarrier.C2A then elseif flight.actype == AIRBOSS.AircraftCarrier.E2D or flight.actype == AIRBOSS.AircraftCarrier.C2A then
Speed = UTILS.KnotsToKmph( 150 ) Speed = UTILS.KnotsToKmph( 150 )
elseif self:_IsTomcat( flight.actype, true ) then elseif flight.actype == AIRBOSS.AircraftCarrier.F14A_AI or flight.actype == AIRBOSS.AircraftCarrier.F14A or flight.actype == AIRBOSS.AircraftCarrier.F14B then
Speed = UTILS.KnotsToKmph( 175 ) Speed = UTILS.KnotsToKmph( 175 )
elseif flight.actype == AIRBOSS.AircraftCarrier.S3B or flight.actype == AIRBOSS.AircraftCarrier.S3BTANKER then elseif flight.actype == AIRBOSS.AircraftCarrier.S3B or flight.actype == AIRBOSS.AircraftCarrier.S3BTANKER then
Speed = UTILS.KnotsToKmph( 140 ) Speed = UTILS.KnotsToKmph( 140 )
@@ -8212,7 +8200,7 @@ function AIRBOSS:_CheckPlayerStatus()
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
local tomcat = self:_IsTomcat( playerData.actype ) local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
-- VNAO Edit - Added wrapped up call to LSO grading Hornet -- VNAO Edit - Added wrapped up call to LSO grading Hornet
if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then-- VNAO Edit - Added if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then-- VNAO Edit - Added
@@ -9467,7 +9455,7 @@ function AIRBOSS:_Initial( playerData )
-- Hook down for students. -- Hook down for students.
if playerData.difficulty == AIRBOSS.Difficulty.EASY and playerData.actype ~= AIRBOSS.AircraftCarrier.AV8B then if playerData.difficulty == AIRBOSS.Difficulty.EASY and playerData.actype ~= AIRBOSS.AircraftCarrier.AV8B then
if self:_IsTomcat( playerData.actype ) then if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
hint = hint .. " - Hook down, SAS on, Wing Sweep 68°!" hint = hint .. " - Hook down, SAS on, Wing Sweep 68°!"
else else
hint = hint .. " - Hook down!" hint = hint .. " - Hook down!"
@@ -9632,7 +9620,8 @@ function AIRBOSS:_DirtyUp( playerData )
-- Radio call "Say/Fly needles". Delayed by 10/15 seconds. -- Radio call "Say/Fly needles". Delayed by 10/15 seconds.
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
or self:_IsTomcat( playerData.actype ) or playerData.actype == AIRBOSS.AircraftCarrier.F14A
or playerData.actype == AIRBOSS.AircraftCarrier.F14B
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
@@ -10826,7 +10815,7 @@ function AIRBOSS:_Trapped( playerData )
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then
dcorr = 100 dcorr = 100
elseif self:_IsTomcat( playerData.actype ) then elseif playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
-- TODO: Check Tomcat. -- TODO: Check Tomcat.
dcorr = 100 dcorr = 100
elseif playerData.actype == AIRBOSS.AircraftCarrier.A4EC then elseif playerData.actype == AIRBOSS.AircraftCarrier.A4EC then
@@ -11639,7 +11628,7 @@ function AIRBOSS:_AttitudeMonitor( playerData )
local unitClient = Unit.getByName(unit:GetName()) -- VNAO Edit - Added local unitClient = Unit.getByName(unit:GetName()) -- VNAO Edit - Added
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET -- VNAO Edit - Added local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET -- VNAO Edit - Added
local tomcat = self:_IsTomcat( playerData.actype ) -- VNAO Edit - Added local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early -- VNAO Edit - Added
if hornet then -- VNAO Edit - Added if hornet then -- VNAO Edit - Added
local nozzlePosL = 0 -- VNAO Edit - Added local nozzlePosL = 0 -- VNAO Edit - Added
@@ -11872,7 +11861,7 @@ function AIRBOSS:_NozzleArgumentLeft( unit ) -- VNAO Edit - Added
else -- VNAO Edit - Added else -- VNAO Edit - Added
nozzlePosL = 0 -- VNAO Edit - Added nozzlePosL = 0 -- VNAO Edit - Added
end -- VNAO Edit - Added end -- VNAO Edit - Added
elseif self:_IsTomcat( typeName ) then -- VNAO Edit - Added elseif typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
nozzlePosL = unitClient:getDrawArgumentValue(434) -- VNAO Edit - Added nozzlePosL = unitClient:getDrawArgumentValue(434) -- VNAO Edit - Added
end -- VNAO Edit - Added end -- VNAO Edit - Added
@@ -11897,7 +11886,7 @@ function AIRBOSS:_NozzleArgumentRight( unit ) -- VNAO Edit - Added
else -- VNAO Edit - Added else -- VNAO Edit - Added
nozzlePosR = 0 -- VNAO Edit - Added nozzlePosR = 0 -- VNAO Edit - Added
end -- VNAO Edit - Added end -- VNAO Edit - Added
elseif self:_IsTomcat( typeName ) then -- VNAO Edit - Added elseif typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
nozzlePosR = unitClient:getDrawArgumentValue(433) -- VNAO Edit - Added nozzlePosR = unitClient:getDrawArgumentValue(433) -- VNAO Edit - Added
end -- VNAO Edit - Added end -- VNAO Edit - Added
return nozzlePosR -- VNAO Edit - Added return nozzlePosR -- VNAO Edit - Added
@@ -13160,7 +13149,7 @@ function AIRBOSS:_Flightdata2Text( playerData, groovestep )
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET-- VNAO Edit - Added local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET-- VNAO Edit - Added
local tomcat = self:_IsTomcat( playerData.actype )-- 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 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? 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?
@@ -13676,7 +13665,7 @@ function AIRBOSS:_StepHint( playerData, step )
-- Late break. -- Late break.
if step == AIRBOSS.PatternStep.LATEBREAK then if step == AIRBOSS.PatternStep.LATEBREAK then
if self:_IsTomcat( playerData.actype ) then if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
hint = hint .. "\nWing Sweep 20°, Gear DOWN < 280 KIAS." hint = hint .. "\nWing Sweep 20°, Gear DOWN < 280 KIAS."
end end
end end
@@ -13685,7 +13674,7 @@ function AIRBOSS:_StepHint( playerData, step )
if step == AIRBOSS.PatternStep.ABEAM then if step == AIRBOSS.PatternStep.ABEAM then
if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then
hint = hint .. "\nNozzles 50°-60°. Antiskid OFF. Lights OFF." hint = hint .. "\nNozzles 50°-60°. Antiskid OFF. Lights OFF."
elseif self:_IsTomcat( playerData.actype ) then elseif playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
hint = hint .. "\nSlats/Flaps EXTENDED < 225 KIAS. DLC SELECTED. Auto Throttle IF DESIRED." hint = hint .. "\nSlats/Flaps EXTENDED < 225 KIAS. DLC SELECTED. Auto Throttle IF DESIRED."
else else
hint = hint .. "\nDirty up! Gear DOWN, flaps DOWN. Check hook down." hint = hint .. "\nDirty up! Gear DOWN, flaps DOWN. Check hook down."
@@ -15120,7 +15109,7 @@ function AIRBOSS:_GetACNickname( actype )
nickname = "Hawkeye" nickname = "Hawkeye"
elseif actype == AIRBOSS.AircraftCarrier.C2A then elseif actype == AIRBOSS.AircraftCarrier.C2A then
nickname = "Greyhound" nickname = "Greyhound"
elseif self:_IsTomcat( actype, true ) then elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B or actype == AIRBOSS.AircraftCarrier.F14A_Early then
nickname = "Tomcat" nickname = "Tomcat"
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
nickname = "Hornet" nickname = "Hornet"
@@ -15257,29 +15246,6 @@ function AIRBOSS:_GetGoodBadScore( playerData )
return lowscore, badscore return lowscore, badscore
end end
--- Check whether an aircraft type name is any F-14 Tomcat variant.
-- Central predicate so that adding a new Tomcat variant only requires editing
-- this function and the AIRBOSS.AircraftCarrier enum.
-- @param #AIRBOSS self
-- @param #string actype Aircraft type name, e.g. from `unit:GetTypeName()` or `playerData.actype`.
-- @param #boolean IncludeAI If true, also match the AI-only F-14A (AIRBOSS.AircraftCarrier.F14A_AI).
-- @return #boolean If true, actype is an F-14 variant.
function AIRBOSS:_IsTomcat( actype, IncludeAI )
if actype == AIRBOSS.AircraftCarrier.F14A
or actype == AIRBOSS.AircraftCarrier.F14B
or actype == AIRBOSS.AircraftCarrier.F14BU
or actype == AIRBOSS.AircraftCarrier.F14A_Early then
return true
end
if IncludeAI and actype == AIRBOSS.AircraftCarrier.F14A_AI then
return true
end
return false
end
--- Check if aircraft is capable of landing on this aircraft carrier. --- Check if aircraft is capable of landing on this aircraft carrier.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param Wrapper.Unit#UNIT unit Aircraft unit. (Will also work with groups as given parameter.) -- @param Wrapper.Unit#UNIT unit Aircraft unit. (Will also work with groups as given parameter.)
@@ -19428,4 +19394,4 @@ function AIRBOSS:onafterLSOGrade(From, Event, To, playerData, grade)
self.funkmanSocket:SendTable(result) self.funkmanSocket:SendTable(result)
end end
end end
+7 -71
View File
@@ -191,8 +191,6 @@
-- @field #boolean optionEmission Emission is on or off. -- @field #boolean optionEmission Emission is on or off.
-- @field #boolean optionInvisible Invisible is on/off. -- @field #boolean optionInvisible Invisible is on/off.
-- @field #boolean optionImmortal Immortal is on/off. -- @field #boolean optionImmortal Immortal is on/off.
--
-- @field #AUFTRAG.Summary summary Auftrag summary.
-- --
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@@ -658,19 +656,6 @@ AUFTRAG.Category={
-- @field #string DAMAGED Target was damaged. -- @field #string DAMAGED Target was damaged.
-- @field #string DESTROYED Target was destroyed. -- @field #string DESTROYED Target was destroyed.
--- Mission summary.
-- @type AUFTRAG.Summary
-- @field #boolean success If true, mission was successful.
-- @field #number Ntargets0 Number of initial targets.
-- @field #number Ntargets Number of final targets after mission is done.
-- @field #number damage Target damage in per cent.
-- @field #number Ndestroyed Number of destroyed targets.
-- @field #number Nkills Number of kills from assigned groups.
-- @field #number Nelements Number of elements assigned to mission.
-- @field #number targetLife Target life points after mission is over.
-- @field #number category Target category.
-- @field #number Ncasualties Number of own casualties.
--- Generic mission condition. --- Generic mission condition.
-- @type AUFTRAG.Condition -- @type AUFTRAG.Condition
-- @field #function func Callback function to check for a condition. Should return a #boolean. -- @field #function func Callback function to check for a condition. Should return a #boolean.
@@ -691,7 +676,7 @@ AUFTRAG.Category={
--- AUFTRAG class version. --- AUFTRAG class version.
-- @field #string version -- @field #string version
AUFTRAG.version="1.5.0" AUFTRAG.version="1.4.2"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -791,7 +776,6 @@ function AUFTRAG:New(Type)
self:AddTransition("*", "Cancel", AUFTRAG.Status.CANCELLED) -- Command to cancel the mission. self:AddTransition("*", "Cancel", AUFTRAG.Status.CANCELLED) -- Command to cancel the mission.
self:AddTransition("*", "Evaluated", "*")
self:AddTransition("*", "Success", AUFTRAG.Status.SUCCESS) self:AddTransition("*", "Success", AUFTRAG.Status.SUCCESS)
self:AddTransition("*", "Failed", AUFTRAG.Status.FAILED) self:AddTransition("*", "Failed", AUFTRAG.Status.FAILED)
@@ -962,24 +946,6 @@ function AUFTRAG:New(Type)
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
--- Triggers the FSM event "Evaluated".
-- @function [parent=#AUFTRAG] Evaluated
-- @param #AUFTRAG self
-- @param #AUFTRAG.Summary
--- Triggers the FSM event "Evaluated" after a delay.
-- @function [parent=#AUFTRAG] __Evaluated
-- @param #AUFTRAG self
-- @param #number delay Delay in seconds.
-- @param #AUFTRAG.Summary Summary Mission summary.
--- On after "Evaluated" event.
-- @function [parent=#AUFTRAG] OnAfterEvaluated
-- @param #AUFTRAG self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #AUFTRAG.Summary Summary Mission summary.
--- Triggers the FSM event "Success". --- Triggers the FSM event "Success".
-- @function [parent=#AUFTRAG] Success -- @function [parent=#AUFTRAG] Success
@@ -1784,9 +1750,6 @@ function AUFTRAG:NewBAI(Target, Altitude)
mission.missionFraction=0.75 mission.missionFraction=0.75
mission.optionROE=ENUMS.ROE.OpenFire mission.optionROE=ENUMS.ROE.OpenFire
mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionROT=ENUMS.ROT.PassiveDefense
-- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed.
mission.dTevaluate=5*60
mission.categories={AUFTRAG.Category.AIRCRAFT} mission.categories={AUFTRAG.Category.AIRCRAFT}
@@ -2340,9 +2303,6 @@ end
function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude) function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude)
local mission=AUFTRAG:New(AUFTRAG.Type.ARTY) local mission=AUFTRAG:New(AUFTRAG.Type.ARTY)
printf("FF nshots=%s", tostring(Nshots))
printf("FF radius=%s", tostring(Radius))
mission:_TargetFromObject(Target) mission:_TargetFromObject(Target)
@@ -2447,7 +2407,7 @@ end
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param Ops.OpsZone#OPSZONE OpsZone The OPS zone to capture. -- @param Ops.OpsZone#OPSZONE OpsZone The OPS zone to capture.
-- @param #number Coalition The coalition which should capture the zone for the mission to be successful. -- @param #number Coalition The coalition which should capture the zone for the mission to be successful.
-- @param #number Speed (Optional) Speed in knots. -- @param #number Speed Speed in knots.
-- @param #number Altitude (Optional) Altitude in feet. Only for airborne units. Default 2000 feet ASL. -- @param #number Altitude (Optional) Altitude in feet. Only for airborne units. Default 2000 feet ASL.
-- @param #string Formation (Optional) Formation used by ground units during patrol. Default "Off Road". -- @param #string Formation (Optional) 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. -- @param #number StayInZoneTime Stay this many seconds in the zone when done, only then drive back.
@@ -4729,18 +4689,6 @@ function AUFTRAG:Evaluate()
elseif successCondition then elseif successCondition then
failed=false failed=false
end end
self.summary={} --#AUFTRAG.Summary
self.summary.success=not failed
self.summary.damage=targetdamage
self.summary.Ntargets=Ntargets
self.summary.Ntargets0=Ntargets0
self.summary.Ncasualties=self.Ncasualties
self.summary.Ndestroyed=self.engageTarget.Ndestroyed
self.summary.Nelements=self.Nelements
self.summary.Nkills=self.Nkills
self.summary.category=self.engageTarget:GetCategory()
self.summary.targetLife=Life
-- Debug text. -- Debug text.
if self.verbose > 0 then if self.verbose > 0 then
@@ -4760,9 +4708,6 @@ function AUFTRAG:Evaluate()
text=text..string.format("=========================") text=text..string.format("=========================")
self:I(self.lid..text) self:I(self.lid..text)
end end
-- Trigger evaluated result and pass summary.
self:Evaluated(self.summary)
-- Trigger events. -- Trigger events.
if failed then if failed then
@@ -5073,7 +5018,7 @@ function AUFTRAG:CheckGroupsDone()
-- Check status of all OPS groups. -- Check status of all OPS groups.
for groupname,data in pairs(self.groupdata) do for groupname,data in pairs(self.groupdata) do
local groupdata=data --#AUFTRAG.GroupData local groupdata=data --#AUFTRAG.GroupData
if groupdata and not groupdata.opsgroup:IsDestroyed() then if groupdata then
if not (groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED) then if not (groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED) then
-- At least this group is not DONE or CANCELLED. -- At least this group is not DONE or CANCELLED.
self:T2(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!", groupdata.opsgroup.groupname, groupdata.status:upper())) self:T2(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!", groupdata.opsgroup.groupname, groupdata.status:upper()))
@@ -5175,6 +5120,7 @@ end
-- FSM Functions -- FSM Functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "Planned" event. --- On after "Planned" event.
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @param #string From From state. -- @param #string From From state.
@@ -5484,7 +5430,7 @@ function AUFTRAG:onafterSuccess(From, Event, To)
-- Stop mission. -- Stop mission.
self:T(self.lid..string.format("Mission SUCCESS! Number of max repeats %d reached ==> Stopping mission!", self.repeated+1)) self:T(self.lid..string.format("Mission SUCCESS! Number of max repeats %d reached ==> Stopping mission!", self.repeated+1))
self:__Stop(-120) self:Stop()
end end
@@ -5526,7 +5472,7 @@ function AUFTRAG:onafterFailed(From, Event, To)
-- Stop mission. -- Stop mission.
self:T(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!", self.repeated+1)) self:T(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!", self.repeated+1))
self:__Stop(-120) self:Stop()
end end
@@ -5639,7 +5585,6 @@ function AUFTRAG:onafterRepeat(From, Event, To)
self.Ngroups=0 self.Ngroups=0
self.Nassigned=nil self.Nassigned=nil
self.Ndead=0 self.Ndead=0
self.summary=nil
-- Update DCS mission task. Could be that the initial task (e.g. for bombing) was destroyed. Then we need to update the coordinate. -- Update DCS mission task. Could be that the initial task (e.g. for bombing) was destroyed. Then we need to update the coordinate.
self.DCStask=self:GetDCSMissionTask() self.DCStask=self:GetDCSMissionTask()
@@ -5690,8 +5635,6 @@ function AUFTRAG:onafterStop(From, Event, To)
-- No group data. -- No group data.
self.groupdata={} self.groupdata={}
self.summary=nil
-- Clear pending scheduler calls. -- Clear pending scheduler calls.
self.CallScheduler:Clear() self.CallScheduler:Clear()
@@ -6037,7 +5980,6 @@ function AUFTRAG:CountOpsGroups()
local groupdata=_groupdata --#AUFTRAG.GroupData local groupdata=_groupdata --#AUFTRAG.GroupData
if groupdata and groupdata.opsgroup and groupdata.opsgroup:IsAlive() and not groupdata.opsgroup:IsDead() then if groupdata and groupdata.opsgroup and groupdata.opsgroup:IsAlive() and not groupdata.opsgroup:IsDead() then
N=N+1 N=N+1
printf("FF Count Group N=%d %s", N, tostring(groupdata.opsgroup.groupname))
end end
end end
return N return N
@@ -6524,13 +6466,7 @@ function AUFTRAG:GetDCSMissionTask(MissionGroup)
-- ESCORT Mission -- -- ESCORT Mission --
-------------------- --------------------
local FollowGroup=self.engageTarget:GetObject() local DCStask=CONTROLLABLE.TaskEscort(nil, self.engageTarget:GetObject(), self.escortVec3, nil, self.engageMaxDistance, self.engageTargetTypes)
local DCStask=nil
if next(self.engageTargetTypes)==nil then
DCStask=FollowGroup:TaskFollow(FollowGroup, self.escortVec3)
else
DCStask=CONTROLLABLE.TaskEscort(nil, FollowGroup, self.escortVec3, nil, self.engageMaxDistance, self.engageTargetTypes)
end
table.insert(DCStasks, DCStask) table.insert(DCStasks, DCStask)
-3
View File
@@ -150,9 +150,6 @@ function BRIGADE:New(WarehouseName, BrigadeName)
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission. -- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self return self
end end
+64 -70
View File
@@ -183,7 +183,6 @@ do
-- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted. -- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted.
-- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true` -- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true`
-- my_ctld.movecratesbeforebuild = true -- crates must be moved once before they can be build. Set to false for direct builds. -- my_ctld.movecratesbeforebuild = true -- crates must be moved once before they can be build. Set to false for direct builds.
-- my_ctld.useaircraftheadingforbuilds = false -- Matches the heading of the spawned groups from a crate build to the aircraft heading when starting the build. Default is false for template heading.
-- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects. -- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects.
-- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. -- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you.
-- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. -- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz.
@@ -2038,7 +2037,7 @@ function CTLD:_C130DcAutoRegisterEntry(SetId, Cargo)
local pos = Cargo:GetPositionable() local pos = Cargo:GetPositionable()
local pname = pos and pos.GetName and pos:GetName() or nil local pname = pos and pos.GetName and pos:GetName() or nil
local pcoord = pos and pos.GetCoord and pos:GetCoord() or nil local pcoord = pos and pos.GetCoordinate and pos:GetCoordinate() or nil
local entryId = string.format("%s#%d", SetId, #setData.entries + 1) local entryId = string.format("%s#%d", SetId, #setData.entries + 1)
local entry = { local entry = {
id = entryId, id = entryId,
@@ -2749,14 +2748,16 @@ function CTLD:_EventHandler(EventData)
loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo
loaded.Cratesloaded = loaded.Cratesloaded - 1 loaded.Cratesloaded = loaded.Cratesloaded - 1
if loaded.Cratesloaded < 0 then loaded.Cratesloaded = 0 end if loaded.Cratesloaded < 0 then loaded.Cratesloaded = 0 end
-- TODO zap cargo from list
local Loaded = {} local Loaded = {}
for _,_item in pairs (loaded.Cargo or {}) do for _,_item in pairs (loaded.Cargo or {}) do
self:T(self.lid.."UNLOAD checking: ".._item:GetName()) self:T(self.lid.."UNLOAD checking: ".._item:GetName())
self:T(self.lid.."UNLOAD state: ".. tostring(_item:WasDropped())) self:T(self.lid.."UNLOAD state: ".. tostring(_item:WasDropped()))
local unloadedItem = _item and _item:GetType() == CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName == _item:GetName() if _item and _item:GetType() == CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName ~= _item:GetName() and not _item:WasDropped() then
if not unloadedItem then
table.insert(Loaded,_item) table.insert(Loaded,_item)
end else
table.insert(Loaded,_item)
end
end end
loaded.Cargo = nil loaded.Cargo = nil
loaded.Cargo = Loaded loaded.Cargo = Loaded
@@ -3124,14 +3125,14 @@ end
function CTLD:_FindRepairNearby(Group, Unit, Repairtype) function CTLD:_FindRepairNearby(Group, Unit, Repairtype)
self:T(self.lid .. " _FindRepairNearby") self:T(self.lid .. " _FindRepairNearby")
--self:T({Group:GetName(),Unit:GetName(),Repairtype}) --self:T({Group:GetName(),Unit:GetName(),Repairtype})
local unitcoord = Unit:GetCoord() local unitcoord = Unit:GetCoordinate()
-- find nearest group of deployed groups -- find nearest group of deployed groups
local nearestGroup = nil local nearestGroup = nil
local nearestGroupIndex = -1 local nearestGroupIndex = -1
local nearestDistance = 10000 local nearestDistance = 10000
for k,v in pairs(self.DroppedTroops) do for k,v in pairs(self.DroppedTroops) do
local distance = self:_GetDistance(v:GetCoord(),unitcoord) local distance = self:_GetDistance(v:GetCoordinate(),unitcoord)
local unit = v:GetUnit(1) -- Wrapper.Unit#UNIT local unit = v:GetUnit(1) -- Wrapper.Unit#UNIT
local desc = unit:GetDesc() or nil local desc = unit:GetDesc() or nil
if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then
@@ -3276,7 +3277,7 @@ end
local capabilities = self:_GetUnitCapabilities(Unit) local capabilities = self:_GetUnitCapabilities(Unit)
local cantroops = capabilities.troops -- #boolean local cantroops = capabilities.troops -- #boolean
local trooplimit = capabilities.trooplimit -- #number local trooplimit = capabilities.trooplimit -- #number
local unitcoord = unit:GetCoord() local unitcoord = unit:GetCoordinate()
-- find nearest group of deployed troops -- find nearest group of deployed troops
local nearestGroup = nil local nearestGroup = nil
@@ -3287,7 +3288,7 @@ end
local distancekeys = {} local distancekeys = {}
local extractdistance = self.CrateDistance * self.ExtractFactor local extractdistance = self.CrateDistance * self.ExtractFactor
for k,v in pairs(self.DroppedTroops) do for k,v in pairs(self.DroppedTroops) do
local distance = self:_GetDistance(v:GetCoord(),unitcoord) local distance = self:_GetDistance(v:GetCoordinate(),unitcoord)
local TNow = timer.getTime() local TNow = timer.getTime()
local vtime = v.ExtractTime or TNow-310 local vtime = v.ExtractTime or TNow-310
if distance <= extractdistance and distance ~= -1 and (TNow - vtime > 300) then if distance <= extractdistance and distance ~= -1 and (TNow - vtime > 300) then
@@ -3375,7 +3376,7 @@ end
self:_UpdateUnitCargoMass(Unit) self:_UpdateUnitCargoMass(Unit)
local groupname = nearestGroup:GetName() local groupname = nearestGroup:GetName()
self:__TroopsExtracted(running,Group, Unit, nearestGroup, groupname) self:__TroopsExtracted(running,Group, Unit, nearestGroup, groupname)
local coord = Unit:GetCoord() or Group:GetCoord() -- Core.Point#COORDINATE local coord = Unit:GetCoordinate() or Group:GetCoordinate() -- Core.Point#COORDINATE
local Point local Point
if coord then if coord then
local heading = unit:GetHeading() or 0 local heading = unit:GetHeading() or 0
@@ -3746,16 +3747,16 @@ function CTLD:_C130GetUnits(Group, Unit, Name)
return self return self
end end
local coord = Unit:GetCoord() or Group:GetCoord() local coord = Unit:GetCoordinate() or Group:GetCoordinate()
local capabilities = self:_GetUnitCapabilities(Unit) local capabilities = self:_GetUnitCapabilities(Unit)
local innerDist = (capabilities.length and capabilities.length/2) or 15 local innerDist = (capabilities.length and capabilities.length/2) or 15
local maxUnitsNearby = self.maxUnitsNearby or 3 local maxUnitsNearby = self.maxUnitsNearby or 3
local searchRadius = self.UnitDistance or 90 local searchRadius = self.UnitDistance or 90
local checkZone = ZONE_RADIUS:New("CTLD_C130UnitsZone",coord:GetVec2(),searchRadius,false) local checkZone = ZONE_RADIUS:New("CTLD_C130UnitsZone",coord:GetVec2(),searchRadius,false)
local nearGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({checkZone}):FilterOnce() local nearGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({checkZone}):FilterOnce()
local nearbyCount = 0 local nearbyCount = 0
for _,gr in pairs(nearGroups.Set) do for _,gr in pairs(nearGroups.Set) do
local gc = gr:GetCoord() local gc = gr:GetCoordinate()
if gc then if gc then
local dist = coord:Get2DDistance(gc) local dist = coord:Get2DDistance(gc)
if dist > innerDist then if dist > innerDist then
@@ -3805,7 +3806,6 @@ function CTLD:_C130GetUnits(Group, Unit, Name)
if canmove then if canmove then
SPAWN:NewWithAlias(_template,alias) SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(true,10,2) :InitRandomizeUnits(true,10,2)
:InitCoalition(self.coalition)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits, 70) :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits, 70)
:InitDelayOff() :InitDelayOff()
:OnSpawnGroup(function(grp,TimeStamp) :OnSpawnGroup(function(grp,TimeStamp)
@@ -3819,7 +3819,6 @@ function CTLD:_C130GetUnits(Group, Unit, Name)
SPAWN:NewWithAlias(_template,alias) SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(true,10,2) :InitRandomizeUnits(true,10,2)
:InitDelayOff() :InitDelayOff()
:InitCoalition(self.coalition)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits, 70) :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits, 70)
:OnSpawnGroup(function(grp,TimeStamp) :OnSpawnGroup(function(grp,TimeStamp)
grp.spawntime = TimeStamp or timer.getTime() grp.spawntime = TimeStamp or timer.getTime()
@@ -3926,7 +3925,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
-- Check cargo location if available -- Check cargo location if available
local location = Cargo:GetLocation() local location = Cargo:GetLocation()
if location then if location then
local unitcoord = Unit:GetCoord() or Group:GetCoord() local unitcoord = Unit:GetCoordinate() or Group:GetCoordinate()
if unitcoord then if unitcoord then
if not location:IsCoordinateInZone(unitcoord) then if not location:IsCoordinateInZone(unitcoord) then
-- no we're not at the right spot -- no we're not at the right spot
@@ -3973,7 +3972,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
end end
-- get position and heading of heli -- get position and heading of heli
local position = Unit:GetCoord() local position = Unit:GetCoordinate()
local heading = Unit:GetHeading() + 1 local heading = Unit:GetHeading() + 1
local height = Unit:GetHeight() local height = Unit:GetHeight()
local droppedcargo = {} local droppedcargo = {}
@@ -4137,8 +4136,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
if type(ship) == "string" then if type(ship) == "string" then
self:T("Spawning on ship "..ship) self:T("Spawning on ship "..ship)
local Ship = UNIT:FindByName(ship) local Ship = UNIT:FindByName(ship)
local shipcoord = Ship:GetCoord() local shipcoord = Ship:GetCoordinate()
local unitcoord = Unit:GetCoord() local unitcoord = Unit:GetCoordinate()
local dist = shipcoord:Get2DDistance(unitcoord) local dist = shipcoord:Get2DDistance(unitcoord)
dist = dist - (20 + math.random(1, 10)) dist = dist - (20 + math.random(1, 10))
local halfwidth = (width or 20) / 2 local halfwidth = (width or 20) / 2
@@ -4390,17 +4389,17 @@ end
function CTLD:_C130RemoveUnitsNearby(_group,_unit) function CTLD:_C130RemoveUnitsNearby(_group,_unit)
self:T(self.lid .. " _C130RemoveUnitsNearby") self:T(self.lid .. " _C130RemoveUnitsNearby")
if not _group or not _unit then return self end if not _group or not _unit then return self end
local location = _group:GetCoord() local location = _group:GetCoordinate()
if not location then return self end if not location then return self end
local capabilities = self:_GetUnitCapabilities(_unit) local capabilities = self:_GetUnitCapabilities(_unit)
local innerDist = (capabilities.length and capabilities.length/2) or 15 local innerDist = (capabilities.length and capabilities.length/2) or 15
local finddist = self.PackDistance or (self.CrateDistance or 35) local finddist = self.PackDistance or (self.CrateDistance or 35)
local zone = ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false) local zone = ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false)
local nearestGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({zone}):FilterOnce() local nearestGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce()
local removedAny = false local removedAny = false
local removedTable = {} local removedTable = {}
for _, gr in pairs(nearestGroups.Set) do for _, gr in pairs(nearestGroups.Set) do
local gc = gr:GetCoord() local gc = gr:GetCoordinate()
if gc then if gc then
local dist = location:Get2DDistance(gc) local dist = location:Get2DDistance(gc)
if dist > innerDist then if dist > innerDist then
@@ -4534,7 +4533,7 @@ end
function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype, ignoreHercInner) function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype, ignoreHercInner)
self:T(self.lid .. " _FindCratesNearby") self:T(self.lid .. " _FindCratesNearby")
local finddist = _dist local finddist = _dist
local location = _group:GetCoord() local location = _group:GetCoordinate()
local existingcrates = self.Spawned_Cargo -- #table local existingcrates = self.Spawned_Cargo -- #table
-- cycle -- cycle
local index = 0 local index = 0
@@ -5160,6 +5159,7 @@ function CTLD:_UnloadTroops(Group, Unit)
-- unload troops -- unload troops
local name = cargo:GetName() or "none" local name = cargo:GetName() or "none"
local temptable = cargo:GetTemplates() or {} local temptable = cargo:GetTemplates() or {}
local position = Group:GetCoordinate()
local zoneradius = self.troopdropzoneradius or 100 -- drop zone radius local zoneradius = self.troopdropzoneradius or 100 -- drop zone radius
local factor = 1 local factor = 1
if IsHerc then if IsHerc then
@@ -5196,7 +5196,6 @@ function CTLD:_UnloadTroops(Group, Unit)
local Positions = self:_GetUnitPositions(randomcoord,rad,heading,_template) local Positions = self:_GetUnitPositions(randomcoord,rad,heading,_template)
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
:InitDelayOff() :InitDelayOff()
:InitCoalition(self.coalition)
:InitSetUnitAbsolutePositions(Positions) :InitSetUnitAbsolutePositions(Positions)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
@@ -5507,8 +5506,8 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop,NotifyGroup)
local required = Crate:GetCratesNeeded() local required = Crate:GetCratesNeeded()
local template = Crate:GetTemplates() local template = Crate:GetTemplates()
local ctype = Crate:GetType() local ctype = Crate:GetType()
local ccoord = Crate:GetPositionable():GetCoord() -- Core.Point#COORDINATE local ccoord = Crate:GetPositionable():GetCoordinate() -- Core.Point#COORDINATE
local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoord())or 0 local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0
local isHercDrop=Crate:WasDropped(true) local isHercDrop=Crate:WasDropped(true)
if not isHercDrop and distToUnit>baseDist then if not isHercDrop and distToUnit>baseDist then
elseif self:IsC130J(Unit) and distToUnit<15 then elseif self:IsC130J(Unit) and distToUnit<15 then
@@ -5527,7 +5526,6 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop,NotifyGroup)
object.Found = 1 object.Found = 1
object.Template = template object.Template = template
object.CanBuild = false object.CanBuild = false
object.Heading = Unit:GetHeading()
object.Type = ctype -- #CTLD_CARGO.Enum object.Type = ctype -- #CTLD_CARGO.Enum
object.Coord = ccoord:GetVec2() object.Coord = ccoord:GetVec2()
buildables[name] = object buildables[name] = object
@@ -5613,7 +5611,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop,NotifyGroup)
local sep = self.buildPairSeparation or 25 local sep = self.buildPairSeparation or 25
local hdg = (Unit:GetHeading()+180)%360 local hdg = (Unit:GetHeading()+180)%360
local lat = (hdg+90)%360 local lat = (hdg+90)%360
local base = Unit:GetCoord():Translate(20,hdg) local base = Unit:GetCoordinate():Translate(20,hdg)
if full == 1 then if full == 1 then
local cratesNow, numberNow = self:_FindCratesNearby(Group,Unit, finddist,true,true, not Engineering) local cratesNow, numberNow = self:_FindCratesNearby(Group,Unit, finddist,true,true, not Engineering)
@@ -5647,7 +5645,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop,NotifyGroup)
self:_RefreshPackMenus(Group,Unit) self:_RefreshPackMenus(Group,Unit)
local off = start + (n-1)*sep local off = start + (n-1)*sep
local coord = base:Translate(off,lat):GetVec2() local coord = base:Translate(off,lat):GetVec2()
local b = { Name=build.Name, Required=build.Required, Template=build.Template, CanBuild=true, Type=build.Type, Coord=coord, Heading=build.Heading } local b = { Name=build.Name, Required=build.Required, Template=build.Template, CanBuild=true, Type=build.Type, Coord=coord }
if self.buildtime and self.buildtime > 0 then if self.buildtime and self.buildtime > 0 then
local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop) local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop)
buildtimer:Start(self.buildtime) buildtimer:Start(self.buildtime)
@@ -5685,18 +5683,18 @@ end
-- @return #number Number -- @return #number Number
function CTLD:_FindPackableGroupsNearby(Group, Unit) function CTLD:_FindPackableGroupsNearby(Group, Unit)
self:T(self.lid .. " _FindPackableGroupsNearby") self:T(self.lid .. " _FindPackableGroupsNearby")
local location = Group:GetCoord() local location = Group:GetCoordinate()
if not location then return {}, 0 end if not location then return {}, 0 end
local capabilities = self:_GetUnitCapabilities(Unit) local capabilities = self:_GetUnitCapabilities(Unit)
local innerDist = (capabilities.length and capabilities.length/2) or 15 local innerDist = (capabilities.length and capabilities.length/2) or 15
local finddist = self.PackDistance or (self.CrateDistance or 35) local finddist = self.PackDistance or (self.CrateDistance or 35)
local zone = ZONE_RADIUS:New("CTLD_PackableZone", location:GetVec2(), finddist, false) local zone = ZONE_RADIUS:New("CTLD_PackableZone", location:GetVec2(), finddist, false)
local nearestGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({zone}):FilterOnce() local nearestGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce()
local packable = {} local packable = {}
for _, gr in pairs(nearestGroups.Set) do for _, gr in pairs(nearestGroups.Set) do
if gr and gr:GetName() ~= Group:GetName() then if gr and gr:GetName() ~= Group:GetName() then
local gc = gr:GetCoord() local gc = gr:GetCoordinate()
if gc then if gc then
local dist = location:Get2DDistance(gc) local dist = location:Get2DDistance(gc)
if dist > innerDist and dist <= finddist then if dist > innerDist and dist <= finddist then
@@ -5829,12 +5827,12 @@ function CTLD:_LoadPackedCratesByIds(Group, Unit, crateIds, cargoName)
local matchingCrates = {} local matchingCrates = {}
local finddist = self.CrateDistance or 35 local finddist = self.CrateDistance or 35
local location = Group:GetCoord() local location = Group:GetCoordinate()
for _, crateObj in pairs(self.Spawned_Cargo or {}) do for _, crateObj in pairs(self.Spawned_Cargo or {}) do
if crateObj and idLookup[crateObj:GetID()] then if crateObj and idLookup[crateObj:GetID()] then
local pos = crateObj:GetPositionable() local pos = crateObj:GetPositionable()
if pos and pos:IsAlive() then if pos and pos:IsAlive() then
local dist = location:Get2DDistance(pos:GetCoord()) local dist = location:Get2DDistance(pos:GetCoordinate())
if dist <= finddist then if dist <= finddist then
matchingCrates[#matchingCrates + 1] = crateObj matchingCrates[#matchingCrates + 1] = crateObj
end end
@@ -5944,12 +5942,12 @@ function CTLD:_RemovePackedCratesByIds(Group, Unit, crateIds)
local crates = {} local crates = {}
local finddist = self.CrateDistance or 35 local finddist = self.CrateDistance or 35
local location = Group:GetCoord() local location = Group:GetCoordinate()
for _, entry in pairs(self.Spawned_Cargo or {}) do for _, entry in pairs(self.Spawned_Cargo or {}) do
if entry and idLookup[entry:GetID()] then if entry and idLookup[entry:GetID()] then
local pos = entry:GetPositionable() local pos = entry:GetPositionable()
if pos and pos:IsAlive() then if pos and pos:IsAlive() then
local dist = location:Get2DDistance(pos:GetCoord()) local dist = location:Get2DDistance(pos:GetCoordinate())
if dist <= finddist then if dist <= finddist then
crates[#crates + 1] = entry crates[#crates + 1] = entry
end end
@@ -6173,11 +6171,11 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
local ctype = Build.Type -- #CTLD_CARGO.Enum local ctype = Build.Type -- #CTLD_CARGO.Enum
local canmove = false local canmove = false
if ctype == CTLD_CARGO.Enum.VEHICLE then canmove = true end if ctype == CTLD_CARGO.Enum.VEHICLE then canmove = true end
if ctype == CTLD_CARGO.Enum.STATIC then if ctype == CTLD_CARGO.Enum.STATIC then
return self return self
end end
local temptable = Build.Template or {} local temptable = Build.Template or {}
if type(temptable) == "string" then if type(temptable) == "string" then
temptable = {temptable} temptable = {temptable}
end end
local zone = nil -- Core.Zone#ZONE_RADIUS local zone = nil -- Core.Zone#ZONE_RADIUS
@@ -6199,31 +6197,28 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
for _,_template in pairs(temptable) do for _,_template in pairs(temptable) do
self.TroopCounter = self.TroopCounter + 1 self.TroopCounter = self.TroopCounter + 1
local alias = string.format("%s-%d", _template, math.random(1,100000)) local alias = string.format("%s-%d", _template, math.random(1,100000))
local spawn = SPAWN:NewWithAlias(_template,alias) if canmove then
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
--:InitRandomizeUnits(true,20,2)
:InitDelayOff() :InitDelayOff()
:InitCoalition(self.coalition)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
if canmove then :SpawnFromVec2(randomcoord)
--spawn:InitRandomizeUnits(true,20,2) 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 end
if Build.Heading and self.useaircraftheadingforbuilds then
spawn:InitGroupHeading(Build.Heading)
end
local spawnedGroup = spawn:SpawnFromVec2(randomcoord)
self.DroppedTroops[self.TroopCounter] = spawnedGroup
if Repair then if Repair then
self:__CratesRepaired(1,Group,Unit,spawnedGroup) self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
else else
self:__CratesBuild(1,Group,Unit,spawnedGroup) self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
end end
end -- template loop end -- template loop
self:_RefreshLoadCratesMenu(Group, Unit)
if Group and Group:IsAlive() and Group:GetID() then self:_RefreshPackMenus(Group, Unit)
self:_RefreshLoadCratesMenu(Group, Unit)
self:_RefreshPackMenus(Group, Unit)
end
else else
self:T(self.lid.."Group KIA while building!") self:T(self.lid.."Group KIA while building!")
end end
@@ -6246,11 +6241,14 @@ end
-- @param Wrapper.Group#GROUP Group The Group to move. -- @param Wrapper.Group#GROUP Group The Group to move.
function CTLD:_MoveGroupToZone(Group) function CTLD:_MoveGroupToZone(Group)
self:T(self.lid .. " _MoveGroupToZone") self:T(self.lid .. " _MoveGroupToZone")
local groupname = Group:GetName() or "none"
local groupcoord = Group:GetCoordinate()
-- Get closest zone of type -- Get closest zone of type
local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE)
self:T({canmove=outcome, name=name, zone=zone, dist=distance,max=self.movetroopsdistance}) self:T({canmove=outcome, name=name, zone=zone, dist=distance,max=self.movetroopsdistance})
if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then
-- yes, we can ;) -- yes, we can ;)
local groupname = Group:GetName()
local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE
local formation = self:_GetVehicleFormation() local formation = self:_GetVehicleFormation()
--local coordinate = zonecoord:GetVec2() --local coordinate = zonecoord:GetVec2()
@@ -7933,7 +7931,6 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID, qty)
local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template) local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template)
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias)
:InitDelayOff() :InitDelayOff()
:InitCoalition(self.coalition)
:InitSetUnitAbsolutePositions(Positions) :InitSetUnitAbsolutePositions(Positions)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
@@ -9011,7 +9008,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
local zoneret = nil local zoneret = nil
local zonewret = nil local zonewret = nil
local zonenameret = nil local zonenameret = nil
local unitcoord = Unit:GetCoord() local unitcoord = Unit:GetCoordinate()
if not unitcoord then if not unitcoord then
if Zonetype == CTLD.CargoZoneType.SHIP then if Zonetype == CTLD.CargoZoneType.SHIP then
return false, nil, nil, 1000000, nil return false, nil, nil, 1000000, nil
@@ -9032,7 +9029,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
self:T("Checking Type Ship: "..zonename) self:T("Checking Type Ship: "..zonename)
local ZoneUNIT = UNIT:FindByName(zonename) local ZoneUNIT = UNIT:FindByName(zonename)
if not ZoneUNIT then return false end if not ZoneUNIT then return false end
zonecoord = ZoneUNIT:GetCoord() zonecoord = ZoneUNIT:GetCoordinate()
zoneradius = czone.shiplength zoneradius = czone.shiplength
zonewidth = czone.shipwidth zonewidth = czone.shipwidth
zone = ZONE_UNIT:New( ZoneUNIT:GetName(), ZoneUNIT, zoneradius/2) zone = ZONE_UNIT:New( ZoneUNIT:GetName(), ZoneUNIT, zoneradius/2)
@@ -9081,7 +9078,7 @@ function CTLD:SmokePositionNow(Unit, Flare, SmokeColor)
end end
local FlareColor = self.FlareColor or FLARECOLOR.Red local FlareColor = self.FlareColor or FLARECOLOR.Red
-- table of #CTLD.CargoZone table -- table of #CTLD.CargoZone table
local unitcoord = Unit:GetCoord() -- Core.Point#COORDINATE local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE
local Group = Unit:GetGroup() local Group = Unit:GetGroup()
if Flare then if Flare then
unitcoord:Flare(FlareColor, 90) unitcoord:Flare(FlareColor, 90)
@@ -9100,7 +9097,7 @@ end
function CTLD:SmokeZoneNearBy(Unit, Flare) function CTLD:SmokeZoneNearBy(Unit, Flare)
self:T(self.lid .. " SmokeZoneNearBy") self:T(self.lid .. " SmokeZoneNearBy")
-- table of #CTLD.CargoZone table -- table of #CTLD.CargoZone table
local unitcoord = Unit:GetCoord() local unitcoord = Unit:GetCoordinate()
local Group = Unit:GetGroup() local Group = Unit:GetGroup()
local smokedistance = self.smokedistance local smokedistance = self.smokedistance
local smoked = false local smoked = false
@@ -9237,7 +9234,7 @@ end
-- get speed and height -- get speed and height
local uspeed = Unit:GetVelocityMPS() local uspeed = Unit:GetVelocityMPS()
local uheight = Unit:GetHeight() local uheight = Unit:GetHeight()
local ucoord = Unit:GetCoord() local ucoord = Unit:GetCoordinate()
if not ucoord then if not ucoord then
return false return false
end end
@@ -9266,7 +9263,7 @@ end
-- get speed and height -- get speed and height
local uspeed = Unit:GetVelocityMPS() local uspeed = Unit:GetVelocityMPS()
local uheight = Unit:GetHeight() local uheight = Unit:GetHeight()
local ucoord = Unit:GetCoord() local ucoord = Unit:GetCoordinate()
if not ucoord then if not ucoord then
return false return false
end end
@@ -9358,7 +9355,7 @@ end
minheight = 5.1 -- herc is 5m AGL on the ground minheight = 5.1 -- herc is 5m AGL on the ground
end end
local uheight = Unit:GetHeight() local uheight = Unit:GetHeight()
local ucoord = Unit:GetCoord() local ucoord = Unit:GetCoordinate()
if not ucoord then if not ucoord then
return false return false
end end
@@ -10144,7 +10141,6 @@ end
local alias = string.format("%s-%d", _template, math.random(1,100000)) local alias = string.format("%s-%d", _template, math.random(1,100000))
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(randompositions,20,2) :InitRandomizeUnits(randompositions,20,2)
:InitCoalition(self.coalition)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:InitDelayOff() :InitDelayOff()
:OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp)
@@ -10299,7 +10295,6 @@ end
if canmove then if canmove then
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
:InitRandomizeUnits(true,20,2) :InitRandomizeUnits(true,20,2)
:InitCoalition(self.coalition)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:InitDelayOff() :InitDelayOff()
:OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp)
@@ -10307,7 +10302,6 @@ end
else -- don't random position of e.g. SAM units build as FOB else -- don't random position of e.g. SAM units build as FOB
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
:InitDelayOff() :InitDelayOff()
:InitCoalition(self.coalition)
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits) :InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
:OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp)
:SpawnFromVec2(randomcoord) :SpawnFromVec2(randomcoord)
@@ -10589,7 +10583,7 @@ end
self:T({From, Event, To}) self:T({From, Event, To})
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
local playername = Unit:GetPlayerName() local playername = Unit:GetPlayerName()
local dropcoord = Troops:GetCoord() or COORDINATE:New(0,0,0) local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0)
local dropvec2 = dropcoord:GetVec2() local dropvec2 = dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach( self.PlayerTaskQueue:ForEach(
function (Task) function (Task)
@@ -10646,7 +10640,7 @@ end
for _,_cargo in pairs(Cargotable) do for _,_cargo in pairs(Cargotable) do
local Vehicle = _cargo.Positionable local Vehicle = _cargo.Positionable
if Vehicle then if Vehicle then
local dropcoord = Vehicle:GetCoord() or COORDINATE:New(0,0,0) local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0)
local dropvec2 = dropcoord:GetVec2() local dropvec2 = dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach( self.PlayerTaskQueue:ForEach(
function (Task) function (Task)
@@ -10711,7 +10705,7 @@ end
self:T({From, Event, To}) self:T({From, Event, To})
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
local playername = Unit:GetPlayerName() local playername = Unit:GetPlayerName()
local dropcoord = Vehicle:GetCoord() or COORDINATE:New(0,0,0) local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0)
local dropvec2 = dropcoord:GetVec2() local dropvec2 = dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach( self.PlayerTaskQueue:ForEach(
function (Task) function (Task)
-25
View File
@@ -1102,31 +1102,6 @@ function COHORT:CountAssets(InStock, MissionTypes, Attributes)
return N return N
end end
--- Count available assets in legion warehouse stock. Spawned, requested and reserved assets are not counted.
-- @param #COHORT self
-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types.
-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`.
-- @return #number Number of assets.
function COHORT:CountAvailableAssets(MissionTypes, Attributes)
local N=0
for _,_asset in pairs(self.assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
if not (asset.spawned or asset.requested or asset.isReserved) then
if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes, self.missiontypes) then
if Attributes==nil or self:CheckAttribute(Attributes) then
N=N+1 --This is in stock.
end
end
end
end
return N
end
--- Get OPSGROUPs. --- Get OPSGROUPs.
-- @param #COHORT self -- @param #COHORT self
-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. -- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types.
-18
View File
@@ -1882,8 +1882,6 @@ function COMMANDER:RecruitAssetsForMission(Mission)
-- Recruite assets. -- Recruite assets.
local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads, local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, Mission.type, Mission.alert5MissionType, NreqMin, NreqMax, TargetVec2, Payloads,
Mission.engageRange, Mission.refuelSystem, nil, nil, MaxWeight, nil, Mission.attributes, Mission.properties, {Mission.engageWeaponType}) Mission.engageRange, Mission.refuelSystem, nil, nil, MaxWeight, nil, Mission.attributes, Mission.properties, {Mission.engageWeaponType})
self:T(self.lid..string.format("Recruited=%s Nassets=%d", tostring(recruited), #assets))
return recruited, assets, legions return recruited, assets, legions
end end
@@ -2114,22 +2112,6 @@ function COMMANDER:CountAssets(InStock, MissionTypes, Attributes)
return N return N
end end
--- Count available assets of all assigned legions, which are in stock. Spawned and reserved assets are not counted.
-- @param #COMMANDER self
-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types.
-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`.
-- @return #number Amount of asset groups.
function COMMANDER:CountAvailableAssets(MissionTypes, Attributes)
local N=0
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
N=N+legion:CountAvailableAssets(MissionTypes, Attributes)
end
return N
end
--- Count assets of all assigned legions. --- Count assets of all assigned legions.
-- @param #COMMANDER self -- @param #COMMANDER self
-- @param #table MissionTypes (Optional) Count only missions of these types. Default is all types. -- @param #table MissionTypes (Optional) Count only missions of these types. Default is all types.
-3
View File
@@ -167,9 +167,6 @@ function FLEET:New(WarehouseName, FleetName)
-- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission. -- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission. -- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- Add legion to DB
_DATABASE:AddLegion(self)
return self return self
end end
-3
View File
@@ -73,9 +73,6 @@ function FLOTILLA:New(TemplateGroupName, Ngroups, FlotillaName)
-- Get initial ammo. -- Get initial ammo.
self.ammo=self:_CheckAmmo() self.ammo=self:_CheckAmmo()
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self return self
end end
@@ -350,18 +350,6 @@ INTEL.RCS_NoseOnFraction = 0.15
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor -- Constructor
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new INTEL object and start the FSM.
-- @param #INTEL self
-- @return #INTEL self
function INTEL:_UpdateAgents()
-- Filter coalition.
if self.coalition then
local coalitionname=UTILS.GetCoalitionName(self.coalition):lower()
self.detectionset:FilterCoalitions(coalitionname):FilterAlive():FilterOnce()
end
end
--- Create a new INTEL object and start the FSM. --- Create a new INTEL object and start the FSM.
-- @param #INTEL self -- @param #INTEL self
@@ -849,18 +837,6 @@ function INTEL:AddAgent(AgentGroup)
return self return self
end end
--- Set whether the detection set, aka agents, are updated periodically. With this all alive groups of the INTEL coalition are included.
-- @param #INTEL self
-- @param #boolean switch If `true` or nil, all groups of the coalition will be added automatically to the Agent set.
-- @return #INTEL self
function INTEL:SetAgentAuto(switch)
if switch==nil then
switch=true
end
self.update_detectionset=switch
return self
end
--- Enable or disable cluster analysis of detected targets. --- Enable or disable cluster analysis of detected targets.
-- Targets will be grouped in coupled clusters. -- Targets will be grouped in coupled clusters.
-- @param #INTEL self -- @param #INTEL self
@@ -1049,10 +1025,6 @@ function INTEL:onafterStatus(From, Event, To)
-- FSM state. -- FSM state.
local fsmstate=self:GetState() local fsmstate=self:GetState()
if self.update_detectionset then
self:_UpdateAgents()
end
-- Fresh arrays. -- Fresh arrays.
self.ContactsLost={} self.ContactsLost={}
+2 -18
View File
@@ -306,6 +306,7 @@ function LEGION:New(WarehouseName, LegionName)
-- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to. -- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to.
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned. -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned.
return self return self
end end
@@ -2098,23 +2099,6 @@ function LEGION:CountAssets(InStock, MissionTypes, Attributes)
return N return N
end end
--- Count total number of available assets in the legion stock.
-- @param #LEGION self
-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types.
-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `GROUP.Attribute.AIR_BOMBER`.
-- @return #number Amount of asset groups in stock.
function LEGION:CountAvailableAssets(MissionTypes, Attributes)
local N=0
for _,_cohort in pairs(self.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
N=N+cohort:CountAvailableAssets(MissionTypes,Attributes)
end
return N
end
--- Get OPSGROUPs that are spawned and alive. --- Get OPSGROUPs that are spawned and alive.
-- @param #LEGION self -- @param #LEGION self
-- @param #table MissionTypes (Optional) Get only assest that can perform certain mission type(s). Default is all types. -- @param #table MissionTypes (Optional) Get only assest that can perform certain mission type(s). Default is all types.
@@ -2677,7 +2661,7 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti
local RangeMax = RangeMax or 0 local RangeMax = RangeMax or 0
local InRange=(RangeMax and math.max(RangeMax, Rmax) or Rmax) >= TargetDistance and TargetDistance > RangeMin local InRange=(RangeMax and math.max(RangeMax, Rmax) or Rmax) >= TargetDistance and TargetDistance > RangeMin
--env.info(string.format("Range TargetDist=%.1f Rmax=%.1f RangeMax=%.1f InRange=%s", TargetDistance, Rmax, RangeMax, tostring(InRange))) --env.info(string.format("Range TargetDist=%.1f Rmax=%.1f RangeMax=%.1f InRange=%s RangeMin=%s", TargetDistance, Rmax, RangeMax, tostring(InRange),tostring(RangeMin)))
return InRange return InRange
end end
+1 -1
View File
@@ -4691,7 +4691,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
self:T(self.lid..string.format("Zone %s captured ==> Task DONE!", zoneCurr:GetName())) self:T(self.lid..string.format("Zone %s captured ==> Task DONE!", zoneCurr:GetName()))
-- Task done. -- Task done.
if Task.StayInZoneTime and Task.StayInZoneTime>0 then if Task.StayInZoneTime then
local stay = Task.StayInZoneTime local stay = Task.StayInZoneTime
self:__TaskDone(stay,Task) self:__TaskDone(stay,Task)
else else
-3
View File
@@ -71,9 +71,6 @@ function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName)
-- Get ammo. -- Get ammo.
self.ammo=self:_CheckAmmo() self.ammo=self:_CheckAmmo()
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self return self
end end
-3
View File
@@ -116,9 +116,6 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
-- See COHORT class -- See COHORT class
-- Add cohort to DB
_DATABASE:AddCohort(self)
return self return self
end end
+54 -94
View File
@@ -153,7 +153,7 @@ _TARGETID=0
--- TARGET class version. --- TARGET class version.
-- @field #string version -- @field #string version
TARGET.version="0.8.0" TARGET.version="0.7.1"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -206,9 +206,6 @@ function TARGET:New(TargetObject)
self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "Status", "*") -- Status update.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self:AddTransition("*", "ElementDestroyed", "*") -- A target element was destroyed.
self:AddTransition("*", "ElementDead", "*") -- A target element is dead (destroyed or despawned).
self:AddTransition("*", "ObjectDamaged", "*") -- A target object was damaged. self:AddTransition("*", "ObjectDamaged", "*") -- A target object was damaged.
self:AddTransition("*", "ObjectDestroyed", "*") -- A target object was destroyed. self:AddTransition("*", "ObjectDestroyed", "*") -- A target object was destroyed.
self:AddTransition("*", "ObjectDead", "*") -- A target object is dead (destroyed or despawned). self:AddTransition("*", "ObjectDead", "*") -- A target object is dead (destroyed or despawned).
@@ -248,19 +245,6 @@ function TARGET:New(TargetObject)
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
--- Triggers the FSM event "ElementDestroyed".
-- @function [parent=#TARGET] ElementDestroyed
-- @param #TARGET self
-- @param #string ElementName Name of the element.
-- @param #TARGET.Object Target Target object.
--- Triggers the FSM event "ElementDead".
-- @function [parent=#TARGET] ElementDead
-- @param #TARGET self
-- @param #string ElementName Name of the element.
-- @param #TARGET.Object Target Target object.
--- Triggers the FSM event "ObjectDamaged". --- Triggers the FSM event "ObjectDamaged".
-- @function [parent=#TARGET] ObjectDamaged -- @function [parent=#TARGET] ObjectDamaged
-- @param #TARGET self -- @param #TARGET self
@@ -658,19 +642,6 @@ function TARGET:onafterStatus(From, Event, To)
-- FSM state. -- FSM state.
local fsmstate=self:GetState() local fsmstate=self:GetState()
-- First we check any target has been destroyed and the dead/unitlost event was not fired
for i,_target in pairs(self.targets) do
local target=_target --#TARGET.Object
local life=self:GetTargetLife(target)
if life<1 and target.Status~=TARGET.ObjectStatus.DEAD then
self:E(self.lid..string.format("FF life is zero but no object dead event fired ==> waiting for target object %s events!", tostring(target.Name)))
-- We wait 60 seconds for the events to occur
self:__Status(-60)
return self
end
end
-- Update damage. -- Update damage.
local damaged=false local damaged=false
@@ -765,61 +736,6 @@ end
-- FSM Events -- FSM Events
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "ElementDestroyed" event.
-- @param #TARGET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Name Name of the element.
-- @param #TARGET.Object Target Target object.
function TARGET:onafterElementDestroyed(From, Event, To, Name, Target)
-- Debug message.
self:T(self.lid..string.format("Element %s of target object %s destroyed", Name, Target.Name))
-- Increase destroyed counter.
Target.Ndestroyed=Target.Ndestroyed+1
-- Increase dead counter.
self.Ndestroyed=self.Ndestroyed+1
self:ElementDead(Name, Target)
return self
end
--- On after "ElementDead" event.
-- @param #TARGET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Name Name of the element.
-- @param #TARGET.Object Target Target object.
function TARGET:onafterElementDead(From, Event, To, Name, Target)
-- Debug message.
self:T(self.lid..string.format("Element %s of target object %s dead", Name, Target.Name))
-- Increase dead counter.
Target.Ndead=Target.Ndead+1
-- Increase dead counter.
self.Ndead=self.Ndead+1
-- All dead ==> Trigger event.
if Target.Ndestroyed==Target.N0 then
self:ObjectDestroyed(Target)
elseif Target.Ndead==Target.N0 then
self:ObjectDead(Target)
end
return self
end
--- On after "ObjectDamaged" event. --- On after "ObjectDamaged" event.
-- @param #TARGET self -- @param #TARGET self
-- @param #string From From state. -- @param #string From From state.
@@ -845,6 +761,11 @@ function TARGET:onafterObjectDestroyed(From, Event, To, Target)
-- Debug message. -- Debug message.
self:T(self.lid..string.format("Object %s destroyed", Target.Name)) self:T(self.lid..string.format("Object %s destroyed", Target.Name))
-- Increase destroyed counter.
self.Ndestroyed=self.Ndestroyed+1
Target.Ndestroyed=Target.Ndestroyed+1
Target.Life=0 Target.Life=0
-- Call object dead event. -- Call object dead event.
@@ -866,10 +787,16 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
-- Set target status. -- Set target status.
Target.Status=TARGET.ObjectStatus.DEAD Target.Status=TARGET.ObjectStatus.DEAD
-- Increase dead object counter
Target.Ndead=Target.Ndead+1
-- Set target object life to 0. -- Set target object life to 0.
Target.Life=0 Target.Life=0
-- Increase dead counter.
self.Ndead=self.Ndead+1
-- Check if anyone is alive? -- Check if anyone is alive?
local dead=true local dead=true
for _,_target in pairs(self.targets) do for _,_target in pairs(self.targets) do
@@ -962,21 +889,54 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
-- Add to the list of casualties. -- Add to the list of casualties.
table.insert(self.casualties, Name) table.insert(self.casualties, Name)
-- Get target from Group or Unit. -- Try to get target Group.
local target=self:GetTargetByName(EventData.IniGroupName) or self:GetTargetByName(EventData.IniUnitName) local target=self:GetTargetByName(EventData.IniGroupName)
-- Try unit target.
if not target then
target=self:GetTargetByName(EventData.IniUnitName)
end
-- Check if we could find a target object. -- Check if we could find a target object.
if target then if target then
-- Increase dead/destroyed counter local Ndead=target.Ndead
local Ndestroyed=target.Ndestroyed
if EventData.id==EVENTS.RemoveUnit then if EventData.id==EVENTS.RemoveUnit then
self:ElementDead(Name, target) Ndead=Ndead+1
else else
self:ElementDestroyed(Name, target) Ndestroyed=Ndestroyed+1
Ndead=Ndead+1
end
-- Check if ALL objects are dead
if Ndead==target.N0 then
if Ndestroyed>=target.N0 then
-- Debug message.
self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name)))
target.Life = 0
-- Trigger object destroyed event. This sets the Life to zero and increases Ndestroyed
self:ObjectDestroyed(target)
else
-- Debug message.
self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead", EventData.id, tostring(target.Name)))
target.Life = 0
-- Trigger object dead event. This sets the Life to zero and increases Ndead counter
self:ObjectDead(target)
end
end end
else
self:E(self.lid..string.format("ERROR: Could not get target from IniGroup or IniUnit name when event Dead or UnitLost occured! Stats are not correctly updated :("))
end -- Event belongs to this TARGET end -- Event belongs to this TARGET
end end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,240 +0,0 @@
-- Optional AUFTRAG tracing and diagnostic extension for MOOSE Bridge.
--
-- Load after MooseBridge.lua and, when used together with execution commands,
-- after MooseBridgeAuftragExecutionExtension.lua. This file only adds read-only
-- tracing helpers and does not change AUFTRAG execution semantics.
if not MOOSE_BRIDGE then error("Load MooseBridge.lua before MooseBridgeAuftragTraceExtension.lua") end
local function trace_safe_tostring(value)
if value == nil then return "nil" end
return tostring(value)
end
local function trace_split_object_id(object_id)
if type(object_id) ~= "string" then return nil, nil end
local prefix, name = string.match(object_id, "^([^:]+):(.+)$")
if not prefix or not name then return nil, nil end
return string.upper(prefix), name
end
local function trace_append_unique(result, seen, value)
if value == nil then return end
local text = tostring(value)
if text == "" or seen[text] then return end
result[#result + 1] = text
seen[text] = true
end
local function trace_bool(value)
if value == nil then return false end
return value and true or false
end
local function trace_table_count(value)
if type(value) ~= "table" then return 0 end
local count = 0
for _, _ in pairs(value) do count = count + 1 end
return count
end
function MOOSE_BRIDGE:_TraceAuftragId(value)
if type(value) ~= "table" then return nil end
return self:_AuftragObjectId(value)
end
function MOOSE_BRIDGE:_TraceCollectAuftragIds(queue)
local result = {}; local seen = {}
if type(queue) ~= "table" then return result end
for _, auftrag in pairs(queue) do
trace_append_unique(result, seen, self:_TraceAuftragId(auftrag))
end
return result
end
function MOOSE_BRIDGE:_TraceQueueContainsAuftrag(queue, auftrag_id)
if type(queue) ~= "table" then return false end
for _, auftrag in pairs(queue) do
if self:_TraceAuftragId(auftrag) == auftrag_id then return true end
end
return false
end
function MOOSE_BRIDGE:_TraceFindAuftrag(auftrag_id)
if type(auftrag_id) ~= "string" or auftrag_id == "" then return nil, nil end
if type(self.TrackedAuftraege) == "table" and type(self.TrackedAuftraege[auftrag_id]) == "table" then
return self.TrackedAuftraege[auftrag_id], "bridge.tracked"
end
if _DATABASE and type(_DATABASE.LEGIONS) == "table" then
for _, legion in pairs(_DATABASE.LEGIONS) do
local queues = {legion.missionqueue, legion.missions, legion.auftraege, legion.missionQueue}
for _, queue in ipairs(queues) do
if type(queue) == "table" then
for _, auftrag in pairs(queue) do
if self:_TraceAuftragId(auftrag) == auftrag_id then return auftrag, "legion.queue" end
end
end
end
end
end
-- MOOSE stores all OPSGROUP specializations here despite the FLIGHTGROUPS name.
if _DATABASE and type(_DATABASE.FLIGHTGROUPS) == "table" then
for _, opsgroup in pairs(_DATABASE.FLIGHTGROUPS) do
local current = opsgroup.currentmission or opsgroup.missioncurrent or opsgroup.currentMission
if self:_TraceAuftragId(current) == auftrag_id then return current, "opsgroup.current" end
local queues = {opsgroup.missionqueue, opsgroup.missions, opsgroup.auftraege, opsgroup.missionQueue}
for _, queue in ipairs(queues) do
if type(queue) == "table" then
for _, auftrag in pairs(queue) do
if self:_TraceAuftragId(auftrag) == auftrag_id then return auftrag, "opsgroup.queue" end
end
end
end
end
end
return nil, nil
end
function MOOSE_BRIDGE:_TraceLegionItem(legion_name, legion, auftrag_id, source)
local item = self:_BuildLegionSnapshotItem(legion_name, legion, source)
if type(item) ~= "table" then return nil end
item.missionqueue_count = trace_table_count(legion and legion.missionqueue)
item.missionqueue_contains_auftrag = self:_TraceQueueContainsAuftrag(legion and legion.missionqueue, auftrag_id)
item.is_running = tostring(item.state or "") == "Running"
return item
end
function MOOSE_BRIDGE:_TraceCohortItem(cohort_name, cohort, source)
local item = self:_BuildCohortSnapshotItem(cohort_name, cohort, source)
if type(item) ~= "table" then return nil end
item.asset_count = item.asset_count or trace_table_count(cohort and cohort.assets)
item.stock_asset_count = item.stock_asset_count or trace_table_count(cohort and cohort.stock)
item.spawned_asset_count = item.spawned_asset_count or trace_table_count(cohort and cohort.spawnedassets)
item.opsgroup_count = item.opsgroup_count or trace_table_count(cohort and cohort.opsgroups)
return item
end
function MOOSE_BRIDGE:_TraceOpsGroupItem(opsgroup_name, opsgroup, auftrag_id, source)
local item = self:_BuildOpsGroupSnapshotItem(opsgroup_name, opsgroup, source)
if type(item) ~= "table" then return nil end
item.current_contains_auftrag = item.auftrag_current_id == auftrag_id
item.queue_contains_auftrag = self:_TraceQueueContainsAuftrag(opsgroup and opsgroup.missionqueue, auftrag_id)
item.missionqueue_count = trace_table_count(opsgroup and opsgroup.missionqueue)
return item
end
function MOOSE_BRIDGE:_TraceCollectLegions(auftrag_id)
local result = {}
if _DATABASE and type(_DATABASE.LEGIONS) == "table" then
for name, legion in pairs(_DATABASE.LEGIONS) do
local ok, item = pcall(function() return self:_TraceLegionItem(name, legion, auftrag_id, "database.LEGIONS") end)
if ok and item then result[#result + 1] = item end
end
end
return result
end
function MOOSE_BRIDGE:_TraceCollectCohorts()
local result = {}; local seen = {}
if _DATABASE and type(_DATABASE.LEGIONS) == "table" then
for _, legion in pairs(_DATABASE.LEGIONS) do
if type(legion.cohorts) == "table" then
for name, cohort in pairs(legion.cohorts) do
local ok, item = pcall(function() return self:_TraceCohortItem(name, cohort, "legion.cohorts") end)
if ok and item and item.object_id and not seen[item.object_id] then
result[#result + 1] = item
seen[item.object_id] = true
end
end
end
end
end
return result
end
function MOOSE_BRIDGE:_TraceCollectOpsGroups(auftrag_id)
local result = {}; local seen = {}
for name, opsgroup in pairs(self.RegisteredOpsGroups or {}) do
local ok, item = pcall(function() return self:_TraceOpsGroupItem(name, opsgroup, auftrag_id, "registered") end)
if ok and item and item.object_id and not seen[item.object_id] then
result[#result + 1] = item
seen[item.object_id] = true
end
end
-- MOOSE stores all OPSGROUP specializations here despite the FLIGHTGROUPS name.
if _DATABASE and type(_DATABASE.FLIGHTGROUPS) == "table" then
for name, opsgroup in pairs(_DATABASE.FLIGHTGROUPS) do
local ok, item = pcall(function() return self:_TraceOpsGroupItem(name, opsgroup, auftrag_id, "database.FLIGHTGROUPS") end)
if ok and item and item.object_id and not seen[item.object_id] then
result[#result + 1] = item
seen[item.object_id] = true
end
end
end
return result
end
function MOOSE_BRIDGE:_TraceBuild(auftrag_id)
local auftrag, source = self:_TraceFindAuftrag(auftrag_id)
local auftrag_item = nil
if type(auftrag) == "table" then
local ok, item = pcall(function() return self:_BuildAuftragSnapshotItem(auftrag, source or "trace") end)
if ok then auftrag_item = item end
end
local legions = self:_TraceCollectLegions(auftrag_id)
local cohorts = self:_TraceCollectCohorts()
local opsgroups = self:_TraceCollectOpsGroups(auftrag_id)
local matching_legions = {}
for _, legion in ipairs(legions) do
if legion.missionqueue_contains_auftrag then matching_legions[#matching_legions + 1] = legion.object_id end
end
local matching_opsgroups = {}
for _, opsgroup in ipairs(opsgroups) do
if opsgroup.current_contains_auftrag or opsgroup.queue_contains_auftrag then matching_opsgroups[#matching_opsgroups + 1] = opsgroup.object_id end
end
return {
action="auftrag.trace",
auftrag_id=auftrag_id,
found=auftrag_item ~= nil,
source=source,
auftrag=auftrag_item,
legions=legions,
cohorts=cohorts,
opsgroups=opsgroups,
matching_legion_ids=matching_legions,
matching_opsgroup_ids=matching_opsgroups,
counts={
legions=#legions,
cohorts=#cohorts,
opsgroups=#opsgroups,
matching_legions=#matching_legions,
matching_opsgroups=#matching_opsgroups,
},
}
end
function MOOSE_BRIDGE:RegisterAuftragTraceCommands()
self:RegisterCommand("auftrag.trace", function(cmd)
local p = self:_CommandParams(cmd)
local auftrag_id = p.auftrag_id or p.object_id or p.id
if type(auftrag_id) ~= "string" or auftrag_id == "" then error("auftrag.trace requires auftrag_id") end
local prefix, _ = trace_split_object_id(auftrag_id)
if prefix ~= "AUFTRAG" then error("auftrag.trace requires an AUFTRAG:<id> object id") end
return self:_TraceBuild(auftrag_id)
end)
end
local _moose_bridge_base_register_default_commands_for_trace = MOOSE_BRIDGE.RegisterDefaultCommands
function MOOSE_BRIDGE:RegisterDefaultCommands()
_moose_bridge_base_register_default_commands_for_trace(self)
self:RegisterAuftragTraceCommands()
end
@@ -1,297 +0,0 @@
--- DCS world event forwarding for MOOSE_BRIDGE.
--
-- Load after MooseBridge.lua and before constructing/starting the bridge.
-- DCS events are normalized here; Python never needs to understand the raw
-- world event table or MOOSE EVENTDATA implementation details.
if not MOOSE_BRIDGE then error("Load MooseBridge.lua before MooseBridgeDcsEventsExtension.lua") end
local function bridge_event_available(event_id)
return type(event_id) == "number" and event_id > 0
end
--- Cache current DCS ownership for all known MOOSE AIRBASE objects.
-- The DCS BaseCaptured event already exposes the new owner, so the cache is
-- needed to include the previous owner in the normalized bridge event.
function MOOSE_BRIDGE:_CacheAirbaseCoalitions()
self.AirbaseCoalitions = self.AirbaseCoalitions or {}
if not _DATABASE or type(_DATABASE.AIRBASES) ~= "table" then return self end
for airbase_name, airbase in pairs(_DATABASE.AIRBASES) do
local ok, result = pcall(function()
local name = self:_SafeCall(airbase, "GetName") or airbase.AirbaseName or airbase_name
local owner = self:_CoalitionToName(self:_SafeCall(airbase, "GetCoalition"))
return name and {object_id="AIRBASE:" .. tostring(name), coalition=owner} or nil
end)
if ok and result and result.object_id then
self.AirbaseCoalitions[result.object_id] = result.coalition
elseif not ok then
self:_Log("Failed to cache airbase " .. tostring(airbase_name) .. ": " .. tostring(result))
end
end
return self
end
--- Resolve the authoritative MOOSE AIRBASE wrapper from an EVENTDATA object.
function MOOSE_BRIDGE:_AirbaseFromCapturedEvent(EventData)
if type(EventData) ~= "table" then return nil, nil end
local place = EventData.Place
local name = EventData.PlaceName
if not name and place then
name = self:_SafeCall(place, "GetName")
end
if not name and EventData.place then
local ok, value = pcall(function() return EventData.place:getName() end)
if ok then name = value end
end
if not name then return nil, nil end
local airbase = type(place) == "table" and place or nil
if _DATABASE and type(_DATABASE.AIRBASES) == "table" then
airbase = _DATABASE.AIRBASES[name] or airbase
end
if not airbase and AIRBASE and AIRBASE.FindByName then
local ok, value = pcall(function() return AIRBASE:FindByName(name) end)
if ok then airbase = value end
end
return airbase, tostring(name)
end
--- Subscribe to selected low-frequency DCS events through MOOSE.
function MOOSE_BRIDGE:_StartDcsEventForwarding()
if self.DcsEventForwardingStarted then return self end
if not EVENTS or not self.HandleEvent then
self:_Log("DCS event forwarding unavailable")
return self
end
self.DcsRegisteredEvents = {}
if bridge_event_available(EVENTS.BaseCaptured) then
self:_CacheAirbaseCoalitions()
self:HandleEvent(EVENTS.BaseCaptured)
self.DcsRegisteredEvents[#self.DcsRegisteredEvents + 1] = EVENTS.BaseCaptured
self:_Log("DCS BaseCaptured event forwarding enabled")
end
if bridge_event_available(EVENTS.UnitLost) then
self:HandleEvent(EVENTS.UnitLost)
self.DcsRegisteredEvents[#self.DcsRegisteredEvents + 1] = EVENTS.UnitLost
self:_Log("DCS UnitLost event forwarding enabled")
end
if bridge_event_available(EVENTS.Dead) then
self:HandleEvent(EVENTS.Dead)
self.DcsRegisteredEvents[#self.DcsRegisteredEvents + 1] = EVENTS.Dead
self:_Log("DCS Dead event forwarding enabled")
end
if bridge_event_available(EVENTS.Kill) then
self:HandleEvent(EVENTS.Kill)
self.DcsRegisteredEvents[#self.DcsRegisteredEvents + 1] = EVENTS.Kill
self:_Log("DCS Kill event forwarding enabled")
end
if bridge_event_available(EVENTS.MissionEnd) then
self:HandleEvent(EVENTS.MissionEnd)
self.DcsRegisteredEvents[#self.DcsRegisteredEvents + 1] = EVENTS.MissionEnd
self:_Log("DCS MissionEnd event forwarding enabled")
end
if #self.DcsRegisteredEvents == 0 then
self:_Log("No supported DCS events available for forwarding")
return self
end
self.DcsEventForwardingStarted = true
return self
end
--- Unsubscribe from DCS events owned by this bridge instance.
function MOOSE_BRIDGE:_StopDcsEventForwarding()
if self.DcsEventForwardingStarted and self.UnHandleEvent then
for _, event_id in ipairs(self.DcsRegisteredEvents or {}) do
self:UnHandleEvent(event_id)
end
end
self.DcsRegisteredEvents = {}
self.DcsEventForwardingStarted = false
return self
end
--- Forward DCS S_EVENT_BASE_CAPTURED as airbase.coalition_changed.
-- @param Core.Event#EVENTDATA EventData MOOSE-normalized DCS event data.
function MOOSE_BRIDGE:OnEventBaseCaptured(EventData)
local ok, err = pcall(function()
local airbase, airbase_name = self:_AirbaseFromCapturedEvent(EventData)
if not airbase or not airbase_name then
error("BaseCaptured event has no resolvable AIRBASE")
end
local item = self:_BuildAirbaseSnapshotItem(airbase_name, airbase)
if not item or not item.object_id then
error("Could not build AIRBASE snapshot for " .. tostring(airbase_name))
end
self.AirbaseCoalitions = self.AirbaseCoalitions or {}
local previous = self.AirbaseCoalitions[item.object_id]
local current = item.coalition
self.AirbaseCoalitions[item.object_id] = current
if previous ~= current then
self:SendEvent("airbase.coalition_changed", {
dcs_event_id=EventData.id,
dcs_event_name="S_EVENT_BASE_CAPTURED",
dcs_event_time=EventData.time,
airbase_id=item.object_id,
previous_coalition=previous,
coalition=current,
capturing_unit_id=EventData.IniUnitName and ("UNIT:" .. tostring(EventData.IniUnitName)) or nil,
capturing_group_id=EventData.IniGroupName and ("GROUP:" .. tostring(EventData.IniGroupName)) or nil,
capturing_coalition=self:_CoalitionToName(EventData.IniCoalition),
capturing_unit_type=EventData.IniTypeName and tostring(EventData.IniTypeName) or nil,
airbase=item,
})
end
end)
if not ok then
self:_Log("Failed to forward BaseCaptured event: " .. tostring(err))
end
end
--- Build a tombstone and current group snapshot for a lost DCS object.
function MOOSE_BRIDGE:_BuildUnitLostPayload(EventData)
if type(EventData) ~= "table" then error("UnitLost event data is missing") end
local name = EventData.IniUnitName or EventData.IniDCSUnitName
if not name then error("UnitLost event has no initiator name") end
local is_static = Object and Object.Category
and EventData.IniObjectCategory == Object.Category.STATIC
local object_type = is_static and "STATIC" or "UNIT"
local object_id = object_type .. ":" .. tostring(name)
local item = nil
if is_static then
local static = EventData.IniUnit
if not static and _DATABASE and _DATABASE.STATICS then static = _DATABASE.STATICS[name] end
if static then
local ok, value = pcall(function() return self:_BuildStaticSnapshotItem(name, static) end)
if ok then item = value end
end
else
local unit = EventData.IniUnit
if not unit and _DATABASE and _DATABASE.UNITS then unit = _DATABASE.UNITS[name] end
if unit then
local ok, value = pcall(function() return self:_BuildUnitSnapshotItem(name, unit) end)
if ok then item = value end
end
end
item = item or {
object_id=object_id,
dcs_name=tostring(name),
object_type=object_type,
}
item.object_id = object_id
item.object_type = object_type
item.alive = false
item.active = false
item.coalition = item.coalition or self:_CoalitionToName(EventData.IniCoalition)
item.category = item.category or (EventData.IniCategory and tostring(EventData.IniCategory) or nil)
item.dcs_type = item.dcs_type or (EventData.IniTypeName and tostring(EventData.IniTypeName) or nil)
local group_name = EventData.IniGroupName or EventData.IniDCSGroupName or item.group_name
local group_item = nil
if not is_static and group_name then
local group = EventData.IniGroup
if not group and _DATABASE and _DATABASE.GROUPS then group = _DATABASE.GROUPS[group_name] end
if group then
local ok, value = pcall(function() return self:_BuildGroupSnapshotItem(group_name, group) end)
if ok then group_item = value end
end
item.group_name = tostring(group_name)
end
return {
object_id=object_id,
object_type=object_type,
group_id=group_name and ("GROUP:" .. tostring(group_name)) or nil,
object=item,
group=group_item,
}
end
--- Forward one DCS destruction event as object.destroyed.
-- UnitLost and Dead can describe the same loss, depending on the DCS object
-- and destruction path. Suppress the second event without polling object state.
function MOOSE_BRIDGE:_ForwardObjectDestroyed(EventData, event_name)
local ok, err = pcall(function()
local payload = self:_BuildUnitLostPayload(EventData)
local event_time = tonumber(EventData.time)
local dedup_time = event_time or (timer and timer.getTime and timer.getTime()) or 0
self.DcsDestroyedEventTimes = self.DcsDestroyedEventTimes or {}
local previous_time = self.DcsDestroyedEventTimes[payload.object_id]
if previous_time and math.abs(dedup_time - previous_time) <= 2 then return end
self.DcsDestroyedEventTimes[payload.object_id] = dedup_time
payload.dcs_event_id = EventData.id
payload.dcs_event_name = event_name
payload.dcs_event_time = event_time
self:SendEvent("object.destroyed", payload)
end)
if not ok then
self:_Log("Failed to forward " .. tostring(event_name) .. " event: " .. tostring(err))
end
end
--- Forward DCS S_EVENT_UNIT_LOST as object.destroyed.
-- @param Core.Event#EVENTDATA EventData MOOSE-normalized DCS event data.
function MOOSE_BRIDGE:OnEventUnitLost(EventData)
self:_ForwardObjectDestroyed(EventData, "S_EVENT_UNIT_LOST")
end
--- Forward DCS S_EVENT_DEAD as object.destroyed.
-- @param Core.Event#EVENTDATA EventData MOOSE-normalized DCS event data.
function MOOSE_BRIDGE:OnEventDead(EventData)
self:_ForwardObjectDestroyed(EventData, "S_EVENT_DEAD")
end
--- Forward an attributed DCS kill without replacing UnitLost/Dead state events.
-- @param Core.Event#EVENTDATA EventData MOOSE-normalized DCS event data.
function MOOSE_BRIDGE:OnEventKill(EventData)
local ok, err = pcall(function()
if type(EventData) ~= "table" then error("Kill event data is missing") end
local killer_name = EventData.IniUnitName or EventData.IniDCSUnitName
local target_name = EventData.TgtUnitName or EventData.TgtDCSUnitName
if not killer_name or not target_name then error("Kill event has no killer or target name") end
local target_is_static = Object and Object.Category
and EventData.TgtObjectCategory == Object.Category.STATIC
self:SendEvent("combat.kill", {
dcs_event_id=EventData.id,
dcs_event_name="S_EVENT_KILL",
dcs_event_time=EventData.time,
killer_object_id="UNIT:" .. tostring(killer_name),
killer_group_id=EventData.IniGroupName and ("GROUP:" .. tostring(EventData.IniGroupName)) or nil,
killer_coalition=self:_CoalitionToName(EventData.IniCoalition),
killer_type=EventData.IniTypeName and tostring(EventData.IniTypeName) or nil,
target_object_id=(target_is_static and "STATIC:" or "UNIT:") .. tostring(target_name),
target_group_id=EventData.TgtGroupName and ("GROUP:" .. tostring(EventData.TgtGroupName)) or nil,
target_coalition=self:_CoalitionToName(EventData.TgtCoalition),
target_type=EventData.TgtTypeName and tostring(EventData.TgtTypeName) or nil,
weapon_name=EventData.WeaponName and tostring(EventData.WeaponName) or nil,
})
end)
if not ok then
self:_Log("Failed to forward Kill event: " .. tostring(err))
end
end
--- Forward DCS S_EVENT_MISSION_END as the authoritative Python session boundary.
-- Flush immediately because normal bridge scheduling stops with the mission.
-- @param Core.Event#EVENTDATA EventData MOOSE-normalized DCS event data.
function MOOSE_BRIDGE:OnEventMissionEnd(EventData)
local ok, err = pcall(function()
self:SendEvent("mission.ended", {
dcs_event_id=EventData and EventData.id or nil,
dcs_event_name="S_EVENT_MISSION_END",
dcs_event_time=EventData and EventData.time or nil,
reason="dcs_mission_end",
})
self:_FlushOutQueue()
end)
if not ok then
self:_Log("Failed to forward MissionEnd event: " .. tostring(err))
end
end
@@ -1,404 +0,0 @@
-- Optional INTEL snapshot and event extension for MOOSE Bridge.
--
-- MOOSE remains the owner of INTEL tactical logic. This extension only mirrors
-- registered INTEL objects and forwards their FSM events to Python.
local function bridge_intel_safe_tostring(value)
if value == nil then return nil end
return tostring(value)
end
local function bridge_intel_object_name(object)
if not object then return nil end
if MOOSE_BRIDGE and MOOSE_BRIDGE._ObjectName then
local ok, value = pcall(function() return MOOSE_BRIDGE:_ObjectName(object) end)
if ok and value then return value end
end
if object.alias then return tostring(object.alias) end
if object.name then return tostring(object.name) end
if object.Name then return tostring(object.Name) end
if object.groupname then return tostring(object.groupname) end
return nil
end
local function bridge_intel_auftrag_id(bridge, auftrag)
if not auftrag then return nil end
if bridge and bridge._AuftragObjectId then
local ok, value = pcall(function() return bridge:_AuftragObjectId(auftrag) end)
if ok and value then return value end
end
if auftrag.auftragsnummer then return "AUFTRAG:" .. tostring(auftrag.auftragsnummer) end
if auftrag.name then return "AUFTRAG:" .. tostring(auftrag.name) end
return nil
end
local function bridge_intel_point(bridge, value)
if not value then return nil end
if bridge and bridge._PointFromMooseObject then
local ok, point = pcall(function() return bridge:_PointFromMooseObject(value) end)
if ok and point then return point end
end
if type(value) == "table" and type(value.x) == "number" and type(value.z) == "number" then
return value
end
return nil
end
local function bridge_intel_velocity(value)
if type(value) ~= "table" then return nil end
return {
x=type(value.x) == "number" and value.x or nil,
y=type(value.y) == "number" and value.y or nil,
z=type(value.z) == "number" and value.z or nil,
}
end
function MOOSE_BRIDGE:_EnsureIntelRegistry()
self.RegisteredIntels = self.RegisteredIntels or {}
return self.RegisteredIntels
end
function MOOSE_BRIDGE:RegisterIntel(intel, name)
if not intel then return self end
local intel_name = name or bridge_intel_object_name(intel) or intel.alias
if not intel_name then return self end
intel_name = tostring(intel_name)
self:_EnsureIntelRegistry()[intel_name] = intel
self:_AttachIntelEventForwarders(intel, intel_name)
if type(intel.SetAgentAuto) ~= "function" then error("INTEL:SetAgentAuto is not available") end
intel:SetAgentAuto()
return self
end
function MOOSE_BRIDGE:RegisterIntels(intels)
if type(intels) ~= "table" then return self end
for name, intel in pairs(intels) do self:RegisterIntel(intel, name) end
return self
end
function MOOSE_BRIDGE:_IntelObjectId(intel_name)
return "INTEL:" .. tostring(intel_name)
end
function MOOSE_BRIDGE:_IntelContactId(intel_name, contact)
local name = contact and contact.groupname or nil
if not name then return nil end
return "INTELCONTACT:" .. tostring(intel_name) .. ":" .. tostring(name)
end
function MOOSE_BRIDGE:_IntelClusterId(intel_name, cluster)
local index = cluster and cluster.index or nil
if not index then return nil end
return "INTELCLUSTER:" .. tostring(intel_name) .. ":" .. tostring(index)
end
function MOOSE_BRIDGE:_IntelContactTargetObjectId(contact)
if not contact then return nil end
local name = contact.groupname
if not name then return nil end
if contact.isStatic then return "STATIC:" .. tostring(name) end
return "GROUP:" .. tostring(name)
end
function MOOSE_BRIDGE:_IntelAgentCounts(intel)
local detectionset = intel and intel.detectionset or nil
if not detectionset then return 0, 0 end
return detectionset:Count(), detectionset:CountAlive()
end
function MOOSE_BRIDGE:_IntelAgentSnapshot(intel)
local result = {}
local detectionset = intel and intel.detectionset or nil
if not detectionset then return result, 0, 0 end
for name, _ in pairs(detectionset.Set or {}) do
result[#result + 1] = "GROUP:" .. tostring(name)
end
local agent_count, alive_agent_count = self:_IntelAgentCounts(intel)
return result, agent_count, alive_agent_count
end
function MOOSE_BRIDGE:_ResolveRegisteredIntel(intel_id)
local name = type(intel_id) == "string" and string.match(intel_id, "^INTEL:(.+)$") or nil
if not name then return nil, nil, "Invalid INTEL object id: " .. tostring(intel_id) end
local intel = self:_EnsureIntelRegistry()[name]
if not intel then return nil, nil, "INTEL not registered: " .. name end
return intel, name, nil
end
function MOOSE_BRIDGE:_ResolveIntelAgent(agent_id)
local prefix, name
if type(agent_id) == "string" then prefix, name = string.match(agent_id, "^([^:]+):(.+)$") end
if not prefix or not name then return nil, "Invalid agent object id: " .. tostring(agent_id) end
if prefix == "GROUP" then
local group = GROUP and GROUP.FindByName and GROUP:FindByName(name) or nil
if not group then return nil, "GROUP not found: " .. name end
return group, nil
end
if prefix == "OPSGROUP" then
if not self._ResolveOpsGroupById then
return nil, "OPSGROUP agents require MooseBridgeAuftragExecutionExtension.lua"
end
return self:_ResolveOpsGroupById(agent_id)
end
return nil, "Agent must be GROUP:<name> or OPSGROUP:<name>"
end
function MOOSE_BRIDGE:_BuildIntelContactSnapshotItem(intel_name, contact, source)
if type(contact) ~= "table" then return nil end
local point = bridge_intel_point(self, contact.position)
local recce_name = bridge_intel_safe_tostring(contact.recce)
local recce_unit = recce_name and UNIT and UNIT.FindByName and UNIT:FindByName(recce_name) or nil
local recce_group = recce_unit and self:_SafeCall(recce_unit, "GetGroup") or nil
local recce_group_name = recce_group and self:_SafeCall(recce_group, "GetName") or nil
local item = {
object_id=self:_IntelContactId(intel_name, contact),
dcs_name=bridge_intel_safe_tostring(contact.groupname),
object_type="INTELCONTACT",
category=bridge_intel_safe_tostring(contact.ctype or contact.categoryname),
source=source,
intel_id=self:_IntelObjectId(intel_name),
target_object_id=self:_IntelContactTargetObjectId(contact),
typename=bridge_intel_safe_tostring(contact.typename),
attribute=bridge_intel_safe_tostring(contact.attribute),
category_id=contact.category,
category_name=bridge_intel_safe_tostring(contact.categoryname),
threat_level=contact.threatlevel,
detected_time=contact.Tdetected,
recce=recce_name,
recce_unit_id=recce_name and "UNIT:" .. recce_name or nil,
recce_group_id=recce_group_name and "GROUP:" .. tostring(recce_group_name) or nil,
contact_type=bridge_intel_safe_tostring(contact.ctype),
speed_mps=contact.speed,
velocity=bridge_intel_velocity(contact.velocity),
is_ground=contact.isground and true or false,
is_ship=contact.isship and true or false,
is_static=contact.isStatic and true or false,
platform=bridge_intel_safe_tostring(contact.platform),
heading=contact.heading,
maneuvering=contact.maneuvering and true or false,
altitude_m=contact.altitude,
rcs=contact.rcs,
mission_id=bridge_intel_auftrag_id(self, contact.mission),
}
if point then self:_AddPointFields(item, point) end
return item
end
function MOOSE_BRIDGE:_BuildIntelClusterSnapshotItem(intel_name, cluster, source)
if type(cluster) ~= "table" then return nil end
local point = bridge_intel_point(self, cluster.coordinate)
local contact_ids = {}
if type(cluster.Contacts) == "table" then
for _, contact in pairs(cluster.Contacts) do
local contact_id = self:_IntelContactId(intel_name, contact)
if contact_id then contact_ids[#contact_ids + 1] = contact_id end
end
end
local item = {
object_id=self:_IntelClusterId(intel_name, cluster),
dcs_name="Cluster " .. tostring(cluster.index or "?"),
object_type="INTELCLUSTER",
category=bridge_intel_safe_tostring(cluster.ctype),
source=source,
intel_id=self:_IntelObjectId(intel_name),
index=cluster.index,
size=cluster.size,
contact_ids=contact_ids,
threat_level_max=cluster.threatlevelMax,
threat_level_sum=cluster.threatlevelSum,
threat_level_avg=cluster.threatlevelAve,
contact_type=bridge_intel_safe_tostring(cluster.ctype),
altitude_m=cluster.altitude,
mission_id=bridge_intel_auftrag_id(self, cluster.mission),
}
if point then self:_AddPointFields(item, point) end
return item
end
function MOOSE_BRIDGE:_BuildIntelSnapshotItem(intel_name, intel, source)
local contacts = self:_SafeCall(intel, "GetContactTable") or intel.Contacts or {}
local clusters = self:_SafeCall(intel, "GetClusterTable") or intel.Clusters or {}
local agent_ids, agent_count, alive_agent_count = self:_IntelAgentSnapshot(intel)
return {
object_id=self:_IntelObjectId(intel_name),
dcs_name=tostring(intel_name),
object_type="INTEL",
category="INTEL",
source=source,
alias=bridge_intel_safe_tostring(intel.alias),
coalition=self:_CoalitionToName(intel.coalition),
state=self:_SafeCallArg(intel, "GetState") or nil,
is_running=self:_SafeCallArg(intel, "Is", "Running") and true or false,
cluster_analysis=intel.clusteranalysis and true or false,
cluster_markers=intel.clustermarkers and true or false,
cluster_arrows=intel.clusterarrows and true or false,
cluster_radius_m=intel.clusterradius,
detect_statics=intel.detectStatics and true or false,
detect_accoustic=intel.DetectAccoustic and true or false,
detect_accoustic_radius_m=intel.DetectAccousticRadius,
doppler_radar=intel.DopplerRadar and true or false,
contact_count=type(contacts) == "table" and #contacts or 0,
cluster_count=type(clusters) == "table" and #clusters or 0,
agent_count=agent_count,
alive_agent_count=alive_agent_count,
agent_ids=agent_ids,
}
end
function MOOSE_BRIDGE:BuildIntelSnapshot()
local result = {}
for name, intel in pairs(self:_EnsureIntelRegistry()) do
local ok, item = pcall(function() return self:_BuildIntelSnapshotItem(name, intel, "registered") end)
if ok and item then result[#result + 1] = item end
end
return result
end
function MOOSE_BRIDGE:BuildIntelContactSnapshot()
local result = {}
for name, intel in pairs(self:_EnsureIntelRegistry()) do
local contacts = self:_SafeCall(intel, "GetContactTable") or intel.Contacts or {}
if type(contacts) == "table" then
for _, contact in pairs(contacts) do
local ok, item = pcall(function() return self:_BuildIntelContactSnapshotItem(name, contact, "registered") end)
if ok and item and item.object_id then result[#result + 1] = item end
end
end
end
return result
end
function MOOSE_BRIDGE:BuildIntelClusterSnapshot()
local result = {}
for name, intel in pairs(self:_EnsureIntelRegistry()) do
local clusters = self:_SafeCall(intel, "GetClusterTable") or intel.Clusters or {}
if type(clusters) == "table" then
for _, cluster in pairs(clusters) do
local ok, item = pcall(function() return self:_BuildIntelClusterSnapshotItem(name, cluster, "registered") end)
if ok and item and item.object_id then result[#result + 1] = item end
end
end
end
return result
end
function MOOSE_BRIDGE:_SendIntelEvent(event_name, intel_name, fsm_event, from_state, to_state, item_kind, item)
local payload = {
event=event_name,
intel_id=self:_IntelObjectId(intel_name),
fsm_event=fsm_event,
from_state=from_state,
to_state=to_state,
}
if item_kind == "contact" then
payload.contact = item
payload.contact_id = item and item.object_id or nil
payload.target_object_id = item and item.target_object_id or nil
elseif item_kind == "cluster" then
payload.cluster = item
payload.cluster_id = item and item.object_id or nil
end
self:SendEvent(event_name, payload)
end
function MOOSE_BRIDGE:_AttachIntelEventForwarders(intel, intel_name)
if type(intel) ~= "table" or intel.MooseBridgeIntelEventsRegistered then return self end
intel.MooseBridgeIntelEventsRegistered = true
local bridge = self
local previous_new_contact = intel.OnAfterNewContact
local previous_lost_contact = intel.OnAfterLostContact
local previous_new_cluster = intel.OnAfterNewCluster
local previous_lost_cluster = intel.OnAfterLostCluster
intel.OnAfterNewContact = function(intel_self, From, Event, To, Contact)
if type(previous_new_contact) == "function" then pcall(previous_new_contact, intel_self, From, Event, To, Contact) end
local item = bridge:_BuildIntelContactSnapshotItem(intel_name, Contact, "event")
bridge:_SendIntelEvent("intel.new_contact", intel_name, Event, From, To, "contact", item)
end
intel.OnAfterLostContact = function(intel_self, From, Event, To, Contact)
if type(previous_lost_contact) == "function" then pcall(previous_lost_contact, intel_self, From, Event, To, Contact) end
local item = bridge:_BuildIntelContactSnapshotItem(intel_name, Contact, "event")
bridge:_SendIntelEvent("intel.lost_contact", intel_name, Event, From, To, "contact", item)
end
intel.OnAfterNewCluster = function(intel_self, From, Event, To, Cluster)
if type(previous_new_cluster) == "function" then pcall(previous_new_cluster, intel_self, From, Event, To, Cluster) end
local item = bridge:_BuildIntelClusterSnapshotItem(intel_name, Cluster, "event")
bridge:_SendIntelEvent("intel.new_cluster", intel_name, Event, From, To, "cluster", item)
end
intel.OnAfterLostCluster = function(intel_self, From, Event, To, Cluster, Mission)
if type(previous_lost_cluster) == "function" then pcall(previous_lost_cluster, intel_self, From, Event, To, Cluster, Mission) end
local item = bridge:_BuildIntelClusterSnapshotItem(intel_name, Cluster, "event")
if item and not item.mission_id then item.mission_id = bridge_intel_auftrag_id(bridge, Mission) end
bridge:_SendIntelEvent("intel.lost_cluster", intel_name, Event, From, To, "cluster", item)
end
return self
end
local _moose_bridge_base_register_default_commands_for_intel = MOOSE_BRIDGE.RegisterDefaultCommands
function MOOSE_BRIDGE:RegisterDefaultCommands()
_moose_bridge_base_register_default_commands_for_intel(self)
local previous_snapshot_all = self.CommandHandlers["snapshot.all"]
self:RegisterCommand("snapshot.intels", function(cmd)
local intels = self:BuildIntelSnapshot()
self:SendSnapshot("intels", {intels=intels})
return {kind="intels", count=#intels}
end)
self:RegisterCommand("intel.add_agent", function(cmd)
local params = self:_CommandParams(cmd)
local intel, intel_name, intel_err = self:_ResolveRegisteredIntel(params.intel_id)
if not intel then error(intel_err) end
local agent, agent_err = self:_ResolveIntelAgent(params.agent_id)
if not agent then error(agent_err) end
intel:AddAgent(agent)
local agent_count, alive_agent_count = self:_IntelAgentCounts(intel)
return {
action="intel.add_agent",
intel_id=self:_IntelObjectId(intel_name),
agent_id=params.agent_id,
agent_count=agent_count,
alive_agent_count=alive_agent_count,
}
end)
self:RegisterCommand("snapshot.intel_contacts", function(cmd)
local contacts = self:BuildIntelContactSnapshot()
self:SendSnapshot("intel_contacts", {intel_contacts=contacts})
return {kind="intel_contacts", count=#contacts}
end)
self:RegisterCommand("snapshot.intel_clusters", function(cmd)
local clusters = self:BuildIntelClusterSnapshot()
self:SendSnapshot("intel_clusters", {intel_clusters=clusters})
return {kind="intel_clusters", count=#clusters}
end)
if previous_snapshot_all then
self:RegisterCommand("snapshot.all", function(cmd)
local result = previous_snapshot_all(cmd) or {}
local intels = self:BuildIntelSnapshot()
local contacts = self:BuildIntelContactSnapshot()
local clusters = self:BuildIntelClusterSnapshot()
self:SendSnapshot("intels", {intels=intels})
self:SendSnapshot("intel_contacts", {intel_contacts=contacts})
self:SendSnapshot("intel_clusters", {intel_clusters=clusters})
result.intels = #intels
result.intel_contacts = #contacts
result.intel_clusters = #clusters
return result
end)
end
end
@@ -1,258 +0,0 @@
--- Minimal JSON helper for the MOOSE Bridge V1 prototype.
-- This is deliberately small and covers the V1 command/ack/heartbeat/snapshot payloads.
MOOSE_BRIDGE_JSON = MOOSE_BRIDGE_JSON or {}
local json = MOOSE_BRIDGE_JSON
local function escape(value)
value = tostring(value or "")
value = value:gsub('\\', '\\\\')
value = value:gsub('"', '\\"')
value = value:gsub('\n', '\\n')
value = value:gsub('\r', '\\r')
value = value:gsub('\t', '\\t')
return value
end
local function is_array(value)
if type(value) ~= "table" then
return false
end
local max_index = 0
local count = 0
for key, _ in pairs(value) do
if type(key) ~= "number" or key < 1 or key % 1 ~= 0 then
return false
end
if key > max_index then
max_index = key
end
count = count + 1
end
return count == max_index
end
local function encode_value(value)
local t = type(value)
if value == nil then
return "null"
elseif t == "boolean" then
return value and "true" or "false"
elseif t == "number" then
return tostring(value)
elseif t == "string" then
return '"' .. escape(value) .. '"'
elseif t == "table" then
local parts = {}
if is_array(value) then
for index = 1, #value do
parts[#parts + 1] = encode_value(value[index])
end
return "[" .. table.concat(parts, ",") .. "]"
end
for k, v in pairs(value) do
parts[#parts + 1] = '"' .. escape(k) .. '":' .. encode_value(v)
end
return "{" .. table.concat(parts, ",") .. "}"
end
return '"' .. escape(value) .. '"'
end
function json.encode(value)
return encode_value(value)
end
local function decode_error(text, index, message)
error("JSON decode error at byte " .. tostring(index) .. ": " .. message .. " near " .. string.format("%q", text:sub(index, index + 20)))
end
local function skip_ws(text, index)
while index <= #text do
local char = text:sub(index, index)
if char ~= " " and char ~= "\n" and char ~= "\r" and char ~= "\t" then break end
index = index + 1
end
return index
end
local function utf8_char(codepoint)
if codepoint <= 0x7F then
return string.char(codepoint)
elseif codepoint <= 0x7FF then
return string.char(
0xC0 + math.floor(codepoint / 0x40),
0x80 + (codepoint % 0x40)
)
elseif codepoint <= 0xFFFF then
return string.char(
0xE0 + math.floor(codepoint / 0x1000),
0x80 + (math.floor(codepoint / 0x40) % 0x40),
0x80 + (codepoint % 0x40)
)
elseif codepoint <= 0x10FFFF then
return string.char(
0xF0 + math.floor(codepoint / 0x40000),
0x80 + (math.floor(codepoint / 0x1000) % 0x40),
0x80 + (math.floor(codepoint / 0x40) % 0x40),
0x80 + (codepoint % 0x40)
)
end
return "?"
end
local parse_value
local function parse_string(text, index)
if text:sub(index, index) ~= '"' then decode_error(text, index, "expected string") end
index = index + 1
local parts = {}
local start = index
while index <= #text do
local char = text:sub(index, index)
if char == '"' then
parts[#parts + 1] = text:sub(start, index - 1)
return table.concat(parts), index + 1
elseif char == "\\" then
parts[#parts + 1] = text:sub(start, index - 1)
local escape_char = text:sub(index + 1, index + 1)
if escape_char == '"' or escape_char == "\\" or escape_char == "/" then
parts[#parts + 1] = escape_char
index = index + 2
elseif escape_char == "b" then
parts[#parts + 1] = "\b"
index = index + 2
elseif escape_char == "f" then
parts[#parts + 1] = "\f"
index = index + 2
elseif escape_char == "n" then
parts[#parts + 1] = "\n"
index = index + 2
elseif escape_char == "r" then
parts[#parts + 1] = "\r"
index = index + 2
elseif escape_char == "t" then
parts[#parts + 1] = "\t"
index = index + 2
elseif escape_char == "u" then
local hex = text:sub(index + 2, index + 5)
local codepoint = tonumber(hex, 16)
if not codepoint then decode_error(text, index, "invalid unicode escape") end
index = index + 6
if codepoint >= 0xD800 and codepoint <= 0xDBFF and text:sub(index, index + 1) == "\\u" then
local low = tonumber(text:sub(index + 2, index + 5), 16)
if low and low >= 0xDC00 and low <= 0xDFFF then
codepoint = 0x10000 + ((codepoint - 0xD800) * 0x400) + (low - 0xDC00)
index = index + 6
end
end
parts[#parts + 1] = utf8_char(codepoint)
else
decode_error(text, index, "invalid escape sequence")
end
start = index
else
index = index + 1
end
end
decode_error(text, index, "unterminated string")
end
local function parse_number(text, index)
local start = index
if text:sub(index, index) == "-" then index = index + 1 end
while text:sub(index, index):match("%d") do index = index + 1 end
if text:sub(index, index) == "." then
index = index + 1
while text:sub(index, index):match("%d") do index = index + 1 end
end
local exponent = text:sub(index, index)
if exponent == "e" or exponent == "E" then
index = index + 1
local sign = text:sub(index, index)
if sign == "+" or sign == "-" then index = index + 1 end
while text:sub(index, index):match("%d") do index = index + 1 end
end
local raw = text:sub(start, index - 1)
local value = tonumber(raw)
if value == nil then decode_error(text, start, "invalid number") end
return value, index
end
local function parse_array(text, index)
index = skip_ws(text, index + 1)
local result = {}
if text:sub(index, index) == "]" then return result, index + 1 end
while index <= #text do
local value
value, index = parse_value(text, index)
result[#result + 1] = value
index = skip_ws(text, index)
local char = text:sub(index, index)
if char == "]" then return result, index + 1 end
if char ~= "," then decode_error(text, index, "expected ',' or ']'") end
index = skip_ws(text, index + 1)
end
decode_error(text, index, "unterminated array")
end
local function parse_object(text, index)
index = skip_ws(text, index + 1)
local result = {}
if text:sub(index, index) == "}" then return result, index + 1 end
while index <= #text do
local key
key, index = parse_string(text, index)
index = skip_ws(text, index)
if text:sub(index, index) ~= ":" then decode_error(text, index, "expected ':'") end
index = skip_ws(text, index + 1)
local value
value, index = parse_value(text, index)
result[key] = value
index = skip_ws(text, index)
local char = text:sub(index, index)
if char == "}" then return result, index + 1 end
if char ~= "," then decode_error(text, index, "expected ',' or '}'") end
index = skip_ws(text, index + 1)
end
decode_error(text, index, "unterminated object")
end
parse_value = function(text, index)
index = skip_ws(text, index)
local char = text:sub(index, index)
if char == '"' then return parse_string(text, index) end
if char == "{" then return parse_object(text, index) end
if char == "[" then return parse_array(text, index) end
if char == "-" or char:match("%d") then return parse_number(text, index) end
if text:sub(index, index + 3) == "true" then return true, index + 4 end
if text:sub(index, index + 4) == "false" then return false, index + 5 end
if text:sub(index, index + 3) == "null" then return nil, index + 4 end
decode_error(text, index, "unexpected value")
end
function json.decode(text)
if type(text) ~= "string" then error("JSON decode expects a string") end
local value, index = parse_value(text, 1)
index = skip_ws(text, index)
if index <= #text then decode_error(text, index, "trailing characters") end
if type(value) ~= "table" then error("JSON command must decode to an object") end
if type(value.params) ~= "table" then value.params = {} end
return value
end
return json
@@ -1,105 +0,0 @@
-- Optional AIRWING payload snapshot extension for MOOSE Bridge.
--
-- Load after MooseBridge.lua when AIRWING payload availability should be included
-- in COHORT snapshots. The core Python advisory layer can then reject AIRWING
-- candidates without a compatible payload for the requested AUFTRAG type.
if not MOOSE_BRIDGE then error("Load MooseBridge.lua before MooseBridgePayloadExtension.lua") end
local function bridge_safe_tostring(value)
if value == nil then return "nil" end
return tostring(value)
end
local function bridge_string_or_nil(value)
if value == nil then return nil end
return tostring(value)
end
function MOOSE_BRIDGE:_CohortUnitType(cohort)
if not cohort then return nil end
local unit_type = self:_SafeCall(cohort, "GetUnitType")
if not unit_type then unit_type = self:_SafeCall(cohort, "GetTypeName") end
if not unit_type then unit_type = self:_SafeCall(cohort, "GetType") end
if not unit_type then unit_type = cohort.unittype or cohort.unitType or cohort.aircrafttype or cohort.AircraftType or cohort.type end
return unit_type and bridge_safe_tostring(unit_type) or nil
end
function MOOSE_BRIDGE:_PayloadPerformance(payload, mission_type)
if type(payload) ~= "table" or type(payload.capabilities) ~= "table" then return nil end
for _, capability in pairs(payload.capabilities) do
if type(capability) == "table" and capability.MissionType == mission_type then
return self:_NumberOrNil(capability.Performance)
end
end
return nil
end
function MOOSE_BRIDGE:_SummarizePayload(payload, mission_type)
if type(payload) ~= "table" then return nil end
local performance = self:_PayloadPerformance(payload, mission_type)
return {
uid=payload.uid,
unitname=bridge_string_or_nil(payload.unitname),
aircrafttype=bridge_string_or_nil(payload.aircrafttype),
navail=self:_NumberOrNil(payload.navail),
unlimited=self:_BoolOrFalse(payload.unlimited),
performance=performance,
}
end
function MOOSE_BRIDGE:_PayloadAvailabilityForMission(airwing, unit_type, mission_type)
local payloads = self:_SafeCallArg(airwing, "_FilterPlayloads", unit_type, mission_type)
if payloads == nil then payloads = self:_SafeCallArg(airwing, "_FilterPayloads", unit_type, mission_type) end
local result = {
available_count=0,
total_available=0,
unlimited_count=0,
best_performance=nil,
payloads={},
}
if type(payloads) ~= "table" then return result end
for _, payload in pairs(payloads) do
local item = self:_SummarizePayload(payload, mission_type)
if item then
result.payloads[#result.payloads + 1] = item
result.available_count = result.available_count + 1
if item.unlimited then result.unlimited_count = result.unlimited_count + 1 end
if item.navail then result.total_available = result.total_available + item.navail end
if item.performance and (not result.best_performance or item.performance > result.best_performance) then
result.best_performance = item.performance
end
end
end
return result
end
function MOOSE_BRIDGE:_CollectPayloadAvailability(cohort, mission_types)
local result = {}
if not cohort or type(mission_types) ~= "table" then return result end
local airwing = cohort.legion
if not airwing or not self:_SafeCall(airwing, "IsAirwing") then return result end
local unit_type = self:_CohortUnitType(cohort)
if not unit_type then return result end
for _, mission_type in pairs(mission_types) do
result[bridge_safe_tostring(mission_type)] = self:_PayloadAvailabilityForMission(airwing, unit_type, mission_type)
end
return result
end
local _moose_bridge_base_build_cohort_snapshot_item = MOOSE_BRIDGE._BuildCohortSnapshotItem
function MOOSE_BRIDGE:_BuildCohortSnapshotItem(cohort_name, cohort, source)
local item = _moose_bridge_base_build_cohort_snapshot_item(self, cohort_name, cohort, source)
if not item then return nil end
local unit_type = self:_CohortUnitType(cohort)
item.unit_type = unit_type
item.payloads_by_mission = self:_CollectPayloadAvailability(cohort, item.mission_types)
return item
end
@@ -1,73 +0,0 @@
-- Optional connection tuning extension for MOOSE Bridge.
--
-- Load after MooseBridge.lua and before creating the bridge instance. It reduces
-- DCS main-thread stalls when the Python server is not running by shortening the
-- blocking LuaSocket connect timeout and using less aggressive retry defaults.
if not MOOSE_BRIDGE then error("Load MooseBridge.lua before MooseBridgeSocketTuningExtension.lua") end
local function bridge_tuning_mission_time()
if timer and timer.getTime then return timer.getTime() end
return 0
end
local function bridge_tuning_safe_tostring(value)
if value == nil then return "nil" end
return tostring(value)
end
local function bridge_should_log_connect_failure(bridge, err, now)
if bridge.LogConnectFailures == false then return false end
if err == "timeout" and bridge.LogConnectTimeouts ~= true then return false end
local interval = bridge.ConnectFailureLogInterval or 60
if bridge.LastConnectFailureLog and now - bridge.LastConnectFailureLog < interval then return false end
bridge.LastConnectFailureLog = now
return true
end
local _moose_bridge_tuning_base_new = MOOSE_BRIDGE.New
function MOOSE_BRIDGE:New(host, port)
local bridge = _moose_bridge_tuning_base_new(self, host, port)
-- Keep idle bridge retries low-impact when the Python server is not listening.
bridge.ConnectTimeout = bridge.ConnectTimeout or 0.02
if bridge.ConnectRetryDelay == nil or bridge.ConnectRetryDelay == 5 then bridge.ConnectRetryDelay = 10 end
if bridge.TickInterval == nil or bridge.TickInterval == 0.2 then bridge.TickInterval = 0.5 end
if bridge.HeartbeatInterval == nil or bridge.HeartbeatInterval == 5 then bridge.HeartbeatInterval = 10 end
-- Keep expected idle reconnect timeouts out of dcs.log by default.
if bridge.LogConnectFailures == nil then bridge.LogConnectFailures = true end
if bridge.LogConnectTimeouts == nil then bridge.LogConnectTimeouts = false end
bridge.ConnectFailureLogInterval = bridge.ConnectFailureLogInterval or 60
bridge.LastConnectFailureLog = nil
return bridge
end
function MOOSE_BRIDGE:_Connect()
local now = bridge_tuning_mission_time()
if now - self.LastConnectAttempt < (self.ConnectRetryDelay or 10) then return end
self.LastConnectAttempt = now
local lib = require("socket")
local conn = lib.tcp()
conn:settimeout(self.ConnectTimeout or 0.02)
local ok, err = conn:connect(self.Host, self.Port)
if not ok then
if bridge_should_log_connect_failure(self, err, now) then
self:_Log("Connect failed: " .. bridge_tuning_safe_tostring(err))
end
conn:close()
return
end
-- All regular bridge IO is polled from the scheduler tick and must not block DCS.
conn:settimeout(0)
self.Socket = conn
self.Connected = true
self:_Log("Connected to Python bridge")
end
+13 -98
View File
@@ -131,24 +131,14 @@ ENUMS.WeaponFlag={
Cannons = 805306368, -- GUN_POD + BuiltInCannon Cannons = 805306368, -- GUN_POD + BuiltInCannon
--- Torpedo --- Torpedo
Torpedo = 4294967296, Torpedo = 4294967296,
--- Decoy
Decoys = 8589934592,
--- Shell
SmokeShell = 17179869184,
IlluminationShell = 34359738368,
MarkerShell = 51539607552,
MarkerWeapon = 51539620864,
SubmunitionDispenserShell = 68719476736,
ConventionalShell = 206963736576,
--- ---
-- Even More Genral -- Even More Genral
Auto = 265214230526, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons + Torpedos) Auto = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
AutoDCS = 1073741822, -- Something if often see AutoDCS = 1073741822, -- Something if often see
AnyAG = 2956984318, -- Any Air-To-Ground Weapon AnyAG = 2956984318, -- Any Air-To-Ground Weapon
AnyAA = 264241152, -- Any Air-To-Air Weapon AnyAA = 264241152, -- Any Air-To-Air Weapon
AnyUnguided = 2952822768, -- Any Unguided Weapon AnyUnguided = 2952822768, -- Any Unguided Weapon
AnyGuided = 268402702, -- Any Guided Weapon AnyGuided = 268402702, -- Any Guided Weapon
AnyShell = 258503344128, -- Any Shell
} }
--- Weapon types by category. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on hoggit wiki. --- Weapon types by category. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on hoggit wiki.
@@ -230,11 +220,11 @@ ENUMS.WeaponType.Torpedo={
} }
ENUMS.WeaponType.Any={ ENUMS.WeaponType.Any={
-- General combinations -- General combinations
Weapon = 265214230526, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons + Torpedos) Weapon = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
AG = 2956984318, -- Any Air-To-Ground Weapon AG = 2956984318, -- Any Air-To-Ground Weapon
AA = 264241152, -- Any Air-To-Air Weapon AA = 264241152, -- Any Air-To-Air Weapon
Unguided = 2952822768, -- Any Unguided Weapon Unguided = 2952822768, -- Any Unguided Weapon
Guided = 268402702, -- Any Guided Weapon Guided = 268402702, -- Any Guided Weapon
} }
@@ -523,7 +513,7 @@ ENUMS.ReportingName =
Tornado = "Tornado", Tornado = "Tornado",
-- Transport / Bomber / Others -- Transport / Bomber / Others
Atlas = "A400", Atlas = "A400",
Lancer = "B-1B", Lancer = "B1-B",
Stratofortress = "B-52H", Stratofortress = "B-52H",
Herc = "C-130", Herc = "C-130",
Hercules = "C-130J-30", Hercules = "C-130J-30",
@@ -568,7 +558,7 @@ ENUMS.ReportingName =
Apache = "AH-64", Apache = "AH-64",
Chinook = "CH-47", Chinook = "CH-47",
Sea_Stallion = "CH-53", Sea_Stallion = "CH-53",
Kiowa = "OH58D", Kiowa = "OH-58",
Seahawk = "SH-60", Seahawk = "SH-60",
Blackhawk = "UH-60", Blackhawk = "UH-60",
Sea_King = "S-61", Sea_King = "S-61",
@@ -1808,21 +1798,13 @@ ENUMS.Storage.weapons.missiles.Igla_S = 'weapons.missiles.Igla_S'
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}' ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}'
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}' ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}'
ENUMS.Storage.weapons.shells.M882_9x19 = 'weapons.shells.9x19_m882' ENUMS.Storage.weapons.shells.M882_9x19 = 'weapons.shells.9x19_m882'
ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = "weapons.droptanks.fuel_tank_370gal"
ENUMS.Storage.weapons.droptanks.fuel_tank_300gal = "weapons.droptanks.fuel_tank_300gal"
-- NEW
ENUMS.Storage.weapons.adapters.HB_F_4E_ORD_LAU_77 = 'weapons.adapters.HB_F-4E_ORD_LAU_77' ENUMS.Storage.weapons.adapters.HB_F_4E_ORD_LAU_77 = 'weapons.adapters.HB_F-4E_ORD_LAU_77'
ENUMS.Storage.weapons.adapters.hb_a_6e_lau7_adu299 = 'weapons.adapters.hb_a-6e_lau7_adu299' ENUMS.Storage.weapons.adapters.hb_a_6e_lau7_adu299 = 'weapons.adapters.hb_a-6e_lau7_adu299'
ENUMS.Storage.weapons.adapters.CHAP_AIM92LN = 'weapons.adapters.CHAP_AIM92LN'
ENUMS.Storage.weapons.adapters.CHAP_HOT3LN = 'weapons.adapters.CHAP_HOT3LN'
ENUMS.Storage.weapons.adapters.CHAP_FZ225 = 'weapons.adapters.CHAP_FZ225'
ENUMS.Storage.weapons.bombs.AH6_SMOKE_BLUE = 'weapons.bombs.AH6_SMOKE_BLUE' ENUMS.Storage.weapons.bombs.AH6_SMOKE_BLUE = 'weapons.bombs.AH6_SMOKE_BLUE'
ENUMS.Storage.weapons.bombs.AH6_SMOKE_GREEN = 'weapons.bombs.AH6_SMOKE_GREEN' ENUMS.Storage.weapons.bombs.AH6_SMOKE_GREEN = 'weapons.bombs.AH6_SMOKE_GREEN'
ENUMS.Storage.weapons.bombs.AH6_SMOKE_RED = 'weapons.bombs.AH6_SMOKE_RED' ENUMS.Storage.weapons.bombs.AH6_SMOKE_RED = 'weapons.bombs.AH6_SMOKE_RED'
ENUMS.Storage.weapons.bombs.AH6_SMOKE_YELLOW = 'weapons.bombs.AH6_SMOKE_YELLOW' ENUMS.Storage.weapons.bombs.AH6_SMOKE_YELLOW = 'weapons.bombs.AH6_SMOKE_YELLOW'
ENUMS.Storage.weapons.bombs.AO_25SL = 'weapons.bombs.AO_25SL'
ENUMS.Storage.weapons.missiles.HB_AGM_78 = 'weapons.missiles.HB_AGM_78' ENUMS.Storage.weapons.missiles.HB_AGM_78 = 'weapons.missiles.HB_AGM_78'
ENUMS.Storage.weapons.missiles.V_1 = 'weapons.missiles.V-1' ENUMS.Storage.weapons.missiles.V_1 = 'weapons.missiles.V-1'
@@ -1869,10 +1851,6 @@ ENUMS.Storage.weapons.shells.M42A1_HE = 'weapons.shells.M42A1_HE'
ENUMS.Storage.weapons.shells.HE_20x138B = 'weapons.shells.20x138B_HE' ENUMS.Storage.weapons.shells.HE_20x138B = 'weapons.shells.20x138B_HE'
ENUMS.Storage.weapons.shells.HE_37x263 = 'weapons.shells.37x263_HE' ENUMS.Storage.weapons.shells.HE_37x263 = 'weapons.shells.37x263_HE'
ENUMS.Storage.weapons.shells.HE_2A20_115mm = 'weapons.shells.2A20_115mm_HE' ENUMS.Storage.weapons.shells.HE_2A20_115mm = 'weapons.shells.2A20_115mm_HE'
ENUMS.Storage.weapons.shells.AP_20x99R = 'weapons.shells.20x99R_AP'
ENUMS.Storage.weapons.shells.IranFAC_DShK_API_T = 'weapons.shells.IranFAC_DShK_API_T'
ENUMS.Storage.weapons.shells.IranFAC_DShK_API = 'weapons.shells.IranFAC_DShK_API'
ENUMS.Storage.weapons.shells.HE_T_20x99R = 'weapons.shells.20x99R_HE_T'
ENUMS.Storage.weapons.gunmounts.B17_TailTurret_M2_L = 'weapons.gunmounts.B17_TailTurret_M2_L' ENUMS.Storage.weapons.gunmounts.B17_TailTurret_M2_L = 'weapons.gunmounts.B17_TailTurret_M2_L'
ENUMS.Storage.weapons.gunmounts.AH6_M134L = 'weapons.gunmounts.{AH6_M134L}' ENUMS.Storage.weapons.gunmounts.AH6_M134L = 'weapons.gunmounts.{AH6_M134L}'
@@ -1903,11 +1881,6 @@ ENUMS.Storage.weapons.gunmounts.AH_6_FN_HMP400 = 'weapons.gunmounts.{AH-6_FN_HMP
ENUMS.Storage.weapons.gunmounts.AH_6_M134L = 'weapons.gunmounts.AH-6_M134L' ENUMS.Storage.weapons.gunmounts.AH_6_M134L = 'weapons.gunmounts.AH-6_M134L'
ENUMS.Storage.weapons.gunmounts.AH_6_M134R = 'weapons.gunmounts.AH-6_M134R' ENUMS.Storage.weapons.gunmounts.AH_6_M134R = 'weapons.gunmounts.AH-6_M134R'
ENUMS.Storage.weapons.gunmounts.AH_6_HMP400 = 'weapons.gunmounts.AH-6_HMP400' ENUMS.Storage.weapons.gunmounts.AH_6_HMP400 = 'weapons.gunmounts.AH-6_HMP400'
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}'
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}'
ENUMS.Storage.weapons.gunmounts.CHAP_HMP400LC = 'weapons.gunmounts.{CHAP_HMP400LC}'
ENUMS.Storage.weapons.gunmounts.HMP400LC = 'weapons.gunmounts.HMP400LC'
ENUMS.Storage.weapons.gunmounts.SHVAK_GUN = 'weapons.gunmounts.SHVAK_GUN'
ENUMS.Storage.weapons.droptanks.PTB_800 = 'weapons.droptanks.PTB-800' ENUMS.Storage.weapons.droptanks.PTB_800 = 'weapons.droptanks.PTB-800'
ENUMS.Storage.weapons.droptanks.PTB_275 = 'weapons.droptanks.PTB-275' ENUMS.Storage.weapons.droptanks.PTB_275 = 'weapons.droptanks.PTB-275'
@@ -1958,7 +1931,6 @@ ENUMS.Storage.weapons.droptanks.S_3_PTB = 'weapons.droptanks.S-3-PTB'
ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = 'weapons.droptanks.fuel_tank_370gal' ENUMS.Storage.weapons.droptanks.fuel_tank_370gal = 'weapons.droptanks.fuel_tank_370gal'
ENUMS.Storage.weapons.droptanks.MIG_23_PTB = 'weapons.droptanks.MIG-23-PTB' ENUMS.Storage.weapons.droptanks.MIG_23_PTB = 'weapons.droptanks.MIG-23-PTB'
ENUMS.Storage.weapons.droptanks.f_18c_ptb = 'weapons.droptanks.f-18c-ptb' ENUMS.Storage.weapons.droptanks.f_18c_ptb = 'weapons.droptanks.f-18c-ptb'
ENUMS.Storage.weapons.droptanks.CHAP_TigerUHT_fueltank = 'weapons.droptanks.CHAP_TigerUHT_fueltank'
ENUMS.Storage.weapons.containers.FN_HMP400_100 = 'weapons.containers.{FN_HMP400_100}' ENUMS.Storage.weapons.containers.FN_HMP400_100 = 'weapons.containers.{FN_HMP400_100}'
ENUMS.Storage.weapons.containers.AN_M3 = 'weapons.containers.{AN-M3}' ENUMS.Storage.weapons.containers.AN_M3 = 'weapons.containers.{AN-M3}'
@@ -2090,13 +2062,6 @@ ENUMS.Storage.weapons.containers.GIAT_M621_AP = 'weapons.containers.{GIAT_M621_A
ENUMS.Storage.weapons.containers.M134_R = 'weapons.containers.M134_R' ENUMS.Storage.weapons.containers.M134_R = 'weapons.containers.M134_R'
ENUMS.Storage.weapons.containers.OH58D_M3P_L300 = 'weapons.containers.OH58D_M3P_L300' ENUMS.Storage.weapons.containers.OH58D_M3P_L300 = 'weapons.containers.OH58D_M3P_L300'
ENUMS.Storage.weapons.containers.AH6_M134R = 'weapons.containers.{AH6_M134R}' ENUMS.Storage.weapons.containers.AH6_M134R = 'weapons.containers.{AH6_M134R}'
ENUMS.Storage.weapons.containers.CHAP_HMP400LC = 'weapons.containers.{CHAP_HMP400LC}'
ENUMS.Storage.weapons.missiles.C701_AShM = 'weapons.missiles.C701 AShM'
ENUMS.Storage.weapons.missiles.IGLA_9M39 = 'weapons.missiles.9M39_IGLA'
ENUMS.Storage.weapons.missiles.CHAP_AIM92 = 'weapons.missiles.CHAP_AIM92'
ENUMS.Storage.weapons.missiles.AM39 = 'weapons.missiles.AM39'
ENUMS.Storage.weapons.missiles.Shahed136_LM = 'weapons.missiles.Shahed136_LM'
ENUMS.Storage.weapons.torpedoes.G7A_T1 = 'weapons.torpedoes.G7A_T1' ENUMS.Storage.weapons.torpedoes.G7A_T1 = 'weapons.torpedoes.G7A_T1'
@@ -2126,56 +2091,6 @@ ENUMS.Storage.weapons.containers.UH60_GAU19_RIGHT = 'weapons.containers.{UH60_GA
ENUMS.Storage.weapons.containers.UH60_M230_RIGHT = 'weapons.containers.{UH60_M230_RIGHT}' ENUMS.Storage.weapons.containers.UH60_M230_RIGHT = 'weapons.containers.{UH60_M230_RIGHT}'
ENUMS.Storage.weapons.containers.UH60L_M2_GUNNER = 'weapons.containers.{UH60L_M2_GUNNER}' ENUMS.Storage.weapons.containers.UH60L_M2_GUNNER = 'weapons.containers.{UH60L_M2_GUNNER}'
ENUMS.Storage.weapons.adapters.GD_F100_SUU_13 = 'weapons.adapters.GD_F100_SUU-13'
ENUMS.Storage.weapons.containers.GPU_5_POD = 'weapons.containers.{GPU_5_POD}'
ENUMS.Storage.weapons.bombs.BOLT_117 = 'weapons.bombs.BOLT-117'
ENUMS.Storage.weapons.shells.F100_M39_20_TP_T = 'weapons.shells.F100_M39_20_TP_T'
ENUMS.Storage.weapons.gunmounts.M_39_F_100 = 'weapons.gunmounts.M-39 F-100'
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_275_L = 'weapons.droptanks.GD_F100_TANK_275_L'
ENUMS.Storage.weapons.bombs.BLU_4B_R_GROUP_R = 'weapons.bombs.BLU-4B_R_GROUP_R'
ENUMS.Storage.weapons.containers.F100_CAMERA_P2 = 'weapons.containers.F100_CAMERA_P2'
ENUMS.Storage.weapons.containers.HB_ORD_SUU_7 = 'weapons.containers.HB_ORD_SUU_7'
ENUMS.Storage.weapons.adapters.SidewinderPylon = 'weapons.adapters.SidewinderPylon'
ENUMS.Storage.weapons.adapters.apu_470 = 'weapons.adapters.apu_470'
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_335_R = 'weapons.droptanks.GD_F100_TANK_335_R'
ENUMS.Storage.weapons.bombs.BLU_3B_R_GROUP_R = 'weapons.bombs.BLU-3B_R_GROUP_R'
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_335_L = 'weapons.droptanks.GD_F100_TANK_335_L'
ENUMS.Storage.weapons.adapters.mbdz_u2t = 'weapons.adapters.mbdz_u2t'
ENUMS.Storage.weapons.containers.ZellRocket = 'weapons.containers.ZellRocket'
ENUMS.Storage.weapons.containers.apu_73 = 'weapons.containers.apu-73'
ENUMS.Storage.weapons.gunmounts.GAU_13 = 'weapons.gunmounts.GAU-13'
ENUMS.Storage.weapons.shells.F100_M39_20_TP = 'weapons.shells.F100_M39_20_TP'
ENUMS.Storage.weapons.shells.F100_M39_20_API = 'weapons.shells.F100_M39_20_API'
ENUMS.Storage.weapons.adapters.bdz_umk2b = 'weapons.adapters.bdz_umk2b'
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_450 = 'weapons.droptanks.GD_F100_TANK_450'
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_275_R = 'weapons.droptanks.GD_F100_TANK_275_R'
ENUMS.Storage.weapons.bombs.BLU_18_B_GROUP = 'weapons.bombs.BLU-18/B_GROUP'
ENUMS.Storage.weapons.containers.ZELL_Booster = 'weapons.containers.ZELL Booster'
ENUMS.Storage.weapons.adapters.apu_60 = 'weapons.adapters.apu_60'
ENUMS.Storage.weapons.gunmounts.GPU_5_POD = 'weapons.gunmounts.{GPU_5_POD}'
ENUMS.Storage.weapons.containers.F100_CAMERA_KA71A = 'weapons.containers.F100_CAMERA_KA71A'
ENUMS.Storage.weapons.bombs.BLU_3_R_GROUP_R = 'weapons.bombs.BLU-3_R_GROUP_R'
ENUMS.Storage.weapons.shells.F100_M39_20_HEI = 'weapons.shells.F100_M39_20_HEI'
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_200 = 'weapons.droptanks.GD_F100_TANK_200'
ENUMS.Storage.weapons.adapters.apu_73 = 'weapons.adapters.apu-73'
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.AKAN_NO_TRC'
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.AKAN'
ENUMS.Storage.weapons.shells.F100_M39_20_HEI_T = 'weapons.shells.F100_M39_20_HEI_T'
ENUMS.Storage.weapons.shells.F100_M39_20_API_T = 'weapons.shells.F100_M39_20_API_T'
ENUMS.Storage.weapons.containers.GD_F100_STRIKE_CAMERA = 'weapons.containers.GD_F100_STRIKE_CAMERA'
ENUMS.Storage.weapons.bombs.BLU_49_B = 'weapons.bombs.BLU-49/B'
ENUMS.Storage.weapons.bombs.GBU_24E = 'weapons.bombs.GBU_24E'
ENUMS.Storage.weapons.containers.GD_F100_SUU_13 = 'weapons.containers.GD_F100_SUU-13'
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}'
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}'
ENUMS.Storage.weapons.gunmounts.SUU_23_POD_Wing = 'weapons.gunmounts.{SUU_23_POD_Wing}'
ENUMS.Storage.weapons.containers.SUU_23_POD_Wing = 'weapons.containers.{SUU_23_POD_Wing}'
ENUMS.Storage.weapons.containers.SUU_23_POD_Centerline = 'weapons.containers.{SUU_23_POD_Centerline}'
ENUMS.Storage.weapons.gunmounts.SUU_23_POD_Centerline = 'weapons.gunmounts.{SUU_23_POD_Centerline}'
ENUMS.Storage.weapons.containers.HB_F_4E_BRU_42 = 'weapons.containers.HB_F-4E_BRU-42'
--- ---
-- @type ENUMS.FARPType -- @type ENUMS.FARPType
-- @field #string FARP -- @field #string FARP
+2 -2
View File
@@ -753,8 +753,8 @@ UTILS.KnotsToKmph = function(knots)
return knots * 1.852 return knots * 1.852
end end
UTILS.KmphToKnots = function(knots) UTILS.KmphToKnots = function(kmph)
return knots / 1.852 return kmph / 1.852
end end
UTILS.KmphToMps = function( kmph ) UTILS.KmphToMps = function( kmph )
+16 -35
View File
@@ -1963,25 +1963,6 @@ function AIRBASE:Register(AirbaseName)
-- Category. -- Category.
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
-- Get DCS object.
local airbase=self:GetDCSObject()
if airbase then
self.objectcategory=Object.getCategory(airbase)
else
self.objectcategory=Object.Category.BASE
end
if self.objectcategory==Object.Category.BASE then
self.objectcategoryName="BASE"
elseif self.objectcategory==Object.Category.STATIC then
self.objectcategoryName="STATIC"
elseif self.objectcategory==Object.Category.UNIT then
self.objectcategoryName="UNIT"
else
self.objectcategoryName="OTHER"
end
-- H2 is bugged -- H2 is bugged
--if self.AirbaseName == "H4" and self.descriptors == nil then --if self.AirbaseName == "H4" and self.descriptors == nil then
@@ -1990,24 +1971,24 @@ function AIRBASE:Register(AirbaseName)
--end --end
-- Set category. -- Set category.
if self.category==Airbase.Category.AIRDROME then if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true self.isAirdrome=true
elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01" then elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01" then
self.isHelipad=true
self.category=Airbase.Category.HELIPAD
elseif self.category==Airbase.Category.SHIP then
self.isShip=true
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
self.isHelipad=true self.isHelipad=true
self.isShip=false
self.category=Airbase.Category.HELIPAD self.category=Airbase.Category.HELIPAD
elseif self.category==Airbase.Category.SHIP then _DATABASE:AddStatic(AirbaseName)
self.isShip=true
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
self.isHelipad=true
self.isShip=false
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
if self:GetTypeName() == "Zell" then self.isZell = true end
else
self:E("ERROR: Unknown airbase category!")
end end
if self:GetTypeName() == "Zell" then self.isZell = true end
else
self:E("ERROR: Unknown airbase category!")
end
-- Init Runways. -- Init Runways.
self:_InitRunways() self:_InitRunways()
@@ -2633,7 +2614,7 @@ function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark)
local spots = self:GetParkingSpotsVec2s() local spots = self:GetParkingSpotsVec2s()
if #spots == 0 then return self.AirbaseZone end if #spots == 0 then return self.AirbaseZone end
local center, radius = UTILS.GetMinimumBoundingCircle(spots) local center, radius = UTILS.GetMinimumBoundingCircle(spots)
self.parkingCircle = ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle", center, radius+50, true) self.parkingCircle = ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle",center,radius+50)
if mark == true then if mark == true then
self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3) self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3)
end end
+4
View File
@@ -76,6 +76,10 @@ Functional/Tiresias.lua
Functional/Stratego.lua Functional/Stratego.lua
Functional/ClientWatch.lua Functional/ClientWatch.lua
Functional/Formation.lua Functional/Formation.lua
Functional/RedGCIKernel.lua
Functional/RedGCI.lua
Functional/RedGCI2v2.lua
Functional/RedGCIDispatcher.lua
Ops/Airboss.lua Ops/Airboss.lua
Ops/RecoveryTanker.lua Ops/RecoveryTanker.lua