mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-08-16 16:06:44 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 049aca91aa | |||
| a46bbcc2d2 | |||
| 7026a94301 | |||
| 92bc0389d3 | |||
| 7a5f6ad3c6 | |||
| 2a73d8d34a | |||
| dfe4db25ae | |||
| f66c77b027 | |||
| de3dc95c92 | |||
| d9a6f5daaf | |||
| 27fa920a8f | |||
| cf65cbf8fc | |||
| 2f229c1d8c | |||
| 7e07f2b4ea | |||
| d214d115c6 | |||
| fc1059c5d6 | |||
| 6e09ea7089 | |||
| 7587119843 | |||
| a5880b89c1 | |||
| 1be2c92a5c | |||
| 4c8a17fe57 | |||
| 64dea17982 | |||
| b1f86c702d | |||
| 1a9540d0d4 | |||
| cdfaac527c | |||
| 4e3bf9969b | |||
| 74c16bd4f0 | |||
| 55e048b322 | |||
| b31fb7ece3 | |||
| 149e3421d9 | |||
| 338ec34604 | |||
| 3d79a16d3a | |||
| efa08c1280 | |||
| 589e65963f | |||
| af4f20af67 | |||
| b1b8630d21 | |||
| 98ed7d50c1 | |||
| 4ba81cb8bc | |||
| 626b12f486 | |||
| e8f5214e0b | |||
| e4c7e809cb | |||
| aab16dd966 | |||
| 8b356f0913 | |||
| c3086b0265 | |||
| 599a2ba6fb |
@@ -736,6 +736,12 @@ do -- Event Handling
|
|||||||
-- @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.
|
||||||
-- @function [parent=#BASE] OnEventNewDynamicCargo
|
-- @function [parent=#BASE] OnEventNewDynamicCargo
|
||||||
@@ -881,6 +887,22 @@ 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
|
||||||
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
-- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ DATABASE = {
|
|||||||
ZONES_GOAL = {},
|
ZONES_GOAL = {},
|
||||||
WAREHOUSES = {},
|
WAREHOUSES = {},
|
||||||
FLIGHTGROUPS = {},
|
FLIGHTGROUPS = {},
|
||||||
|
COHORTS={},
|
||||||
|
LEGIONS={},
|
||||||
FLIGHTCONTROLS = {},
|
FLIGHTCONTROLS = {},
|
||||||
OPSZONES = {},
|
OPSZONES = {},
|
||||||
PATHLINES = {},
|
PATHLINES = {},
|
||||||
@@ -2087,6 +2089,40 @@ 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
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ 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,
|
||||||
@@ -291,23 +292,25 @@ 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) The initiating @{DCS#Unit} or @{DCS#StaticObject}.
|
-- @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#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) The initiating {DCSGroup#Group}.
|
-- @field DCS#Group IniDCSGroup (UNIT/GROUP) The initiating @{DCS#Group}.
|
||||||
-- @field #string IniDCSGroupName (UNIT) The initiating Group name.
|
-- @field #string IniDCSGroupName (UNIT/GROUP) The initiating Group name.
|
||||||
-- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object.
|
-- @field Wrapper.Group#GROUP IniGroup (UNIT/GROUP) The initiating MOOSE wrapper @{Wrapper.Group#GROUP} of the initiator Group object.
|
||||||
-- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName).
|
-- @field #string IniGroupName (UNIT/GROUP) 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) The coalition of the initiator.
|
-- @field DCS#coalition.side IniCoalition (UNIT/GROUP) 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.
|
||||||
--
|
--
|
||||||
@@ -734,6 +737,12 @@ 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] = {
|
||||||
@@ -1234,7 +1243,16 @@ function EVENT:onEvent( Event )
|
|||||||
|
|
||||||
if Event.initiator then
|
if Event.initiator then
|
||||||
|
|
||||||
|
if Event.id == EVENTS.GroupChangeOption then
|
||||||
|
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)
|
Event.IniObjectCategory = Object.getCategory(Event.initiator)
|
||||||
|
end
|
||||||
|
|
||||||
if Event.IniObjectCategory == Object.Category.STATIC then
|
if Event.IniObjectCategory == Object.Category.STATIC then
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ 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_MAX = 61
|
-- @field S_EVENT_GROUP_CHANGE_OPTION = 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
|
||||||
@@ -614,7 +615,6 @@ 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
@@ -1,760 +0,0 @@
|
|||||||
--- **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 10–15 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 8–13km 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 |
|
|
||||||
-- | | (300–600m 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
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
--- **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
|
||||||
@@ -79,11 +79,7 @@ __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' )
|
||||||
@@ -142,4 +138,13 @@ __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' )
|
||||||
|
|||||||
@@ -290,6 +290,9 @@ 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
|
||||||
|
|
||||||
@@ -433,6 +436,14 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance)
|
|||||||
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",
|
||||||
payload.unitname, payload.aircrafttype, payload.uid, payload.navail, tostring(payload.unlimited), Performance, table.concat(MissionTypes, ", ")))
|
payload.unitname, payload.aircrafttype, payload.uid, payload.navail, tostring(payload.unlimited), Performance, table.concat(MissionTypes, ", ")))
|
||||||
@@ -541,6 +552,47 @@ 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
|
||||||
@@ -589,34 +641,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _checkPayloads(payload)
|
local payloads=self:_FilterPlayloads(UnitType, MissionType, Payloads)
|
||||||
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
|
||||||
|
|||||||
@@ -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/A Early Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
-- * [F-14A/B/B(U)/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,6 +1276,7 @@ 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).
|
||||||
@@ -1297,6 +1298,7 @@ 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",
|
||||||
@@ -5508,7 +5510,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 = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early
|
local tomcat = self:_IsTomcat( playerData.actype )
|
||||||
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.
|
||||||
@@ -5571,6 +5573,18 @@ 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
|
||||||
@@ -5587,7 +5601,7 @@ function AIRBOSS:_AoAUnit2Deg( playerData, aoaunits )
|
|||||||
local degrees = aoaunits
|
local degrees = aoaunits
|
||||||
|
|
||||||
-- Check aircraft type of player.
|
-- Check aircraft type of player.
|
||||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
if self:_IsTomcat( playerData.actype ) then
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
-- F-14A/B --
|
-- F-14A/B --
|
||||||
@@ -5630,7 +5644,7 @@ function AIRBOSS:_AoADeg2Units( playerData, degrees )
|
|||||||
local aoaunits = degrees
|
local aoaunits = degrees
|
||||||
|
|
||||||
-- Check aircraft type of player.
|
-- Check aircraft type of player.
|
||||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
if self:_IsTomcat( playerData.actype ) then
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
-- F-14A/B --
|
-- F-14A/B --
|
||||||
@@ -5680,7 +5694,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 = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
local tomcat = self:_IsTomcat( playerData.actype )
|
||||||
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
|
||||||
@@ -6613,9 +6627,7 @@ 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
|
||||||
actype==AIRBOSS.AircraftCarrier.F14A or
|
self:_IsTomcat( actype, true ) 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
|
||||||
@@ -6725,7 +6737,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 flight.actype == AIRBOSS.AircraftCarrier.F14A_AI or flight.actype == AIRBOSS.AircraftCarrier.F14A or flight.actype == AIRBOSS.AircraftCarrier.F14B then
|
elseif self:_IsTomcat( flight.actype, true ) 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 )
|
||||||
@@ -8200,7 +8212,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 = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
local tomcat = self:_IsTomcat( playerData.actype )
|
||||||
|
|
||||||
-- 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
|
||||||
@@ -9455,7 +9467,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 playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
if self:_IsTomcat( playerData.actype ) 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!"
|
||||||
@@ -9620,8 +9632,7 @@ 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 playerData.actype == AIRBOSS.AircraftCarrier.F14A
|
or self:_IsTomcat( playerData.actype )
|
||||||
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
|
||||||
@@ -10815,7 +10826,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 playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
elseif self:_IsTomcat( playerData.actype ) 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
|
||||||
@@ -11628,7 +11639,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 = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early -- VNAO Edit - Added
|
local tomcat = self:_IsTomcat( playerData.actype ) -- 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
|
||||||
@@ -11861,7 +11872,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 typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
|
elseif self:_IsTomcat( typeName ) 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
|
||||||
|
|
||||||
@@ -11886,7 +11897,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 typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
|
elseif self:_IsTomcat( typeName ) 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
|
||||||
@@ -13149,7 +13160,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 = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B-- VNAO Edit - Added
|
local tomcat = self:_IsTomcat( playerData.actype )-- 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?
|
||||||
@@ -13665,7 +13676,7 @@ function AIRBOSS:_StepHint( playerData, step )
|
|||||||
|
|
||||||
-- Late break.
|
-- Late break.
|
||||||
if step == AIRBOSS.PatternStep.LATEBREAK then
|
if step == AIRBOSS.PatternStep.LATEBREAK then
|
||||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
if self:_IsTomcat( playerData.actype ) then
|
||||||
hint = hint .. "\nWing Sweep 20°, Gear DOWN < 280 KIAS."
|
hint = hint .. "\nWing Sweep 20°, Gear DOWN < 280 KIAS."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -13674,7 +13685,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 playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
elseif self:_IsTomcat( playerData.actype ) 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."
|
||||||
@@ -15109,7 +15120,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 actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B or actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
elseif self:_IsTomcat( actype, true ) 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"
|
||||||
@@ -15246,6 +15257,29 @@ 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.)
|
||||||
|
|||||||
@@ -192,6 +192,8 @@
|
|||||||
-- @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
|
||||||
|
|
||||||
--- *A warrior's mission is to foster the success of others.* -- Morihei Ueshiba
|
--- *A warrior's mission is to foster the success of others.* -- Morihei Ueshiba
|
||||||
@@ -656,6 +658,19 @@ 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.
|
||||||
@@ -676,7 +691,7 @@ AUFTRAG.Category={
|
|||||||
|
|
||||||
--- AUFTRAG class version.
|
--- AUFTRAG class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
AUFTRAG.version="1.4.2"
|
AUFTRAG.version="1.5.0"
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- TODO list
|
-- TODO list
|
||||||
@@ -776,6 +791,7 @@ 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)
|
||||||
|
|
||||||
@@ -946,6 +962,24 @@ 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
|
||||||
@@ -1751,6 +1785,9 @@ function AUFTRAG:NewBAI(Target, Altitude)
|
|||||||
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}
|
||||||
|
|
||||||
mission.DCStask=mission:GetDCSMissionTask()
|
mission.DCStask=mission:GetDCSMissionTask()
|
||||||
@@ -2304,6 +2341,9 @@ 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)
|
||||||
|
|
||||||
mission.artyShots=Nshots or nil
|
mission.artyShots=Nshots or nil
|
||||||
@@ -2407,7 +2447,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 Speed in knots.
|
-- @param #number Speed (Optional) 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.
|
||||||
@@ -4690,6 +4730,18 @@ function AUFTRAG:Evaluate()
|
|||||||
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
|
||||||
local text=string.format("Evaluating mission:\n")
|
local text=string.format("Evaluating mission:\n")
|
||||||
@@ -4709,6 +4761,9 @@ function AUFTRAG:Evaluate()
|
|||||||
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
|
||||||
self:I(self.lid..string.format("Mission %d [%s] failed!", self.auftragsnummer, self.type))
|
self:I(self.lid..string.format("Mission %d [%s] failed!", self.auftragsnummer, self.type))
|
||||||
@@ -5018,7 +5073,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 then
|
if groupdata and not groupdata.opsgroup:IsDestroyed() 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()))
|
||||||
@@ -5120,7 +5175,6 @@ 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.
|
||||||
@@ -5430,7 +5484,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()
|
self:__Stop(-120)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -5472,7 +5526,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()
|
self:__Stop(-120)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -5585,6 +5639,7 @@ 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()
|
||||||
@@ -5636,6 +5691,8 @@ 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()
|
||||||
|
|
||||||
@@ -5980,6 +6037,7 @@ 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
|
||||||
@@ -6466,7 +6524,13 @@ function AUFTRAG:GetDCSMissionTask(MissionGroup)
|
|||||||
-- ESCORT Mission --
|
-- ESCORT Mission --
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
local DCStask=CONTROLLABLE.TaskEscort(nil, self.engageTarget:GetObject(), self.escortVec3, nil, self.engageMaxDistance, self.engageTargetTypes)
|
local FollowGroup=self.engageTarget:GetObject()
|
||||||
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ 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.
|
||||||
@@ -2037,7 +2038,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.GetCoordinate and pos:GetCoordinate() or nil
|
local pcoord = pos and pos.GetCoord and pos:GetCoord() 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,
|
||||||
@@ -2748,14 +2749,12 @@ 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()))
|
||||||
if _item and _item:GetType() == CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName ~= _item:GetName() and not _item:WasDropped() then
|
local unloadedItem = _item and _item:GetType() == CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName == _item:GetName()
|
||||||
table.insert(Loaded,_item)
|
if not unloadedItem then
|
||||||
else
|
|
||||||
table.insert(Loaded,_item)
|
table.insert(Loaded,_item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -3125,14 +3124,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:GetCoordinate()
|
local unitcoord = Unit:GetCoord()
|
||||||
|
|
||||||
-- 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:GetCoordinate(),unitcoord)
|
local distance = self:_GetDistance(v:GetCoord(),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
|
||||||
@@ -3277,7 +3276,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:GetCoordinate()
|
local unitcoord = unit:GetCoord()
|
||||||
|
|
||||||
-- find nearest group of deployed troops
|
-- find nearest group of deployed troops
|
||||||
local nearestGroup = nil
|
local nearestGroup = nil
|
||||||
@@ -3288,7 +3287,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:GetCoordinate(),unitcoord)
|
local distance = self:_GetDistance(v:GetCoord(),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
|
||||||
@@ -3376,7 +3375,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:GetCoordinate() or Group:GetCoordinate() -- Core.Point#COORDINATE
|
local coord = Unit:GetCoord() or Group:GetCoord() -- 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
|
||||||
@@ -3747,16 +3746,16 @@ function CTLD:_C130GetUnits(Group, Unit, Name)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
local coord = Unit:GetCoordinate() or Group:GetCoordinate()
|
local coord = Unit:GetCoord() or Group:GetCoord()
|
||||||
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("blue"):FilterZones({checkZone}):FilterOnce()
|
local nearGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):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:GetCoordinate()
|
local gc = gr:GetCoord()
|
||||||
if gc then
|
if gc then
|
||||||
local dist = coord:Get2DDistance(gc)
|
local dist = coord:Get2DDistance(gc)
|
||||||
if dist > innerDist then
|
if dist > innerDist then
|
||||||
@@ -3806,6 +3805,7 @@ 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,6 +3819,7 @@ 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()
|
||||||
@@ -3925,7 +3926,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:GetCoordinate() or Group:GetCoordinate()
|
local unitcoord = Unit:GetCoord() or Group:GetCoord()
|
||||||
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
|
||||||
@@ -3972,7 +3973,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:GetCoordinate()
|
local position = Unit:GetCoord()
|
||||||
local heading = Unit:GetHeading() + 1
|
local heading = Unit:GetHeading() + 1
|
||||||
local height = Unit:GetHeight()
|
local height = Unit:GetHeight()
|
||||||
local droppedcargo = {}
|
local droppedcargo = {}
|
||||||
@@ -4136,8 +4137,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:GetCoordinate()
|
local shipcoord = Ship:GetCoord()
|
||||||
local unitcoord = Unit:GetCoordinate()
|
local unitcoord = Unit:GetCoord()
|
||||||
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
|
||||||
@@ -4389,17 +4390,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:GetCoordinate()
|
local location = _group:GetCoord()
|
||||||
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("blue"):FilterZones({zone}):FilterOnce()
|
local nearestGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):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:GetCoordinate()
|
local gc = gr:GetCoord()
|
||||||
if gc then
|
if gc then
|
||||||
local dist = location:Get2DDistance(gc)
|
local dist = location:Get2DDistance(gc)
|
||||||
if dist > innerDist then
|
if dist > innerDist then
|
||||||
@@ -4533,7 +4534,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:GetCoordinate()
|
local location = _group:GetCoord()
|
||||||
local existingcrates = self.Spawned_Cargo -- #table
|
local existingcrates = self.Spawned_Cargo -- #table
|
||||||
-- cycle
|
-- cycle
|
||||||
local index = 0
|
local index = 0
|
||||||
@@ -5159,7 +5160,6 @@ 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,6 +5196,7 @@ 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)
|
||||||
@@ -5506,8 +5507,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():GetCoordinate() -- Core.Point#COORDINATE
|
local ccoord = Crate:GetPositionable():GetCoord() -- Core.Point#COORDINATE
|
||||||
local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0
|
local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoord())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
|
||||||
@@ -5526,6 +5527,7 @@ 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
|
||||||
@@ -5611,7 +5613,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:GetCoordinate():Translate(20,hdg)
|
local base = Unit:GetCoord():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)
|
||||||
@@ -5645,7 +5647,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 }
|
local b = { Name=build.Name, Required=build.Required, Template=build.Template, CanBuild=true, Type=build.Type, Coord=coord, Heading=build.Heading }
|
||||||
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)
|
||||||
@@ -5683,18 +5685,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:GetCoordinate()
|
local location = Group:GetCoord()
|
||||||
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("blue"):FilterZones({zone}):FilterOnce()
|
local nearestGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):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:GetCoordinate()
|
local gc = gr:GetCoord()
|
||||||
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
|
||||||
@@ -5827,12 +5829,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:GetCoordinate()
|
local location = Group:GetCoord()
|
||||||
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:GetCoordinate())
|
local dist = location:Get2DDistance(pos:GetCoord())
|
||||||
if dist <= finddist then
|
if dist <= finddist then
|
||||||
matchingCrates[#matchingCrates + 1] = crateObj
|
matchingCrates[#matchingCrates + 1] = crateObj
|
||||||
end
|
end
|
||||||
@@ -5942,12 +5944,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:GetCoordinate()
|
local location = Group:GetCoord()
|
||||||
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:GetCoordinate())
|
local dist = location:Get2DDistance(pos:GetCoord())
|
||||||
if dist <= finddist then
|
if dist <= finddist then
|
||||||
crates[#crates + 1] = entry
|
crates[#crates + 1] = entry
|
||||||
end
|
end
|
||||||
@@ -6197,28 +6199,31 @@ 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)
|
||||||
|
:InitDelayOff()
|
||||||
|
:InitCoalition(self.coalition)
|
||||||
|
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||||
|
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
|
||||||
if canmove then
|
if canmove then
|
||||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
--spawn:InitRandomizeUnits(true,20,2)
|
||||||
--:InitRandomizeUnits(true,20,2)
|
|
||||||
:InitDelayOff()
|
|
||||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
|
||||||
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
|
|
||||||
:SpawnFromVec2(randomcoord)
|
|
||||||
else -- don't random position of e.g. SAM units build as FOB
|
|
||||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
|
||||||
:InitDelayOff()
|
|
||||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
|
||||||
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
|
|
||||||
:SpawnFromVec2(randomcoord)
|
|
||||||
end
|
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,self.DroppedTroops[self.TroopCounter])
|
self:__CratesRepaired(1,Group,Unit,spawnedGroup)
|
||||||
else
|
else
|
||||||
self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
|
self:__CratesBuild(1,Group,Unit,spawnedGroup)
|
||||||
end
|
end
|
||||||
end -- template loop
|
end -- template loop
|
||||||
|
|
||||||
|
if Group and Group:IsAlive() and Group:GetID() then
|
||||||
self:_RefreshLoadCratesMenu(Group, Unit)
|
self:_RefreshLoadCratesMenu(Group, Unit)
|
||||||
self:_RefreshPackMenus(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
|
||||||
@@ -6241,14 +6246,11 @@ 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()
|
||||||
@@ -7931,6 +7933,7 @@ 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)
|
||||||
@@ -9008,7 +9011,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:GetCoordinate()
|
local unitcoord = Unit:GetCoord()
|
||||||
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
|
||||||
@@ -9029,7 +9032,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:GetCoordinate()
|
zonecoord = ZoneUNIT:GetCoord()
|
||||||
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)
|
||||||
@@ -9078,7 +9081,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:GetCoordinate() -- Core.Point#COORDINATE
|
local unitcoord = Unit:GetCoord() -- 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)
|
||||||
@@ -9097,7 +9100,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:GetCoordinate()
|
local unitcoord = Unit:GetCoord()
|
||||||
local Group = Unit:GetGroup()
|
local Group = Unit:GetGroup()
|
||||||
local smokedistance = self.smokedistance
|
local smokedistance = self.smokedistance
|
||||||
local smoked = false
|
local smoked = false
|
||||||
@@ -9234,7 +9237,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:GetCoordinate()
|
local ucoord = Unit:GetCoord()
|
||||||
if not ucoord then
|
if not ucoord then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -9263,7 +9266,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:GetCoordinate()
|
local ucoord = Unit:GetCoord()
|
||||||
if not ucoord then
|
if not ucoord then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -9355,7 +9358,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:GetCoordinate()
|
local ucoord = Unit:GetCoord()
|
||||||
if not ucoord then
|
if not ucoord then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -10141,6 +10144,7 @@ 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)
|
||||||
@@ -10295,6 +10299,7 @@ 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)
|
||||||
@@ -10302,6 +10307,7 @@ 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)
|
||||||
@@ -10583,7 +10589,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:GetCoordinate() or COORDINATE:New(0,0,0)
|
local dropcoord = Troops:GetCoord() or COORDINATE:New(0,0,0)
|
||||||
local dropvec2 = dropcoord:GetVec2()
|
local dropvec2 = dropcoord:GetVec2()
|
||||||
self.PlayerTaskQueue:ForEach(
|
self.PlayerTaskQueue:ForEach(
|
||||||
function (Task)
|
function (Task)
|
||||||
@@ -10640,7 +10646,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:GetCoordinate() or COORDINATE:New(0,0,0)
|
local dropcoord = Vehicle:GetCoord() or COORDINATE:New(0,0,0)
|
||||||
local dropvec2 = dropcoord:GetVec2()
|
local dropvec2 = dropcoord:GetVec2()
|
||||||
self.PlayerTaskQueue:ForEach(
|
self.PlayerTaskQueue:ForEach(
|
||||||
function (Task)
|
function (Task)
|
||||||
@@ -10705,7 +10711,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:GetCoordinate() or COORDINATE:New(0,0,0)
|
local dropcoord = Vehicle:GetCoord() or COORDINATE:New(0,0,0)
|
||||||
local dropvec2 = dropcoord:GetVec2()
|
local dropvec2 = dropcoord:GetVec2()
|
||||||
self.PlayerTaskQueue:ForEach(
|
self.PlayerTaskQueue:ForEach(
|
||||||
function (Task)
|
function (Task)
|
||||||
|
|||||||
@@ -1102,6 +1102,31 @@ 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.
|
||||||
|
|||||||
@@ -1883,6 +1883,8 @@ function COMMANDER:RecruitAssetsForMission(Mission)
|
|||||||
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
|
||||||
|
|
||||||
@@ -2112,6 +2114,22 @@ 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.
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ 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,6 +350,18 @@ 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
|
||||||
@@ -837,6 +849,18 @@ 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
|
||||||
@@ -1026,6 +1050,10 @@ 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={}
|
||||||
self.ContactsUnknown={}
|
self.ContactsUnknown={}
|
||||||
|
|||||||
@@ -306,7 +306,6 @@ 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
|
||||||
|
|
||||||
@@ -2099,6 +2098,23 @@ 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.
|
||||||
@@ -2661,7 +2677,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 RangeMin=%s", TargetDistance, Rmax, RangeMax, tostring(InRange),tostring(RangeMin)))
|
--env.info(string.format("Range TargetDist=%.1f Rmax=%.1f RangeMax=%.1f InRange=%s", TargetDistance, Rmax, RangeMax, tostring(InRange)))
|
||||||
|
|
||||||
return InRange
|
return InRange
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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 then
|
if Task.StayInZoneTime and Task.StayInZoneTime>0 then
|
||||||
local stay = Task.StayInZoneTime
|
local stay = Task.StayInZoneTime
|
||||||
self:__TaskDone(stay,Task)
|
self:__TaskDone(stay,Task)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ _TARGETID=0
|
|||||||
|
|
||||||
--- TARGET class version.
|
--- TARGET class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
TARGET.version="0.7.1"
|
TARGET.version="0.8.0"
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- TODO list
|
-- TODO list
|
||||||
@@ -206,6 +206,9 @@ 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).
|
||||||
@@ -245,6 +248,19 @@ 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
|
||||||
@@ -643,6 +659,19 @@ 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
|
||||||
for i,_target in pairs(self.targets) do
|
for i,_target in pairs(self.targets) do
|
||||||
@@ -736,6 +765,61 @@ 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.
|
||||||
@@ -761,11 +845,6 @@ 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.
|
||||||
@@ -788,15 +867,9 @@ 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
|
||||||
@@ -889,54 +962,21 @@ 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)
|
||||||
|
|
||||||
-- Try to get target Group.
|
-- Get target from Group or Unit.
|
||||||
local target=self:GetTargetByName(EventData.IniGroupName)
|
local target=self:GetTargetByName(EventData.IniGroupName) or self:GetTargetByName(EventData.IniUnitName)
|
||||||
|
|
||||||
-- 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
|
||||||
|
|
||||||
local Ndead=target.Ndead
|
-- Increase dead/destroyed counter
|
||||||
local Ndestroyed=target.Ndestroyed
|
|
||||||
if EventData.id==EVENTS.RemoveUnit then
|
if EventData.id==EVENTS.RemoveUnit then
|
||||||
Ndead=Ndead+1
|
self:ElementDead(Name, target)
|
||||||
else
|
else
|
||||||
Ndestroyed=Ndestroyed+1
|
self:ElementDestroyed(Name, target)
|
||||||
Ndead=Ndead+1
|
|
||||||
end
|
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
|
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 :("))
|
||||||
-- 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 -- 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
@@ -0,0 +1,240 @@
|
|||||||
|
-- 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
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
--- 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
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
-- 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
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
--- 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
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
-- 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
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
-- 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
|
||||||
@@ -131,14 +131,24 @@ 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 = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
|
Auto = 265214230526, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons + Torpedos)
|
||||||
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.
|
||||||
@@ -220,7 +230,7 @@ ENUMS.WeaponType.Torpedo={
|
|||||||
}
|
}
|
||||||
ENUMS.WeaponType.Any={
|
ENUMS.WeaponType.Any={
|
||||||
-- General combinations
|
-- General combinations
|
||||||
Weapon = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
|
Weapon = 265214230526, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons + Torpedos)
|
||||||
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
|
||||||
@@ -513,7 +523,7 @@ ENUMS.ReportingName =
|
|||||||
Tornado = "Tornado",
|
Tornado = "Tornado",
|
||||||
-- Transport / Bomber / Others
|
-- Transport / Bomber / Others
|
||||||
Atlas = "A400",
|
Atlas = "A400",
|
||||||
Lancer = "B1-B",
|
Lancer = "B-1B",
|
||||||
Stratofortress = "B-52H",
|
Stratofortress = "B-52H",
|
||||||
Herc = "C-130",
|
Herc = "C-130",
|
||||||
Hercules = "C-130J-30",
|
Hercules = "C-130J-30",
|
||||||
@@ -558,7 +568,7 @@ ENUMS.ReportingName =
|
|||||||
Apache = "AH-64",
|
Apache = "AH-64",
|
||||||
Chinook = "CH-47",
|
Chinook = "CH-47",
|
||||||
Sea_Stallion = "CH-53",
|
Sea_Stallion = "CH-53",
|
||||||
Kiowa = "OH-58",
|
Kiowa = "OH58D",
|
||||||
Seahawk = "SH-60",
|
Seahawk = "SH-60",
|
||||||
Blackhawk = "UH-60",
|
Blackhawk = "UH-60",
|
||||||
Sea_King = "S-61",
|
Sea_King = "S-61",
|
||||||
@@ -1798,13 +1808,21 @@ 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'
|
||||||
@@ -1851,6 +1869,10 @@ 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}'
|
||||||
@@ -1881,6 +1903,11 @@ 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'
|
||||||
@@ -1931,6 +1958,7 @@ 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}'
|
||||||
@@ -2062,6 +2090,13 @@ 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'
|
||||||
|
|
||||||
@@ -2091,6 +2126,56 @@ 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
|
||||||
|
|||||||
@@ -753,8 +753,8 @@ UTILS.KnotsToKmph = function(knots)
|
|||||||
return knots * 1.852
|
return knots * 1.852
|
||||||
end
|
end
|
||||||
|
|
||||||
UTILS.KmphToKnots = function(kmph)
|
UTILS.KmphToKnots = function(knots)
|
||||||
return kmph / 1.852
|
return knots / 1.852
|
||||||
end
|
end
|
||||||
|
|
||||||
UTILS.KmphToMps = function( kmph )
|
UTILS.KmphToMps = function( kmph )
|
||||||
|
|||||||
@@ -1964,6 +1964,25 @@ 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
|
||||||
--self:E("***** H4 on Syria map is currently bugged!")
|
--self:E("***** H4 on Syria map is currently bugged!")
|
||||||
@@ -2614,7 +2633,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)
|
self.parkingCircle = ZONE_RADIUS:New(self.AirbaseName.." ParkingCircle", center, radius+50, true)
|
||||||
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
|
||||||
|
|||||||
@@ -76,10 +76,6 @@ 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
|
||||||
|
|||||||
Reference in New Issue
Block a user