mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-08-09 18:27:47 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a46bbcc2d2 | |||
| 7026a94301 | |||
| 92bc0389d3 | |||
| 7a5f6ad3c6 | |||
| 2a73d8d34a | |||
| dfe4db25ae | |||
| f66c77b027 | |||
| de3dc95c92 | |||
| d9a6f5daaf | |||
| 27fa920a8f | |||
| cf65cbf8fc | |||
| 2f229c1d8c | |||
| 7e07f2b4ea | |||
| d214d115c6 | |||
| fc1059c5d6 | |||
| 6e09ea7089 | |||
| 7587119843 | |||
| a5880b89c1 | |||
| 1be2c92a5c | |||
| 4c8a17fe57 | |||
| 64dea17982 | |||
| b1f86c702d | |||
| 1a9540d0d4 | |||
| 4e3bf9969b | |||
| b31fb7ece3 | |||
| efa08c1280 | |||
| b1b8630d21 | |||
| 4ba81cb8bc | |||
| 36c0297344 | |||
| 6829d75464 | |||
| 4efd71e36a | |||
| e8f5214e0b | |||
| fed9174bcb | |||
| e4c7e809cb | |||
| aab16dd966 | |||
| 8b356f0913 | |||
| 9963c737a3 | |||
| 73d3ed119c | |||
| 23112c9954 | |||
| 23c59d906f | |||
| cc62655ffe | |||
| 401e9d9e8e | |||
| 9b1a3e4894 | |||
| 6c32aa6044 | |||
| edf70e971c | |||
| 7c5af54473 | |||
| c3086b0265 | |||
| 599a2ba6fb | |||
| 2aa9b91ce5 | |||
| fdc19a6898 | |||
| d3ebf023fb | |||
| d40c559d2d |
@@ -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
|
||||
|
||||
@@ -2043,35 +2043,31 @@ do
|
||||
-- @param #MANTIS self
|
||||
-- @param #number height
|
||||
-- @param Core.Point#COORDINATE SamCoordinate
|
||||
-- @param #table contactSnapshot Contact facts captured once for the current check.
|
||||
-- @return #table set
|
||||
function MANTIS:_PreFilterHeight(height,SamCoordinate)
|
||||
function MANTIS:_PreFilterHeight(height,SamCoordinate,contactSnapshot)
|
||||
self:T(self.lid.."_PreFilterHeight")
|
||||
local set = {}
|
||||
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
|
||||
local detectedgroups = dlink:GetContactTable()
|
||||
for _,_contact in pairs(detectedgroups) do
|
||||
local contact = _contact -- Ops.Intel#INTEL.Contact
|
||||
for _,contact in pairs(contactSnapshot) do
|
||||
local grp = contact.group -- Wrapper.Group#GROUP
|
||||
if grp:IsAlive() then
|
||||
local coord = grp:GetCoordinate()
|
||||
local dist = 0
|
||||
local include = true
|
||||
if grp:IsGround() then include = false end
|
||||
if grp:IsShip() then include = false end -- MANTIS is anti-air: surface contacts are not engageable targets
|
||||
if grp:GetCoalition() == self.coalition then include = false end
|
||||
if coord and SamCoordinate and grp:IsHelicopter() then
|
||||
dist = coord:Get2DDistance(SamCoordinate) or 0
|
||||
if dist > self.ShoradActDistance then include = false end -- we do not want long range shooting at helos
|
||||
end
|
||||
if self.debug then
|
||||
local text = "Looking at Group: "..grp:GetName() or "N/A"
|
||||
text = text .. " Include = "..tostring(include)
|
||||
MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog()
|
||||
end
|
||||
local grpalt = grp:GetHeight(true)
|
||||
if grpalt < height and grpalt > 10 and include == true then
|
||||
table.insert(set,coord)
|
||||
end
|
||||
local coord = contact.coordinate
|
||||
local dist = 0
|
||||
local include = true
|
||||
if contact.isGround then include = false end
|
||||
if contact.isShip then include = false end -- MANTIS is anti-air: surface contacts are not engageable targets
|
||||
if contact.coalition == self.coalition then include = false end
|
||||
if coord and SamCoordinate and contact.isHelicopter then
|
||||
dist = coord:Get2DDistance(SamCoordinate) or 0
|
||||
if dist > self.ShoradActDistance then include = false end -- we do not want long range shooting at helos
|
||||
end
|
||||
if self.debug then
|
||||
local text = "Looking at Group: "..grp:GetName() or "N/A"
|
||||
text = text .. " Include = "..tostring(include)
|
||||
MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.verbose):ToLog()
|
||||
end
|
||||
local grpalt = contact.height
|
||||
if grpalt < height and grpalt > 10 and include == true then
|
||||
table.insert(set,coord)
|
||||
end
|
||||
end
|
||||
return set
|
||||
@@ -2084,16 +2080,17 @@ do
|
||||
-- @param #number radius Radius to check.
|
||||
-- @param #number height Height to check.
|
||||
-- @param #boolean dlink Data from DLINK.
|
||||
-- @param #table contactSnapshot Contact facts captured once for the current check.
|
||||
-- @return #boolean True if in any zone, else false
|
||||
-- @return #number Distance Target distance in meters or zero when no object is in zone
|
||||
function MANTIS:_CheckObjectInZone(dectset, samcoordinate, radius, height, dlink)
|
||||
function MANTIS:_CheckObjectInZone(dectset, samcoordinate, radius, height, dlink, contactSnapshot)
|
||||
self:T(self.lid.."_CheckObjectInZone")
|
||||
-- check if non of the coordinate is in the given defense zone
|
||||
local rad = radius or self.checkradius
|
||||
local set = dectset
|
||||
if dlink then
|
||||
-- DEBUG
|
||||
set = self:_PreFilterHeight(height,samcoordinate)
|
||||
set = self:_PreFilterHeight(height,samcoordinate,contactSnapshot)
|
||||
end
|
||||
--self.friendlyset -- Core.Set#SET_GROUP
|
||||
if self.checkforfriendlies == true and self.friendlyset == nil then
|
||||
@@ -2732,7 +2729,8 @@ do
|
||||
group:OptionEngageRange(engagerange) --engagement will be 95% of firing range
|
||||
if (group:IsGround() or group:IsShip()) and group:IsAlive() then
|
||||
local grpname = group:GetName()
|
||||
local grpcoord = group:GetCoordinate()
|
||||
local grpcoord = group:GetCoord()
|
||||
if grpcoord then grpcoord.Heading = group:GetHeading() or 0 end
|
||||
if group:IsShip() and self.NavalPerUnit
|
||||
and self:_BuildNavalUnitEntries(group, grpname, SAM_Tbl, SAM_Tbl_lg, SAM_Tbl_md, SAM_Tbl_sh, SAM_Tbl_pt, SEAD_Grps) then
|
||||
self:T(grpname.." handled as per-unit naval group")
|
||||
@@ -2944,10 +2942,11 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
|
||||
-- @param #table detset Table of COORDINATES
|
||||
-- @param #boolean dlink Using DLINK
|
||||
-- @param #number limit of SAM sites to go active on a contact
|
||||
-- @param #table contactSnapshot Contact facts captured once for the current check.
|
||||
-- @return #number instatusred
|
||||
-- @return #number instatusgreen
|
||||
-- @return #number activeshorads
|
||||
function MANTIS:_CheckLoop(samset,detset,dlink,limit)
|
||||
function MANTIS:_CheckLoop(samset,detset,dlink,limit,contactSnapshot)
|
||||
self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates")
|
||||
local switchedon = 0
|
||||
local instatusred = 0
|
||||
@@ -2976,7 +2975,7 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
|
||||
local samalive = false
|
||||
if navalparent then samalive = (samunit ~= nil) and samunit:IsAlive() or false
|
||||
elseif samgroup then samalive = samgroup:IsAlive() or false end
|
||||
local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink)
|
||||
local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink, contactSnapshot)
|
||||
-- Naval surface wake-up: quiet ships activate when enemy surface combatants
|
||||
-- close within the trigger radius. Jamming has precedence and holds them down.
|
||||
if (not IsInZone) and self.NavalSurfaceWakeup and self._navalSAMs and self._navalSAMs[name]
|
||||
@@ -3095,22 +3094,41 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
|
||||
self:_RefreshSAMTable()
|
||||
end
|
||||
self.checkcounter = self.checkcounter + 1
|
||||
local contactSnapshot = nil
|
||||
if dlink then
|
||||
contactSnapshot = {}
|
||||
for _,contact in pairs(detection:GetContactTable()) do
|
||||
local grp = contact.group -- Wrapper.Group#GROUP
|
||||
if grp:IsAlive() then
|
||||
local coord = grp:GetCoord()
|
||||
contactSnapshot[#contactSnapshot + 1] = {
|
||||
group = grp,
|
||||
coordinate = coord,
|
||||
height = grp:GetHeight(true),
|
||||
coalition = grp:GetCoalition(),
|
||||
isGround = grp:IsGround(),
|
||||
isShip = grp:IsShip(),
|
||||
isHelicopter = grp:IsHelicopter(),
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
local instatusred = 0
|
||||
local instatusgreen = 0
|
||||
local activeshorads = 0
|
||||
-- switch SAMs on/off if (n)one of the detected groups is inside their reach
|
||||
if self.automode then
|
||||
local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
local instatusredl, instatusgreenl, activeshoradsl = self:_CheckLoop(samset,detset,dlink,self.maxlongrange)
|
||||
local instatusredl, instatusgreenl, activeshoradsl = self:_CheckLoop(samset,detset,dlink,self.maxlongrange,contactSnapshot)
|
||||
local samset = self.SAM_Table_Medium -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
local instatusredm, instatusgreenm, activeshoradsm = self:_CheckLoop(samset,detset,dlink,self.maxmidrange)
|
||||
local instatusredm, instatusgreenm, activeshoradsm = self:_CheckLoop(samset,detset,dlink,self.maxmidrange,contactSnapshot)
|
||||
local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange)
|
||||
local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange,contactSnapshot)
|
||||
local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange)
|
||||
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange,contactSnapshot)
|
||||
else
|
||||
local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height
|
||||
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic)
|
||||
local instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic,contactSnapshot)
|
||||
end
|
||||
|
||||
local function GetReport()
|
||||
@@ -4090,8 +4108,8 @@ function MANTIS:SeadAllowSuppression(targetGroup, targetName, attackerGroup, wea
|
||||
end
|
||||
|
||||
--- [Internal] Override: _CheckLoop with jammer suppression.
|
||||
function MANTIS:_CheckLoop(samset, detset, dlink, limit)
|
||||
local r, g, s = self:_CheckLoopOriginal(samset, detset, dlink, limit)
|
||||
function MANTIS:_CheckLoop(samset, detset, dlink, limit, contactSnapshot)
|
||||
local r, g, s = self:_CheckLoopOriginal(samset, detset, dlink, limit, contactSnapshot)
|
||||
if self._jammerEnabled and self._jammedSAMs then
|
||||
for _, _data in pairs(samset) do
|
||||
local name = _data[1]
|
||||
@@ -4355,4 +4373,4 @@ end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- MANTIS end
|
||||
-----------------------------------------------------------------------
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -541,6 +544,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 +633,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
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
-- **Supported Aircraft:**
|
||||
--
|
||||
-- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI)
|
||||
-- * [F-14A/B/A Early Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
||||
-- * [F-14A/B/B(U)/A Early Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
||||
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
|
||||
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI)
|
||||
-- * [T-45C Goshawk](https://forum.dcs.world/topic/203816-vnao-t-45-goshawk/) (VNAO mod) (Player & AI)
|
||||
@@ -1276,6 +1276,7 @@ AIRBOSS = {
|
||||
-- @field #string A4EC A-4E Community mod.
|
||||
-- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics.
|
||||
-- @field #string F14A F-14A by Heatblur.
|
||||
-- @field #string F14BU F-14BU by Heatblur.
|
||||
-- @field #string F14A_Early F-14A-135-GR-Early by Heatblur.
|
||||
-- @field #string F14B F-14B by Heatblur.
|
||||
-- @field #string F14A_AI F-14A Tomcat (AI).
|
||||
@@ -1297,6 +1298,7 @@ AIRBOSS.AircraftCarrier={
|
||||
F14A="F-14A-135-GR",
|
||||
F14A_Early="F-14A-135-GR-Early",
|
||||
F14B="F-14B",
|
||||
F14BU="F-14BU",
|
||||
F14A_AI="F-14A",
|
||||
FA18C="F/A-18C",
|
||||
T45C="T-45",
|
||||
@@ -5508,7 +5510,7 @@ function AIRBOSS:_GetAircraftAoA( playerData )
|
||||
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
|
||||
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
|
||||
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early
|
||||
local tomcat = self:_IsTomcat( playerData.actype )
|
||||
local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW
|
||||
|
||||
-- Table with AoA values.
|
||||
@@ -5571,6 +5573,18 @@ function AIRBOSS:_GetAircraftAoA( playerData )
|
||||
aoa.OnSpeedMin = 9.5
|
||||
aoa.Fast = 8.0
|
||||
aoa.FAST = 7.5
|
||||
else
|
||||
-- Unknown carrier-capable type: _IsCarrierAircraft() is enum-driven, so a type can
|
||||
-- reach here with no AoA branch. Returning an empty table nil-propagates into
|
||||
-- _GetAircraftParameters() and the AoA waveoff check. Fall back to Hornet values.
|
||||
self:E( self.lid .. string.format( "ERROR: No AoA parameters defined for aircraft type %s! Falling back to F/A-18C values.", tostring( playerData.actype ) ) )
|
||||
aoa.SLOW = 9.8
|
||||
aoa.Slow = 9.3
|
||||
aoa.OnSpeedMax = 8.8
|
||||
aoa.OnSpeed = 8.1
|
||||
aoa.OnSpeedMin = 7.4
|
||||
aoa.Fast = 6.9
|
||||
aoa.FAST = 6.3
|
||||
end
|
||||
|
||||
return aoa
|
||||
@@ -5587,7 +5601,7 @@ function AIRBOSS:_AoAUnit2Deg( playerData, aoaunits )
|
||||
local degrees = aoaunits
|
||||
|
||||
-- Check aircraft type of player.
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
||||
if self:_IsTomcat( playerData.actype ) then
|
||||
|
||||
-------------
|
||||
-- F-14A/B --
|
||||
@@ -5630,7 +5644,7 @@ function AIRBOSS:_AoADeg2Units( playerData, degrees )
|
||||
local aoaunits = degrees
|
||||
|
||||
-- Check aircraft type of player.
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
||||
if self:_IsTomcat( playerData.actype ) then
|
||||
|
||||
-------------
|
||||
-- F-14A/B --
|
||||
@@ -5680,7 +5694,7 @@ function AIRBOSS:_GetAircraftParameters( playerData, step )
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
local tomcat = self:_IsTomcat( playerData.actype )
|
||||
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
|
||||
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
|
||||
local corsair = playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR or playerData.actype == AIRBOSS.AircraftCarrier.CORSAIR_CW
|
||||
@@ -6613,9 +6627,7 @@ function AIRBOSS:_RefuelAI( flight )
|
||||
local refuelac=false
|
||||
local actype=flight.group:GetTypeName()
|
||||
if actype==AIRBOSS.AircraftCarrier.AV8B or
|
||||
actype==AIRBOSS.AircraftCarrier.F14A or
|
||||
actype==AIRBOSS.AircraftCarrier.F14B or
|
||||
actype==AIRBOSS.AircraftCarrier.F14A_AI or
|
||||
self:_IsTomcat( actype, true ) or
|
||||
actype==AIRBOSS.AircraftCarrier.HORNET or
|
||||
actype==AIRBOSS.AircraftCarrier.RHINOE or
|
||||
actype==AIRBOSS.AircraftCarrier.RHINOF or
|
||||
@@ -6725,7 +6737,7 @@ function AIRBOSS:_LandAI( flight )
|
||||
Speed = UTILS.KnotsToKmph( 200 )
|
||||
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D or flight.actype == AIRBOSS.AircraftCarrier.C2A then
|
||||
Speed = UTILS.KnotsToKmph( 150 )
|
||||
elseif flight.actype == AIRBOSS.AircraftCarrier.F14A_AI or flight.actype == AIRBOSS.AircraftCarrier.F14A or flight.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
elseif self:_IsTomcat( flight.actype, true ) then
|
||||
Speed = UTILS.KnotsToKmph( 175 )
|
||||
elseif flight.actype == AIRBOSS.AircraftCarrier.S3B or flight.actype == AIRBOSS.AircraftCarrier.S3BTANKER then
|
||||
Speed = UTILS.KnotsToKmph( 140 )
|
||||
@@ -8200,7 +8212,7 @@ function AIRBOSS:_CheckPlayerStatus()
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
local tomcat = self:_IsTomcat( playerData.actype )
|
||||
|
||||
-- VNAO Edit - Added wrapped up call to LSO grading Hornet
|
||||
if playerData.step==AIRBOSS.PatternStep.WAKE and hornet then-- VNAO Edit - Added
|
||||
@@ -9455,7 +9467,7 @@ function AIRBOSS:_Initial( playerData )
|
||||
|
||||
-- Hook down for students.
|
||||
if playerData.difficulty == AIRBOSS.Difficulty.EASY and playerData.actype ~= AIRBOSS.AircraftCarrier.AV8B then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
if self:_IsTomcat( playerData.actype ) then
|
||||
hint = hint .. " - Hook down, SAS on, Wing Sweep 68°!"
|
||||
else
|
||||
hint = hint .. " - Hook down!"
|
||||
@@ -9620,8 +9632,7 @@ function AIRBOSS:_DirtyUp( playerData )
|
||||
|
||||
-- Radio call "Say/Fly needles". Delayed by 10/15 seconds.
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.F14A
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
or self:_IsTomcat( playerData.actype )
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
@@ -10815,7 +10826,7 @@ function AIRBOSS:_Trapped( playerData )
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
dcorr = 100
|
||||
elseif playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
elseif self:_IsTomcat( playerData.actype ) then
|
||||
-- TODO: Check Tomcat.
|
||||
dcorr = 100
|
||||
elseif playerData.actype == AIRBOSS.AircraftCarrier.A4EC then
|
||||
@@ -11628,7 +11639,7 @@ function AIRBOSS:_AttitudeMonitor( playerData )
|
||||
|
||||
local unitClient = Unit.getByName(unit:GetName()) -- VNAO Edit - Added
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET -- VNAO Edit - Added
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B or playerData.actype == AIRBOSS.AircraftCarrier.F14A_Early -- VNAO Edit - Added
|
||||
local tomcat = self:_IsTomcat( playerData.actype ) -- VNAO Edit - Added
|
||||
|
||||
if hornet then -- VNAO Edit - Added
|
||||
local nozzlePosL = 0 -- VNAO Edit - Added
|
||||
@@ -11861,7 +11872,7 @@ function AIRBOSS:_NozzleArgumentLeft( unit ) -- VNAO Edit - Added
|
||||
else -- VNAO Edit - Added
|
||||
nozzlePosL = 0 -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
|
||||
elseif self:_IsTomcat( typeName ) then -- VNAO Edit - Added
|
||||
nozzlePosL = unitClient:getDrawArgumentValue(434) -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
|
||||
@@ -11886,7 +11897,7 @@ function AIRBOSS:_NozzleArgumentRight( unit ) -- VNAO Edit - Added
|
||||
else -- VNAO Edit - Added
|
||||
nozzlePosR = 0 -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
elseif typeName == "F-14A-135-GR" or typeName == "F-14B" or typeName == "F-14A-135-GR-Early" then -- VNAO Edit - Added
|
||||
elseif self:_IsTomcat( typeName ) then -- VNAO Edit - Added
|
||||
nozzlePosR = unitClient:getDrawArgumentValue(433) -- VNAO Edit - Added
|
||||
end -- VNAO Edit - Added
|
||||
return nozzlePosR -- VNAO Edit - Added
|
||||
@@ -13149,7 +13160,7 @@ function AIRBOSS:_Flightdata2Text( playerData, groovestep )
|
||||
|
||||
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET-- VNAO Edit - Added
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B-- VNAO Edit - Added
|
||||
local tomcat = self:_IsTomcat( playerData.actype )-- VNAO Edit - Added
|
||||
|
||||
if hornet then-- VNAO Edit - Added
|
||||
if Lnoz > 0.6 and Rnoz > 0.6 then -- VNAO Edit - Added check them both, it's possilbe there could be a single engine landing and one is in idle perhaps?
|
||||
@@ -13665,7 +13676,7 @@ function AIRBOSS:_StepHint( playerData, step )
|
||||
|
||||
-- Late break.
|
||||
if step == AIRBOSS.PatternStep.LATEBREAK then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
if self:_IsTomcat( playerData.actype ) then
|
||||
hint = hint .. "\nWing Sweep 20°, Gear DOWN < 280 KIAS."
|
||||
end
|
||||
end
|
||||
@@ -13674,7 +13685,7 @@ function AIRBOSS:_StepHint( playerData, step )
|
||||
if step == AIRBOSS.PatternStep.ABEAM then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.AV8B then
|
||||
hint = hint .. "\nNozzles 50°-60°. Antiskid OFF. Lights OFF."
|
||||
elseif playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
elseif self:_IsTomcat( playerData.actype ) then
|
||||
hint = hint .. "\nSlats/Flaps EXTENDED < 225 KIAS. DLC SELECTED. Auto Throttle IF DESIRED."
|
||||
else
|
||||
hint = hint .. "\nDirty up! Gear DOWN, flaps DOWN. Check hook down."
|
||||
@@ -15109,7 +15120,7 @@ function AIRBOSS:_GetACNickname( actype )
|
||||
nickname = "Hawkeye"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.C2A then
|
||||
nickname = "Greyhound"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B or actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
||||
elseif self:_IsTomcat( actype, true ) then
|
||||
nickname = "Tomcat"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
nickname = "Hornet"
|
||||
@@ -15246,6 +15257,29 @@ function AIRBOSS:_GetGoodBadScore( playerData )
|
||||
return lowscore, badscore
|
||||
end
|
||||
|
||||
--- Check whether an aircraft type name is any F-14 Tomcat variant.
|
||||
-- Central predicate so that adding a new Tomcat variant only requires editing
|
||||
-- this function and the AIRBOSS.AircraftCarrier enum.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #string actype Aircraft type name, e.g. from `unit:GetTypeName()` or `playerData.actype`.
|
||||
-- @param #boolean IncludeAI If true, also match the AI-only F-14A (AIRBOSS.AircraftCarrier.F14A_AI).
|
||||
-- @return #boolean If true, actype is an F-14 variant.
|
||||
function AIRBOSS:_IsTomcat( actype, IncludeAI )
|
||||
|
||||
if actype == AIRBOSS.AircraftCarrier.F14A
|
||||
or actype == AIRBOSS.AircraftCarrier.F14B
|
||||
or actype == AIRBOSS.AircraftCarrier.F14BU
|
||||
or actype == AIRBOSS.AircraftCarrier.F14A_Early then
|
||||
return true
|
||||
end
|
||||
|
||||
if IncludeAI and actype == AIRBOSS.AircraftCarrier.F14A_AI then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Check if aircraft is capable of landing on this aircraft carrier.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param Wrapper.Unit#UNIT unit Aircraft unit. (Will also work with groups as given parameter.)
|
||||
@@ -19394,4 +19428,4 @@ function AIRBOSS:onafterLSOGrade(From, Event, To, playerData, grade)
|
||||
self.funkmanSocket:SendTable(result)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -191,6 +191,8 @@
|
||||
-- @field #boolean optionEmission Emission is on or off.
|
||||
-- @field #boolean optionInvisible Invisible is on/off.
|
||||
-- @field #boolean optionImmortal Immortal is on/off.
|
||||
--
|
||||
-- @field #AUFTRAG.Summary summary Auftrag summary.
|
||||
--
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
@@ -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
|
||||
@@ -1750,6 +1784,9 @@ function AUFTRAG:NewBAI(Target, Altitude)
|
||||
mission.missionFraction=0.75
|
||||
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}
|
||||
|
||||
@@ -2303,6 +2340,9 @@ end
|
||||
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)
|
||||
|
||||
@@ -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.
|
||||
@@ -4689,6 +4729,18 @@ function AUFTRAG:Evaluate()
|
||||
elseif successCondition then
|
||||
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
|
||||
@@ -4708,6 +4760,9 @@ function AUFTRAG:Evaluate()
|
||||
text=text..string.format("=========================")
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
-- Trigger evaluated result and pass summary.
|
||||
self:Evaluated(self.summary)
|
||||
|
||||
-- Trigger events.
|
||||
if failed then
|
||||
@@ -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()
|
||||
@@ -5635,6 +5690,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
|
||||
@@ -6466,7 +6524,13 @@ function AUFTRAG:GetDCSMissionTask(MissionGroup)
|
||||
-- ESCORT Mission --
|
||||
--------------------
|
||||
|
||||
local DCStask=CONTROLLABLE.TaskEscort(nil, self.engageTarget:GetObject(), self.escortVec3, nil, self.engageMaxDistance, self.engageTargetTypes)
|
||||
local FollowGroup=self.engageTarget:GetObject()
|
||||
local DCStask=nil
|
||||
if next(self.engageTargetTypes)==nil then
|
||||
DCStask=FollowGroup:TaskFollow(FollowGroup, self.escortVec3)
|
||||
else
|
||||
DCStask=CONTROLLABLE.TaskEscort(nil, FollowGroup, self.escortVec3, nil, self.engageMaxDistance, self.engageTargetTypes)
|
||||
end
|
||||
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@ do
|
||||
-- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted.
|
||||
-- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true`
|
||||
-- my_ctld.movecratesbeforebuild = true -- crates must be moved once before they can be build. Set to false for direct builds.
|
||||
-- my_ctld.useaircraftheadingforbuilds = false -- Matches the heading of the spawned groups from a crate build to the aircraft heading when starting the build. Default is false for template heading.
|
||||
-- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects.
|
||||
-- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you.
|
||||
-- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz.
|
||||
@@ -2037,7 +2038,7 @@ function CTLD:_C130DcAutoRegisterEntry(SetId, Cargo)
|
||||
|
||||
local pos = Cargo:GetPositionable()
|
||||
local pname = pos and pos.GetName and pos:GetName() or nil
|
||||
local pcoord = pos and pos.GetCoordinate and pos:GetCoordinate() or nil
|
||||
local pcoord = pos and pos.GetCoord and pos:GetCoord() or nil
|
||||
local entryId = string.format("%s#%d", SetId, #setData.entries + 1)
|
||||
local entry = {
|
||||
id = entryId,
|
||||
@@ -2748,16 +2749,14 @@ function CTLD:_EventHandler(EventData)
|
||||
loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo
|
||||
loaded.Cratesloaded = loaded.Cratesloaded - 1
|
||||
if loaded.Cratesloaded < 0 then loaded.Cratesloaded = 0 end
|
||||
-- TODO zap cargo from list
|
||||
local Loaded = {}
|
||||
for _,_item in pairs (loaded.Cargo or {}) do
|
||||
self:T(self.lid.."UNLOAD checking: ".._item:GetName())
|
||||
self:T(self.lid.."UNLOAD state: ".. tostring(_item:WasDropped()))
|
||||
if _item and _item:GetType() == CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName ~= _item:GetName() and not _item:WasDropped() then
|
||||
local unloadedItem = _item and _item:GetType() == CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName == _item:GetName()
|
||||
if not unloadedItem then
|
||||
table.insert(Loaded,_item)
|
||||
else
|
||||
table.insert(Loaded,_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
loaded.Cargo = nil
|
||||
loaded.Cargo = Loaded
|
||||
@@ -3125,14 +3124,14 @@ end
|
||||
function CTLD:_FindRepairNearby(Group, Unit, Repairtype)
|
||||
self:T(self.lid .. " _FindRepairNearby")
|
||||
--self:T({Group:GetName(),Unit:GetName(),Repairtype})
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
|
||||
-- find nearest group of deployed groups
|
||||
local nearestGroup = nil
|
||||
local nearestGroupIndex = -1
|
||||
local nearestDistance = 10000
|
||||
for k,v in pairs(self.DroppedTroops) do
|
||||
local distance = self:_GetDistance(v:GetCoordinate(),unitcoord)
|
||||
local distance = self:_GetDistance(v:GetCoord(),unitcoord)
|
||||
local unit = v:GetUnit(1) -- Wrapper.Unit#UNIT
|
||||
local desc = unit:GetDesc() or nil
|
||||
if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then
|
||||
@@ -3277,7 +3276,7 @@ end
|
||||
local capabilities = self:_GetUnitCapabilities(Unit)
|
||||
local cantroops = capabilities.troops -- #boolean
|
||||
local trooplimit = capabilities.trooplimit -- #number
|
||||
local unitcoord = unit:GetCoordinate()
|
||||
local unitcoord = unit:GetCoord()
|
||||
|
||||
-- find nearest group of deployed troops
|
||||
local nearestGroup = nil
|
||||
@@ -3288,7 +3287,7 @@ end
|
||||
local distancekeys = {}
|
||||
local extractdistance = self.CrateDistance * self.ExtractFactor
|
||||
for k,v in pairs(self.DroppedTroops) do
|
||||
local distance = self:_GetDistance(v:GetCoordinate(),unitcoord)
|
||||
local distance = self:_GetDistance(v:GetCoord(),unitcoord)
|
||||
local TNow = timer.getTime()
|
||||
local vtime = v.ExtractTime or TNow-310
|
||||
if distance <= extractdistance and distance ~= -1 and (TNow - vtime > 300) then
|
||||
@@ -3376,7 +3375,7 @@ end
|
||||
self:_UpdateUnitCargoMass(Unit)
|
||||
local groupname = nearestGroup:GetName()
|
||||
self:__TroopsExtracted(running,Group, Unit, nearestGroup, groupname)
|
||||
local coord = Unit:GetCoordinate() or Group:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local coord = Unit:GetCoord() or Group:GetCoord() -- Core.Point#COORDINATE
|
||||
local Point
|
||||
if coord then
|
||||
local heading = unit:GetHeading() or 0
|
||||
@@ -3747,16 +3746,16 @@ function CTLD:_C130GetUnits(Group, Unit, Name)
|
||||
return self
|
||||
end
|
||||
|
||||
local coord = Unit:GetCoordinate() or Group:GetCoordinate()
|
||||
local coord = Unit:GetCoord() or Group:GetCoord()
|
||||
local capabilities = self:_GetUnitCapabilities(Unit)
|
||||
local innerDist = (capabilities.length and capabilities.length/2) or 15
|
||||
local maxUnitsNearby = self.maxUnitsNearby or 3
|
||||
local searchRadius = self.UnitDistance or 90
|
||||
local checkZone = ZONE_RADIUS:New("CTLD_C130UnitsZone",coord:GetVec2(),searchRadius,false)
|
||||
local nearGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({checkZone}):FilterOnce()
|
||||
local nearGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({checkZone}):FilterOnce()
|
||||
local nearbyCount = 0
|
||||
for _,gr in pairs(nearGroups.Set) do
|
||||
local gc = gr:GetCoordinate()
|
||||
local gc = gr:GetCoord()
|
||||
if gc then
|
||||
local dist = coord:Get2DDistance(gc)
|
||||
if dist > innerDist then
|
||||
@@ -3806,6 +3805,7 @@ function CTLD:_C130GetUnits(Group, Unit, Name)
|
||||
if canmove then
|
||||
SPAWN:NewWithAlias(_template,alias)
|
||||
:InitRandomizeUnits(true,10,2)
|
||||
:InitCoalition(self.coalition)
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits, 70)
|
||||
:InitDelayOff()
|
||||
:OnSpawnGroup(function(grp,TimeStamp)
|
||||
@@ -3819,6 +3819,7 @@ function CTLD:_C130GetUnits(Group, Unit, Name)
|
||||
SPAWN:NewWithAlias(_template,alias)
|
||||
:InitRandomizeUnits(true,10,2)
|
||||
:InitDelayOff()
|
||||
:InitCoalition(self.coalition)
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits, 70)
|
||||
:OnSpawnGroup(function(grp,TimeStamp)
|
||||
grp.spawntime = TimeStamp or timer.getTime()
|
||||
@@ -3925,7 +3926,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
-- Check cargo location if available
|
||||
local location = Cargo:GetLocation()
|
||||
if location then
|
||||
local unitcoord = Unit:GetCoordinate() or Group:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoord() or Group:GetCoord()
|
||||
if unitcoord then
|
||||
if not location:IsCoordinateInZone(unitcoord) then
|
||||
-- no we're not at the right spot
|
||||
@@ -3972,7 +3973,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
end
|
||||
|
||||
-- get position and heading of heli
|
||||
local position = Unit:GetCoordinate()
|
||||
local position = Unit:GetCoord()
|
||||
local heading = Unit:GetHeading() + 1
|
||||
local height = Unit:GetHeight()
|
||||
local droppedcargo = {}
|
||||
@@ -4136,8 +4137,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
if type(ship) == "string" then
|
||||
self:T("Spawning on ship "..ship)
|
||||
local Ship = UNIT:FindByName(ship)
|
||||
local shipcoord = Ship:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local shipcoord = Ship:GetCoord()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
local dist = shipcoord:Get2DDistance(unitcoord)
|
||||
dist = dist - (20 + math.random(1, 10))
|
||||
local halfwidth = (width or 20) / 2
|
||||
@@ -4389,17 +4390,17 @@ end
|
||||
function CTLD:_C130RemoveUnitsNearby(_group,_unit)
|
||||
self:T(self.lid .. " _C130RemoveUnitsNearby")
|
||||
if not _group or not _unit then return self end
|
||||
local location = _group:GetCoordinate()
|
||||
local location = _group:GetCoord()
|
||||
if not location then return self end
|
||||
local capabilities = self:_GetUnitCapabilities(_unit)
|
||||
local innerDist = (capabilities.length and capabilities.length/2) or 15
|
||||
local finddist = self.PackDistance or (self.CrateDistance or 35)
|
||||
local zone = ZONE_RADIUS:New("CTLD_C130RemoveZone",location:GetVec2(),finddist,false)
|
||||
local nearestGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce()
|
||||
local nearestGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({zone}):FilterOnce()
|
||||
local removedAny = false
|
||||
local removedTable = {}
|
||||
for _, gr in pairs(nearestGroups.Set) do
|
||||
local gc = gr:GetCoordinate()
|
||||
local gc = gr:GetCoord()
|
||||
if gc then
|
||||
local dist = location:Get2DDistance(gc)
|
||||
if dist > innerDist then
|
||||
@@ -4533,7 +4534,7 @@ end
|
||||
function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype, ignoreHercInner)
|
||||
self:T(self.lid .. " _FindCratesNearby")
|
||||
local finddist = _dist
|
||||
local location = _group:GetCoordinate()
|
||||
local location = _group:GetCoord()
|
||||
local existingcrates = self.Spawned_Cargo -- #table
|
||||
-- cycle
|
||||
local index = 0
|
||||
@@ -5159,7 +5160,6 @@ function CTLD:_UnloadTroops(Group, Unit)
|
||||
-- unload troops
|
||||
local name = cargo:GetName() or "none"
|
||||
local temptable = cargo:GetTemplates() or {}
|
||||
local position = Group:GetCoordinate()
|
||||
local zoneradius = self.troopdropzoneradius or 100 -- drop zone radius
|
||||
local factor = 1
|
||||
if IsHerc then
|
||||
@@ -5196,6 +5196,7 @@ function CTLD:_UnloadTroops(Group, Unit)
|
||||
local Positions = self:_GetUnitPositions(randomcoord,rad,heading,_template)
|
||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
||||
:InitDelayOff()
|
||||
:InitCoalition(self.coalition)
|
||||
:InitSetUnitAbsolutePositions(Positions)
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
|
||||
@@ -5506,8 +5507,8 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop,NotifyGroup)
|
||||
local required = Crate:GetCratesNeeded()
|
||||
local template = Crate:GetTemplates()
|
||||
local ctype = Crate:GetType()
|
||||
local ccoord = Crate:GetPositionable():GetCoordinate() -- Core.Point#COORDINATE
|
||||
local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoordinate())or 0
|
||||
local ccoord = Crate:GetPositionable():GetCoord() -- Core.Point#COORDINATE
|
||||
local distToUnit=Unit and ccoord:Get2DDistance(Unit:GetCoord())or 0
|
||||
local isHercDrop=Crate:WasDropped(true)
|
||||
if not isHercDrop and distToUnit>baseDist then
|
||||
elseif self:IsC130J(Unit) and distToUnit<15 then
|
||||
@@ -5526,6 +5527,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop,NotifyGroup)
|
||||
object.Found = 1
|
||||
object.Template = template
|
||||
object.CanBuild = false
|
||||
object.Heading = Unit:GetHeading()
|
||||
object.Type = ctype -- #CTLD_CARGO.Enum
|
||||
object.Coord = ccoord:GetVec2()
|
||||
buildables[name] = object
|
||||
@@ -5611,7 +5613,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop,NotifyGroup)
|
||||
local sep = self.buildPairSeparation or 25
|
||||
local hdg = (Unit:GetHeading()+180)%360
|
||||
local lat = (hdg+90)%360
|
||||
local base = Unit:GetCoordinate():Translate(20,hdg)
|
||||
local base = Unit:GetCoord():Translate(20,hdg)
|
||||
|
||||
if full == 1 then
|
||||
local cratesNow, numberNow = self:_FindCratesNearby(Group,Unit, finddist,true,true, not Engineering)
|
||||
@@ -5645,7 +5647,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering,MultiDrop,NotifyGroup)
|
||||
self:_RefreshPackMenus(Group,Unit)
|
||||
local off = start + (n-1)*sep
|
||||
local coord = base:Translate(off,lat):GetVec2()
|
||||
local b = { Name=build.Name, Required=build.Required, Template=build.Template, CanBuild=true, Type=build.Type, Coord=coord }
|
||||
local b = { Name=build.Name, Required=build.Required, Template=build.Template, CanBuild=true, Type=build.Type, Coord=coord, Heading=build.Heading }
|
||||
if self.buildtime and self.buildtime > 0 then
|
||||
local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,b,false,Group:GetCoordinate(),MultiDrop)
|
||||
buildtimer:Start(self.buildtime)
|
||||
@@ -5683,18 +5685,18 @@ end
|
||||
-- @return #number Number
|
||||
function CTLD:_FindPackableGroupsNearby(Group, Unit)
|
||||
self:T(self.lid .. " _FindPackableGroupsNearby")
|
||||
local location = Group:GetCoordinate()
|
||||
local location = Group:GetCoord()
|
||||
if not location then return {}, 0 end
|
||||
local capabilities = self:_GetUnitCapabilities(Unit)
|
||||
local innerDist = (capabilities.length and capabilities.length/2) or 15
|
||||
local finddist = self.PackDistance or (self.CrateDistance or 35)
|
||||
local zone = ZONE_RADIUS:New("CTLD_PackableZone", location:GetVec2(), finddist, false)
|
||||
local nearestGroups = SET_GROUP:New():FilterCoalitions("blue"):FilterZones({zone}):FilterOnce()
|
||||
local nearestGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterZones({zone}):FilterOnce()
|
||||
local packable = {}
|
||||
|
||||
for _, gr in pairs(nearestGroups.Set) do
|
||||
if gr and gr:GetName() ~= Group:GetName() then
|
||||
local gc = gr:GetCoordinate()
|
||||
local gc = gr:GetCoord()
|
||||
if gc then
|
||||
local dist = location:Get2DDistance(gc)
|
||||
if dist > innerDist and dist <= finddist then
|
||||
@@ -5827,12 +5829,12 @@ function CTLD:_LoadPackedCratesByIds(Group, Unit, crateIds, cargoName)
|
||||
|
||||
local matchingCrates = {}
|
||||
local finddist = self.CrateDistance or 35
|
||||
local location = Group:GetCoordinate()
|
||||
local location = Group:GetCoord()
|
||||
for _, crateObj in pairs(self.Spawned_Cargo or {}) do
|
||||
if crateObj and idLookup[crateObj:GetID()] then
|
||||
local pos = crateObj:GetPositionable()
|
||||
if pos and pos:IsAlive() then
|
||||
local dist = location:Get2DDistance(pos:GetCoordinate())
|
||||
local dist = location:Get2DDistance(pos:GetCoord())
|
||||
if dist <= finddist then
|
||||
matchingCrates[#matchingCrates + 1] = crateObj
|
||||
end
|
||||
@@ -5942,12 +5944,12 @@ function CTLD:_RemovePackedCratesByIds(Group, Unit, crateIds)
|
||||
|
||||
local crates = {}
|
||||
local finddist = self.CrateDistance or 35
|
||||
local location = Group:GetCoordinate()
|
||||
local location = Group:GetCoord()
|
||||
for _, entry in pairs(self.Spawned_Cargo or {}) do
|
||||
if entry and idLookup[entry:GetID()] then
|
||||
local pos = entry:GetPositionable()
|
||||
if pos and pos:IsAlive() then
|
||||
local dist = location:Get2DDistance(pos:GetCoordinate())
|
||||
local dist = location:Get2DDistance(pos:GetCoord())
|
||||
if dist <= finddist then
|
||||
crates[#crates + 1] = entry
|
||||
end
|
||||
@@ -6197,24 +6199,24 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation,Mult
|
||||
for _,_template in pairs(temptable) do
|
||||
self.TroopCounter = self.TroopCounter + 1
|
||||
local alias = string.format("%s-%d", _template, math.random(1,100000))
|
||||
local spawn = SPAWN:NewWithAlias(_template,alias)
|
||||
:InitDelayOff()
|
||||
:InitCoalition(self.coalition)
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
|
||||
if canmove then
|
||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
||||
--:InitRandomizeUnits(true,20,2)
|
||||
:InitDelayOff()
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
|
||||
:SpawnFromVec2(randomcoord)
|
||||
else -- don't random position of e.g. SAM units build as FOB
|
||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
||||
:InitDelayOff()
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
|
||||
:SpawnFromVec2(randomcoord)
|
||||
--spawn:InitRandomizeUnits(true,20,2)
|
||||
end
|
||||
if Build.Heading and self.useaircraftheadingforbuilds then
|
||||
spawn:InitGroupHeading(Build.Heading)
|
||||
end
|
||||
local spawnedGroup = spawn:SpawnFromVec2(randomcoord)
|
||||
self.DroppedTroops[self.TroopCounter] = spawnedGroup
|
||||
|
||||
if Repair then
|
||||
self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
|
||||
self:__CratesRepaired(1,Group,Unit,spawnedGroup)
|
||||
else
|
||||
self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter])
|
||||
self:__CratesBuild(1,Group,Unit,spawnedGroup)
|
||||
end
|
||||
end -- template loop
|
||||
|
||||
@@ -6244,14 +6246,11 @@ end
|
||||
-- @param Wrapper.Group#GROUP Group The Group to move.
|
||||
function CTLD:_MoveGroupToZone(Group)
|
||||
self:T(self.lid .. " _MoveGroupToZone")
|
||||
local groupname = Group:GetName() or "none"
|
||||
local groupcoord = Group:GetCoordinate()
|
||||
-- Get closest zone of type
|
||||
local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE)
|
||||
self:T({canmove=outcome, name=name, zone=zone, dist=distance,max=self.movetroopsdistance})
|
||||
if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then
|
||||
-- yes, we can ;)
|
||||
local groupname = Group:GetName()
|
||||
local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE
|
||||
local formation = self:_GetVehicleFormation()
|
||||
--local coordinate = zonecoord:GetVec2()
|
||||
@@ -7934,6 +7933,7 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID, qty)
|
||||
local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template)
|
||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias)
|
||||
:InitDelayOff()
|
||||
:InitCoalition(self.coalition)
|
||||
:InitSetUnitAbsolutePositions(Positions)
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||
:OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end)
|
||||
@@ -9011,7 +9011,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
|
||||
local zoneret = nil
|
||||
local zonewret = nil
|
||||
local zonenameret = nil
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
if not unitcoord then
|
||||
if Zonetype == CTLD.CargoZoneType.SHIP then
|
||||
return false, nil, nil, 1000000, nil
|
||||
@@ -9032,7 +9032,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
|
||||
self:T("Checking Type Ship: "..zonename)
|
||||
local ZoneUNIT = UNIT:FindByName(zonename)
|
||||
if not ZoneUNIT then return false end
|
||||
zonecoord = ZoneUNIT:GetCoordinate()
|
||||
zonecoord = ZoneUNIT:GetCoord()
|
||||
zoneradius = czone.shiplength
|
||||
zonewidth = czone.shipwidth
|
||||
zone = ZONE_UNIT:New( ZoneUNIT:GetName(), ZoneUNIT, zoneradius/2)
|
||||
@@ -9081,7 +9081,7 @@ function CTLD:SmokePositionNow(Unit, Flare, SmokeColor)
|
||||
end
|
||||
local FlareColor = self.FlareColor or FLARECOLOR.Red
|
||||
-- table of #CTLD.CargoZone table
|
||||
local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE
|
||||
local unitcoord = Unit:GetCoord() -- Core.Point#COORDINATE
|
||||
local Group = Unit:GetGroup()
|
||||
if Flare then
|
||||
unitcoord:Flare(FlareColor, 90)
|
||||
@@ -9100,7 +9100,7 @@ end
|
||||
function CTLD:SmokeZoneNearBy(Unit, Flare)
|
||||
self:T(self.lid .. " SmokeZoneNearBy")
|
||||
-- table of #CTLD.CargoZone table
|
||||
local unitcoord = Unit:GetCoordinate()
|
||||
local unitcoord = Unit:GetCoord()
|
||||
local Group = Unit:GetGroup()
|
||||
local smokedistance = self.smokedistance
|
||||
local smoked = false
|
||||
@@ -9237,7 +9237,7 @@ end
|
||||
-- get speed and height
|
||||
local uspeed = Unit:GetVelocityMPS()
|
||||
local uheight = Unit:GetHeight()
|
||||
local ucoord = Unit:GetCoordinate()
|
||||
local ucoord = Unit:GetCoord()
|
||||
if not ucoord then
|
||||
return false
|
||||
end
|
||||
@@ -9266,7 +9266,7 @@ end
|
||||
-- get speed and height
|
||||
local uspeed = Unit:GetVelocityMPS()
|
||||
local uheight = Unit:GetHeight()
|
||||
local ucoord = Unit:GetCoordinate()
|
||||
local ucoord = Unit:GetCoord()
|
||||
if not ucoord then
|
||||
return false
|
||||
end
|
||||
@@ -9358,7 +9358,7 @@ end
|
||||
minheight = 5.1 -- herc is 5m AGL on the ground
|
||||
end
|
||||
local uheight = Unit:GetHeight()
|
||||
local ucoord = Unit:GetCoordinate()
|
||||
local ucoord = Unit:GetCoord()
|
||||
if not ucoord then
|
||||
return false
|
||||
end
|
||||
@@ -10144,6 +10144,7 @@ end
|
||||
local alias = string.format("%s-%d", _template, math.random(1,100000))
|
||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
||||
:InitRandomizeUnits(randompositions,20,2)
|
||||
:InitCoalition(self.coalition)
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||
:InitDelayOff()
|
||||
:OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp)
|
||||
@@ -10298,6 +10299,7 @@ end
|
||||
if canmove then
|
||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
||||
:InitRandomizeUnits(true,20,2)
|
||||
:InitCoalition(self.coalition)
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||
:InitDelayOff()
|
||||
:OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp)
|
||||
@@ -10305,6 +10307,7 @@ end
|
||||
else -- don't random position of e.g. SAM units build as FOB
|
||||
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias)
|
||||
:InitDelayOff()
|
||||
:InitCoalition(self.coalition)
|
||||
:InitValidateAndRepositionGroundUnits(self.validateAndRepositionUnits)
|
||||
:OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp)
|
||||
:SpawnFromVec2(randomcoord)
|
||||
@@ -10586,7 +10589,7 @@ end
|
||||
self:T({From, Event, To})
|
||||
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
|
||||
local playername = Unit:GetPlayerName()
|
||||
local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
local dropcoord = Troops:GetCoord() or COORDINATE:New(0,0,0)
|
||||
local dropvec2 = dropcoord:GetVec2()
|
||||
self.PlayerTaskQueue:ForEach(
|
||||
function (Task)
|
||||
@@ -10643,7 +10646,7 @@ end
|
||||
for _,_cargo in pairs(Cargotable) do
|
||||
local Vehicle = _cargo.Positionable
|
||||
if Vehicle then
|
||||
local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
local dropcoord = Vehicle:GetCoord() or COORDINATE:New(0,0,0)
|
||||
local dropvec2 = dropcoord:GetVec2()
|
||||
self.PlayerTaskQueue:ForEach(
|
||||
function (Task)
|
||||
@@ -10708,7 +10711,7 @@ end
|
||||
self:T({From, Event, To})
|
||||
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
|
||||
local playername = Unit:GetPlayerName()
|
||||
local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
local dropcoord = Vehicle:GetCoord() or COORDINATE:New(0,0,0)
|
||||
local dropvec2 = dropcoord:GetVec2()
|
||||
self.PlayerTaskQueue:ForEach(
|
||||
function (Task)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1882,6 +1882,8 @@ function COMMANDER:RecruitAssetsForMission(Mission)
|
||||
-- Recruite assets.
|
||||
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
|
||||
@@ -1025,6 +1049,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={}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -71,6 +71,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
|
||||
@@ -642,6 +658,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
|
||||
@@ -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.
|
||||
@@ -787,16 +866,10 @@ 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
|
||||
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
|
||||
|
||||
self:ElementDestroyed(Name, target)
|
||||
end
|
||||
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: Could not get target from IniGroup or IniUnit name when event Dead or UnitLost occured! Stats are not correctly updated :("))
|
||||
end -- Event belongs to this TARGET
|
||||
|
||||
end
|
||||
|
||||
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
|
||||
@@ -2126,6 +2126,56 @@ ENUMS.Storage.weapons.containers.UH60_GAU19_RIGHT = 'weapons.containers.{UH60_GA
|
||||
ENUMS.Storage.weapons.containers.UH60_M230_RIGHT = 'weapons.containers.{UH60_M230_RIGHT}'
|
||||
ENUMS.Storage.weapons.containers.UH60L_M2_GUNNER = 'weapons.containers.{UH60L_M2_GUNNER}'
|
||||
|
||||
|
||||
ENUMS.Storage.weapons.adapters.GD_F100_SUU_13 = 'weapons.adapters.GD_F100_SUU-13'
|
||||
ENUMS.Storage.weapons.containers.GPU_5_POD = 'weapons.containers.{GPU_5_POD}'
|
||||
ENUMS.Storage.weapons.bombs.BOLT_117 = 'weapons.bombs.BOLT-117'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_TP_T = 'weapons.shells.F100_M39_20_TP_T'
|
||||
ENUMS.Storage.weapons.gunmounts.M_39_F_100 = 'weapons.gunmounts.M-39 F-100'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_275_L = 'weapons.droptanks.GD_F100_TANK_275_L'
|
||||
ENUMS.Storage.weapons.bombs.BLU_4B_R_GROUP_R = 'weapons.bombs.BLU-4B_R_GROUP_R'
|
||||
ENUMS.Storage.weapons.containers.F100_CAMERA_P2 = 'weapons.containers.F100_CAMERA_P2'
|
||||
ENUMS.Storage.weapons.containers.HB_ORD_SUU_7 = 'weapons.containers.HB_ORD_SUU_7'
|
||||
ENUMS.Storage.weapons.adapters.SidewinderPylon = 'weapons.adapters.SidewinderPylon'
|
||||
ENUMS.Storage.weapons.adapters.apu_470 = 'weapons.adapters.apu_470'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_335_R = 'weapons.droptanks.GD_F100_TANK_335_R'
|
||||
ENUMS.Storage.weapons.bombs.BLU_3B_R_GROUP_R = 'weapons.bombs.BLU-3B_R_GROUP_R'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_335_L = 'weapons.droptanks.GD_F100_TANK_335_L'
|
||||
ENUMS.Storage.weapons.adapters.mbdz_u2t = 'weapons.adapters.mbdz_u2t'
|
||||
ENUMS.Storage.weapons.containers.ZellRocket = 'weapons.containers.ZellRocket'
|
||||
ENUMS.Storage.weapons.containers.apu_73 = 'weapons.containers.apu-73'
|
||||
ENUMS.Storage.weapons.gunmounts.GAU_13 = 'weapons.gunmounts.GAU-13'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_TP = 'weapons.shells.F100_M39_20_TP'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_API = 'weapons.shells.F100_M39_20_API'
|
||||
ENUMS.Storage.weapons.adapters.bdz_umk2b = 'weapons.adapters.bdz_umk2b'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_450 = 'weapons.droptanks.GD_F100_TANK_450'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_275_R = 'weapons.droptanks.GD_F100_TANK_275_R'
|
||||
ENUMS.Storage.weapons.bombs.BLU_18_B_GROUP = 'weapons.bombs.BLU-18/B_GROUP'
|
||||
ENUMS.Storage.weapons.containers.ZELL_Booster = 'weapons.containers.ZELL Booster'
|
||||
ENUMS.Storage.weapons.adapters.apu_60 = 'weapons.adapters.apu_60'
|
||||
ENUMS.Storage.weapons.gunmounts.GPU_5_POD = 'weapons.gunmounts.{GPU_5_POD}'
|
||||
ENUMS.Storage.weapons.containers.F100_CAMERA_KA71A = 'weapons.containers.F100_CAMERA_KA71A'
|
||||
ENUMS.Storage.weapons.bombs.BLU_3_R_GROUP_R = 'weapons.bombs.BLU-3_R_GROUP_R'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_HEI = 'weapons.shells.F100_M39_20_HEI'
|
||||
ENUMS.Storage.weapons.droptanks.GD_F100_TANK_200 = 'weapons.droptanks.GD_F100_TANK_200'
|
||||
ENUMS.Storage.weapons.adapters.apu_73 = 'weapons.adapters.apu-73'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.AKAN_NO_TRC'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.AKAN'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_HEI_T = 'weapons.shells.F100_M39_20_HEI_T'
|
||||
ENUMS.Storage.weapons.shells.F100_M39_20_API_T = 'weapons.shells.F100_M39_20_API_T'
|
||||
ENUMS.Storage.weapons.containers.GD_F100_STRIKE_CAMERA = 'weapons.containers.GD_F100_STRIKE_CAMERA'
|
||||
ENUMS.Storage.weapons.bombs.BLU_49_B = 'weapons.bombs.BLU-49/B'
|
||||
|
||||
ENUMS.Storage.weapons.bombs.GBU_24E = 'weapons.bombs.GBU_24E'
|
||||
ENUMS.Storage.weapons.containers.GD_F100_SUU_13 = 'weapons.containers.GD_F100_SUU-13'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN = 'weapons.gunmounts.{AKAN}'
|
||||
ENUMS.Storage.weapons.gunmounts.AKAN_NO_TRC = 'weapons.gunmounts.{AKAN_NO_TRC}'
|
||||
ENUMS.Storage.weapons.gunmounts.SUU_23_POD_Wing = 'weapons.gunmounts.{SUU_23_POD_Wing}'
|
||||
ENUMS.Storage.weapons.containers.SUU_23_POD_Wing = 'weapons.containers.{SUU_23_POD_Wing}'
|
||||
ENUMS.Storage.weapons.containers.SUU_23_POD_Centerline = 'weapons.containers.{SUU_23_POD_Centerline}'
|
||||
ENUMS.Storage.weapons.gunmounts.SUU_23_POD_Centerline = 'weapons.gunmounts.{SUU_23_POD_Centerline}'
|
||||
ENUMS.Storage.weapons.containers.HB_F_4E_BRU_42 = 'weapons.containers.HB_F-4E_BRU-42'
|
||||
|
||||
---
|
||||
-- @type ENUMS.FARPType
|
||||
-- @field #string FARP
|
||||
|
||||
@@ -1963,6 +1963,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
|
||||
@@ -1971,24 +1990,24 @@ function AIRBASE:Register(AirbaseName)
|
||||
--end
|
||||
|
||||
-- Set category.
|
||||
if self.category==Airbase.Category.AIRDROME then
|
||||
self.isAirdrome=true
|
||||
elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01" then
|
||||
self.isHelipad=true
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
elseif self.category==Airbase.Category.SHIP then
|
||||
self.isShip=true
|
||||
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
|
||||
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
|
||||
if self.category==Airbase.Category.AIRDROME then
|
||||
self.isAirdrome=true
|
||||
elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01" then
|
||||
self.isHelipad=true
|
||||
self.isShip=false
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
_DATABASE:AddStatic(AirbaseName)
|
||||
elseif self.category==Airbase.Category.SHIP then
|
||||
self.isShip=true
|
||||
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
|
||||
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
|
||||
self.isHelipad=true
|
||||
self.isShip=false
|
||||
self.category=Airbase.Category.HELIPAD
|
||||
_DATABASE:AddStatic(AirbaseName)
|
||||
end
|
||||
if self:GetTypeName() == "Zell" then self.isZell = true end
|
||||
else
|
||||
self:E("ERROR: Unknown airbase category!")
|
||||
end
|
||||
if self:GetTypeName() == "Zell" then self.isZell = true end
|
||||
else
|
||||
self:E("ERROR: Unknown airbase category!")
|
||||
end
|
||||
|
||||
-- Init Runways.
|
||||
self:_InitRunways()
|
||||
@@ -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