mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-08-20 17:07:54 +00:00
Update DynamicCargo.lua
- Added C-130-specific transport state handling to avoid false unloads with `ropelength = 0`. - Added state flow for C-130 cargo: - attach (grounded proximity) - detach (airborne + distance) - airborne confirm - landed-stable confirm - unload event only after landed-stable check - Added owner re-resolve fallback for C-130 when owner/unit refs are stale. - Added C-130 config values: - `C130AttachDistance = 10` - `C130DetachDistance = 14` - `C130AirborneAGL = 8` - `C130LandedAGL = 0.5` - `C130StabilityEpsilon = 0.05` - `C130RequireAirborne = true` - owner resolve thresholds (`Move2D`, `Near2D`, `Max3D`) - Updated default scheduler interval to `5` seconds. - Changed tracking to one global timer for all tracked DynamicCargo objects: - starts when cargo exists - stops when no tracked cargo remains - Added/updated aircraft dimensions table entries: - `CH-47Fbl1`, `Mi-8MTV2`, `Mi-8MT`, `UH-1H`, `Mi-24P`, `UH-60L`, `UH-60L_DAP`, `C-130J-30` - Added helper getters: - `IsAttached()` - `IsDetached()` - `WasAirborneTransport()` - `IsLandedStable()` - `GetCarrierUnitName()` - `GetCarrierTypeName()` - `GetCarrierGroupName()` **Behavior note** - Non-C130 behavior stays on the existing logic path. - Existing DynamicCargo event IDs and flow are kept (`New`, `Loaded`, `Unloaded`, `Removed`).
This commit is contained in:
@@ -28,9 +28,9 @@
|
|||||||
-- @field #string version.
|
-- @field #string version.
|
||||||
-- @field #string CargoState.
|
-- @field #string CargoState.
|
||||||
-- @field #table DCS#Vec3 LastPosition.
|
-- @field #table DCS#Vec3 LastPosition.
|
||||||
-- @field #number Interval Check Interval. 20 secs default.
|
-- @field #number Interval Check interval. 5 secs default.
|
||||||
-- @field #boolean testing
|
-- @field #boolean testing
|
||||||
-- @field Core.Timer#TIMER timer Timmer to run intervals
|
-- @field Core.Timer#TIMER timer Compatibility field; updates are handled by one class-wide scheduler.
|
||||||
-- @field #string Owner The playername who has created, loaded or unloaded this cargo. Depends on state.
|
-- @field #string Owner The playername who has created, loaded or unloaded this cargo. Depends on state.
|
||||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||||
|
|
||||||
@@ -50,7 +50,16 @@ DYNAMICCARGO = {
|
|||||||
ClassName = "DYNAMICCARGO",
|
ClassName = "DYNAMICCARGO",
|
||||||
verbose = 0,
|
verbose = 0,
|
||||||
testing = false,
|
testing = false,
|
||||||
Interval = 10,
|
Interval = 5,
|
||||||
|
C130AttachDistance = 10,
|
||||||
|
C130DetachDistance = 14,
|
||||||
|
C130AirborneAGL = 8,
|
||||||
|
C130LandedAGL = 0.5,
|
||||||
|
C130StabilityEpsilon = 0.05,
|
||||||
|
C130RequireAirborne = true,
|
||||||
|
C130OwnerResolveMove2D = 10,
|
||||||
|
C130OwnerResolveNear2D = 4,
|
||||||
|
C130OwnerResolveMax3D = 250,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +119,10 @@ DYNAMICCARGO.AircraftTypes = {
|
|||||||
["CH-47Fbl1"] = "CH-47Fbl1",
|
["CH-47Fbl1"] = "CH-47Fbl1",
|
||||||
["Mi-8MTV2"] = "Mi-8MTV2",
|
["Mi-8MTV2"] = "Mi-8MTV2",
|
||||||
["Mi-8MT"] = "Mi-8MT",
|
["Mi-8MT"] = "Mi-8MT",
|
||||||
|
["UH-1H"] = "UH-1H",
|
||||||
|
["Mi-24P"] = "Mi-24P",
|
||||||
|
["UH-60L"] = "UH-60L",
|
||||||
|
["UH-60L_DAP"] = "UH-60L_DAP",
|
||||||
["C-130J-30"] = "C-130J-30",
|
["C-130J-30"] = "C-130J-30",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,17 +148,46 @@ DYNAMICCARGO.AircraftDimensions = {
|
|||||||
["length"] = 15,
|
["length"] = 15,
|
||||||
["ropelength"] = 30,
|
["ropelength"] = 30,
|
||||||
},
|
},
|
||||||
|
["UH-1H"] = {
|
||||||
|
["width"] = 4,
|
||||||
|
["height"] = 4,
|
||||||
|
["length"] = 9,
|
||||||
|
["ropelength"] = 25,
|
||||||
|
},
|
||||||
|
["Mi-24P"] = {
|
||||||
|
["width"] = 4,
|
||||||
|
["height"] = 5,
|
||||||
|
["length"] = 11,
|
||||||
|
["ropelength"] = 25,
|
||||||
|
},
|
||||||
|
["UH-60L"] = {
|
||||||
|
["width"] = 4,
|
||||||
|
["height"] = 5,
|
||||||
|
["length"] = 10,
|
||||||
|
["ropelength"] = 25,
|
||||||
|
},
|
||||||
|
["UH-60L_DAP"] = {
|
||||||
|
["width"] = 4,
|
||||||
|
["height"] = 5,
|
||||||
|
["length"] = 10,
|
||||||
|
["ropelength"] = 25,
|
||||||
|
},
|
||||||
["C-130J-30"] = {
|
["C-130J-30"] = {
|
||||||
["width"] = 4,
|
["width"] = 4,
|
||||||
["height"] = 12,
|
["height"] = 12,
|
||||||
["length"] = 35,
|
["length"] = 35,
|
||||||
["ropelength"] = 0,
|
["ropelength"] = 0,
|
||||||
|
["attach"] = 10,
|
||||||
|
["detach"] = 14,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
--- DYNAMICCARGO class version.
|
--- DYNAMICCARGO class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
DYNAMICCARGO.version="0.1.0"
|
DYNAMICCARGO.version="0.1.0"
|
||||||
|
DYNAMICCARGO._TrackedCargo = DYNAMICCARGO._TrackedCargo or {}
|
||||||
|
DYNAMICCARGO._GlobalTimer = DYNAMICCARGO._GlobalTimer or nil
|
||||||
|
DYNAMICCARGO._GlobalTimerInterval = DYNAMICCARGO._GlobalTimerInterval or nil
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- TODO list
|
-- TODO list
|
||||||
@@ -170,8 +212,17 @@ function DYNAMICCARGO:Register(CargoName)
|
|||||||
self.StaticName = CargoName
|
self.StaticName = CargoName
|
||||||
|
|
||||||
self.LastPosition = self:GetCoordinate()
|
self.LastPosition = self:GetCoordinate()
|
||||||
|
self._spawnVec3 = self.LastPosition and self.LastPosition:GetVec3() or nil
|
||||||
|
|
||||||
self.CargoState = DYNAMICCARGO.State.NEW
|
self.CargoState = DYNAMICCARGO.State.NEW
|
||||||
|
self._attached = false
|
||||||
|
self._detached = false
|
||||||
|
self._wasAirborne = false
|
||||||
|
self._landAglConfirm = nil
|
||||||
|
self._ownerResolved = false
|
||||||
|
self._carrierUnitName = nil
|
||||||
|
self._carrierGroupName = nil
|
||||||
|
self._carrierTypeName = nil
|
||||||
|
|
||||||
self.Interval = DYNAMICCARGO.Interval or 10
|
self.Interval = DYNAMICCARGO.Interval or 10
|
||||||
|
|
||||||
@@ -185,9 +236,10 @@ function DYNAMICCARGO:Register(CargoName)
|
|||||||
self.lid = string.format("DYNAMICCARGO %s", CargoName)
|
self.lid = string.format("DYNAMICCARGO %s", CargoName)
|
||||||
|
|
||||||
self.Owner = string.match(CargoName,"^(.+)|%d%d:%d%d|PKG%d+") or "None"
|
self.Owner = string.match(CargoName,"^(.+)|%d%d:%d%d|PKG%d+") or "None"
|
||||||
|
|
||||||
self.timer = TIMER:New(DYNAMICCARGO._UpdatePosition,self)
|
-- Keep a compatibility field, while updates are driven by one class-wide scheduler.
|
||||||
self.timer:Start(self.Interval,self.Interval)
|
self.timer = nil
|
||||||
|
DYNAMICCARGO._TrackCargo(self)
|
||||||
|
|
||||||
if not _DYNAMICCARGO_HELOS then
|
if not _DYNAMICCARGO_HELOS then
|
||||||
_DYNAMICCARGO_HELOS = SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart()
|
_DYNAMICCARGO_HELOS = SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart()
|
||||||
@@ -267,6 +319,55 @@ function DYNAMICCARGO:IsRemoved()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Returns true if this cargo is attached to a detected carrier.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @return #boolean Outcome
|
||||||
|
function DYNAMICCARGO:IsAttached()
|
||||||
|
return self._attached == true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns true if this cargo was detached from a detected carrier.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @return #boolean Outcome
|
||||||
|
function DYNAMICCARGO:IsDetached()
|
||||||
|
return self._detached == true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns true if this cargo has seen airborne transport in this cycle.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @return #boolean Outcome
|
||||||
|
function DYNAMICCARGO:WasAirborneTransport()
|
||||||
|
return self._wasAirborne == true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns true if this cargo has reached landed stable confirmation.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @return #boolean Outcome
|
||||||
|
function DYNAMICCARGO:IsLandedStable()
|
||||||
|
return self.CargoState == DYNAMICCARGO.State.UNLOADED and self._detached == true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns last known carrier unit name.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @return #string Unit name
|
||||||
|
function DYNAMICCARGO:GetCarrierUnitName()
|
||||||
|
return self._carrierUnitName
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns last known carrier type name.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @return #string Type name
|
||||||
|
function DYNAMICCARGO:GetCarrierTypeName()
|
||||||
|
return self._carrierTypeName
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns last known carrier group name.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @return #string Group name
|
||||||
|
function DYNAMICCARGO:GetCarrierGroupName()
|
||||||
|
return self._carrierGroupName
|
||||||
|
end
|
||||||
|
|
||||||
--- [CTLD] Get number of crates this DYNAMICCARGO consists of. Always one.
|
--- [CTLD] Get number of crates this DYNAMICCARGO consists of. Always one.
|
||||||
-- @param #DYNAMICCARGO self
|
-- @param #DYNAMICCARGO self
|
||||||
-- @return #number crate number, always one
|
-- @return #number crate number, always one
|
||||||
@@ -398,6 +499,372 @@ end
|
|||||||
-- Private Functions
|
-- Private Functions
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
--- [Internal] Check whether an aircraft typename is a C-130J for special handling.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param #string TypeName
|
||||||
|
-- @return #boolean Outcome
|
||||||
|
function DYNAMICCARGO:_IsC130Type(TypeName)
|
||||||
|
return TypeName == "C-130J-30"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Safe AGL calculation helper.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param Core.Point#COORDINATE Coord
|
||||||
|
-- @return #number AGL in meters
|
||||||
|
function DYNAMICCARGO:_GetAGL(Coord)
|
||||||
|
if not Coord then return -1 end
|
||||||
|
return (Coord.y or 0) - Coord:GetLandHeight()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Resolve player name for a client.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param Wrapper.Client#CLIENT Client
|
||||||
|
-- @return #string Player name
|
||||||
|
function DYNAMICCARGO:_GetPlayerNameForClient(Client)
|
||||||
|
if not Client then return self.Owner or "None" end
|
||||||
|
return Client:GetPlayerName() or _DATABASE:_FindPlayerNameByUnitName(Client:GetName()) or self.Owner or "None"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Store current carrier identity fields.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param Wrapper.Client#CLIENT Client
|
||||||
|
-- @param #string PlayerName
|
||||||
|
-- @return #DYNAMICCARGO self
|
||||||
|
function DYNAMICCARGO:_SetCarrierFromClient(Client, PlayerName)
|
||||||
|
if not Client then return self end
|
||||||
|
self._carrierUnitName = Client:GetName() or self._carrierUnitName
|
||||||
|
self._carrierTypeName = Client:GetTypeName() or self._carrierTypeName
|
||||||
|
local grp = Client:GetGroup()
|
||||||
|
if grp then
|
||||||
|
self._carrierGroupName = grp:GetName() or self._carrierGroupName
|
||||||
|
end
|
||||||
|
self.Owner = PlayerName or self:_GetPlayerNameForClient(Client)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Get scheduler interval for global dynamic cargo updates.
|
||||||
|
-- @return #number Interval in seconds
|
||||||
|
function DYNAMICCARGO._GetSchedulerInterval()
|
||||||
|
return DYNAMICCARGO.Interval or 5
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Count currently tracked dynamic cargo objects.
|
||||||
|
-- @return #number Count
|
||||||
|
function DYNAMICCARGO._CountTracked()
|
||||||
|
local n = 0
|
||||||
|
for _,_ in pairs(DYNAMICCARGO._TrackedCargo or {}) do
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Stop global scheduler when no tracked cargo remains.
|
||||||
|
function DYNAMICCARGO._StopGlobalSchedulerIfIdle()
|
||||||
|
if DYNAMICCARGO._CountTracked() > 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if DYNAMICCARGO._GlobalTimer and DYNAMICCARGO._GlobalTimer:IsRunning() then
|
||||||
|
DYNAMICCARGO._GlobalTimer:Stop()
|
||||||
|
end
|
||||||
|
DYNAMICCARGO._GlobalTimer = nil
|
||||||
|
DYNAMICCARGO._GlobalTimerInterval = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Ensure global scheduler is running for tracked cargo updates.
|
||||||
|
function DYNAMICCARGO._EnsureGlobalScheduler()
|
||||||
|
local interval = DYNAMICCARGO._GetSchedulerInterval()
|
||||||
|
if DYNAMICCARGO._GlobalTimer and DYNAMICCARGO._GlobalTimer:IsRunning() then
|
||||||
|
if DYNAMICCARGO._GlobalTimerInterval == interval then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
DYNAMICCARGO._GlobalTimer:Stop()
|
||||||
|
DYNAMICCARGO._GlobalTimer = nil
|
||||||
|
DYNAMICCARGO._GlobalTimerInterval = nil
|
||||||
|
end
|
||||||
|
if DYNAMICCARGO._CountTracked() < 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
DYNAMICCARGO._GlobalTimer = TIMER:New(DYNAMICCARGO._UpdateAllTracked)
|
||||||
|
DYNAMICCARGO._GlobalTimer:Start(interval, interval)
|
||||||
|
DYNAMICCARGO._GlobalTimerInterval = interval
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Add one dynamic cargo object to global tracking.
|
||||||
|
-- @param #DYNAMICCARGO Cargo
|
||||||
|
function DYNAMICCARGO._TrackCargo(Cargo)
|
||||||
|
if not Cargo or not Cargo.StaticName then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
DYNAMICCARGO._TrackedCargo = DYNAMICCARGO._TrackedCargo or {}
|
||||||
|
DYNAMICCARGO._TrackedCargo[Cargo.StaticName] = Cargo
|
||||||
|
DYNAMICCARGO._EnsureGlobalScheduler()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Remove one dynamic cargo object from global tracking.
|
||||||
|
-- @param #string CargoName
|
||||||
|
function DYNAMICCARGO._UntrackCargo(CargoName)
|
||||||
|
if not CargoName or not DYNAMICCARGO._TrackedCargo then
|
||||||
|
DYNAMICCARGO._StopGlobalSchedulerIfIdle()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
DYNAMICCARGO._TrackedCargo[CargoName] = nil
|
||||||
|
DYNAMICCARGO._StopGlobalSchedulerIfIdle()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Update all tracked dynamic cargo objects in one scheduler tick.
|
||||||
|
function DYNAMICCARGO._UpdateAllTracked()
|
||||||
|
local tracked = DYNAMICCARGO._TrackedCargo or {}
|
||||||
|
local names = {}
|
||||||
|
for name,_ in pairs(tracked) do
|
||||||
|
names[#names + 1] = name
|
||||||
|
end
|
||||||
|
for _,name in ipairs(names) do
|
||||||
|
local cargo = tracked[name]
|
||||||
|
if cargo then
|
||||||
|
cargo:_UpdatePosition()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
DYNAMICCARGO._StopGlobalSchedulerIfIdle()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Find tracked client by unit name in current dynamic cargo helo set.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param #string UnitName
|
||||||
|
-- @return Wrapper.Client#CLIENT Client
|
||||||
|
function DYNAMICCARGO:_FindClientByUnitName(UnitName)
|
||||||
|
if not UnitName or UnitName == "" or not _DYNAMICCARGO_HELOS then return nil end
|
||||||
|
for _,_helo in pairs(_DYNAMICCARGO_HELOS:GetAliveSet() or {}) do
|
||||||
|
local helo = _helo -- Wrapper.Client#CLIENT
|
||||||
|
if helo and helo:IsAlive() and helo:GetName() == UnitName then
|
||||||
|
return helo
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Get best known carrier client from stored names/owner.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @return Wrapper.Client#CLIENT Client
|
||||||
|
function DYNAMICCARGO:_GetKnownCarrierClient()
|
||||||
|
local client = nil
|
||||||
|
if self._carrierUnitName then
|
||||||
|
client = self:_FindClientByUnitName(self._carrierUnitName)
|
||||||
|
end
|
||||||
|
if (not client) and self.Owner and self.Owner ~= "None" then
|
||||||
|
local byPlayer = CLIENT:FindByPlayerName(self.Owner)
|
||||||
|
if byPlayer and byPlayer:IsAlive() then
|
||||||
|
client = byPlayer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return client
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Find nearest live C-130 client.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param Core.Point#COORDINATE Pos
|
||||||
|
-- @param #number Max3D Maximum 3D distance
|
||||||
|
-- @return Wrapper.Client#CLIENT Client
|
||||||
|
-- @return #string PlayerName
|
||||||
|
-- @return #number Dist2D
|
||||||
|
-- @return #number Dist3D
|
||||||
|
function DYNAMICCARGO:_FindNearestC130(Pos, Max3D)
|
||||||
|
if not Pos or not _DYNAMICCARGO_HELOS then return nil, nil, nil, nil end
|
||||||
|
local bestClient = nil
|
||||||
|
local bestName = nil
|
||||||
|
local best2D = math.huge
|
||||||
|
local best3D = math.huge
|
||||||
|
local bestOwnerMatch = false
|
||||||
|
local max3D = Max3D or DYNAMICCARGO.C130OwnerResolveMax3D
|
||||||
|
local preferredOwner = self.Owner
|
||||||
|
if preferredOwner == "" or preferredOwner == "None" then
|
||||||
|
preferredOwner = nil
|
||||||
|
end
|
||||||
|
for _,_helo in pairs(_DYNAMICCARGO_HELOS:GetAliveSet() or {}) do
|
||||||
|
local helo = _helo -- Wrapper.Client#CLIENT
|
||||||
|
if helo and helo:IsAlive() then
|
||||||
|
local typename = helo:GetTypeName()
|
||||||
|
if self:_IsC130Type(typename) then
|
||||||
|
local hpos = helo:GetCoordinate()
|
||||||
|
if hpos then
|
||||||
|
local d3 = hpos:Get3DDistance(Pos)
|
||||||
|
if d3 <= max3D then
|
||||||
|
local d2 = hpos:Get2DDistance(Pos)
|
||||||
|
local pname = self:_GetPlayerNameForClient(helo)
|
||||||
|
local ownerMatch = preferredOwner and pname and pname == preferredOwner or false
|
||||||
|
if (ownerMatch and not bestOwnerMatch) or ((ownerMatch == bestOwnerMatch) and d3 < best3D) then
|
||||||
|
bestClient = helo
|
||||||
|
bestName = pname
|
||||||
|
best2D = d2
|
||||||
|
best3D = d3
|
||||||
|
bestOwnerMatch = ownerMatch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return bestClient, bestName, best2D, best3D
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Resolve/rebind owner to nearest valid C-130 after movement from spawn.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param Core.Point#COORDINATE Pos
|
||||||
|
-- @return Wrapper.Client#CLIENT Client
|
||||||
|
function DYNAMICCARGO:_ResolveC130Owner(Pos)
|
||||||
|
if not Pos or not self._spawnVec3 then return nil end
|
||||||
|
local moved2D = UTILS.VecDist2D(Pos, self._spawnVec3)
|
||||||
|
if moved2D < (DYNAMICCARGO.C130OwnerResolveMove2D or 10) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local max3D = DYNAMICCARGO.C130OwnerResolveMax3D or 250
|
||||||
|
local known = self:_GetKnownCarrierClient()
|
||||||
|
if known and known:IsAlive() and self:_IsC130Type(known:GetTypeName()) then
|
||||||
|
local kpos = known:GetCoordinate()
|
||||||
|
if kpos and kpos:Get3DDistance(Pos) <= max3D then
|
||||||
|
self:_SetCarrierFromClient(known)
|
||||||
|
return known
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local nearest, playerName, d2 = self:_FindNearestC130(Pos, DYNAMICCARGO.C130OwnerResolveMax3D)
|
||||||
|
if nearest and d2 and d2 <= (DYNAMICCARGO.C130OwnerResolveNear2D or 4) then
|
||||||
|
self:_SetCarrierFromClient(nearest, playerName)
|
||||||
|
self._ownerResolved = true
|
||||||
|
self:T(self.lid.." C130 owner re-resolved to "..tostring(self._carrierUnitName).." / "..tostring(self.Owner))
|
||||||
|
return nearest
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] Determine whether to run C-130 transport-state handling for this cargo tick.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param Core.Point#COORDINATE Pos
|
||||||
|
-- @return #boolean Outcome
|
||||||
|
function DYNAMICCARGO:_ShouldUseC130State(Pos)
|
||||||
|
if self:_IsC130Type(self._carrierTypeName) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local known = self:_GetKnownCarrierClient()
|
||||||
|
if known and self:_IsC130Type(known:GetTypeName()) then
|
||||||
|
self:_SetCarrierFromClient(known)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if self._attached or self._detached or self._wasAirborne then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if self.CargoState == DYNAMICCARGO.State.NEW or self.CargoState == DYNAMICCARGO.State.UNLOADED then
|
||||||
|
local nearest, _, d2 = self:_FindNearestC130(Pos, DYNAMICCARGO.C130AttachDistance + 50)
|
||||||
|
if nearest and d2 and d2 <= (DYNAMICCARGO.C130AttachDistance + 5) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- [Internal] C-130-specific transport state update.
|
||||||
|
-- @param #DYNAMICCARGO self
|
||||||
|
-- @param Core.Point#COORDINATE Pos
|
||||||
|
-- @return #DYNAMICCARGO self
|
||||||
|
function DYNAMICCARGO:_UpdatePositionC130(Pos)
|
||||||
|
local attachDist = DYNAMICCARGO.C130AttachDistance or 10
|
||||||
|
local detachDist = DYNAMICCARGO.C130DetachDistance or 14
|
||||||
|
local airborneAgl = DYNAMICCARGO.C130AirborneAGL or 8
|
||||||
|
local landedAgl = DYNAMICCARGO.C130LandedAGL or 0.5
|
||||||
|
local stableEps = DYNAMICCARGO.C130StabilityEpsilon or 0.05
|
||||||
|
local requireAirborne = DYNAMICCARGO.C130RequireAirborne ~= false
|
||||||
|
|
||||||
|
local cargoAgl = self:_GetAGL(Pos)
|
||||||
|
local carrier = self:_GetKnownCarrierClient()
|
||||||
|
if carrier and not self:_IsC130Type(carrier:GetTypeName()) then
|
||||||
|
carrier = nil
|
||||||
|
end
|
||||||
|
if not carrier then
|
||||||
|
carrier = self:_ResolveC130Owner(Pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Attach detection: grounded C-130 close to cargo.
|
||||||
|
if (self.CargoState == DYNAMICCARGO.State.NEW or self.CargoState == DYNAMICCARGO.State.UNLOADED) and (not self._attached) then
|
||||||
|
if not carrier then
|
||||||
|
local nearest, pname, d2 = self:_FindNearestC130(Pos, DYNAMICCARGO.C130OwnerResolveMax3D)
|
||||||
|
if nearest and d2 and d2 <= attachDist and not nearest:InAir() then
|
||||||
|
carrier = nearest
|
||||||
|
self:_SetCarrierFromClient(nearest, pname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if carrier and carrier:IsAlive() then
|
||||||
|
local hpos = carrier:GetCoordinate()
|
||||||
|
if hpos and (not carrier:InAir()) and hpos:Get2DDistance(Pos) <= attachDist then
|
||||||
|
self._attached = true
|
||||||
|
self._detached = false
|
||||||
|
self._wasAirborne = false
|
||||||
|
self._landAglConfirm = nil
|
||||||
|
self:_SetCarrierFromClient(carrier)
|
||||||
|
if self.CargoState ~= DYNAMICCARGO.State.LOADED then
|
||||||
|
self.CargoState = DYNAMICCARGO.State.LOADED
|
||||||
|
self:T(self.lid.." C130 attach: "..tostring(self.Owner))
|
||||||
|
_DATABASE:CreateEventDynamicCargoLoaded(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.CargoState == DYNAMICCARGO.State.LOADED then
|
||||||
|
if not carrier then
|
||||||
|
carrier = self:_ResolveC130Owner(Pos)
|
||||||
|
end
|
||||||
|
local carrierInAir = false
|
||||||
|
local dist2D = math.huge
|
||||||
|
local carrierAgl = -1
|
||||||
|
if carrier and carrier:IsAlive() then
|
||||||
|
local hpos = carrier:GetCoordinate()
|
||||||
|
if hpos then
|
||||||
|
dist2D = hpos:Get2DDistance(Pos)
|
||||||
|
carrierAgl = self:_GetAGL(hpos)
|
||||||
|
end
|
||||||
|
carrierInAir = carrier:InAir()
|
||||||
|
self:_SetCarrierFromClient(carrier)
|
||||||
|
end
|
||||||
|
|
||||||
|
if cargoAgl >= airborneAgl or carrierAgl >= airborneAgl then
|
||||||
|
self._wasAirborne = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Detach only when actually airborne and separated.
|
||||||
|
if self._attached and carrierInAir and dist2D > detachDist then
|
||||||
|
self._attached = false
|
||||||
|
self._detached = true
|
||||||
|
self._landAglConfirm = nil
|
||||||
|
self:T(self.lid.." C130 detach at d2="..tostring(UTILS.Round(dist2D,2)))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Fallback detach if carrier reference is stale but cargo already transitioned airborne.
|
||||||
|
if self._attached and (not carrier or not carrier:IsAlive()) and self._wasAirborne and cargoAgl <= airborneAgl then
|
||||||
|
self._attached = false
|
||||||
|
self._detached = true
|
||||||
|
self._landAglConfirm = nil
|
||||||
|
self:T(self.lid.." C130 detach fallback (carrier stale)")
|
||||||
|
end
|
||||||
|
|
||||||
|
local canUnload = self._detached and ((not requireAirborne) or self._wasAirborne)
|
||||||
|
if canUnload then
|
||||||
|
local moved3D = self.LastPosition and UTILS.VecDist3D(Pos, self.LastPosition) or math.huge
|
||||||
|
local stable = moved3D <= stableEps
|
||||||
|
if cargoAgl <= landedAgl and stable then
|
||||||
|
if self._landAglConfirm then
|
||||||
|
self.CargoState = DYNAMICCARGO.State.UNLOADED
|
||||||
|
self:T(self.lid.." C130 landed-stable unload by "..tostring(self.Owner))
|
||||||
|
_DATABASE:CreateEventDynamicCargoUnloaded(self)
|
||||||
|
else
|
||||||
|
self._landAglConfirm = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self._landAglConfirm = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
--- [Internal] _Get helo hovering intel
|
--- [Internal] _Get helo hovering intel
|
||||||
-- @param #DYNAMICCARGO self
|
-- @param #DYNAMICCARGO self
|
||||||
-- @param Wrapper.Unit#UNIT Unit The Unit to test
|
-- @param Wrapper.Unit#UNIT Unit The Unit to test
|
||||||
@@ -444,35 +911,37 @@ function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading)
|
|||||||
local hpos = helo:GetCoordinate()
|
local hpos = helo:GetCoordinate()
|
||||||
-- TODO Check unloading via sling load?
|
-- TODO Check unloading via sling load?
|
||||||
local typename = helo:GetTypeName()
|
local typename = helo:GetTypeName()
|
||||||
local dimensions = DYNAMICCARGO.AircraftDimensions[typename]
|
if not self:_IsC130Type(typename) then
|
||||||
local hovering, height = self:_HeloHovering(helo,dimensions.ropelength)
|
local dimensions = DYNAMICCARGO.AircraftDimensions[typename]
|
||||||
local helolanded = not helo:InAir()
|
if hpos and typename and dimensions then
|
||||||
self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering))
|
local hovering, height = self:_HeloHovering(helo,dimensions.ropelength)
|
||||||
if hpos and typename and dimensions then
|
local helolanded = not helo:InAir()
|
||||||
local delta2D = hpos:Get2DDistance(pos)
|
self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering))
|
||||||
local delta3D = hpos:Get3DDistance(pos)
|
local delta2D = hpos:Get2DDistance(pos)
|
||||||
if self.testing then
|
local delta3D = hpos:Get3DDistance(pos)
|
||||||
self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D))
|
if self.testing then
|
||||||
self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength))
|
self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D))
|
||||||
self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height))
|
self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength))
|
||||||
end
|
self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height))
|
||||||
-- unloading from ground
|
end
|
||||||
if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then -- Theoretically the cargo could still be attached to the sling if landed next to the cargo. But once moved again it would go back into loaded state once lifted again.
|
-- unloading from ground
|
||||||
success = true
|
if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then -- Theoretically the cargo could still be attached to the sling if landed next to the cargo. But once moved again it would go back into loaded state once lifted again.
|
||||||
Helo = helo
|
success = true
|
||||||
Playername = name
|
Helo = helo
|
||||||
end
|
Playername = name
|
||||||
-- unloading from hover/rope
|
end
|
||||||
if loading~=true and delta3D > dimensions.ropelength then
|
-- unloading from hover/rope
|
||||||
success = true
|
if loading~=true and delta3D > dimensions.ropelength then
|
||||||
Helo = helo
|
success = true
|
||||||
Playername = name
|
Helo = helo
|
||||||
end
|
Playername = name
|
||||||
-- loading
|
end
|
||||||
if loading == true and ((delta2D < dimensions.length and delta2D < dimensions.width and helolanded) or (delta3D == dimensions.ropelength and helo:InAir())) then -- Loaded via ground or sling
|
-- loading
|
||||||
success = true
|
if loading == true and ((delta2D < dimensions.length and delta2D < dimensions.width and helolanded) or (delta3D == dimensions.ropelength and helo:InAir())) then -- Loaded via ground or sling
|
||||||
Helo = helo
|
success = true
|
||||||
Playername = name
|
Helo = helo
|
||||||
|
Playername = name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -490,21 +959,31 @@ function DYNAMICCARGO:_UpdatePosition()
|
|||||||
self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z))
|
self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z))
|
||||||
self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z))
|
self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z))
|
||||||
end
|
end
|
||||||
if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then -- This checks if the cargo has moved more than 0.5m since last check. If so then the cargo is loaded
|
local moved = UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5
|
||||||
---------------
|
if self:_ShouldUseC130State(pos) then
|
||||||
-- LOAD Cargo
|
self:_UpdatePositionC130(pos)
|
||||||
---------------
|
self.LastPosition = pos
|
||||||
if self.CargoState == DYNAMICCARGO.State.NEW or self.CargoState == DYNAMICCARGO.State.UNLOADED then
|
elseif moved then -- This checks if the cargo has moved more than 0.5m since last check. If so then the cargo is loaded
|
||||||
local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true)
|
---------------
|
||||||
self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername))
|
-- LOAD Cargo
|
||||||
self.CargoState = DYNAMICCARGO.State.LOADED
|
---------------
|
||||||
self.Owner = playername
|
if self.CargoState == DYNAMICCARGO.State.NEW or self.CargoState == DYNAMICCARGO.State.UNLOADED then
|
||||||
_DATABASE:CreateEventDynamicCargoLoaded(self)
|
local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true)
|
||||||
end
|
if isloaded then
|
||||||
|
self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername))
|
||||||
|
self.CargoState = DYNAMICCARGO.State.LOADED
|
||||||
|
self.Owner = playername
|
||||||
|
if client then
|
||||||
|
self:_SetCarrierFromClient(client, playername)
|
||||||
|
end
|
||||||
|
_DATABASE:CreateEventDynamicCargoLoaded(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.LastPosition = pos
|
||||||
---------------
|
---------------
|
||||||
-- UNLOAD Cargo
|
-- UNLOAD Cargo
|
||||||
---------------
|
---------------
|
||||||
-- If the cargo is stationary then we need to end this condition here to check whether it is unloaded or still onboard or still hooked if anyone can hover that precisly
|
-- If the cargo is stationary then we need to end this condition here to check whether it is unloaded or still onboard or still hooked if anyone can hover that precisly
|
||||||
elseif self.CargoState == DYNAMICCARGO.State.LOADED then
|
elseif self.CargoState == DYNAMICCARGO.State.LOADED then
|
||||||
-- TODO add checker if we are in flight somehow
|
-- TODO add checker if we are in flight somehow
|
||||||
-- ensure not just the helo is moving
|
-- ensure not just the helo is moving
|
||||||
@@ -518,36 +997,37 @@ function DYNAMICCARGO:_UpdatePosition()
|
|||||||
local client
|
local client
|
||||||
local playername = self.Owner
|
local playername = self.Owner
|
||||||
if count > 0 then
|
if count > 0 then
|
||||||
self:T(self.lid.." Possible alive helos: "..count or -1)
|
self:T(self.lid.." Possible alive helos: "..count or -1)
|
||||||
isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false)
|
isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false)
|
||||||
if isunloaded then
|
if isunloaded then
|
||||||
self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername))
|
self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername))
|
||||||
self.CargoState = DYNAMICCARGO.State.UNLOADED
|
self.CargoState = DYNAMICCARGO.State.UNLOADED
|
||||||
self.Owner = playername
|
self.Owner = playername
|
||||||
|
if client then
|
||||||
|
self:_SetCarrierFromClient(client, playername)
|
||||||
|
end
|
||||||
_DATABASE:CreateEventDynamicCargoUnloaded(self)
|
_DATABASE:CreateEventDynamicCargoUnloaded(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.LastPosition = pos
|
|
||||||
--end
|
|
||||||
else
|
else
|
||||||
---------------
|
---------------
|
||||||
-- REMOVED Cargo
|
-- REMOVED Cargo
|
||||||
---------------
|
---------------
|
||||||
if self.timer and self.timer:IsRunning() then
|
if self.CargoState ~= DYNAMICCARGO.State.REMOVED then
|
||||||
self.timer:Stop()
|
DYNAMICCARGO._UntrackCargo(self.StaticName)
|
||||||
self.timer=nil
|
self.timer=nil
|
||||||
|
self:T(self.lid.." dead! " ..self.CargoState.."-> REMOVED")
|
||||||
|
self.CargoState = DYNAMICCARGO.State.REMOVED
|
||||||
|
_DATABASE:CreateEventDynamicCargoRemoved(self)
|
||||||
end
|
end
|
||||||
self:T(self.lid.." dead! " ..self.CargoState.."-> REMOVED")
|
|
||||||
self.CargoState = DYNAMICCARGO.State.REMOVED
|
|
||||||
_DATABASE:CreateEventDynamicCargoRemoved(self)
|
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--- [USER] Destroy a DYNAMICCARGO object.
|
--- [USER] Destroy a DYNAMICCARGO object.
|
||||||
-- @param #DYNAMICCARGO self
|
-- @param #DYNAMICCARGO self
|
||||||
-- @param #boolean GenerateEvent (Optional) Set to false to remove an item silently. Defaults to true.
|
-- @param #boolean GenerateEvent Set to false to remove an item silently. Defaults to true.
|
||||||
-- @return #boolean Return Returns nil if the object could not be found, else returns true.
|
-- @return #boolean Return Returns nil if the object could not be found, else returns true.
|
||||||
function DYNAMICCARGO:Destroy(GenerateEvent)
|
function DYNAMICCARGO:Destroy(GenerateEvent)
|
||||||
local DCSObject = self:GetDCSObject()
|
local DCSObject = self:GetDCSObject()
|
||||||
|
|||||||
Reference in New Issue
Block a user