mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-08-11 19:58:22 +00:00
Compare commits
12 Commits
master-ng
...
FF/PyBridge
| Author | SHA1 | Date | |
|---|---|---|---|
| 049aca91aa | |||
| a46bbcc2d2 | |||
| 7026a94301 | |||
| 92bc0389d3 | |||
| 7a5f6ad3c6 | |||
| 2a73d8d34a | |||
| e8f5214e0b | |||
| e4c7e809cb | |||
| aab16dd966 | |||
| 8b356f0913 | |||
| c3086b0265 | |||
| 599a2ba6fb |
@@ -92,6 +92,8 @@ DATABASE = {
|
||||
ZONES_GOAL = {},
|
||||
WAREHOUSES = {},
|
||||
FLIGHTGROUPS = {},
|
||||
COHORTS={},
|
||||
LEGIONS={},
|
||||
FLIGHTCONTROLS = {},
|
||||
OPSZONES = {},
|
||||
PATHLINES = {},
|
||||
@@ -2087,6 +2089,40 @@ function DATABASE:FindOpsGroupFromUnit(unitname)
|
||||
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.
|
||||
-- @param #DATABASE self
|
||||
-- @param OPS.FlightControl#FLIGHTCONTROL flightcontrol
|
||||
|
||||
@@ -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,6 +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/ClientWatch.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/Ops/Airboss.lua' )
|
||||
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' )
|
||||
@@ -137,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/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' )
|
||||
|
||||
@@ -290,6 +290,9 @@ function AIRWING:New(warehousename, airwingname)
|
||||
-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FLIGHTGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
-- Add legion to DB
|
||||
_DATABASE:AddLegion(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -433,6 +436,14 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance)
|
||||
table.insert(payload.capabilities, capability)
|
||||
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
|
||||
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, ", ")))
|
||||
@@ -541,6 +552,47 @@ function AIRWING:AddPayloadCapability(Payload, MissionTypes, Performance)
|
||||
return self
|
||||
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.
|
||||
-- The payload with the highest priority is preferred.
|
||||
-- @param #AIRWING self
|
||||
@@ -589,34 +641,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
local payloads=self:_FilterPlayloads(UnitType, MissionType, Payloads)
|
||||
|
||||
-- Debug.
|
||||
if self.verbose>=4 then
|
||||
|
||||
@@ -192,6 +192,8 @@
|
||||
-- @field #boolean optionInvisible Invisible is on/off.
|
||||
-- @field #boolean optionImmortal Immortal is on/off.
|
||||
--
|
||||
-- @field #AUFTRAG.Summary summary Auftrag summary.
|
||||
--
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- *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 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.
|
||||
-- @type AUFTRAG.Condition
|
||||
-- @field #function func Callback function to check for a condition. Should return a #boolean.
|
||||
@@ -676,7 +691,7 @@ AUFTRAG.Category={
|
||||
|
||||
--- AUFTRAG class version.
|
||||
-- @field #string version
|
||||
AUFTRAG.version="1.4.2"
|
||||
AUFTRAG.version="1.5.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -776,6 +791,7 @@ function AUFTRAG:New(Type)
|
||||
|
||||
self:AddTransition("*", "Cancel", AUFTRAG.Status.CANCELLED) -- Command to cancel the mission.
|
||||
|
||||
self:AddTransition("*", "Evaluated", "*")
|
||||
self:AddTransition("*", "Success", AUFTRAG.Status.SUCCESS)
|
||||
self:AddTransition("*", "Failed", AUFTRAG.Status.FAILED)
|
||||
|
||||
@@ -946,6 +962,24 @@ function AUFTRAG:New(Type)
|
||||
-- @param #string Event Event.
|
||||
-- @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".
|
||||
-- @function [parent=#AUFTRAG] Success
|
||||
@@ -1751,6 +1785,9 @@ function AUFTRAG:NewBAI(Target, Altitude)
|
||||
mission.optionROE=ENUMS.ROE.OpenFire
|
||||
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.DCStask=mission:GetDCSMissionTask()
|
||||
@@ -2304,6 +2341,9 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude)
|
||||
|
||||
local mission=AUFTRAG:New(AUFTRAG.Type.ARTY)
|
||||
|
||||
printf("FF nshots=%s", tostring(Nshots))
|
||||
printf("FF radius=%s", tostring(Radius))
|
||||
|
||||
mission:_TargetFromObject(Target)
|
||||
|
||||
mission.artyShots=Nshots or nil
|
||||
@@ -2407,7 +2447,7 @@ end
|
||||
-- @param #AUFTRAG self
|
||||
-- @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 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 #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.
|
||||
@@ -4690,6 +4730,18 @@ function AUFTRAG:Evaluate()
|
||||
failed=false
|
||||
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.
|
||||
if self.verbose > 0 then
|
||||
local text=string.format("Evaluating mission:\n")
|
||||
@@ -4709,6 +4761,9 @@ function AUFTRAG:Evaluate()
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Trigger evaluated result and pass summary.
|
||||
self:Evaluated(self.summary)
|
||||
|
||||
-- Trigger events.
|
||||
if failed then
|
||||
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.
|
||||
for groupname,data in pairs(self.groupdata) do
|
||||
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
|
||||
-- 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()))
|
||||
@@ -5120,7 +5175,6 @@ end
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
--- On after "Planned" event.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param #string From From state.
|
||||
@@ -5430,7 +5484,7 @@ function AUFTRAG:onafterSuccess(From, Event, To)
|
||||
|
||||
-- Stop mission.
|
||||
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
|
||||
|
||||
@@ -5472,7 +5526,7 @@ function AUFTRAG:onafterFailed(From, Event, To)
|
||||
|
||||
-- Stop mission.
|
||||
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
|
||||
|
||||
@@ -5585,6 +5639,7 @@ function AUFTRAG:onafterRepeat(From, Event, To)
|
||||
self.Ngroups=0
|
||||
self.Nassigned=nil
|
||||
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.
|
||||
self.DCStask=self:GetDCSMissionTask()
|
||||
@@ -5636,6 +5691,8 @@ function AUFTRAG:onafterStop(From, Event, To)
|
||||
-- No group data.
|
||||
self.groupdata={}
|
||||
|
||||
self.summary=nil
|
||||
|
||||
-- Clear pending scheduler calls.
|
||||
self.CallScheduler:Clear()
|
||||
|
||||
@@ -5980,6 +6037,7 @@ function AUFTRAG:CountOpsGroups()
|
||||
local groupdata=_groupdata --#AUFTRAG.GroupData
|
||||
if groupdata and groupdata.opsgroup and groupdata.opsgroup:IsAlive() and not groupdata.opsgroup:IsDead() then
|
||||
N=N+1
|
||||
printf("FF Count Group N=%d %s", N, tostring(groupdata.opsgroup.groupname))
|
||||
end
|
||||
end
|
||||
return N
|
||||
|
||||
@@ -150,6 +150,9 @@ function BRIGADE:New(WarehouseName, BrigadeName)
|
||||
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
|
||||
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
-- Add legion to DB
|
||||
_DATABASE:AddLegion(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -1102,6 +1102,31 @@ function COHORT:CountAssets(InStock, MissionTypes, Attributes)
|
||||
return N
|
||||
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.
|
||||
-- @param #COHORT self
|
||||
-- @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,
|
||||
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
|
||||
end
|
||||
|
||||
@@ -2112,6 +2114,22 @@ function COMMANDER:CountAssets(InStock, MissionTypes, Attributes)
|
||||
return N
|
||||
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.
|
||||
-- @param #COMMANDER self
|
||||
-- @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.Auftrag#AUFTRAG Mission The mission.
|
||||
|
||||
-- Add legion to DB
|
||||
_DATABASE:AddLegion(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ function FLOTILLA:New(TemplateGroupName, Ngroups, FlotillaName)
|
||||
-- Get initial ammo.
|
||||
self.ammo=self:_CheckAmmo()
|
||||
|
||||
-- Add cohort to DB
|
||||
_DATABASE:AddCohort(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -350,6 +350,18 @@ INTEL.RCS_NoseOnFraction = 0.15
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- 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.
|
||||
-- @param #INTEL self
|
||||
@@ -837,6 +849,18 @@ function INTEL:AddAgent(AgentGroup)
|
||||
return self
|
||||
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.
|
||||
-- Targets will be grouped in coupled clusters.
|
||||
-- @param #INTEL self
|
||||
@@ -1026,6 +1050,10 @@ function INTEL:onafterStatus(From, Event, To)
|
||||
-- FSM state.
|
||||
local fsmstate=self:GetState()
|
||||
|
||||
if self.update_detectionset then
|
||||
self:_UpdateAgents()
|
||||
end
|
||||
|
||||
-- Fresh arrays.
|
||||
self.ContactsLost={}
|
||||
self.ContactsUnknown={}
|
||||
|
||||
@@ -306,7 +306,6 @@ function LEGION:New(WarehouseName, LegionName)
|
||||
-- @param Ops.Cohort#COHORT Cohort The cohort the asset belongs to.
|
||||
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset that returned.
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -2099,6 +2098,23 @@ function LEGION:CountAssets(InStock, MissionTypes, Attributes)
|
||||
return N
|
||||
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.
|
||||
-- @param #LEGION self
|
||||
-- @param #table MissionTypes (Optional) Get only assest that can perform certain mission type(s). Default is all types.
|
||||
|
||||
@@ -4691,7 +4691,7 @@ function OPSGROUP:_UpdateTask(Task, Mission)
|
||||
self:T(self.lid..string.format("Zone %s captured ==> Task DONE!", zoneCurr:GetName()))
|
||||
|
||||
-- Task done.
|
||||
if Task.StayInZoneTime then
|
||||
if Task.StayInZoneTime and Task.StayInZoneTime>0 then
|
||||
local stay = Task.StayInZoneTime
|
||||
self:__TaskDone(stay,Task)
|
||||
else
|
||||
|
||||
@@ -72,6 +72,9 @@ function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName)
|
||||
-- Get ammo.
|
||||
self.ammo=self:_CheckAmmo()
|
||||
|
||||
-- Add cohort to DB
|
||||
_DATABASE:AddCohort(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -116,6 +116,9 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
|
||||
|
||||
-- See COHORT class
|
||||
|
||||
-- Add cohort to DB
|
||||
_DATABASE:AddCohort(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ _TARGETID=0
|
||||
|
||||
--- TARGET class version.
|
||||
-- @field #string version
|
||||
TARGET.version="0.7.1"
|
||||
TARGET.version="0.8.0"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -206,6 +206,9 @@ function TARGET:New(TargetObject)
|
||||
self:AddTransition("*", "Status", "*") -- Status update.
|
||||
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("*", "ObjectDestroyed", "*") -- A target object was destroyed.
|
||||
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.
|
||||
|
||||
|
||||
--- 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".
|
||||
-- @function [parent=#TARGET] ObjectDamaged
|
||||
-- @param #TARGET self
|
||||
@@ -643,6 +659,19 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
-- FSM state.
|
||||
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.
|
||||
local damaged=false
|
||||
for i,_target in pairs(self.targets) do
|
||||
@@ -736,6 +765,61 @@ end
|
||||
-- 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.
|
||||
-- @param #TARGET self
|
||||
-- @param #string From From state.
|
||||
@@ -761,11 +845,6 @@ function TARGET:onafterObjectDestroyed(From, Event, To, Target)
|
||||
-- Debug message.
|
||||
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
|
||||
|
||||
-- Call object dead event.
|
||||
@@ -788,15 +867,9 @@ function TARGET:onafterObjectDead(From, Event, To, Target)
|
||||
-- Set target status.
|
||||
Target.Status=TARGET.ObjectStatus.DEAD
|
||||
|
||||
-- Increase dead object counter
|
||||
Target.Ndead=Target.Ndead+1
|
||||
|
||||
-- Set target object life to 0.
|
||||
Target.Life=0
|
||||
|
||||
-- Increase dead counter.
|
||||
self.Ndead=self.Ndead+1
|
||||
|
||||
-- Check if anyone is alive?
|
||||
local dead=true
|
||||
for _,_target in pairs(self.targets) do
|
||||
@@ -889,54 +962,21 @@ function TARGET:OnEventUnitDeadOrLost(EventData)
|
||||
-- Add to the list of casualties.
|
||||
table.insert(self.casualties, Name)
|
||||
|
||||
-- Try to get target Group.
|
||||
local target=self:GetTargetByName(EventData.IniGroupName)
|
||||
|
||||
-- Try unit target.
|
||||
if not target then
|
||||
target=self:GetTargetByName(EventData.IniUnitName)
|
||||
end
|
||||
-- Get target from Group or Unit.
|
||||
local target=self:GetTargetByName(EventData.IniGroupName) or self:GetTargetByName(EventData.IniUnitName)
|
||||
|
||||
-- Check if we could find a target object.
|
||||
if target then
|
||||
|
||||
local Ndead=target.Ndead
|
||||
local Ndestroyed=target.Ndestroyed
|
||||
-- Increase dead/destroyed counter
|
||||
if EventData.id==EVENTS.RemoveUnit then
|
||||
Ndead=Ndead+1
|
||||
self:ElementDead(Name, target)
|
||||
else
|
||||
Ndestroyed=Ndestroyed+1
|
||||
Ndead=Ndead+1
|
||||
self:ElementDestroyed(Name, target)
|
||||
end
|
||||
|
||||
|
||||
-- Check if ALL objects are dead
|
||||
if Ndead==target.N0 then
|
||||
|
||||
if Ndestroyed>=target.N0 then
|
||||
|
||||
-- Debug message.
|
||||
self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name)))
|
||||
|
||||
target.Life = 0
|
||||
|
||||
-- Trigger object destroyed event. This sets the Life to zero and increases Ndestroyed
|
||||
self:ObjectDestroyed(target)
|
||||
|
||||
else
|
||||
|
||||
-- Debug message.
|
||||
self:T2(self.lid..string.format("EVENT ID=%d: target %s removed ==> dead", EventData.id, tostring(target.Name)))
|
||||
|
||||
target.Life = 0
|
||||
|
||||
-- Trigger object dead event. This sets the Life to zero and increases Ndead counter
|
||||
self:ObjectDead(target)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:E(self.lid..string.format("ERROR: Could not get target from IniGroup or IniUnit name when event Dead or UnitLost occured! Stats are not correctly updated :("))
|
||||
end -- Event belongs to this TARGET
|
||||
|
||||
end
|
||||
|
||||
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
|
||||
@@ -1964,6 +1964,25 @@ function AIRBASE:Register(AirbaseName)
|
||||
-- Category.
|
||||
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
|
||||
--if self.AirbaseName == "H4" and self.descriptors == nil then
|
||||
--self:E("***** H4 on Syria map is currently bugged!")
|
||||
@@ -2614,7 +2633,7 @@ function AIRBASE:GetMinimumBoundingCircleFromParkingSpots(mark)
|
||||
local spots = self:GetParkingSpotsVec2s()
|
||||
if #spots == 0 then return self.AirbaseZone end
|
||||
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
|
||||
self.parkingCircle:DrawZone(-1,{1,0,0},1,{0,1,0},0.2,3)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user