mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
4549 lines
176 KiB
Lua
4549 lines
176 KiB
Lua
--- **Ops** -- Combat Troops & Logistics Department.
|
|
--
|
|
-- ===
|
|
--
|
|
-- **CTLD** - MOOSE based Helicopter CTLD Operations.
|
|
--
|
|
-- ===
|
|
--
|
|
-- ## Missions:
|
|
--
|
|
-- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CTLD)
|
|
--
|
|
-- ===
|
|
--
|
|
-- **Main Features:**
|
|
--
|
|
-- * MOOSE-based Helicopter CTLD Operations for Players.
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), bbirchnz (additional code!!)
|
|
-- @module Ops.CTLD
|
|
-- @image OPS_CTLD.jpg
|
|
|
|
-- Date: Dec 2021
|
|
|
|
do
|
|
------------------------------------------------------
|
|
--- **CTLD_ENGINEERING** class, extends Core.Base#BASE
|
|
-- @type CTLD_ENGINEERING
|
|
-- @field #string ClassName
|
|
-- @field #string lid
|
|
-- @field #string Name
|
|
-- @field Wrapper.Group#GROUP Group
|
|
-- @field Wrapper.Unit#UNIT Unit
|
|
-- @field Wrapper.Group#GROUP HeliGroup
|
|
-- @field Wrapper.Unit#UNIT HeliUnit
|
|
-- @field #string State
|
|
-- @extends Core.Base#BASE
|
|
CTLD_ENGINEERING = {
|
|
ClassName = "CTLD_ENGINEERING",
|
|
lid = "",
|
|
Name = "none",
|
|
Group = nil,
|
|
Unit = nil,
|
|
-- C_Ops = nil,
|
|
HeliGroup = nil,
|
|
HeliUnit = nil,
|
|
State = "",
|
|
}
|
|
|
|
--- CTLD_ENGINEERING class version.
|
|
-- @field #string version
|
|
CTLD_ENGINEERING.Version = "0.0.3"
|
|
|
|
--- Create a new instance.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @param #string Name
|
|
-- @param #string GroupName Name of Engineering #GROUP object
|
|
-- @param Wrapper.Group#GROUP HeliGroup HeliGroup
|
|
-- @param Wrapper.Unit#UNIT HeliUnit HeliUnit
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:New( Name, GroupName, HeliGroup, HeliUnit )
|
|
|
|
-- Inherit everything from BASE class.
|
|
local self = BASE:Inherit( self, BASE:New() ) -- #CTLD_ENGINEERING
|
|
|
|
-- BASE:I({Name, GroupName})
|
|
|
|
self.Name = Name or "Engineer Squad" -- #string
|
|
self.Group = GROUP:FindByName( GroupName ) -- Wrapper.Group#GROUP
|
|
self.Unit = self.Group:GetUnit( 1 ) -- Wrapper.Unit#UNIT
|
|
-- self.C_Ops = C_Ops -- Ops.CTLD#CTLD
|
|
self.HeliGroup = HeliGroup -- Wrapper.Group#GROUP
|
|
self.HeliUnit = HeliUnit -- Wrapper.Unit#UNIT
|
|
-- self.distance = Distance or UTILS.NMToMeters(1)
|
|
self.currwpt = nil -- Core.Point#COORDINATE
|
|
self.lid = string.format( "%s (%s) | ", self.Name, self.Version )
|
|
-- Start State.
|
|
self.State = "Stopped"
|
|
self.marktimer = 300 -- wait this many secs before trying a crate again
|
|
|
|
--[[ Add FSM transitions.
|
|
-- From State --> Event --> To State
|
|
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
|
|
self:AddTransition("*", "Status", "*")
|
|
self:AddTransition("*", "Search", "Searching")
|
|
self:AddTransition("*", "Move", "Moving")
|
|
self:AddTransition("*", "Arrive", "Arrived")
|
|
self:AddTransition("*", "Build", "Building")
|
|
self:AddTransition("*", "Done", "Running")
|
|
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
|
|
|
self:__Start(5)
|
|
--]]
|
|
self:Start()
|
|
local parent = self:GetParent( self )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Set the status
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @param #string State
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:SetStatus( State )
|
|
self.State = State
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Get the status
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @return #string State
|
|
function CTLD_ENGINEERING:GetStatus()
|
|
return self.State
|
|
end
|
|
|
|
--- (Internal) Check the status
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @param #string State
|
|
-- @return #boolean Outcome
|
|
function CTLD_ENGINEERING:IsStatus( State )
|
|
return self.State == State
|
|
end
|
|
|
|
--- (Internal) Check the negative status
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @param #string State
|
|
-- @return #boolean Outcome
|
|
function CTLD_ENGINEERING:IsNotStatus( State )
|
|
return self.State ~= State
|
|
end
|
|
|
|
--- (Internal) Set start status.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:Start()
|
|
self:T( self.lid .. "Start" )
|
|
self:SetStatus( "Running" )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Set stop status.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:Stop()
|
|
self:T( self.lid .. "Stop" )
|
|
self:SetStatus( "Stopped" )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Set build status.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:Build()
|
|
self:T( self.lid .. "Build" )
|
|
self:SetStatus( "Building" )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Set done status.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:Done()
|
|
self:T( self.lid .. "Done" )
|
|
local grp = self.Group -- Wrapper.Group#GROUP
|
|
grp:RelocateGroundRandomInRadius( 7, 100, false, false, "Diamond" )
|
|
self:SetStatus( "Running" )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Search for crates in reach.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @param #table crates Table of found crate Ops.CTLD#CTLD_CARGO objects.
|
|
-- @param #number number Number of crates found.
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:Search( crates, number )
|
|
self:T( self.lid .. "Search" )
|
|
self:SetStatus( "Searching" )
|
|
-- find crates close by
|
|
-- local COps = self.C_Ops -- Ops.CTLD#CTLD
|
|
local dist = self.distance -- #number
|
|
local group = self.Group -- Wrapper.Group#GROUP
|
|
-- local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table
|
|
local ctable = {}
|
|
local ind = 0
|
|
if number > 0 then
|
|
-- get set of dropped only
|
|
for _, _cargo in pairs( crates ) do
|
|
local cgotype = _cargo:GetType()
|
|
if _cargo:WasDropped() and cgotype ~= CTLD_CARGO.Enum.STATIC then
|
|
local ok = false
|
|
local chalk = _cargo:GetMark()
|
|
if chalk == nil then
|
|
ok = true
|
|
else
|
|
-- have we tried this cargo recently?
|
|
local tag = chalk.tag or "none"
|
|
local timestamp = chalk.timestamp or 0
|
|
-- self:I({chalk})
|
|
-- enough time gone?
|
|
local gone = timer.getAbsTime() - timestamp
|
|
-- self:I({time=gone})
|
|
if gone >= self.marktimer then
|
|
ok = true
|
|
_cargo:WipeMark()
|
|
end -- end time check
|
|
end -- end chalk
|
|
if ok then
|
|
local chalk = {}
|
|
chalk.tag = "Engineers"
|
|
chalk.timestamp = timer.getAbsTime()
|
|
_cargo:AddMark( chalk )
|
|
ind = ind + 1
|
|
table.insert( ctable, ind, _cargo )
|
|
end
|
|
end -- end dropped
|
|
end -- end for
|
|
end -- end number
|
|
|
|
if ind > 0 then
|
|
local crate = ctable[1] -- Ops.CTLD#CTLD_CARGO
|
|
local static = crate:GetPositionable() -- Wrapper.Static#STATIC
|
|
local crate_pos = static:GetCoordinate() -- Core.Point#COORDINATE
|
|
local gpos = group:GetCoordinate() -- Core.Point#COORDINATE
|
|
-- see how far we are from the crate
|
|
local distance = self:_GetDistance( gpos, crate_pos )
|
|
self:T( string.format( "%s Distance to crate: %d", self.lid, distance ) )
|
|
-- move there
|
|
if distance > 30 and distance ~= -1 and self:IsStatus( "Searching" ) then
|
|
group:RouteGroundTo( crate_pos, 15, "Line abreast", 1 )
|
|
self.currwpt = crate_pos -- Core.Point#COORDINATE
|
|
self:Move()
|
|
elseif distance <= 30 and distance ~= -1 then
|
|
-- arrived
|
|
self:Arrive()
|
|
end
|
|
else
|
|
self:T( self.lid .. "No crates in reach!" )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Move towards crates in reach.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:Move()
|
|
self:T( self.lid .. "Move" )
|
|
self:SetStatus( "Moving" )
|
|
-- check if we arrived on target
|
|
-- local COps = self.C_Ops -- Ops.CTLD#CTLD
|
|
local group = self.Group -- Wrapper.Group#GROUP
|
|
local tgtpos = self.currwpt -- Core.Point#COORDINATE
|
|
local gpos = group:GetCoordinate() -- Core.Point#COORDINATE
|
|
-- see how far we are from the crate
|
|
local distance = self:_GetDistance( gpos, tgtpos )
|
|
self:T( string.format( "%s Distance remaining: %d", self.lid, distance ) )
|
|
if distance <= 30 and distance ~= -1 then
|
|
-- arrived
|
|
self:Arrive()
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Arrived at crates in reach. Stop group.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @return #CTLD_ENGINEERING self
|
|
function CTLD_ENGINEERING:Arrive()
|
|
self:T( self.lid .. "Arrive" )
|
|
self:SetStatus( "Arrived" )
|
|
self.currwpt = nil
|
|
local Grp = self.Group -- Wrapper.Group#GROUP
|
|
Grp:RouteStop()
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Return distance in meters between two coordinates.
|
|
-- @param #CTLD_ENGINEERING self
|
|
-- @param Core.Point#COORDINATE _point1 Coordinate one
|
|
-- @param Core.Point#COORDINATE _point2 Coordinate two
|
|
-- @return #number Distance in meters or -1
|
|
function CTLD_ENGINEERING:_GetDistance( _point1, _point2 )
|
|
self:T( self.lid .. " _GetDistance" )
|
|
if _point1 and _point2 then
|
|
local distance1 = _point1:Get2DDistance( _point2 )
|
|
local distance2 = _point1:DistanceFromPointVec2( _point2 )
|
|
-- self:I({dist1=distance1, dist2=distance2})
|
|
if distance1 and type( distance1 ) == "number" then
|
|
return distance1
|
|
elseif distance2 and type( distance2 ) == "number" then
|
|
return distance2
|
|
else
|
|
self:E( "*****Cannot calculate distance!" )
|
|
self:E( { _point1, _point2 } )
|
|
return -1
|
|
end
|
|
else
|
|
self:E( "******Cannot calculate distance!" )
|
|
self:E( { _point1, _point2 } )
|
|
return -1
|
|
end
|
|
end
|
|
------------------------------------------------------
|
|
--- **CTLD_CARGO** class, extends Core.Base#BASE
|
|
-- @type CTLD_CARGO
|
|
-- @field #number ID ID of this cargo.
|
|
-- @field #string Name Name for menu.
|
|
-- @field #table Templates Table of #POSITIONABLE objects.
|
|
-- @field #CTLD_CARGO.Enum CargoType Enumerator of Type.
|
|
-- @field #boolean HasBeenMoved Flag for moving.
|
|
-- @field #boolean LoadDirectly Flag for direct loading.
|
|
-- @field #number CratesNeeded Crates needed to build.
|
|
-- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission.
|
|
-- @field #boolean HasBeenDropped True if dropped from heli.
|
|
-- @field #number PerCrateMass Mass in kg
|
|
-- @field #number Stock Number of builds available, -1 for unlimited
|
|
-- @extends Core.Base#BASE
|
|
CTLD_CARGO = {
|
|
ClassName = "CTLD_CARGO",
|
|
ID = 0,
|
|
Name = "none",
|
|
Templates = {
|
|
},
|
|
CargoType = "none",
|
|
HasBeenMoved = false,
|
|
LoadDirectly = false,
|
|
CratesNeeded = 0,
|
|
Positionable = nil,
|
|
HasBeenDropped = false,
|
|
PerCrateMass = 0,
|
|
Stock = nil,
|
|
Mark = nil,
|
|
}
|
|
|
|
--- Define cargo types.
|
|
-- @type CTLD_CARGO.Enum
|
|
-- @field #string Type Type of Cargo.
|
|
CTLD_CARGO.Enum = {
|
|
["VEHICLE"] = "Vehicle", -- #string vehicles
|
|
["TROOPS"] = "Troops", -- #string troops
|
|
["FOB"] = "FOB", -- #string FOB
|
|
["CRATE"] = "Crate", -- #string crate
|
|
["REPAIR"] = "Repair", -- #string repair
|
|
["ENGINEERS"] = "Engineers", -- #string engineers
|
|
["STATIC"] = "Static", -- #string engineers
|
|
}
|
|
|
|
--- Function to create new CTLD_CARGO object.
|
|
-- @param #CTLD_CARGO self
|
|
-- @param #number ID ID of this #CTLD_CARGO
|
|
-- @param #string Name Name for menu.
|
|
-- @param #table Templates Table of #POSITIONABLE objects.
|
|
-- @param #CTLD_CARGO.Enum SortEnum Enumerator of Type.
|
|
-- @param #boolean HasBeenMoved Flag for moving.
|
|
-- @param #boolean LoadDirectly Flag for direct loading.
|
|
-- @param #number CratesNeeded Crates needed to build.
|
|
-- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission.
|
|
-- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper.
|
|
-- @param #number PerCrateMass Mass in kg
|
|
-- @param #number Stock Number of builds available, nil for unlimited
|
|
-- @return #CTLD_CARGO self
|
|
function CTLD_CARGO:New( ID, Name, Templates, SortEnum, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock )
|
|
-- Inherit everything from BASE class.
|
|
local self = BASE:Inherit( self, BASE:New() ) -- #CTLD
|
|
self:T( { ID, Name, Templates, SortEnum, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped } )
|
|
self.ID = ID or math.random( 100000, 1000000 )
|
|
self.Name = Name or "none" -- #string
|
|
self.Templates = Templates or {} -- #table
|
|
self.CargoType = SortEnum or "type" -- #CTLD_CARGO.Enum
|
|
self.HasBeenMoved = HasBeenMoved or false -- #boolean
|
|
self.LoadDirectly = LoadDirectly or false -- #boolean
|
|
self.CratesNeeded = CratesNeeded or 0 -- #number
|
|
self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE
|
|
self.HasBeenDropped = Dropped or false -- #boolean
|
|
self.PerCrateMass = PerCrateMass or 0 -- #number
|
|
self.Stock = Stock or nil -- #number
|
|
self.Mark = nil
|
|
return self
|
|
end
|
|
|
|
--- Query ID.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #number ID
|
|
function CTLD_CARGO:GetID()
|
|
return self.ID
|
|
end
|
|
|
|
--- Query Mass.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #number Mass in kg
|
|
function CTLD_CARGO:GetMass()
|
|
return self.PerCrateMass
|
|
end
|
|
--- Query Name.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #string Name
|
|
function CTLD_CARGO:GetName()
|
|
return self.Name
|
|
end
|
|
|
|
--- Query Templates.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #table Templates
|
|
function CTLD_CARGO:GetTemplates()
|
|
return self.Templates
|
|
end
|
|
|
|
--- Query has moved.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #boolean Has moved
|
|
function CTLD_CARGO:HasMoved()
|
|
return self.HasBeenMoved
|
|
end
|
|
|
|
--- Query was dropped.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #boolean Has been dropped.
|
|
function CTLD_CARGO:WasDropped()
|
|
return self.HasBeenDropped
|
|
end
|
|
|
|
--- Query directly loadable.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #boolean loadable
|
|
function CTLD_CARGO:CanLoadDirectly()
|
|
return self.LoadDirectly
|
|
end
|
|
|
|
--- Query number of crates or troopsize.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #number Crates or size of troops.
|
|
function CTLD_CARGO:GetCratesNeeded()
|
|
return self.CratesNeeded
|
|
end
|
|
|
|
--- Query type.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #CTLD_CARGO.Enum Type
|
|
function CTLD_CARGO:GetType()
|
|
return self.CargoType
|
|
end
|
|
|
|
--- Query type.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return Wrapper.Positionable#POSITIONABLE Positionable
|
|
function CTLD_CARGO:GetPositionable()
|
|
return self.Positionable
|
|
end
|
|
|
|
--- Set HasMoved.
|
|
-- @param #CTLD_CARGO self
|
|
-- @param #boolean moved
|
|
function CTLD_CARGO:SetHasMoved( moved )
|
|
self.HasBeenMoved = moved or false
|
|
end
|
|
|
|
--- Query if cargo has been loaded.
|
|
-- @param #CTLD_CARGO self
|
|
-- @param #boolean loaded
|
|
function CTLD_CARGO:Isloaded()
|
|
if self.HasBeenMoved and not self.WasDropped() then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- Set WasDropped.
|
|
-- @param #CTLD_CARGO self
|
|
-- @param #boolean dropped
|
|
function CTLD_CARGO:SetWasDropped( dropped )
|
|
self.HasBeenDropped = dropped or false
|
|
end
|
|
|
|
--- Get Stock.
|
|
-- @param #CTLD_CARGO self
|
|
-- @return #number Stock
|
|
function CTLD_CARGO:GetStock()
|
|
if self.Stock then
|
|
return self.Stock
|
|
else
|
|
return -1
|
|
end
|
|
end
|
|
|
|
--- Add Stock.
|
|
-- @param #CTLD_CARGO self
|
|
-- @param #number Number to add, one if nil.
|
|
-- @return #CTLD_CARGO self
|
|
function CTLD_CARGO:AddStock( Number )
|
|
if self.Stock then -- Stock nil?
|
|
local number = Number or 1
|
|
self.Stock = self.Stock + number
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Remove Stock.
|
|
-- @param #CTLD_CARGO self
|
|
-- @param #number Number to reduce, one if nil.
|
|
-- @return #CTLD_CARGO self
|
|
function CTLD_CARGO:RemoveStock( Number )
|
|
if self.Stock then -- Stock nil?
|
|
local number = Number or 1
|
|
self.Stock = self.Stock - number
|
|
if self.Stock < 0 then
|
|
self.Stock = 0
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Query crate type for REPAIR
|
|
-- @param #CTLD_CARGO self
|
|
-- @param #boolean
|
|
function CTLD_CARGO:IsRepair()
|
|
if self.CargoType == "Repair" then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- Query crate type for STATIC
|
|
-- @param #CTLD_CARGO self
|
|
-- @param #boolean
|
|
function CTLD_CARGO:IsStatic()
|
|
if self.CargoType == "Static" then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
function CTLD_CARGO:AddMark( Mark )
|
|
self.Mark = Mark
|
|
return self
|
|
end
|
|
|
|
function CTLD_CARGO:GetMark( Mark )
|
|
return self.Mark
|
|
end
|
|
|
|
function CTLD_CARGO:WipeMark()
|
|
self.Mark = nil
|
|
return self
|
|
end
|
|
|
|
end
|
|
|
|
do
|
|
-------------------------------------------------------------------------
|
|
--- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM
|
|
-- @type CTLD
|
|
-- @field #string ClassName Name of the class.
|
|
-- @field #number verbose Verbosity level.
|
|
-- @field #string lid Class id string for output to DCS log file.
|
|
-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`.
|
|
-- @extends Core.Fsm#FSM
|
|
|
|
--- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there's POG stuff to be done.* (Mil Saying)
|
|
--
|
|
-- ===
|
|
--
|
|
-- 
|
|
--
|
|
-- # CTLD Concept
|
|
--
|
|
-- * MOOSE-based CTLD for Players.
|
|
-- * Object oriented refactoring of Ciribob's fantastic CTLD script.
|
|
-- * No need for extra MIST loading.
|
|
-- * Additional events to tailor your mission.
|
|
-- * ANY late activated group can serve as cargo, either as troops, crates, which have to be build on-location, or static like ammo chests.
|
|
-- * Option to persist (save&load) your dropped troops, crates and vehicles.
|
|
--
|
|
-- ## 0. Prerequisites
|
|
--
|
|
-- You need to load an .ogg sound file for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that.
|
|
-- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces.
|
|
--
|
|
-- ## 1. Basic Setup
|
|
--
|
|
-- ## 1.1 Create and start a CTLD instance
|
|
--
|
|
-- A basic setup example is the following:
|
|
--
|
|
-- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I"
|
|
-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I")
|
|
-- my_ctld:__Start(5)
|
|
--
|
|
-- ## 1.2 Add cargo types available
|
|
--
|
|
-- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects:
|
|
--
|
|
-- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3
|
|
-- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed)
|
|
-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3)
|
|
-- -- if you want to add weight to your Heli, troops can have a weight in kg **per person**. Currently no max weight checked. Fly carefully.
|
|
-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3,80)
|
|
--
|
|
-- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4. No weight. We only have 2 in stock:
|
|
-- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4,nil,2)
|
|
--
|
|
-- -- add an engineers unit called "Wrenches" using template "Engineers", of type ENGINEERS with size 2. Engineers can be loaded, dropped,
|
|
-- -- and extracted like troops. However, they will seek to build and/or repair crates found in a given radius. Handy if you can't stay
|
|
-- -- to build or repair or under fire.
|
|
-- my_ctld:AddTroopsCargo("Wrenches",{"Engineers"},CTLD_CARGO.Enum.ENGINEERS,4)
|
|
-- myctld.EngineerSearch = 2000 -- teams will search for crates in this radius.
|
|
--
|
|
-- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build
|
|
-- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects
|
|
-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2)
|
|
-- -- if you want to add weight to your Heli, crates can have a weight in kg **per crate**. Currently no max weight checked. Fly carefully.
|
|
-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775)
|
|
-- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock.
|
|
-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10)
|
|
--
|
|
-- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build:
|
|
-- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4)
|
|
--
|
|
-- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair
|
|
-- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1)
|
|
-- my_ctld.repairtime = 300 -- takes 300 seconds to repair something
|
|
--
|
|
-- -- add static cargo objects, e.g ammo chests - the name needs to refer to a STATIC object in the mission editor,
|
|
-- -- here: it's the UNIT name (not the GROUP name!), the second parameter is the weight in kg.
|
|
-- my_ctld:AddStaticsCargo("Ammunition",500)
|
|
--
|
|
-- ## 1.3 Add logistics zones
|
|
--
|
|
-- Add zones for loading troops and crates and dropping, building crates
|
|
--
|
|
-- -- Add a zone of type LOAD to our setup. Players can load troops and crates.
|
|
-- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside the zone.
|
|
-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon.
|
|
-- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true)
|
|
--
|
|
-- -- Add a zone of type DROP. Players can drop crates here.
|
|
-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon.
|
|
-- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters.
|
|
-- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true)
|
|
--
|
|
-- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options.
|
|
-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon.
|
|
-- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false)
|
|
--
|
|
-- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true)
|
|
--
|
|
-- -- Add a zone of type SHIP to our setup. Players can load troops and crates from this ship
|
|
-- -- "Tarawa" is the unitname (callsign) of the ship from the ME. Players can load, if they are inside the zone.
|
|
-- -- The ship is 240 meters long and 20 meters wide.
|
|
-- -- Note that you need to adjust the max hover height to deck height plus 5 meters or so for loading to work.
|
|
-- -- When the ship is moving, forcing hoverload might not be a good idea.
|
|
-- my_ctld:AddCTLDZone("Tarawa",CTLD.CargoZoneType.SHIP,SMOKECOLOR.Blue,true,true,240,20)
|
|
--
|
|
-- ## 2. Options
|
|
--
|
|
-- The following options are available (with their defaults). Only set the ones you want changed:
|
|
--
|
|
-- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD.
|
|
-- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only.
|
|
-- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere.
|
|
-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load.
|
|
-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load.
|
|
-- my_ctld.forcehoverload = true -- Crates (not: troops) can **only** be loaded while hovering.
|
|
-- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows.
|
|
-- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters).
|
|
-- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone...
|
|
-- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters)
|
|
-- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters)
|
|
-- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages.
|
|
-- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit.
|
|
-- my_ctld.cratecountry = country.id.GERMANY -- ID of crates. Will default to country.id.RUSSIA for RED coalition setups.
|
|
-- my_ctld.allowcratepickupagain = true -- allow re-pickup crates that were dropped.
|
|
-- my_ctld.enableslingload = false -- allow cargos to be slingloaded - might not work for all cargo types
|
|
-- my_ctld.pilotmustopendoors = false -- force opening of doors
|
|
-- my_ctld.SmokeColor = SMOKECOLOR.Red -- color to use when dropping smoke from heli
|
|
-- my_ctld.FlareColor = FLARECOLOR.Red -- color to use when flaring from heli
|
|
--
|
|
-- ## 2.1 User functions
|
|
--
|
|
-- ### 2.1.1 Adjust or add chopper unit-type capabilities
|
|
--
|
|
-- Use this function to adjust what a heli type can or cannot do:
|
|
--
|
|
-- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design.
|
|
-- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type:
|
|
-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8, 12)
|
|
--
|
|
-- -- Default unit type capabilities are:
|
|
--
|
|
-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12},
|
|
-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12},
|
|
-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12},
|
|
-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12},
|
|
-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15},
|
|
-- ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15},
|
|
-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15},
|
|
-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18},
|
|
-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18},
|
|
-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25},
|
|
--
|
|
-- ### 2.1.2 Activate and deactivate zones
|
|
--
|
|
-- Activate a zone:
|
|
--
|
|
-- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType:
|
|
-- my_ctld:ActivateZone(Name,CTLD.CargoZoneType.MOVE)
|
|
--
|
|
-- Deactivate a zone:
|
|
--
|
|
-- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType:
|
|
-- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP)
|
|
--
|
|
-- ## 2.1.3 Limit and manage available resources
|
|
--
|
|
-- When adding generic cargo types, you can effectively limit how many units can be dropped/build by the players, e.g.
|
|
--
|
|
-- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock.
|
|
-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10)
|
|
--
|
|
-- You can manually add or remove the available stock like so:
|
|
--
|
|
-- -- Crates
|
|
-- my_ctld:AddStockCrates("Humvee", 2)
|
|
-- my_ctld:RemoveStockCrates("Humvee", 2)
|
|
--
|
|
-- -- Troops
|
|
-- my_ctld:AddStockTroops("Anti-Air", 2)
|
|
-- my_ctld:RemoveStockTroops("Anti-Air", 2)
|
|
--
|
|
-- Notes:
|
|
-- Troops dropped back into a LOAD zone will effectively be added to the stock. Crates lost in e.g. a heli crash are just that - lost.
|
|
--
|
|
-- ## 3. Events
|
|
--
|
|
-- The class comes with a number of FSM-based events that missions designers can use to shape their mission.
|
|
-- These are:
|
|
--
|
|
-- ## 3.1 OnAfterTroopsPickedUp
|
|
--
|
|
-- This function is called when a player has loaded Troops:
|
|
--
|
|
-- function my_ctld:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo)
|
|
-- ... your code here ...
|
|
-- end
|
|
--
|
|
-- ## 3.2 OnAfterCratesPickedUp
|
|
--
|
|
-- This function is called when a player has picked up crates:
|
|
--
|
|
-- function my_ctld:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo)
|
|
-- ... your code here ...
|
|
-- end
|
|
--
|
|
-- ## 3.3 OnAfterTroopsDeployed
|
|
--
|
|
-- This function is called when a player has deployed troops into the field:
|
|
--
|
|
-- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops)
|
|
-- ... your code here ...
|
|
-- end
|
|
--
|
|
-- ## 3.4 OnAfterTroopsExtracted
|
|
--
|
|
-- This function is called when a player has re-boarded already deployed troops from the field:
|
|
--
|
|
-- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops)
|
|
-- ... your code here ...
|
|
-- end
|
|
--
|
|
-- ## 3.5 OnAfterCratesDropped
|
|
--
|
|
-- This function is called when a player has deployed crates to a DROP zone:
|
|
--
|
|
-- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable)
|
|
-- ... your code here ...
|
|
-- end
|
|
--
|
|
-- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired
|
|
--
|
|
-- This function is called when a player has build a vehicle or FOB:
|
|
--
|
|
-- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle)
|
|
-- ... your code here ...
|
|
-- end
|
|
--
|
|
-- function my_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle)
|
|
-- ... your code here ...
|
|
-- end
|
|
--
|
|
-- ## 3.7 A simple SCORING example:
|
|
--
|
|
-- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport)
|
|
--
|
|
-- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable)
|
|
-- local points = 10
|
|
-- if Unit then
|
|
-- local PlayerName = Unit:GetPlayerName()
|
|
-- my_scoring:_AddPlayerFromUnit( Unit )
|
|
-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points)
|
|
-- end
|
|
-- end
|
|
--
|
|
-- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle)
|
|
-- local points = 5
|
|
-- if Unit then
|
|
-- local PlayerName = Unit:GetPlayerName()
|
|
-- my_scoring:_AddPlayerFromUnit( Unit )
|
|
-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points)
|
|
-- end
|
|
-- end
|
|
--
|
|
-- ## 4. F10 Menu structure
|
|
--
|
|
-- CTLD management menu is under the F10 top menu and called "CTLD"
|
|
--
|
|
-- ## 4.1 Manage Crates
|
|
--
|
|
-- Use this entry to get, load, list nearby, drop, build and repair crates. Also see options.
|
|
--
|
|
-- ## 4.2 Manage Troops
|
|
--
|
|
-- Use this entry to load, drop and extract troops. NOTE - with extract you can only load troops from the field that were deployed prior.
|
|
-- Currently limited CTLD_CARGO troops, which are build from **one** template. Also, this will heal/complete your units as they are respawned.
|
|
--
|
|
-- ## 4.3 List boarded cargo
|
|
--
|
|
-- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops.
|
|
--
|
|
-- ## 4.4 Smoke & Flare zones nearby or drop smoke or flare from Heli
|
|
--
|
|
-- Does what it says.
|
|
--
|
|
-- ## 4.5 List active zone beacons
|
|
--
|
|
-- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()`
|
|
--
|
|
-- ## 4.6 Show hover parameters
|
|
--
|
|
-- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights.
|
|
--
|
|
-- ## 4.7 List Inventory
|
|
--
|
|
-- Lists invetory of available units to drop or build.
|
|
--
|
|
-- ## 5. Support for Hercules mod by Anubis
|
|
--
|
|
-- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can
|
|
-- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class.
|
|
--
|
|
-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I")
|
|
--
|
|
-- Enable these options for Hercules support:
|
|
--
|
|
-- my_ctld.enableHercules = true
|
|
-- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft
|
|
-- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft
|
|
-- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn
|
|
--
|
|
-- Also, the following options need to be set to `true`:
|
|
--
|
|
-- my_ctld.useprefix = true -- this is true by default and MUST BE ON.
|
|
--
|
|
-- Standard transport capabilities as per the real Hercules are:
|
|
--
|
|
-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers
|
|
--
|
|
-- ## 6. Save and load back units - persistance
|
|
--
|
|
-- You can save and later load back units dropped or build to make your mission persistent.
|
|
-- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts.
|
|
-- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you.
|
|
--
|
|
-- Use the following options to manage your saves:
|
|
--
|
|
-- my_ctld.enableLoadSave = true -- allow auto-saving and loading of files
|
|
-- my_ctld.saveinterval = 600 -- save every 10 minutes
|
|
-- my_ctld.filename = "missionsave.csv" -- example filename
|
|
-- my_ctld.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path
|
|
-- my_ctld.eventoninject = true -- fire OnAfterCratesBuild and OnAfterTroopsDeployed events when loading (uses Inject functions)
|
|
--
|
|
-- Then use an initial load at the beginning of your mission:
|
|
--
|
|
-- my_ctld:__Load(10)
|
|
--
|
|
-- **Caveat:**
|
|
-- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved.
|
|
--
|
|
-- @field #CTLD
|
|
CTLD = {
|
|
ClassName = "CTLD",
|
|
verbose = 0,
|
|
lid = "",
|
|
coalition = 1,
|
|
coalitiontxt = "blue",
|
|
PilotGroups = {}, -- #GROUP_SET of heli pilots
|
|
CtldUnits = {}, -- Table of helicopter #GROUPs
|
|
FreeVHFFrequencies = {}, -- Table of VHF
|
|
FreeUHFFrequencies = {}, -- Table of UHF
|
|
FreeFMFrequencies = {}, -- Table of FM
|
|
CargoCounter = 0,
|
|
wpZones = {},
|
|
Cargo_Troops = {}, -- generic troops objects
|
|
Cargo_Crates = {}, -- generic crate objects
|
|
Loaded_Cargo = {}, -- cargo aboard units
|
|
Spawned_Crates = {}, -- Holds objects for crates spawned generally
|
|
Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects
|
|
CrateDistance = 35, -- list crates in this radius
|
|
debug = false,
|
|
wpZones = {},
|
|
dropOffZones = {},
|
|
pickupZones = {},
|
|
}
|
|
|
|
------------------------------
|
|
-- DONE: Zone Checks
|
|
-- DONE: TEST Hover load and unload
|
|
-- DONE: Crate unload
|
|
-- DONE: Hover (auto-)load
|
|
-- DONE: (More) Housekeeping
|
|
-- DONE: Troops running to WP Zone
|
|
-- DONE: Zone Radio Beacons
|
|
-- DONE: Stats Running
|
|
-- DONE: Added support for Hercules
|
|
-- TODO: Possibly - either/or loading crates and troops
|
|
-- DONE: Make inject respect existing cargo types
|
|
-- DONE: Drop beacons or flares/smoke
|
|
-- DONE: Add statics as cargo
|
|
-- DONE: List cargo in stock
|
|
-- DONE: Limit of troops, crates buildable?
|
|
-- DONE: Allow saving of Troops & Vehicles
|
|
------------------------------
|
|
|
|
--- Radio Beacons
|
|
-- @type CTLD.ZoneBeacon
|
|
-- @field #string name -- Name of zone for the coordinate
|
|
-- @field #number frequency -- in mHz
|
|
-- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM
|
|
|
|
--- Zone Info.
|
|
-- @type CTLD.CargoZone
|
|
-- @field #string name Name of Zone.
|
|
-- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red.
|
|
-- @field #boolean active Active or not.
|
|
-- @field #string type Type of zone, i.e. load,drop,move,ship
|
|
-- @field #boolean hasbeacon Create and run radio beacons if active.
|
|
-- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon
|
|
-- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon
|
|
-- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon
|
|
-- @field #number shiplength For ships - length of ship
|
|
-- @field #number shipwidth For ships - width of ship
|
|
|
|
--- Zone Type Info.
|
|
-- @type CTLD.CargoZoneType
|
|
CTLD.CargoZoneType = {
|
|
LOAD = "load",
|
|
DROP = "drop",
|
|
MOVE = "move",
|
|
SHIP = "ship",
|
|
}
|
|
|
|
--- Buildable table info.
|
|
-- @type CTLD.Buildable
|
|
-- @field #string Name Name of the object.
|
|
-- @field #number Required Required crates.
|
|
-- @field #number Found Found crates.
|
|
-- @field #table Template Template names for this build.
|
|
-- @field #boolean CanBuild Is buildable or not.
|
|
-- @field #CTLD_CARGO.Enum Type Type enumerator (for moves).
|
|
|
|
--- Unit capabilities.
|
|
-- @type CTLD.UnitCapabilities
|
|
-- @field #string type Unit type.
|
|
-- @field #boolean crates Can transport crate.
|
|
-- @field #boolean troops Can transport troops.
|
|
-- @field #number cratelimit Number of crates transportable.
|
|
-- @field #number trooplimit Number of troop units transportable.
|
|
CTLD.UnitTypes = {
|
|
["SA342Mistral"] = { type = "SA342Mistral", crates = false, troops = true, cratelimit = 0, trooplimit = 4, length = 12 },
|
|
["SA342L"] = { type = "SA342L", crates = false, troops = true, cratelimit = 0, trooplimit = 2, length = 12 },
|
|
["SA342M"] = { type = "SA342M", crates = false, troops = true, cratelimit = 0, trooplimit = 4, length = 12 },
|
|
["SA342Minigun"] = { type = "SA342Minigun", crates = false, troops = true, cratelimit = 0, trooplimit = 2, length = 12 },
|
|
["UH-1H"] = { type = "UH-1H", crates = true, troops = true, cratelimit = 1, trooplimit = 8, length = 15 },
|
|
["Mi-8MTV2"] = { type = "Mi-8MTV2", crates = true, troops = true, cratelimit = 2, trooplimit = 12, length = 15 },
|
|
["Mi-8MT"] = { type = "Mi-8MTV2", crates = true, troops = true, cratelimit = 2, trooplimit = 12, length = 15 },
|
|
["Ka-50"] = { type = "Ka-50", crates = false, troops = false, cratelimit = 0, trooplimit = 0, length = 15 },
|
|
["Mi-24P"] = { type = "Mi-24P", crates = true, troops = true, cratelimit = 2, trooplimit = 8, length = 18 },
|
|
["Mi-24V"] = { type = "Mi-24V", crates = true, troops = true, cratelimit = 2, trooplimit = 8, length = 18 },
|
|
["Hercules"] = { type = "Hercules", crates = true, troops = true, cratelimit = 7, trooplimit = 64, length = 25 }, -- 19t cargo, 64 paratroopers. Actually it's longer, but the center coord is off-center of the model.
|
|
}
|
|
|
|
--- CTLD class version.
|
|
-- @field #string version
|
|
CTLD.version = "1.0.1"
|
|
|
|
--- Instantiate a new CTLD.
|
|
-- @param #CTLD self
|
|
-- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL
|
|
-- @param #table Prefixes Table of pilot prefixes.
|
|
-- @param #string Alias Alias of this CTLD for logging.
|
|
-- @return #CTLD self
|
|
function CTLD:New( Coalition, Prefixes, Alias )
|
|
-- Inherit everything from FSM class.
|
|
local self = BASE:Inherit( self, FSM:New() ) -- #CTLD
|
|
|
|
BASE:T( { Coalition, Prefixes, Alias } )
|
|
|
|
-- set Coalition
|
|
if Coalition and type( Coalition ) == "string" then
|
|
if Coalition == "blue" then
|
|
self.coalition = coalition.side.BLUE
|
|
self.coalitiontxt = Coalition
|
|
elseif Coalition == "red" then
|
|
self.coalition = coalition.side.RED
|
|
self.coalitiontxt = Coalition
|
|
elseif Coalition == "neutral" then
|
|
self.coalition = coalition.side.NEUTRAL
|
|
self.coalitiontxt = Coalition
|
|
else
|
|
self:E( "ERROR: Unknown coalition in CTLD!" )
|
|
end
|
|
else
|
|
self.coalition = Coalition
|
|
self.coalitiontxt = string.lower( UTILS.GetCoalitionName( self.coalition ) )
|
|
end
|
|
|
|
-- Set alias.
|
|
if Alias then
|
|
self.alias = tostring( Alias )
|
|
else
|
|
self.alias = "UNHCR"
|
|
if self.coalition then
|
|
if self.coalition == coalition.side.RED then
|
|
self.alias = "Red CTLD"
|
|
elseif self.coalition == coalition.side.BLUE then
|
|
self.alias = "Blue CTLD"
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Set some string id for output to DCS.log file.
|
|
self.lid = string.format( "%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName( self.coalition ) or "unknown" )
|
|
|
|
-- Start State.
|
|
self:SetStartState( "Stopped" )
|
|
|
|
-- Add FSM transitions.
|
|
-- From State --> Event --> To State
|
|
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
|
|
self:AddTransition("*", "Status", "*") -- CTLD status update.
|
|
self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event.
|
|
self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event.
|
|
self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event.
|
|
self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event.
|
|
self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event.
|
|
self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event.
|
|
self:AddTransition("*", "CratesBuild", "*") -- CTLD build event.
|
|
self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event.
|
|
self:AddTransition("*", "Load", "*") -- CTLD load event.
|
|
self:AddTransition("*", "Save", "*") -- CTLD save event.
|
|
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
|
|
|
-- tables
|
|
self.PilotGroups = {}
|
|
self.CtldUnits = {}
|
|
|
|
-- Beacons
|
|
self.FreeVHFFrequencies = {}
|
|
self.FreeUHFFrequencies = {}
|
|
self.FreeFMFrequencies = {}
|
|
self.UsedVHFFrequencies = {}
|
|
self.UsedUHFFrequencies = {}
|
|
self.UsedFMFrequencies = {}
|
|
|
|
-- radio beacons
|
|
self.RadioSound = "beacon.ogg"
|
|
|
|
-- zones stuff
|
|
self.pickupZones = {}
|
|
self.dropOffZones = {}
|
|
self.wpZones = {}
|
|
self.shipZones = {}
|
|
|
|
-- Cargo
|
|
self.Cargo_Crates = {}
|
|
self.Cargo_Troops = {}
|
|
self.Cargo_Statics = {}
|
|
self.Loaded_Cargo = {}
|
|
self.Spawned_Crates = {}
|
|
self.Spawned_Cargo = {}
|
|
self.MenusDone = {}
|
|
self.DroppedTroops = {}
|
|
self.DroppedCrates = {}
|
|
self.CargoCounter = 0
|
|
self.CrateCounter = 0
|
|
self.TroopCounter = 0
|
|
|
|
-- added engineering
|
|
self.Engineers = 0 -- #number use as counter
|
|
self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects
|
|
self.EngineerSearch = 2000 -- #number search distance for crates to build or repair
|
|
|
|
-- setup
|
|
self.CrateDistance = 35 -- list/load crates in this radius
|
|
self.ExtractFactor = 3.33 -- factor for troops extraction, i.e. CrateDistance * Extractfactor
|
|
self.prefixes = Prefixes or { "Cargoheli" }
|
|
-- self.I({prefixes = self.prefixes})
|
|
self.useprefix = true
|
|
|
|
self.maximumHoverHeight = 15
|
|
self.minimumHoverHeight = 4
|
|
self.forcehoverload = true
|
|
self.hoverautoloading = true
|
|
self.dropcratesanywhere = false -- #1570
|
|
|
|
self.smokedistance = 2000
|
|
self.movetroopstowpzone = true
|
|
self.movetroopsdistance = 5000
|
|
|
|
-- added support Hercules Mod
|
|
self.enableHercules = false
|
|
self.HercMinAngels = 165 -- for troop/cargo drop via chute
|
|
self.HercMaxAngels = 2000 -- for troop/cargo drop via chute
|
|
self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps
|
|
|
|
-- message suppression
|
|
self.suppressmessages = false
|
|
|
|
-- time to repair a unit/group
|
|
self.repairtime = 300
|
|
|
|
-- place spawned crates in front of aircraft
|
|
self.placeCratesAhead = false
|
|
|
|
-- country of crates spawned
|
|
self.cratecountry = country.id.GERMANY
|
|
|
|
-- for opening doors
|
|
self.pilotmustopendoors = false
|
|
|
|
if self.coalition == coalition.side.RED then
|
|
self.cratecountry = country.id.RUSSIA
|
|
end
|
|
|
|
-- load and save dropped TROOPS
|
|
self.enableLoadSave = false
|
|
self.filepath = nil
|
|
self.saveinterval = 600
|
|
self.eventoninject = true
|
|
|
|
local AliaS = string.gsub( self.alias, " ", "_" )
|
|
self.filename = string.format( "CTLD_%s_Persist.csv", AliaS )
|
|
|
|
-- allow re-pickup crates
|
|
self.allowcratepickupagain = true
|
|
|
|
-- slingload
|
|
self.enableslingload = false
|
|
|
|
-- Smokes and Flares
|
|
self.SmokeColor = SMOKECOLOR.Red
|
|
self.FlareColor = FLARECOLOR.Red
|
|
|
|
for i = 1, 100 do
|
|
math.random()
|
|
end
|
|
|
|
self:_GenerateVHFrequencies()
|
|
self:_GenerateUHFrequencies()
|
|
self:_GenerateFMFrequencies()
|
|
|
|
------------------------
|
|
--- Pseudo Functions ---
|
|
------------------------
|
|
|
|
--- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers.
|
|
-- @function [parent=#CTLD] Start
|
|
-- @param #CTLD self
|
|
|
|
--- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers.
|
|
-- @function [parent=#CTLD] __Start
|
|
-- @param #CTLD self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers.
|
|
-- @param #CTLD self
|
|
|
|
--- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers.
|
|
-- @function [parent=#CTLD] __Stop
|
|
-- @param #CTLD self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- Triggers the FSM event "Status".
|
|
-- @function [parent=#CTLD] Status
|
|
-- @param #CTLD self
|
|
|
|
--- Triggers the FSM event "Status" after a delay.
|
|
-- @function [parent=#CTLD] __Status
|
|
-- @param #CTLD self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- Triggers the FSM event "Load".
|
|
-- @function [parent=#CTLD] Load
|
|
-- @param #CTLD self
|
|
|
|
--- Triggers the FSM event "Load" after a delay.
|
|
-- @function [parent=#CTLD] __Load
|
|
-- @param #CTLD self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- Triggers the FSM event "Save".
|
|
-- @function [parent=#CTLD] Load
|
|
-- @param #CTLD self
|
|
|
|
--- Triggers the FSM event "Save" after a delay.
|
|
-- @function [parent=#CTLD] __Save
|
|
-- @param #CTLD self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- FSM Function OnAfterTroopsPickedUp.
|
|
-- @function [parent=#CTLD] OnAfterTroopsPickedUp
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param #CTLD_CARGO Cargo Cargo troops.
|
|
-- @return #CTLD self
|
|
|
|
--- FSM Function OnAfterTroopsExtracted.
|
|
-- @function [parent=#CTLD] OnAfterTroopsExtracted
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param #CTLD_CARGO Cargo Cargo troops.
|
|
-- @return #CTLD self
|
|
|
|
--- FSM Function OnAfterCratesPickedUp.
|
|
-- @function [parent=#CTLD] OnAfterCratesPickedUp
|
|
-- @param #CTLD self
|
|
-- @param #string From State .
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param #CTLD_CARGO Cargo Cargo crate.
|
|
-- @return #CTLD self
|
|
|
|
--- FSM Function OnAfterTroopsDeployed.
|
|
-- @function [parent=#CTLD] OnAfterTroopsDeployed
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param Wrapper.Group#GROUP Troops Troops #GROUP Object.
|
|
-- @return #CTLD self
|
|
|
|
--- FSM Function OnAfterCratesDropped.
|
|
-- @function [parent=#CTLD] OnAfterCratesDropped
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param #table Cargotable Table of #CTLD_CARGO objects dropped.
|
|
-- @return #CTLD self
|
|
|
|
--- FSM Function OnAfterCratesBuild.
|
|
-- @function [parent=#CTLD] OnAfterCratesBuild
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build.
|
|
-- @return #CTLD self
|
|
|
|
--- FSM Function OnAfterCratesRepaired.
|
|
-- @function [parent=#CTLD] OnAfterCratesRepaired
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired.
|
|
-- @return #CTLD self
|
|
|
|
--- FSM Function OnAfterTroopsRTB.
|
|
-- @function [parent=#CTLD] OnAfterTroopsRTB
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
|
|
--- FSM Function OnAfterLoad.
|
|
-- @function [parent=#CTLD] OnAfterLoad
|
|
-- @param #CTLD self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
|
-- @param #string filename (Optional) File name for loading. Default is "CTLD_<alias>_Persist.csv".
|
|
|
|
--- FSM Function OnAfterSave.
|
|
-- @function [parent=#CTLD] OnAfterSave
|
|
-- @param #CTLD self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
|
-- @param #string filename (Optional) File name for saving. Default is "CTLD_<alias>_Persist.csv".
|
|
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------
|
|
-- Helper and User Functions
|
|
-------------------------------------------------------------------
|
|
|
|
--- (Internal) Function to get capabilities of a chopper
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit The unit
|
|
-- @return #table Capabilities Table of caps
|
|
function CTLD:_GetUnitCapabilities( Unit )
|
|
self:T( self.lid .. " _GetUnitCapabilities" )
|
|
local _unit = Unit -- Wrapper.Unit#UNIT
|
|
local unittype = _unit:GetTypeName()
|
|
local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities
|
|
if not capabilities or capabilities == {} then
|
|
-- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0},
|
|
capabilities = {}
|
|
capabilities.troops = false
|
|
capabilities.crates = false
|
|
capabilities.cratelimit = 0
|
|
capabilities.trooplimit = 0
|
|
capabilities.type = "generic"
|
|
capabilities.length = 20
|
|
end
|
|
return capabilities
|
|
end
|
|
|
|
--- (Internal) Function to generate valid UHF Frequencies
|
|
-- @param #CTLD self
|
|
function CTLD:_GenerateUHFrequencies()
|
|
self:T( self.lid .. " _GenerateUHFrequencies" )
|
|
self.FreeUHFFrequencies = {}
|
|
self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies()
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to generate valid FM Frequencies
|
|
-- @param #CTLD self
|
|
function CTLD:_GenerateFMFrequencies()
|
|
self:T( self.lid .. " _GenerateFMrequencies" )
|
|
self.FreeFMFrequencies = {}
|
|
self.FreeFMFrequencies = UTILS.GenerateFMFrequencies()
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Populate table with available VHF beacon frequencies.
|
|
-- @param #CTLD self
|
|
function CTLD:_GenerateVHFrequencies()
|
|
self:T( self.lid .. " _GenerateVHFrequencies" )
|
|
self.FreeVHFFrequencies = {}
|
|
self.UsedVHFFrequencies = {}
|
|
self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies()
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Event handler function
|
|
-- @param #CTLD self
|
|
-- @param Core.Event#EVENTDATA EventData
|
|
function CTLD:_EventHandler( EventData )
|
|
self:T( string.format( "%s Event = %d", self.lid, EventData.id ) )
|
|
local event = EventData -- Core.Event#EVENTDATA
|
|
if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then
|
|
local _coalition = event.IniCoalition
|
|
if _coalition ~= self.coalition then
|
|
return -- ignore!
|
|
end
|
|
-- check is Helicopter
|
|
local _unit = event.IniUnit
|
|
local _group = event.IniGroup
|
|
if _unit:IsHelicopter() or _group:IsHelicopter() then
|
|
local unitname = event.IniUnitName or "none"
|
|
self.Loaded_Cargo[unitname] = nil
|
|
self:_RefreshF10Menus()
|
|
end
|
|
-- Herc support
|
|
-- self:T_unit:GetTypeName())
|
|
if _unit:GetTypeName() == "Hercules" and self.enableHercules then
|
|
self.Loaded_Cargo[unitname] = nil
|
|
self:_RefreshF10Menus()
|
|
end
|
|
return
|
|
elseif event.id == EVENTS.PlayerLeaveUnit then
|
|
-- remove from pilot table
|
|
local unitname = event.IniUnitName or "none"
|
|
self.CtldUnits[unitname] = nil
|
|
self.Loaded_Cargo[unitname] = nil
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to message a group.
|
|
-- @param #CTLD self
|
|
-- @param #string Text The text to display.
|
|
-- @param #number Time Number of seconds to display the message.
|
|
-- @param #boolean Clearscreen Clear screen or not.
|
|
-- @param Wrapper.Group#GROUP Group The group receiving the message.
|
|
function CTLD:_SendMessage( Text, Time, Clearscreen, Group )
|
|
self:T( self.lid .. " _SendMessage" )
|
|
if not self.suppressmessages then
|
|
local m = MESSAGE:New( Text, Time, "CTLD", Clearscreen ):ToGroup( Group )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to load troops into a heli.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @param #CTLD_CARGO Cargotype
|
|
function CTLD:_LoadTroops( Group, Unit, Cargotype )
|
|
self:T( self.lid .. " _LoadTroops" )
|
|
-- check if we have stock
|
|
local instock = Cargotype:GetStock()
|
|
local cgoname = Cargotype:GetName()
|
|
local cgotype = Cargotype:GetType()
|
|
if type( instock ) == "number" and tonumber( instock ) <= 0 and tonumber( instock ) ~= -1 then
|
|
-- nothing left over
|
|
self:_SendMessage( string.format( "Sorry, all %s are gone!", cgoname ), 10, false, Group )
|
|
return self
|
|
end
|
|
-- landed or hovering over load zone?
|
|
local grounded = not self:IsUnitInAir( Unit )
|
|
local hoverload = self:CanHoverLoad( Unit )
|
|
-- local dooropen = UTILS.IsLoadingDoorOpen(Unit:GetName()) and self.pilotmustopendoors
|
|
-- check if we are in LOAD zone
|
|
local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD )
|
|
if not inzone then
|
|
inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP )
|
|
end
|
|
if not inzone then
|
|
self:_SendMessage( "You are not close enough to a logistics zone!", 10, false, Group )
|
|
if not self.debug then
|
|
return self
|
|
end
|
|
elseif not grounded and not hoverload then
|
|
self:_SendMessage( "You need to land or hover in position to load!", 10, false, Group )
|
|
if not self.debug then
|
|
return self
|
|
end
|
|
elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then
|
|
self:_SendMessage( "You need to open the door(s) to load troops!", 10, false, Group )
|
|
if not self.debug then
|
|
return self
|
|
end
|
|
end
|
|
-- load troops into heli
|
|
local group = Group -- Wrapper.Group#GROUP
|
|
local unit = Unit -- Wrapper.Unit#UNIT
|
|
local unitname = unit:GetName()
|
|
local cargotype = Cargotype -- #CTLD_CARGO
|
|
local cratename = cargotype:GetName() -- #string
|
|
-- see if this heli can load troops
|
|
local unittype = unit:GetTypeName()
|
|
local capabilities = self:_GetUnitCapabilities( Unit )
|
|
local cantroops = capabilities.troops -- #boolean
|
|
local trooplimit = capabilities.trooplimit -- #number
|
|
local troopsize = cargotype:GetCratesNeeded() -- #number
|
|
-- have we loaded stuff already?
|
|
local numberonboard = 0
|
|
local loaded = {}
|
|
if self.Loaded_Cargo[unitname] then
|
|
loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo
|
|
numberonboard = loaded.Troopsloaded or 0
|
|
else
|
|
loaded = {} -- #CTLD.LoadedCargo
|
|
loaded.Troopsloaded = 0
|
|
loaded.Cratesloaded = 0
|
|
loaded.Cargo = {}
|
|
end
|
|
if troopsize + numberonboard > trooplimit then
|
|
self:_SendMessage( "Sorry, we\'re crammed already!", 10, false, Group )
|
|
return
|
|
else
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
local loadcargotype = CTLD_CARGO:New( self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded, nil, nil, Cargotype.PerCrateMass )
|
|
self:T( { cargotype = loadcargotype } )
|
|
loaded.Troopsloaded = loaded.Troopsloaded + troopsize
|
|
table.insert( loaded.Cargo, loadcargotype )
|
|
self.Loaded_Cargo[unitname] = loaded
|
|
self:_SendMessage( "Troops boarded!", 10, false, Group )
|
|
self:__TroopsPickedUp( 1, Group, Unit, Cargotype )
|
|
self:_UpdateUnitCargoMass( Unit )
|
|
Cargotype:RemoveStock()
|
|
end
|
|
return self
|
|
end
|
|
|
|
function CTLD:_FindRepairNearby( Group, Unit, Repairtype )
|
|
self:T( self.lid .. " _FindRepairNearby" )
|
|
local unitcoord = Unit:GetCoordinate()
|
|
|
|
-- 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 unit = v:GetUnit( 1 ) -- Wrapper.Unit#UNIT
|
|
local desc = unit:GetDesc() or nil
|
|
-- self:I({desc = desc.attributes})
|
|
if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then
|
|
nearestGroup = v
|
|
nearestGroupIndex = k
|
|
nearestDistance = distance
|
|
end
|
|
end
|
|
|
|
-- found one and matching distance?
|
|
if nearestGroup == nil or nearestDistance > self.EngineerSearch then
|
|
self:_SendMessage( "No unit close enough to repair!", 10, false, Group )
|
|
return nil, nil
|
|
end
|
|
|
|
local groupname = nearestGroup:GetName()
|
|
|
|
-- helper to find matching template
|
|
local function matchstring( String, Table )
|
|
local match = false
|
|
if type( Table ) == "table" then
|
|
for _, _name in pairs( Table ) do
|
|
if string.find( String, _name ) then
|
|
match = true
|
|
break
|
|
end
|
|
end
|
|
else
|
|
if type( String ) == "string" then
|
|
if string.find( String, Table ) then
|
|
match = true
|
|
end
|
|
end
|
|
end
|
|
return match
|
|
end
|
|
|
|
-- walk through generics and find matching type
|
|
local Cargotype = nil
|
|
for k, v in pairs( self.Cargo_Crates ) do
|
|
-- self:I({groupname,v.Templates})
|
|
if matchstring( groupname, v.Templates ) and matchstring( groupname, Repairtype ) then
|
|
Cargotype = v -- #CTLD_CARGO
|
|
break
|
|
end
|
|
end
|
|
|
|
if Cargotype == nil then
|
|
-- self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group)
|
|
return nil, nil
|
|
else
|
|
return nearestGroup, Cargotype
|
|
end
|
|
|
|
end
|
|
|
|
--- (Internal) Function to repair an object.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @param #table Crates Table of #CTLD_CARGO objects near the unit.
|
|
-- @param #CTLD.Buildable Build Table build object.
|
|
-- @param #number Number Number of objects in Crates (found) to limit search.
|
|
-- @param #boolean Engineering If true it is an Engineering repair.
|
|
function CTLD:_RepairObjectFromCrates( Group, Unit, Crates, Build, Number, Engineering )
|
|
self:T( self.lid .. " _RepairObjectFromCrates" )
|
|
local build = Build -- -- #CTLD.Buildable
|
|
-- self:I({Build=Build})
|
|
local Repairtype = build.Template -- #string
|
|
local NearestGroup, CargoType = self:_FindRepairNearby( Group, Unit, Repairtype ) -- Wrapper.Group#GROUP, #CTLD_CARGO
|
|
-- self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup})
|
|
if NearestGroup ~= nil then
|
|
if self.repairtime < 2 then
|
|
self.repairtime = 30
|
|
end -- noob catch
|
|
if not Engineering then
|
|
self:_SendMessage( string.format( "Repair started using %s taking %d secs", build.Name, self.repairtime ), 10, false, Group )
|
|
end
|
|
-- now we can build ....
|
|
-- NearestGroup:Destroy(false)
|
|
local name = CargoType:GetName()
|
|
local required = CargoType:GetCratesNeeded()
|
|
local template = CargoType:GetTemplates()
|
|
local ctype = CargoType:GetType()
|
|
local object = {} -- #CTLD.Buildable
|
|
object.Name = CargoType:GetName()
|
|
object.Required = required
|
|
object.Found = required
|
|
object.Template = template
|
|
object.CanBuild = true
|
|
object.Type = ctype -- #CTLD_CARGO.Enum
|
|
self:_CleanUpCrates( Crates, Build, Number )
|
|
local desttimer = TIMER:New( function()
|
|
NearestGroup:Destroy( false )
|
|
end, self )
|
|
desttimer:Start( self.repairtime - 1 )
|
|
local buildtimer = TIMER:New( self._BuildObjectFromCrates, self, Group, Unit, object, true, NearestGroup:GetCoordinate() )
|
|
buildtimer:Start( self.repairtime )
|
|
-- self:_BuildObjectFromCrates(Group,Unit,object)
|
|
else
|
|
if not Engineering then
|
|
self:_SendMessage( "Can't repair this unit with " .. build.Name, 10, false, Group )
|
|
else
|
|
self:T( "Can't repair this unit with " .. build.Name )
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to extract (load from the field) troops into a heli.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
function CTLD:_ExtractTroops( Group, Unit ) -- #1574 thanks to @bbirchnz!
|
|
self:T( self.lid .. " _ExtractTroops" )
|
|
-- landed or hovering over load zone?
|
|
local grounded = not self:IsUnitInAir( Unit )
|
|
local hoverload = self:CanHoverLoad( Unit )
|
|
|
|
if not grounded and not hoverload then
|
|
self:_SendMessage( "You need to land or hover in position to load!", 10, false, Group )
|
|
if not self.debug then
|
|
return self
|
|
end
|
|
end
|
|
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then
|
|
self:_SendMessage( "You need to open the door(s) to extract troops!", 10, false, Group )
|
|
if not self.debug then
|
|
return self
|
|
end
|
|
end
|
|
-- load troops into heli
|
|
local unit = Unit -- Wrapper.Unit#UNIT
|
|
local unitname = unit:GetName()
|
|
-- see if this heli can load troops
|
|
local unittype = unit:GetTypeName()
|
|
local capabilities = self:_GetUnitCapabilities( Unit )
|
|
local cantroops = capabilities.troops -- #boolean
|
|
local trooplimit = capabilities.trooplimit -- #number
|
|
local unitcoord = unit:GetCoordinate()
|
|
|
|
-- find nearest group of deployed troops
|
|
local nearestGroup = nil
|
|
local nearestGroupIndex = -1
|
|
local nearestDistance = 10000000
|
|
local nearestList = {}
|
|
local distancekeys = {}
|
|
local extractdistance = self.CrateDistance * self.ExtractFactor
|
|
for k, v in pairs( self.DroppedTroops ) do
|
|
local distance = self:_GetDistance( v:GetCoordinate(), unitcoord )
|
|
if distance <= extractdistance and distance ~= -1 then
|
|
nearestGroup = v
|
|
nearestGroupIndex = k
|
|
nearestDistance = distance
|
|
table.insert( nearestList, math.floor( distance ), v )
|
|
distancekeys[#distancekeys + 1] = math.floor( distance )
|
|
end
|
|
end
|
|
|
|
if nearestGroup == nil or nearestDistance > extractdistance then
|
|
self:_SendMessage( "No units close enough to extract!", 10, false, Group )
|
|
return self
|
|
end
|
|
|
|
-- sort reference keys
|
|
table.sort( distancekeys )
|
|
|
|
local secondarygroups = {}
|
|
|
|
for i = 1, #distancekeys do
|
|
local nearestGroup = nearestList[distancekeys[i]]
|
|
-- find matching cargo type
|
|
local groupType = string.match( nearestGroup:GetName(), "(.+)-(.+)$" )
|
|
local Cargotype = nil
|
|
for k, v in pairs( self.Cargo_Troops ) do
|
|
local comparison = ""
|
|
if type( v.Templates ) == "string" then
|
|
comparison = v.Templates
|
|
else
|
|
comparison = v.Templates[1]
|
|
end
|
|
if comparison == groupType then
|
|
Cargotype = v
|
|
break
|
|
end
|
|
end
|
|
if Cargotype == nil then
|
|
self:_SendMessage( "Can't onboard " .. groupType, 10, false, Group )
|
|
else
|
|
|
|
local troopsize = Cargotype:GetCratesNeeded() -- #number
|
|
-- have we loaded stuff already?
|
|
local numberonboard = 0
|
|
local loaded = {}
|
|
if self.Loaded_Cargo[unitname] then
|
|
loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo
|
|
numberonboard = loaded.Troopsloaded or 0
|
|
else
|
|
loaded = {} -- #CTLD.LoadedCargo
|
|
loaded.Troopsloaded = 0
|
|
loaded.Cratesloaded = 0
|
|
loaded.Cargo = {}
|
|
end
|
|
if troopsize + numberonboard > trooplimit then
|
|
self:_SendMessage( "Sorry, we\'re crammed already!", 10, false, Group )
|
|
-- return self
|
|
else
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
local loadcargotype = CTLD_CARGO:New( self.CargoCounter, Cargotype.Name, Cargotype.Templates, Cargotype.CargoType, true, true, Cargotype.CratesNeeded, nil, nil, Cargotype.PerCrateMass )
|
|
self:T( { cargotype = loadcargotype } )
|
|
loaded.Troopsloaded = loaded.Troopsloaded + troopsize
|
|
table.insert( loaded.Cargo, loadcargotype )
|
|
self.Loaded_Cargo[unitname] = loaded
|
|
self:_SendMessage( "Troops boarded!", 10, false, Group )
|
|
self:_UpdateUnitCargoMass( Unit )
|
|
self:__TroopsExtracted( 1, Group, Unit, nearestGroup )
|
|
|
|
-- clean up:
|
|
-- table.remove(self.DroppedTroops, nearestGroupIndex)
|
|
if type( Cargotype.Templates ) == "table" and Cargotype.Templates[2] then
|
|
-- self:I("*****This CargoType has multiple templates: "..Cargotype.Name)
|
|
for _, _key in pairs( Cargotype.Templates ) do
|
|
table.insert( secondarygroups, _key )
|
|
end
|
|
end
|
|
nearestGroup:Destroy( false )
|
|
end
|
|
end
|
|
end
|
|
-- clean up secondary groups
|
|
for _, _name in pairs( secondarygroups ) do
|
|
for _, _group in pairs( nearestList ) do
|
|
if _group and _group:IsAlive() then
|
|
local groupname = string.match( _group:GetName(), "(.+)-(.+)$" )
|
|
if _name == groupname then
|
|
_group:Destroy( false )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self:CleanDroppedTroops()
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to spawn crates in front of the heli.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @param #CTLD_CARGO Cargo
|
|
-- @param #number number Number of crates to generate (for dropping)
|
|
-- @param #boolean drop If true we're dropping from heli rather than loading.
|
|
function CTLD:_GetCrates( Group, Unit, Cargo, number, drop )
|
|
self:T( self.lid .. " _GetCrates" )
|
|
if not drop then
|
|
local cgoname = Cargo:GetName()
|
|
-- check if we have stock
|
|
local instock = Cargo:GetStock()
|
|
if type( instock ) == "number" and tonumber( instock ) <= 0 and tonumber( instock ) ~= -1 then
|
|
-- nothing left over
|
|
self:_SendMessage( string.format( "Sorry, we ran out of %s", cgoname ), 10, false, Group )
|
|
return self
|
|
end
|
|
end
|
|
-- check if we are in LOAD zone
|
|
local inzone = false
|
|
local drop = drop or false
|
|
local ship = nil
|
|
local width = 20
|
|
if not drop then
|
|
inzone = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD )
|
|
if not inzone then
|
|
inzone, ship, zone, distance, width = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP )
|
|
end
|
|
else
|
|
if self.dropcratesanywhere then -- #1570
|
|
inzone = true
|
|
else
|
|
inzone = self:IsUnitInZone( Unit, CTLD.CargoZoneType.DROP )
|
|
end
|
|
end
|
|
|
|
if not inzone then
|
|
self:_SendMessage( "You are not close enough to a logistics zone!", 10, false, Group )
|
|
if not self.debug then
|
|
return self
|
|
end
|
|
end
|
|
|
|
-- avoid crate spam
|
|
local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities
|
|
local canloadcratesno = capabilities.cratelimit
|
|
local loaddist = self.CrateDistance or 35
|
|
local nearcrates, numbernearby = self:_FindCratesNearby( Group, Unit, loaddist )
|
|
if numbernearby >= canloadcratesno and not drop then
|
|
self:_SendMessage( "There are enough crates nearby already! Take care of those first!", 10, false, Group )
|
|
return self
|
|
end
|
|
-- spawn crates in front of helicopter
|
|
local IsHerc = self:IsHercules( Unit ) -- Herc
|
|
local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO
|
|
local number = number or cargotype:GetCratesNeeded() -- #number
|
|
local cratesneeded = cargotype:GetCratesNeeded() -- #number
|
|
local cratename = cargotype:GetName()
|
|
local cratetemplate = "Container" -- #string
|
|
local cgotype = cargotype:GetType()
|
|
local cgomass = cargotype:GetMass()
|
|
local isstatic = false
|
|
if cgotype == CTLD_CARGO.Enum.STATIC then
|
|
cratetemplate = cargotype:GetTemplates()
|
|
isstatic = true
|
|
end
|
|
-- get position and heading of heli
|
|
local position = Unit:GetCoordinate()
|
|
local heading = Unit:GetHeading() + 1
|
|
local height = Unit:GetHeight()
|
|
local droppedcargo = {}
|
|
local cratedistance = 0
|
|
local rheading = 0
|
|
local angleOffNose = 0
|
|
local addon = 0
|
|
if IsHerc then
|
|
-- spawn behind the Herc
|
|
addon = 180
|
|
end
|
|
-- loop crates needed
|
|
for i = 1, number do
|
|
local cratealias = string.format( "%s-%d", cratetemplate, math.random( 1, 100000 ) )
|
|
if not self.placeCratesAhead then
|
|
cratedistance = (i - 1) * 2.5 + capabilities.length
|
|
if cratedistance > self.CrateDistance then
|
|
cratedistance = self.CrateDistance
|
|
end
|
|
-- altered heading logic
|
|
-- DONE: right standard deviation?
|
|
rheading = UTILS.RandomGaussian( 0, 30, -90, 90, 100 )
|
|
rheading = math.fmod( (heading + rheading + addon), 360 )
|
|
else
|
|
local initialSpacing = IsHerc and 16 or 12 -- initial spacing of the first crates
|
|
local crateSpacing = 4 -- further spacing of remaining crates
|
|
local lateralSpacing = 4 -- lateral spacing of crates
|
|
local nrSideBySideCrates = 3 -- number of crates that are placed side-by-side
|
|
|
|
if cratesneeded == 1 then
|
|
-- single crate needed spawns straight ahead
|
|
cratedistance = initialSpacing
|
|
rheading = heading
|
|
else
|
|
if (i - 1) % nrSideBySideCrates == 0 then
|
|
cratedistance = i == 1 and initialSpacing or cratedistance + crateSpacing
|
|
angleOffNose = math.ceil( math.deg( math.atan( lateralSpacing / cratedistance ) ) )
|
|
rheading = heading - angleOffNose
|
|
else
|
|
rheading = rheading + angleOffNose
|
|
end
|
|
end
|
|
end
|
|
local cratecoord = position:Translate( cratedistance, rheading )
|
|
local cratevec2 = cratecoord:GetVec2()
|
|
self.CrateCounter = self.CrateCounter + 1
|
|
local basetype = "container_cargo"
|
|
if isstatic then
|
|
basetype = cratetemplate
|
|
end
|
|
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 dist = shipcoord:Get2DDistance( unitcoord )
|
|
dist = dist - (20 + math.random( 1, 10 ))
|
|
local width = width / 2
|
|
local Offy = math.random( -width, width )
|
|
self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry ) -- :InitCoordinate(cratecoord)
|
|
:InitCargoMass( cgomass )
|
|
:InitCargo( self.enableslingload )
|
|
:InitLinkToUnit( Ship, dist, Offy, 0 )
|
|
:Spawn( 270, cratealias )
|
|
else
|
|
self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry )
|
|
:InitCoordinate( cratecoord )
|
|
:InitCargoMass( cgomass )
|
|
:InitCargo( self.enableslingload ) -- :InitLinkToUnit(Unit,OffsetX,OffsetY,OffsetAngle)
|
|
:Spawn( 270, cratealias )
|
|
end
|
|
local templ = cargotype:GetTemplates()
|
|
local sorte = cargotype:GetType()
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
local realcargo = nil
|
|
if drop then
|
|
realcargo = CTLD_CARGO:New( self.CargoCounter, cratename, templ, sorte, true, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], true, cargotype.PerCrateMass )
|
|
table.insert( droppedcargo, realcargo )
|
|
else
|
|
realcargo = CTLD_CARGO:New( self.CargoCounter, cratename, templ, sorte, false, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], true, cargotype.PerCrateMass )
|
|
Cargo:RemoveStock()
|
|
end
|
|
table.insert( self.Spawned_Cargo, realcargo )
|
|
end
|
|
local text = string.format( "Crates for %s have been positioned near you!", cratename )
|
|
if drop then
|
|
text = string.format( "Crates for %s have been dropped!", cratename )
|
|
self:__CratesDropped( 1, Group, Unit, droppedcargo )
|
|
end
|
|
self:_SendMessage( text, 10, false, Group )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Inject crates and static cargo objects.
|
|
-- @param #CTLD self
|
|
-- @param Core.Zone#ZONE Zone Zone to spawn in.
|
|
-- @param #CTLD_CARGO Cargo The cargo type to spawn.
|
|
-- @param #boolean RandomCoord Randomize coordinate.
|
|
-- @return #CTLD self
|
|
function CTLD:InjectStatics( Zone, Cargo, RandomCoord )
|
|
self:T( self.lid .. " InjectStatics" )
|
|
local cratecoord = Zone:GetCoordinate()
|
|
if RandomCoord then
|
|
cratecoord = Zone:GetRandomCoordinate( 5, 20 )
|
|
end
|
|
local surface = cratecoord:GetSurfaceType()
|
|
if surface == land.SurfaceType.WATER then
|
|
return self
|
|
end
|
|
local cargotype = Cargo -- #CTLD_CARGO
|
|
-- local number = 1
|
|
local cratesneeded = cargotype:GetCratesNeeded() -- #number
|
|
local cratetemplate = "Container" -- #string
|
|
local cratealias = string.format( "%s-%d", cratetemplate, math.random( 1, 100000 ) )
|
|
local cratename = cargotype:GetName()
|
|
local cgotype = cargotype:GetType()
|
|
local cgomass = cargotype:GetMass()
|
|
local isstatic = false
|
|
if cgotype == CTLD_CARGO.Enum.STATIC then
|
|
cratetemplate = cargotype:GetTemplates()
|
|
isstatic = true
|
|
end
|
|
local basetype = "container_cargo"
|
|
if isstatic then
|
|
basetype = cratetemplate
|
|
end
|
|
self.CrateCounter = self.CrateCounter + 1
|
|
self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType( basetype, "Cargos", self.cratecountry )
|
|
:InitCargoMass( cgomass )
|
|
:InitCargo( self.enableslingload )
|
|
:InitCoordinate( cratecoord )
|
|
:Spawn( 270, cratealias )
|
|
local templ = cargotype:GetTemplates()
|
|
local sorte = cargotype:GetType()
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
cargotype.Positionable = self.Spawned_Crates[self.CrateCounter]
|
|
table.insert( self.Spawned_Cargo, cargotype )
|
|
return self
|
|
end
|
|
|
|
--- (User) Inject static cargo objects.
|
|
-- @param #CTLD self
|
|
-- @param Core.Zone#ZONE Zone Zone to spawn in. Will be a somewhat random coordinate.
|
|
-- @param #string Template Unit(!) name of the static cargo object to be used as template.
|
|
-- @param #number Mass Mass of the static in kg.
|
|
-- @return #CTLD self
|
|
function CTLD:InjectStaticFromTemplate( Zone, Template, Mass )
|
|
self:T( self.lid .. " InjectStaticFromTemplate" )
|
|
local cargotype = self:GetStaticsCargoFromTemplate( Template, Mass ) -- #CTLD_CARGO
|
|
self:InjectStatics( Zone, cargotype, true )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to find and list nearby crates.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #CTLD self
|
|
function CTLD:_ListCratesNearby( _group, _unit )
|
|
self:T( self.lid .. " _ListCratesNearby" )
|
|
local finddist = self.CrateDistance or 35
|
|
local crates, number = self:_FindCratesNearby( _group, _unit, finddist ) -- #table
|
|
if number > 0 then
|
|
local text = REPORT:New( "Crates Found Nearby:" )
|
|
text:Add( "------------------------------------------------------------" )
|
|
for _, _entry in pairs( crates ) do
|
|
local entry = _entry -- #CTLD_CARGO
|
|
local name = entry:GetName() -- #string
|
|
local dropped = entry:WasDropped()
|
|
if dropped then
|
|
text:Add( string.format( "Dropped crate for %s, %dkg", name, entry.PerCrateMass ) )
|
|
else
|
|
text:Add( string.format( "Crate for %s, %dkg", name, entry.PerCrateMass ) )
|
|
end
|
|
end
|
|
if text:GetCount() == 1 then
|
|
text:Add( " N O N E" )
|
|
end
|
|
text:Add( "------------------------------------------------------------" )
|
|
self:_SendMessage( text:Text(), 30, true, _group )
|
|
else
|
|
self:_SendMessage( string.format( "No (loadable) crates within %d meters!", finddist ), 10, false, _group )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Return distance in meters between two coordinates.
|
|
-- @param #CTLD self
|
|
-- @param Core.Point#COORDINATE _point1 Coordinate one
|
|
-- @param Core.Point#COORDINATE _point2 Coordinate two
|
|
-- @return #number Distance in meters
|
|
function CTLD:_GetDistance( _point1, _point2 )
|
|
self:T( self.lid .. " _GetDistance" )
|
|
if _point1 and _point2 then
|
|
local distance1 = _point1:Get2DDistance( _point2 )
|
|
local distance2 = _point1:DistanceFromPointVec2( _point2 )
|
|
-- self:I({dist1=distance1, dist2=distance2})
|
|
if distance1 and type( distance1 ) == "number" then
|
|
return distance1
|
|
elseif distance2 and type( distance2 ) == "number" then
|
|
return distance2
|
|
else
|
|
self:E( "*****Cannot calculate distance!" )
|
|
self:E( { _point1, _point2 } )
|
|
return -1
|
|
end
|
|
else
|
|
self:E( "******Cannot calculate distance!" )
|
|
self:E( { _point1, _point2 } )
|
|
return -1
|
|
end
|
|
end
|
|
|
|
--- (Internal) Function to find and return nearby crates.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP _group Group
|
|
-- @param Wrapper.Unit#UNIT _unit Unit
|
|
-- @param #number _dist Distance
|
|
-- @return #table Table of crates
|
|
-- @return #number Number Number of crates found
|
|
function CTLD:_FindCratesNearby( _group, _unit, _dist )
|
|
self:T( self.lid .. " _FindCratesNearby" )
|
|
local finddist = _dist
|
|
local location = _group:GetCoordinate()
|
|
local existingcrates = self.Spawned_Cargo -- #table
|
|
-- cycle
|
|
local index = 0
|
|
local found = {}
|
|
for _, _cargoobject in pairs( existingcrates ) do
|
|
local cargo = _cargoobject -- #CTLD_CARGO
|
|
local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates
|
|
local staticid = cargo:GetID()
|
|
if static and static:IsAlive() then
|
|
local staticpos = static:GetCoordinate()
|
|
local distance = self:_GetDistance( location, staticpos )
|
|
if distance <= finddist and static then
|
|
index = index + 1
|
|
table.insert( found, staticid, cargo )
|
|
end
|
|
end
|
|
end
|
|
return found, index
|
|
end
|
|
|
|
--- (Internal) Function to get and load nearby crates.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #CTLD self
|
|
function CTLD:_LoadCratesNearby( Group, Unit )
|
|
self:T( self.lid .. " _LoadCratesNearby" )
|
|
-- load crates into heli
|
|
local group = Group -- Wrapper.Group#GROUP
|
|
local unit = Unit -- Wrapper.Unit#UNIT
|
|
local unitname = unit:GetName()
|
|
-- see if this heli can load crates
|
|
local unittype = unit:GetTypeName()
|
|
local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities
|
|
-- local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities
|
|
local cancrates = capabilities.crates -- #boolean
|
|
local cratelimit = capabilities.cratelimit -- #number
|
|
local grounded = not self:IsUnitInAir( Unit )
|
|
local canhoverload = self:CanHoverLoad( Unit )
|
|
--- cases -------------------------------
|
|
-- Chopper can't do crates - bark & return
|
|
-- Chopper can do crates -
|
|
-- --> hover if forcedhover or bark and return
|
|
-- --> hover or land if not forcedhover
|
|
-----------------------------------------
|
|
if not cancrates then
|
|
self:_SendMessage( "Sorry this chopper cannot carry crates!", 10, false, Group )
|
|
elseif self.forcehoverload and not canhoverload then
|
|
self:_SendMessage( "Hover over the crates to pick them up!", 10, false, Group )
|
|
elseif not grounded and not canhoverload then
|
|
self:_SendMessage( "Land or hover over the crates to pick them up!", 10, false, Group )
|
|
else
|
|
-- have we loaded stuff already?
|
|
local numberonboard = 0
|
|
local massonboard = 0
|
|
local loaded = {}
|
|
if self.Loaded_Cargo[unitname] then
|
|
loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo
|
|
numberonboard = loaded.Cratesloaded or 0
|
|
else
|
|
loaded = {} -- #CTLD.LoadedCargo
|
|
loaded.Troopsloaded = 0
|
|
loaded.Cratesloaded = 0
|
|
loaded.Cargo = {}
|
|
end
|
|
-- get nearby crates
|
|
local finddist = self.CrateDistance or 35
|
|
local nearcrates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table
|
|
if number == 0 and self.hoverautoloading then
|
|
return self -- exit
|
|
elseif number == 0 then
|
|
self:_SendMessage( "Sorry no loadable crates nearby!", 10, false, Group )
|
|
return self -- exit
|
|
elseif numberonboard == cratelimit then
|
|
self:_SendMessage( "Sorry no fully loaded!", 10, false, Group )
|
|
return self -- exit
|
|
else
|
|
-- go through crates and load
|
|
local capacity = cratelimit - numberonboard
|
|
local crateidsloaded = {}
|
|
local loops = 0
|
|
while loaded.Cratesloaded < cratelimit and loops < number do
|
|
loops = loops + 1
|
|
local crateind = 0
|
|
-- get crate with largest index
|
|
for _ind, _crate in pairs( nearcrates ) do
|
|
if self.allowcratepickupagain then
|
|
if _crate:GetID() > crateind and _crate.Positionable ~= nil then
|
|
crateind = _crate:GetID()
|
|
end
|
|
else
|
|
if not _crate:HasMoved() and _crate:WasDropped() and _crate:GetID() > crateind then
|
|
crateind = _crate:GetID()
|
|
end
|
|
end
|
|
end
|
|
-- load one if we found one
|
|
if crateind > 0 then
|
|
local crate = nearcrates[crateind] -- #CTLD_CARGO
|
|
loaded.Cratesloaded = loaded.Cratesloaded + 1
|
|
crate:SetHasMoved( true )
|
|
crate:SetWasDropped( false )
|
|
table.insert( loaded.Cargo, crate )
|
|
table.insert( crateidsloaded, crate:GetID() )
|
|
-- destroy crate
|
|
crate:GetPositionable():Destroy( false )
|
|
crate.Positionable = nil
|
|
self:_SendMessage( string.format( "Crate ID %d for %s loaded!", crate:GetID(), crate:GetName() ), 10, false, Group )
|
|
table.remove( nearcrates, crate:GetID() )
|
|
self:__CratesPickedUp( 1, Group, Unit, crate )
|
|
end
|
|
end
|
|
self.Loaded_Cargo[unitname] = loaded
|
|
self:_UpdateUnitCargoMass( Unit )
|
|
-- clean up real world crates
|
|
self:_CleanupTrackedCrates( crateidsloaded )
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to clean up tracked cargo crates
|
|
function CTLD:_CleanupTrackedCrates( crateIdsToRemove )
|
|
local existingcrates = self.Spawned_Cargo -- #table
|
|
local newexcrates = {}
|
|
for _, _crate in pairs( existingcrates ) do
|
|
local excrate = _crate -- #CTLD_CARGO
|
|
local ID = excrate:GetID()
|
|
local keep = true
|
|
for _, _ID in pairs( crateIdsToRemove ) do
|
|
if ID == _ID then
|
|
keep = false
|
|
end
|
|
end
|
|
-- remove destroyed crates here too
|
|
local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates
|
|
if not static or not static:IsAlive() then
|
|
keep = false
|
|
end
|
|
if keep then
|
|
table.insert( newexcrates, _crate )
|
|
end
|
|
end
|
|
self.Spawned_Cargo = nil
|
|
self.Spawned_Cargo = newexcrates
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to get current loaded mass
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #number mass in kgs
|
|
function CTLD:_GetUnitCargoMass( Unit )
|
|
self:T( self.lid .. " _GetUnitCargoMass" )
|
|
local unitname = Unit:GetName()
|
|
local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo
|
|
local loadedmass = 0 -- #number
|
|
if self.Loaded_Cargo[unitname] then
|
|
local cargotable = loadedcargo.Cargo or {} -- #table
|
|
for _, _cargo in pairs( cargotable ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then
|
|
loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded())
|
|
end
|
|
if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then
|
|
loadedmass = loadedmass + cargo.PerCrateMass
|
|
end
|
|
end
|
|
end
|
|
return loadedmass
|
|
end
|
|
|
|
--- (Internal) Function to calculate and set Unit internal cargo mass
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
function CTLD:_UpdateUnitCargoMass( Unit )
|
|
self:T( self.lid .. " _UpdateUnitCargoMass" )
|
|
local calculatedMass = self:_GetUnitCargoMass( Unit )
|
|
Unit:SetUnitInternalCargo( calculatedMass )
|
|
-- local report = REPORT:New("Loadmaster report")
|
|
-- report:Add("Carrying " .. calculatedMass .. "Kg")
|
|
-- self:_SendMessage(report:Text(),10,false,Unit:GetGroup())
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to list loaded cargo.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #CTLD self
|
|
function CTLD:_ListCargo( Group, Unit )
|
|
self:T( self.lid .. " _ListCargo" )
|
|
local unitname = Unit:GetName()
|
|
local unittype = Unit:GetTypeName()
|
|
local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities
|
|
local trooplimit = capabilities.trooplimit -- #boolean
|
|
local cratelimit = capabilities.cratelimit -- #number
|
|
local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo
|
|
local loadedmass = self:_GetUnitCargoMass( Unit ) -- #number
|
|
if self.Loaded_Cargo[unitname] then
|
|
local no_troops = loadedcargo.Troopsloaded or 0
|
|
local no_crates = loadedcargo.Cratesloaded or 0
|
|
local cargotable = loadedcargo.Cargo or {} -- #table
|
|
local report = REPORT:New( "Transport Checkout Sheet" )
|
|
report:Add( "------------------------------------------------------------" )
|
|
report:Add( string.format( "Troops: %d(%d), Crates: %d(%d)", no_troops, trooplimit, no_crates, cratelimit ) )
|
|
report:Add( "------------------------------------------------------------" )
|
|
report:Add( " -- TROOPS --" )
|
|
for _, _cargo in pairs( cargotable ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then
|
|
report:Add( string.format( "Troop: %s size %d", cargo:GetName(), cargo:GetCratesNeeded() ) )
|
|
end
|
|
end
|
|
if report:GetCount() == 4 then
|
|
report:Add( " N O N E" )
|
|
end
|
|
report:Add( "------------------------------------------------------------" )
|
|
report:Add( " -- CRATES --" )
|
|
local cratecount = 0
|
|
for _, _cargo in pairs( cargotable ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then
|
|
report:Add( string.format( "Crate: %s size 1", cargo:GetName() ) )
|
|
cratecount = cratecount + 1
|
|
end
|
|
end
|
|
if cratecount == 0 then
|
|
report:Add( " N O N E" )
|
|
end
|
|
report:Add( "------------------------------------------------------------" )
|
|
report:Add( "Total Mass: " .. loadedmass .. " kg" )
|
|
local text = report:Text()
|
|
self:_SendMessage( text, 30, true, Group )
|
|
else
|
|
self:_SendMessage( string.format( "Nothing loaded!\nTroop limit: %d | Crate limit %d", trooplimit, cratelimit ), 10, false, Group )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to list loaded cargo.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #CTLD self
|
|
function CTLD:_ListInventory( Group, Unit )
|
|
self:T( self.lid .. " _ListInventory" )
|
|
local unitname = Unit:GetName()
|
|
local unittype = Unit:GetTypeName()
|
|
local cgotypes = self.Cargo_Crates
|
|
local trptypes = self.Cargo_Troops
|
|
local stctypes = self.Cargo_Statics
|
|
|
|
local function countcargo( cgotable )
|
|
local counter = 0
|
|
for _, _cgo in pairs( cgotable ) do
|
|
counter = counter + 1
|
|
end
|
|
return counter
|
|
end
|
|
|
|
local crateno = countcargo( cgotypes )
|
|
local troopno = countcargo( trptypes )
|
|
local staticno = countcargo( stctypes )
|
|
|
|
if (crateno > 0 or troopno > 0 or staticno > 0) then
|
|
|
|
local report = REPORT:New( "Inventory Sheet" )
|
|
report:Add( "------------------------------------------------------------" )
|
|
report:Add( string.format( "Troops: %d, Cratetypes: %d", troopno, crateno + staticno ) )
|
|
report:Add( "------------------------------------------------------------" )
|
|
report:Add( " -- TROOPS --" )
|
|
for _, _cargo in pairs( trptypes ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then
|
|
local stockn = cargo:GetStock()
|
|
local stock = "none"
|
|
if stockn == -1 then
|
|
stock = "unlimited"
|
|
elseif stockn > 0 then
|
|
stock = tostring( stockn )
|
|
end
|
|
report:Add( string.format( "Unit: %s | Soldiers: %d | Stock: %s", cargo:GetName(), cargo:GetCratesNeeded(), stock ) )
|
|
end
|
|
end
|
|
if report:GetCount() == 4 then
|
|
report:Add( " N O N E" )
|
|
end
|
|
report:Add( "------------------------------------------------------------" )
|
|
report:Add( " -- CRATES --" )
|
|
local cratecount = 0
|
|
for _, _cargo in pairs( cgotypes ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then
|
|
local stockn = cargo:GetStock()
|
|
local stock = "none"
|
|
if stockn == -1 then
|
|
stock = "unlimited"
|
|
elseif stockn > 0 then
|
|
stock = tostring( stockn )
|
|
end
|
|
report:Add( string.format( "Type: %s | Crates per Set: %d | Stock: %s", cargo:GetName(), cargo:GetCratesNeeded(), stock ) )
|
|
cratecount = cratecount + 1
|
|
end
|
|
end
|
|
-- Statics
|
|
for _, _cargo in pairs( stctypes ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type == CTLD_CARGO.Enum.STATIC) and not cargo:WasDropped() then
|
|
local stockn = cargo:GetStock()
|
|
local stock = "none"
|
|
if stockn == -1 then
|
|
stock = "unlimited"
|
|
elseif stockn > 0 then
|
|
stock = tostring( stockn )
|
|
end
|
|
report:Add( string.format( "Type: %s | Stock: %s", cargo:GetName(), stock ) )
|
|
cratecount = cratecount + 1
|
|
end
|
|
end
|
|
if cratecount == 0 then
|
|
report:Add( " N O N E" )
|
|
end
|
|
local text = report:Text()
|
|
self:_SendMessage( text, 30, true, Group )
|
|
else
|
|
self:_SendMessage( string.format( "Nothing in stock!" ), 10, false, Group )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to check if a unit is a Hercules C-130.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #boolean Outcome
|
|
function CTLD:IsHercules( Unit )
|
|
if Unit:GetTypeName() == "Hercules" then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- (Internal) Function to unload troops from heli.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
function CTLD:_UnloadTroops( Group, Unit )
|
|
self:T( self.lid .. " _UnloadTroops" )
|
|
-- check if we are in LOAD zone
|
|
local droppingatbase = false
|
|
local canunload = true
|
|
if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen( Unit:GetName() ) then
|
|
self:_SendMessage( "You need to open the door(s) to unload troops!", 10, false, Group )
|
|
if not self.debug then
|
|
return self
|
|
end
|
|
end
|
|
local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD )
|
|
if not inzone then
|
|
inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP )
|
|
end
|
|
if inzone then
|
|
droppingatbase = true
|
|
end
|
|
-- check for hover unload
|
|
local hoverunload = self:IsCorrectHover( Unit ) -- if true we're hovering in parameters
|
|
local IsHerc = self:IsHercules( Unit )
|
|
if IsHerc then
|
|
-- no hover but airdrop here
|
|
hoverunload = self:IsCorrectFlightParameters( Unit )
|
|
end
|
|
-- check if we're landed
|
|
local grounded = not self:IsUnitInAir( Unit )
|
|
-- Get what we have loaded
|
|
local unitname = Unit:GetName()
|
|
if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then
|
|
if not droppingatbase or self.debug then
|
|
local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo
|
|
-- looking for troops
|
|
local cargotable = loadedcargo.Cargo
|
|
for _, _cargo in pairs( cargotable ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then
|
|
-- unload troops
|
|
local name = cargo:GetName() or "none"
|
|
local temptable = cargo:GetTemplates() or {}
|
|
local position = Group:GetCoordinate()
|
|
local zoneradius = 100 -- drop zone radius
|
|
local factor = 1
|
|
if IsHerc then
|
|
factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping
|
|
zoneradius = Unit:GetVelocityMPS() or 100
|
|
end
|
|
local zone = ZONE_GROUP:New( string.format( "Unload zone-%s", unitname ), Group, zoneradius * factor )
|
|
local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2()
|
|
for _, _template in pairs( temptable ) do
|
|
self.TroopCounter = self.TroopCounter + 1
|
|
local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) )
|
|
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias )
|
|
:InitRandomizeUnits( true, 20, 2 )
|
|
:InitDelayOff()
|
|
:SpawnFromVec2( randomcoord )
|
|
if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then
|
|
self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] )
|
|
end
|
|
end -- template loop
|
|
cargo:SetWasDropped( true )
|
|
-- engineering group?
|
|
-- self:I("Dropped Troop Type: "..type)
|
|
if type == CTLD_CARGO.Enum.ENGINEERS then
|
|
self.Engineers = self.Engineers + 1
|
|
local grpname = self.DroppedTroops[self.TroopCounter]:GetName()
|
|
self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New( name, grpname )
|
|
self:_SendMessage( string.format( "Dropped Engineers %s into action!", name ), 10, false, Group )
|
|
else
|
|
self:_SendMessage( string.format( "Dropped Troops %s into action!", name ), 10, false, Group )
|
|
end
|
|
self:__TroopsDeployed( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] )
|
|
end -- if type end
|
|
end -- cargotable loop
|
|
else -- droppingatbase
|
|
self:_SendMessage( "Troops have returned to base!", 10, false, Group )
|
|
self:__TroopsRTB( 1, Group, Unit )
|
|
end
|
|
-- cleanup load list
|
|
local loaded = {} -- #CTLD.LoadedCargo
|
|
loaded.Troopsloaded = 0
|
|
loaded.Cratesloaded = 0
|
|
loaded.Cargo = {}
|
|
local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo
|
|
local cargotable = loadedcargo.Cargo or {}
|
|
for _, _cargo in pairs( cargotable ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
local dropped = cargo:WasDropped()
|
|
if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not dropped then
|
|
table.insert( loaded.Cargo, _cargo )
|
|
loaded.Cratesloaded = loaded.Cratesloaded + 1
|
|
else
|
|
-- add troops back to stock
|
|
if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and droppingatbase then
|
|
-- find right generic type
|
|
local name = cargo:GetName()
|
|
local gentroops = self.Cargo_Troops
|
|
for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO
|
|
if _troop.Name == name then
|
|
local stock = _troop:GetStock()
|
|
-- avoid making unlimited stock limited
|
|
if stock and tonumber( stock ) >= 0 then
|
|
_troop:AddStock()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self.Loaded_Cargo[unitname] = nil
|
|
self.Loaded_Cargo[unitname] = loaded
|
|
self:_UpdateUnitCargoMass( Unit )
|
|
else
|
|
if IsHerc then
|
|
self:_SendMessage( "Nothing loaded or not within airdrop parameters!", 10, false, Group )
|
|
else
|
|
self:_SendMessage( "Nothing loaded or not hovering within parameters!", 10, false, Group )
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to unload crates from heli.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
function CTLD:_UnloadCrates( Group, Unit )
|
|
self:T( self.lid .. " _UnloadCrates" )
|
|
|
|
if not self.dropcratesanywhere then -- #1570
|
|
-- check if we are in DROP zone
|
|
local inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.DROP )
|
|
if not inzone then
|
|
self:_SendMessage( "You are not close enough to a drop zone!", 10, false, Group )
|
|
if not self.debug then
|
|
return self
|
|
end
|
|
end
|
|
end
|
|
-- check for hover unload
|
|
local hoverunload = self:IsCorrectHover( Unit ) -- if true we're hovering in parameters
|
|
local IsHerc = self:IsHercules( Unit )
|
|
if IsHerc then
|
|
-- no hover but airdrop here
|
|
hoverunload = self:IsCorrectFlightParameters( Unit )
|
|
end
|
|
-- check if we're landed
|
|
local grounded = not self:IsUnitInAir( Unit )
|
|
-- Get what we have loaded
|
|
local unitname = Unit:GetName()
|
|
if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then
|
|
local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo
|
|
-- looking for crate
|
|
local cargotable = loadedcargo.Cargo
|
|
for _, _cargo in pairs( cargotable ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and (not cargo:WasDropped() or self.allowcratepickupagain) then
|
|
-- unload crates
|
|
self:_GetCrates( Group, Unit, cargo, 1, true )
|
|
cargo:SetWasDropped( true )
|
|
cargo:SetHasMoved( true )
|
|
end
|
|
end
|
|
-- cleanup load list
|
|
local loaded = {} -- #CTLD.LoadedCargo
|
|
loaded.Troopsloaded = 0
|
|
loaded.Cratesloaded = 0
|
|
loaded.Cargo = {}
|
|
|
|
for _, _cargo in pairs( cargotable ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
local size = cargo:GetCratesNeeded()
|
|
if type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS then
|
|
table.insert( loaded.Cargo, _cargo )
|
|
loaded.Troopsloaded = loaded.Troopsloaded + size
|
|
end
|
|
end
|
|
self.Loaded_Cargo[unitname] = nil
|
|
self.Loaded_Cargo[unitname] = loaded
|
|
|
|
self:_UpdateUnitCargoMass( Unit )
|
|
else
|
|
if IsHerc then
|
|
self:_SendMessage( "Nothing loaded or not within airdrop parameters!", 10, false, Group )
|
|
else
|
|
self:_SendMessage( "Nothing loaded or not hovering within parameters!", 10, false, Group )
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to build nearby crates.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @param #boolean Engineering If true build is by an engineering team.
|
|
function CTLD:_BuildCrates( Group, Unit, Engineering )
|
|
self:T( self.lid .. " _BuildCrates" )
|
|
-- avoid users trying to build from flying Hercs
|
|
local type = Unit:GetTypeName()
|
|
if type == "Hercules" and self.enableHercules and not Engineering then
|
|
local speed = Unit:GetVelocityKMH()
|
|
if speed > 1 then
|
|
self:_SendMessage( "You need to land / stop to build something, Pilot!", 10, false, Group )
|
|
return self
|
|
end
|
|
end
|
|
-- get nearby crates
|
|
local finddist = self.CrateDistance or 35
|
|
local crates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table
|
|
local buildables = {}
|
|
local foundbuilds = false
|
|
local canbuild = false
|
|
if number > 0 then
|
|
-- get dropped crates
|
|
for _, _crate in pairs( crates ) do
|
|
local Crate = _crate -- #CTLD_CARGO
|
|
if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then
|
|
-- we can build these - maybe
|
|
local name = Crate:GetName()
|
|
local required = Crate:GetCratesNeeded()
|
|
local template = Crate:GetTemplates()
|
|
local ctype = Crate:GetType()
|
|
if not buildables[name] then
|
|
local object = {} -- #CTLD.Buildable
|
|
object.Name = name
|
|
object.Required = required
|
|
object.Found = 1
|
|
object.Template = template
|
|
object.CanBuild = false
|
|
object.Type = ctype -- #CTLD_CARGO.Enum
|
|
buildables[name] = object
|
|
foundbuilds = true
|
|
else
|
|
buildables[name].Found = buildables[name].Found + 1
|
|
foundbuilds = true
|
|
end
|
|
if buildables[name].Found >= buildables[name].Required then
|
|
buildables[name].CanBuild = true
|
|
canbuild = true
|
|
end
|
|
self:T( { buildables = buildables } )
|
|
end -- end dropped
|
|
end -- end crate loop
|
|
-- ok let's list what we have
|
|
local report = REPORT:New( "Checklist Buildable Crates" )
|
|
report:Add( "------------------------------------------------------------" )
|
|
for _, _build in pairs( buildables ) do
|
|
local build = _build -- Object table from above
|
|
local name = build.Name
|
|
local needed = build.Required
|
|
local found = build.Found
|
|
local txtok = "NO"
|
|
if build.CanBuild then
|
|
txtok = "YES"
|
|
end
|
|
local text = string.format( "Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok )
|
|
report:Add( text )
|
|
end -- end list buildables
|
|
if not foundbuilds then
|
|
report:Add( " --- None Found ---" )
|
|
end
|
|
report:Add( "------------------------------------------------------------" )
|
|
local text = report:Text()
|
|
if not Engineering then
|
|
self:_SendMessage( text, 30, true, Group )
|
|
else
|
|
self:T( text )
|
|
end
|
|
-- let's get going
|
|
if canbuild then
|
|
-- loop again
|
|
for _, _build in pairs( buildables ) do
|
|
local build = _build -- #CTLD.Buildable
|
|
if build.CanBuild then
|
|
self:_CleanUpCrates( crates, build, number )
|
|
self:_BuildObjectFromCrates( Group, Unit, build )
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if not Engineering then
|
|
self:_SendMessage( string.format( "No crates within %d meters!", finddist ), 10, false, Group )
|
|
end
|
|
end -- number > 0
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to repair nearby vehicles / FOBs
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @param #boolean Engineering If true, this is an engineering role
|
|
function CTLD:_RepairCrates( Group, Unit, Engineering )
|
|
self:T( self.lid .. " _RepairCrates" )
|
|
-- get nearby crates
|
|
local finddist = self.CrateDistance or 35
|
|
local crates, number = self:_FindCratesNearby( Group, Unit, finddist ) -- #table
|
|
local buildables = {}
|
|
local foundbuilds = false
|
|
local canbuild = false
|
|
if number > 0 then
|
|
-- get dropped crates
|
|
for _, _crate in pairs( crates ) do
|
|
local Crate = _crate -- #CTLD_CARGO
|
|
if Crate:WasDropped() and Crate:IsRepair() and not Crate:IsStatic() then
|
|
-- we can build these - maybe
|
|
local name = Crate:GetName()
|
|
local required = Crate:GetCratesNeeded()
|
|
local template = Crate:GetTemplates()
|
|
local ctype = Crate:GetType()
|
|
if not buildables[name] then
|
|
local object = {} -- #CTLD.Buildable
|
|
object.Name = name
|
|
object.Required = required
|
|
object.Found = 1
|
|
object.Template = template
|
|
object.CanBuild = false
|
|
object.Type = ctype -- #CTLD_CARGO.Enum
|
|
buildables[name] = object
|
|
foundbuilds = true
|
|
else
|
|
buildables[name].Found = buildables[name].Found + 1
|
|
foundbuilds = true
|
|
end
|
|
if buildables[name].Found >= buildables[name].Required then
|
|
buildables[name].CanBuild = true
|
|
canbuild = true
|
|
end
|
|
self:T( { repair = buildables } )
|
|
end -- end dropped
|
|
end -- end crate loop
|
|
-- ok let's list what we have
|
|
local report = REPORT:New( "Checklist Repairs" )
|
|
report:Add( "------------------------------------------------------------" )
|
|
for _, _build in pairs( buildables ) do
|
|
local build = _build -- Object table from above
|
|
local name = build.Name
|
|
local needed = build.Required
|
|
local found = build.Found
|
|
local txtok = "NO"
|
|
if build.CanBuild then
|
|
txtok = "YES"
|
|
end
|
|
local text = string.format( "Type: %s | Required %d | Found %d | Can Repair %s", name, needed, found, txtok )
|
|
report:Add( text )
|
|
end -- end list buildables
|
|
if not foundbuilds then
|
|
report:Add( " --- None Found ---" )
|
|
end
|
|
report:Add( "------------------------------------------------------------" )
|
|
local text = report:Text()
|
|
if not Engineering then
|
|
self:_SendMessage( text, 30, true, Group )
|
|
else
|
|
self:T( text )
|
|
end
|
|
-- let's get going
|
|
if canbuild then
|
|
-- loop again
|
|
for _, _build in pairs( buildables ) do
|
|
local build = _build -- #CTLD.Buildable
|
|
if build.CanBuild then
|
|
self:_RepairObjectFromCrates( Group, Unit, crates, build, number, Engineering )
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if not Engineering then
|
|
self:_SendMessage( string.format( "No crates within %d meters!", finddist ), 10, false, Group )
|
|
end
|
|
end -- number > 0
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to actually SPAWN buildables in the mission.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Group#UNIT Unit
|
|
-- @param #CTLD.Buildable Build
|
|
-- @param #boolean Repair If true this is a repair and not a new build
|
|
-- @param Core.Point#COORDINATE Coordinate Location for repair (e.g. where the destroyed unit was)
|
|
function CTLD:_BuildObjectFromCrates( Group, Unit, Build, Repair, RepairLocation )
|
|
self:T( self.lid .. " _BuildObjectFromCrates" )
|
|
-- Spawn-a-crate-content
|
|
if Group and Group:IsAlive() then
|
|
local position = Unit:GetCoordinate() or Group:GetCoordinate()
|
|
local unitname = Unit:GetName() or Group:GetName()
|
|
local name = Build.Name
|
|
local ctype = Build.Type -- #CTLD_CARGO.Enum
|
|
local canmove = false
|
|
if ctype == CTLD_CARGO.Enum.VEHICLE then
|
|
canmove = true
|
|
end
|
|
if ctype == CTLD_CARGO.Enum.STATIC then
|
|
return self
|
|
end
|
|
local temptable = Build.Template or {}
|
|
if type( temptable ) == "string" then
|
|
temptable = { temptable }
|
|
end
|
|
local zone = ZONE_GROUP:New( string.format( "Unload zone-%s", unitname ), Group, 100 )
|
|
local randomcoord = zone:GetRandomCoordinate( 35 ):GetVec2()
|
|
if Repair then
|
|
randomcoord = RepairLocation:GetVec2()
|
|
end
|
|
for _, _template in pairs( temptable ) do
|
|
self.TroopCounter = self.TroopCounter + 1
|
|
local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) )
|
|
if canmove then
|
|
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias )
|
|
:InitRandomizeUnits( true, 20, 2 )
|
|
:InitDelayOff()
|
|
:SpawnFromVec2( randomcoord )
|
|
else -- don't random position of e.g. SAM units build as FOB
|
|
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias )
|
|
:InitDelayOff()
|
|
:SpawnFromVec2( randomcoord )
|
|
end
|
|
if self.movetroopstowpzone and canmove then
|
|
self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] )
|
|
end
|
|
if Repair then
|
|
self:__CratesRepaired( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] )
|
|
else
|
|
self:__CratesBuild( 1, Group, Unit, self.DroppedTroops[self.TroopCounter] )
|
|
end
|
|
end -- template loop
|
|
else
|
|
self:T( self.lid .. "Group KIA while building!" )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to move group to WP zone.
|
|
-- @param #CTLD self
|
|
-- @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:Tstring.format("Closest WP zone %s is %d meters",name,distance))
|
|
if (distance <= self.movetroopsdistance) and zone then
|
|
-- yes, we can ;)
|
|
local groupname = Group:GetName()
|
|
local zonecoord = zone:GetRandomCoordinate( 20, 125 ) -- Core.Point#COORDINATE
|
|
local coordinate = zonecoord:GetVec2()
|
|
Group:SetAIOn()
|
|
Group:OptionAlarmStateAuto()
|
|
Group:OptionDisperseOnAttack( 30 )
|
|
Group:OptionROEOpenFirePossible()
|
|
Group:RouteToVec2( coordinate, 5 )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Housekeeping - Cleanup crates when build
|
|
-- @param #CTLD self
|
|
-- @param #table Crates Table of #CTLD_CARGO objects near the unit.
|
|
-- @param #CTLD.Buildable Build Table build object.
|
|
-- @param #number Number Number of objects in Crates (found) to limit search.
|
|
function CTLD:_CleanUpCrates( Crates, Build, Number )
|
|
self:T( self.lid .. " _CleanUpCrates" )
|
|
-- clean up real world crates
|
|
local build = Build -- #CTLD.Buildable
|
|
local existingcrates = self.Spawned_Cargo -- #table of exising crates
|
|
local newexcrates = {}
|
|
-- get right number of crates to destroy
|
|
local numberdest = Build.Required
|
|
local nametype = Build.Name
|
|
local found = 0
|
|
local rounds = Number
|
|
local destIDs = {}
|
|
|
|
-- loop and find matching IDs in the set
|
|
for _, _crate in pairs( Crates ) do
|
|
local nowcrate = _crate -- #CTLD_CARGO
|
|
local name = nowcrate:GetName()
|
|
local thisID = nowcrate:GetID()
|
|
if name == nametype then -- matching crate type
|
|
table.insert( destIDs, thisID )
|
|
found = found + 1
|
|
nowcrate:GetPositionable():Destroy( false )
|
|
nowcrate.Positionable = nil
|
|
nowcrate.HasBeenDropped = false
|
|
end
|
|
if found == numberdest then
|
|
break
|
|
end -- got enough
|
|
end
|
|
-- loop and remove from real world representation
|
|
self:_CleanupTrackedCrates( destIDs )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Housekeeping - Function to refresh F10 menus.
|
|
-- @param #CTLD self
|
|
-- @return #CTLD self
|
|
function CTLD:_RefreshF10Menus()
|
|
self:T( self.lid .. " _RefreshF10Menus" )
|
|
local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP
|
|
local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects
|
|
-- rebuild units table
|
|
local _UnitList = {}
|
|
for _key, _group in pairs( PlayerTable ) do
|
|
local _unit = _group:GetUnit( 1 ) -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players
|
|
if _unit then
|
|
if _unit:IsAlive() and _unit:IsPlayer() then
|
|
if _unit:IsHelicopter() or (_unit:GetTypeName() == "Hercules" and self.enableHercules) then -- ensure no stupid unit entries here
|
|
local unitName = _unit:GetName()
|
|
_UnitList[unitName] = unitName
|
|
end
|
|
end -- end isAlive
|
|
end -- end if _unit
|
|
end -- end for
|
|
self.CtldUnits = _UnitList
|
|
|
|
-- build unit menus
|
|
local menucount = 0
|
|
local menus = {}
|
|
for _, _unitName in pairs( self.CtldUnits ) do
|
|
if not self.MenusDone[_unitName] then
|
|
local _unit = UNIT:FindByName( _unitName ) -- Wrapper.Unit#UNIT
|
|
if _unit then
|
|
local _group = _unit:GetGroup() -- Wrapper.Group#GROUP
|
|
if _group then
|
|
-- get chopper capabilities
|
|
local unittype = _unit:GetTypeName()
|
|
local capabilities = self:_GetUnitCapabilities( _unit ) -- #CTLD.UnitCapabilities
|
|
local cantroops = capabilities.troops
|
|
local cancrates = capabilities.crates
|
|
-- top menu
|
|
local topmenu = MENU_GROUP:New( _group, "CTLD", nil )
|
|
local toptroops = MENU_GROUP:New( _group, "Manage Troops", topmenu )
|
|
local topcrates = MENU_GROUP:New( _group, "Manage Crates", topmenu )
|
|
local listmenu = MENU_GROUP_COMMAND:New( _group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit )
|
|
local invtry = MENU_GROUP_COMMAND:New( _group, "Inventory", topmenu, self._ListInventory, self, _group, _unit )
|
|
local rbcns = MENU_GROUP_COMMAND:New( _group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit )
|
|
local smoketopmenu = MENU_GROUP:New( _group, "Smokes & Flares", topmenu )
|
|
local smokemenu = MENU_GROUP_COMMAND:New( _group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false )
|
|
local smokeself = MENU_GROUP_COMMAND:New( _group, "Drop smoke now", smoketopmenu, self.SmokePositionNow, self, _unit, false )
|
|
local flaremenu = MENU_GROUP_COMMAND:New( _group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true )
|
|
local flareself = MENU_GROUP_COMMAND:New( _group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true ):Refresh()
|
|
-- sub menus
|
|
-- sub menu troops management
|
|
if cantroops then
|
|
local troopsmenu = MENU_GROUP:New( _group, "Load troops", toptroops )
|
|
for _, _entry in pairs( self.Cargo_Troops ) do
|
|
local entry = _entry -- #CTLD_CARGO
|
|
menucount = menucount + 1
|
|
menus[menucount] = MENU_GROUP_COMMAND:New( _group, entry.Name, troopsmenu, self._LoadTroops, self, _group, _unit, entry )
|
|
end
|
|
local unloadmenu1 = MENU_GROUP_COMMAND:New( _group, "Drop troops", toptroops, self._UnloadTroops, self, _group, _unit ):Refresh()
|
|
local extractMenu1 = MENU_GROUP_COMMAND:New( _group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit ):Refresh()
|
|
end
|
|
-- sub menu crates management
|
|
if cancrates then
|
|
local loadmenu = MENU_GROUP_COMMAND:New( _group, "Load crates", topcrates, self._LoadCratesNearby, self, _group, _unit )
|
|
local cratesmenu = MENU_GROUP:New( _group, "Get Crates", topcrates )
|
|
for _, _entry in pairs( self.Cargo_Crates ) do
|
|
local entry = _entry -- #CTLD_CARGO
|
|
menucount = menucount + 1
|
|
local menutext = string.format( "Crate %s (%dkg)", entry.Name, entry.PerCrateMass or 0 )
|
|
menus[menucount] = MENU_GROUP_COMMAND:New( _group, menutext, cratesmenu, self._GetCrates, self, _group, _unit, entry )
|
|
end
|
|
for _, _entry in pairs( self.Cargo_Statics ) do
|
|
local entry = _entry -- #CTLD_CARGO
|
|
menucount = menucount + 1
|
|
local menutext = string.format( "Crate %s (%dkg)", entry.Name, entry.PerCrateMass or 0 )
|
|
menus[menucount] = MENU_GROUP_COMMAND:New( _group, menutext, cratesmenu, self._GetCrates, self, _group, _unit, entry )
|
|
end
|
|
listmenu = MENU_GROUP_COMMAND:New( _group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit )
|
|
local unloadmenu = MENU_GROUP_COMMAND:New( _group, "Drop crates", topcrates, self._UnloadCrates, self, _group, _unit )
|
|
local buildmenu = MENU_GROUP_COMMAND:New( _group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit )
|
|
local repairmenu = MENU_GROUP_COMMAND:New( _group, "Repair", topcrates, self._RepairCrates, self, _group, _unit ):Refresh()
|
|
end
|
|
if unittype == "Hercules" then
|
|
local hoverpars = MENU_GROUP_COMMAND:New( _group, "Show flight parameters", topmenu, self._ShowFlightParams, self, _group, _unit ):Refresh()
|
|
else
|
|
local hoverpars = MENU_GROUP_COMMAND:New( _group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit ):Refresh()
|
|
end
|
|
self.MenusDone[_unitName] = true
|
|
end -- end group
|
|
end -- end unit
|
|
else -- menu build check
|
|
self:T( self.lid .. " Menus already done for this group!" )
|
|
end -- end menu build check
|
|
end -- end for
|
|
return self
|
|
end
|
|
|
|
--- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small".
|
|
-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop.
|
|
-- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build.
|
|
-- @param #number NoTroops Size of the group in number of Units across combined templates (for loading).
|
|
-- @param #number PerTroopMass Mass in kg of each soldier
|
|
-- @param #number Stock Number of groups in stock. Nil for unlimited.
|
|
function CTLD:AddTroopsCargo( Name, Templates, Type, NoTroops, PerTroopMass, Stock )
|
|
self:T( self.lid .. " AddTroopsCargo" )
|
|
self:T( { Name, Templates, Type, NoTroops, PerTroopMass, Stock } )
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
-- Troops are directly loadable
|
|
local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Templates, Type, false, true, NoTroops, nil, nil, PerTroopMass, Stock )
|
|
table.insert( self.Cargo_Troops, cargo )
|
|
return self
|
|
end
|
|
|
|
--- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Unique name of this type of cargo. E.g. "Humvee".
|
|
-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo.
|
|
-- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put.
|
|
-- @param #number NoCrates Number of crates needed to build this cargo.
|
|
-- @param #number PerCrateMass Mass in kg of each crate
|
|
-- @param #number Stock Number of groups in stock. Nil for unlimited.
|
|
function CTLD:AddCratesCargo( Name, Templates, Type, NoCrates, PerCrateMass, Stock )
|
|
self:T( self.lid .. " AddCratesCargo" )
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
-- Crates are not directly loadable
|
|
local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Templates, Type, false, false, NoCrates, nil, nil, PerCrateMass, Stock )
|
|
table.insert( self.Cargo_Crates, cargo )
|
|
return self
|
|
end
|
|
|
|
--- User function - Add *generic* static-type loadable as cargo. This type will create cargo that needs to be loaded, moved and dropped.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1".
|
|
-- @param #number Mass Mass in kg of each static in kg, e.g. 100.
|
|
-- @param #number Stock Number of groups in stock. Nil for unlimited.
|
|
function CTLD:AddStaticsCargo( Name, Mass, Stock )
|
|
self:T( self.lid .. " AddStaticsCargo" )
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
local type = CTLD_CARGO.Enum.STATIC
|
|
local template = STATIC:FindByName( Name, true ):GetTypeName()
|
|
-- Crates are not directly loadable
|
|
local cargo = CTLD_CARGO:New( self.CargoCounter, Name, template, type, false, false, 1, nil, nil, Mass, Stock )
|
|
table.insert( self.Cargo_Statics, cargo )
|
|
return self
|
|
end
|
|
|
|
--- User function - Get a *generic* static-type loadable as #CTLD_CARGO object.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Unique Unit(!) name of this type of cargo as set in the mission editor (not: GROUP name!), e.g. "Ammunition-1".
|
|
-- @param #number Mass Mass in kg of each static in kg, e.g. 100.
|
|
-- @return #CTLD_CARGO Cargo object
|
|
function CTLD:GetStaticsCargoFromTemplate( Name, Mass )
|
|
self:T( self.lid .. " GetStaticsCargoFromTemplate" )
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
local type = CTLD_CARGO.Enum.STATIC
|
|
local template = STATIC:FindByName( Name, true ):GetTypeName()
|
|
-- Crates are not directly loadable
|
|
local cargo = CTLD_CARGO:New( self.CargoCounter, Name, template, type, false, false, 1, nil, nil, Mass, 1 )
|
|
-- table.insert(self.Cargo_Statics,cargo)
|
|
return cargo
|
|
end
|
|
|
|
--- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Unique name of this type of cargo. E.g. "Humvee".
|
|
-- @param #string Template Template of VEHICLE or FOB cargo that this can repair.
|
|
-- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR.
|
|
-- @param #number NoCrates Number of crates needed to build this cargo.
|
|
-- @param #number PerCrateMass Mass in kg of each crate
|
|
-- @param #number Stock Number of groups in stock. Nil for unlimited.
|
|
function CTLD:AddCratesRepair( Name, Template, Type, NoCrates, PerCrateMass, Stock )
|
|
self:T( self.lid .. " AddCratesRepair" )
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
-- Crates are not directly loadable
|
|
local cargo = CTLD_CARGO:New( self.CargoCounter, Name, Template, Type, false, false, NoCrates, nil, nil, PerCrateMass, Stock )
|
|
table.insert( self.Cargo_Crates, cargo )
|
|
return self
|
|
end
|
|
|
|
--- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance.
|
|
-- @param #CTLD self
|
|
-- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone.
|
|
function CTLD:AddZone( Zone )
|
|
self:T( self.lid .. " AddZone" )
|
|
local zone = Zone -- #CTLD.CargoZone
|
|
if zone.type == CTLD.CargoZoneType.LOAD then
|
|
table.insert( self.pickupZones, zone )
|
|
elseif zone.type == CTLD.CargoZoneType.DROP then
|
|
table.insert( self.dropOffZones, zone )
|
|
elseif zone.type == CTLD.CargoZoneType.SHIP then
|
|
table.insert( self.shipZones, zone )
|
|
else
|
|
table.insert( self.wpZones, zone )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name of the zone to change in the ME.
|
|
-- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to.
|
|
-- @param #boolean NewState (Optional) Set to true to activate, false to switch off.
|
|
function CTLD:ActivateZone( Name, ZoneType, NewState )
|
|
self:T( self.lid .. " AddZone" )
|
|
local newstate = true
|
|
-- set optional in case we're deactivating
|
|
if NewState ~= nil then
|
|
newstate = NewState
|
|
end
|
|
|
|
-- get correct table
|
|
local table = {}
|
|
if ZoneType == CTLD.CargoZoneType.LOAD then
|
|
table = self.pickupZones
|
|
elseif ZoneType == CTLD.CargoZoneType.DROP then
|
|
table = self.dropOffZones
|
|
elseif ZoneType == CTLD.CargoZoneType.SHIP then
|
|
table = self.shipZones
|
|
else
|
|
table = self.wpZones
|
|
end
|
|
-- loop table
|
|
for _, _zone in pairs( table ) do
|
|
local thiszone = _zone -- #CTLD.CargoZone
|
|
if thiszone.name == Name then
|
|
thiszone.active = newstate
|
|
break
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name of the zone to change in the ME.
|
|
-- @param #CTLD.CargoZoneType ZoneType Type of zone this belongs to.
|
|
function CTLD:DeactivateZone( Name, ZoneType )
|
|
self:T( self.lid .. " AddZone" )
|
|
self:ActivateZone( Name, ZoneType, false )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to obtain a valid FM frequency.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name of zone.
|
|
-- @return #CTLD.ZoneBeacon Beacon Beacon table.
|
|
function CTLD:_GetFMBeacon( Name )
|
|
self:T( self.lid .. " _GetFMBeacon" )
|
|
local beacon = {} -- #CTLD.ZoneBeacon
|
|
if #self.FreeFMFrequencies <= 1 then
|
|
self.FreeFMFrequencies = self.UsedFMFrequencies
|
|
self.UsedFMFrequencies = {}
|
|
end
|
|
-- random
|
|
local FM = table.remove( self.FreeFMFrequencies, math.random( #self.FreeFMFrequencies ) )
|
|
table.insert( self.UsedFMFrequencies, FM )
|
|
beacon.name = Name
|
|
beacon.frequency = FM / 1000000
|
|
beacon.modulation = radio.modulation.FM
|
|
return beacon
|
|
end
|
|
|
|
--- (Internal) Function to obtain a valid UHF frequency.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name of zone.
|
|
-- @return #CTLD.ZoneBeacon Beacon Beacon table.
|
|
function CTLD:_GetUHFBeacon( Name )
|
|
self:T( self.lid .. " _GetUHFBeacon" )
|
|
local beacon = {} -- #CTLD.ZoneBeacon
|
|
if #self.FreeUHFFrequencies <= 1 then
|
|
self.FreeUHFFrequencies = self.UsedUHFFrequencies
|
|
self.UsedUHFFrequencies = {}
|
|
end
|
|
-- random
|
|
local UHF = table.remove( self.FreeUHFFrequencies, math.random( #self.FreeUHFFrequencies ) )
|
|
table.insert( self.UsedUHFFrequencies, UHF )
|
|
beacon.name = Name
|
|
beacon.frequency = UHF / 1000000
|
|
beacon.modulation = radio.modulation.AM
|
|
|
|
return beacon
|
|
end
|
|
|
|
--- (Internal) Function to obtain a valid VHF frequency.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name of zone.
|
|
-- @return #CTLD.ZoneBeacon Beacon Beacon table.
|
|
function CTLD:_GetVHFBeacon( Name )
|
|
self:T( self.lid .. " _GetVHFBeacon" )
|
|
local beacon = {} -- #CTLD.ZoneBeacon
|
|
if #self.FreeVHFFrequencies <= 3 then
|
|
self.FreeVHFFrequencies = self.UsedVHFFrequencies
|
|
self.UsedVHFFrequencies = {}
|
|
end
|
|
-- get random
|
|
local VHF = table.remove( self.FreeVHFFrequencies, math.random( #self.FreeVHFFrequencies ) )
|
|
table.insert( self.UsedVHFFrequencies, VHF )
|
|
beacon.name = Name
|
|
beacon.frequency = VHF / 1000000
|
|
beacon.modulation = radio.modulation.FM
|
|
return beacon
|
|
end
|
|
|
|
--- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance.
|
|
-- Zones of type LOAD: Players load crates and troops here.
|
|
-- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere.
|
|
-- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options).
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name of this zone, as in Mission Editor.
|
|
-- @param #string Type Type of this zone, #CTLD.CargoZoneType
|
|
-- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red
|
|
-- @param #string Active Is this zone currently active?
|
|
-- @param #string HasBeacon Does this zone have a beacon if it is active?
|
|
-- @param #number Shiplength Length of Ship for shipzones
|
|
-- @param #number Shipwidth Width of Ship for shipzones
|
|
-- @return #CTLD self
|
|
function CTLD:AddCTLDZone( Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth )
|
|
self:T( self.lid .. " AddCTLDZone" )
|
|
|
|
local ctldzone = {} -- #CTLD.CargoZone
|
|
ctldzone.active = Active or false
|
|
ctldzone.color = Color or SMOKECOLOR.Red
|
|
ctldzone.name = Name or "NONE"
|
|
ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType
|
|
ctldzone.hasbeacon = HasBeacon or false
|
|
|
|
if HasBeacon then
|
|
ctldzone.fmbeacon = self:_GetFMBeacon( Name )
|
|
ctldzone.uhfbeacon = self:_GetUHFBeacon( Name )
|
|
ctldzone.vhfbeacon = self:_GetVHFBeacon( Name )
|
|
else
|
|
ctldzone.fmbeacon = nil
|
|
ctldzone.uhfbeacon = nil
|
|
ctldzone.vhfbeacon = nil
|
|
end
|
|
|
|
if Type == CTLD.CargoZoneType.SHIP then
|
|
ctldzone.shiplength = Shiplength or 100
|
|
ctldzone.shipwidth = Shipwidth or 10
|
|
end
|
|
|
|
self:AddZone( ctldzone )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to show list of radio beacons
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
function CTLD:_ListRadioBeacons( Group, Unit )
|
|
self:T( self.lid .. " _ListRadioBeacons" )
|
|
local report = REPORT:New( "Active Zone Beacons" )
|
|
report:Add( "------------------------------------------------------------" )
|
|
local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones }
|
|
for i = 1, 4 do
|
|
for index, cargozone in pairs( zones[i] ) do
|
|
-- Get Beacon object from zone
|
|
local czone = cargozone -- #CTLD.CargoZone
|
|
if czone.active and czone.hasbeacon then
|
|
local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon
|
|
local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon
|
|
local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon
|
|
local Name = czone.name
|
|
local FM = FMbeacon.frequency -- MHz
|
|
local VHF = VHFbeacon.frequency * 1000 -- KHz
|
|
local UHF = UHFbeacon.frequency -- MHz
|
|
report:AddIndent( string.format( " %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF ), "|" )
|
|
end
|
|
end
|
|
end
|
|
if report:GetCount() == 1 then
|
|
report:Add( " N O N E" )
|
|
end
|
|
report:Add( "------------------------------------------------------------" )
|
|
self:_SendMessage( report:Text(), 30, true, Group )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Add radio beacon to zone. Runs 30 secs.
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name of zone.
|
|
-- @param #string Sound Name of soundfile.
|
|
-- @param #number Mhz Frequency in Mhz.
|
|
-- @param #number Modulation Modulation AM or FM.
|
|
-- @param #boolean IsShip If true zone is a ship.
|
|
function CTLD:_AddRadioBeacon( Name, Sound, Mhz, Modulation, IsShip )
|
|
self:T( self.lid .. " _AddRadioBeacon" )
|
|
local Zone = nil
|
|
if IsShip then
|
|
Zone = UNIT:FindByName( Name )
|
|
else
|
|
Zone = ZONE:FindByName( Name )
|
|
end
|
|
local Sound = Sound or "beacon.ogg"
|
|
if Zone then
|
|
local ZoneCoord = Zone:GetCoordinate()
|
|
local ZoneVec3 = ZoneCoord:GetVec3()
|
|
local Frequency = Mhz * 1000000 -- Freq in Hertz
|
|
local Sound = "l10n/DEFAULT/" .. Sound
|
|
trigger.action.radioTransmission( Sound, ZoneVec3, Modulation, false, Frequency, 1000 ) -- Beacon in MP only runs for 30secs straight
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to refresh radio beacons
|
|
-- @param #CTLD self
|
|
function CTLD:_RefreshRadioBeacons()
|
|
self:T( self.lid .. " _RefreshRadioBeacons" )
|
|
|
|
local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones }
|
|
for i = 1, 4 do
|
|
local IsShip = false
|
|
if i == 4 then
|
|
IsShip = true
|
|
end
|
|
for index, cargozone in pairs( zones[i] ) do
|
|
-- Get Beacon object from zone
|
|
local czone = cargozone -- #CTLD.CargoZone
|
|
local Sound = self.RadioSound
|
|
if czone.active and czone.hasbeacon then
|
|
local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon
|
|
local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon
|
|
local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon
|
|
local Name = czone.name
|
|
local FM = FMbeacon.frequency -- MHz
|
|
local VHF = VHFbeacon.frequency -- KHz
|
|
local UHF = UHFbeacon.frequency -- MHz
|
|
self:_AddRadioBeacon( Name, Sound, FM, radio.modulation.FM, IsShip )
|
|
self:_AddRadioBeacon( Name, Sound, VHF, radio.modulation.FM, IsShip )
|
|
self:_AddRadioBeacon( Name, Sound, UHF, radio.modulation.AM, IsShip )
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Function to see if a unit is in a specific zone type.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit Unit
|
|
-- @param #CTLD.CargoZoneType Zonetype Zonetype
|
|
-- @return #boolean Outcome Is in zone or not
|
|
-- @return #string name Closest zone name
|
|
-- @return Core.Zone#ZONE zone Closest Core.Zone#ZONE object
|
|
-- @return #number distance Distance to closest zone
|
|
-- @return #number width Radius of zone or width of ship
|
|
function CTLD:IsUnitInZone( Unit, Zonetype )
|
|
self:T( self.lid .. " IsUnitInZone" )
|
|
self:T( Zonetype )
|
|
local unitname = Unit:GetName()
|
|
local zonetable = {}
|
|
local outcome = false
|
|
if Zonetype == CTLD.CargoZoneType.LOAD then
|
|
zonetable = self.pickupZones -- #table
|
|
elseif Zonetype == CTLD.CargoZoneType.DROP then
|
|
zonetable = self.dropOffZones -- #table
|
|
elseif Zonetype == CTLD.CargoZoneType.SHIP then
|
|
zonetable = self.shipZones -- #table
|
|
else
|
|
zonetable = self.wpZones -- #table
|
|
end
|
|
--- now see if we're in
|
|
local zonecoord = nil
|
|
local colorret = nil
|
|
local maxdist = 1000000 -- 100km
|
|
local zoneret = nil
|
|
local zonewret = nil
|
|
local zonenameret = nil
|
|
for _, _cargozone in pairs( zonetable ) do
|
|
local czone = _cargozone -- #CTLD.CargoZone
|
|
local unitcoord = Unit:GetCoordinate()
|
|
local zonename = czone.name
|
|
local active = czone.active
|
|
local color = czone.color
|
|
local zone = nil
|
|
local zoneradius = 100
|
|
local zonewidth = 20
|
|
if Zonetype == CTLD.CargoZoneType.SHIP then
|
|
self:T( "Checking Type Ship: " .. zonename )
|
|
zone = UNIT:FindByName( zonename )
|
|
zonecoord = zone:GetCoordinate()
|
|
zoneradius = czone.shiplength
|
|
zonewidth = czone.shipwidth
|
|
else
|
|
zone = ZONE:FindByName( zonename )
|
|
zonecoord = zone:GetCoordinate()
|
|
zoneradius = zone:GetRadius()
|
|
zonewidth = zoneradius
|
|
end
|
|
local distance = self:_GetDistance( zonecoord, unitcoord )
|
|
if distance <= zoneradius and active then
|
|
outcome = true
|
|
end
|
|
if maxdist > distance then
|
|
maxdist = distance
|
|
zoneret = zone
|
|
zonenameret = zonename
|
|
zonewret = zonewidth
|
|
colorret = color
|
|
end
|
|
end
|
|
if Zonetype == CTLD.CargoZoneType.SHIP then
|
|
return outcome, zonenameret, zoneret, maxdist, zonewret
|
|
else
|
|
return outcome, zonenameret, zoneret, maxdist
|
|
end
|
|
end
|
|
|
|
--- User function - Drop a smoke or flare at current location.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit The Unit.
|
|
-- @param #boolean Flare If true, flare instead.
|
|
function CTLD:SmokePositionNow( Unit, Flare )
|
|
self:T( self.lid .. " SmokePositionNow" )
|
|
local SmokeColor = self.SmokeColor or SMOKECOLOR.Red
|
|
local FlareColor = self.FlareColor or FLARECOLOR.Red
|
|
-- table of #CTLD.CargoZone table
|
|
local unitcoord = Unit:GetCoordinate() -- Core.Point#COORDINATE
|
|
local Group = Unit:GetGroup()
|
|
if Flare then
|
|
unitcoord:Flare( FlareColor, 90 )
|
|
else
|
|
local height = unitcoord:GetLandHeight() + 2
|
|
unitcoord.y = height
|
|
unitcoord:Smoke( SmokeColor )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- User function - Start smoke/flare in a zone close to the Unit.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit The Unit.
|
|
-- @param #boolean Flare If true, flare instead.
|
|
function CTLD:SmokeZoneNearBy( Unit, Flare )
|
|
self:T( self.lid .. " SmokeZoneNearBy" )
|
|
-- table of #CTLD.CargoZone table
|
|
local unitcoord = Unit:GetCoordinate()
|
|
local Group = Unit:GetGroup()
|
|
local smokedistance = self.smokedistance
|
|
local smoked = false
|
|
local zones = { [1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones, [4] = self.shipZones }
|
|
for i = 1, 4 do
|
|
for index, cargozone in pairs( zones[i] ) do
|
|
local CZone = cargozone -- #CTLD.CargoZone
|
|
local zonename = CZone.name
|
|
local zone = nil
|
|
if i == 4 then
|
|
zone = UNIT:FindByName( zonename )
|
|
else
|
|
zone = ZONE:FindByName( zonename )
|
|
end
|
|
local zonecoord = zone:GetCoordinate()
|
|
local active = CZone.active
|
|
local color = CZone.color
|
|
local distance = self:_GetDistance( zonecoord, unitcoord )
|
|
if distance < smokedistance and active then
|
|
-- smoke zone since we're nearby
|
|
if not Flare then
|
|
zonecoord:Smoke( color or SMOKECOLOR.White )
|
|
else
|
|
if color == SMOKECOLOR.Blue then
|
|
color = FLARECOLOR.White
|
|
end
|
|
zonecoord:Flare( color or FLARECOLOR.White )
|
|
end
|
|
local txt = "smoking"
|
|
if Flare then
|
|
txt = "flaring"
|
|
end
|
|
self:_SendMessage( string.format( "Roger, %s zone %s!", txt, zonename ), 10, false, Group )
|
|
smoked = true
|
|
end
|
|
end
|
|
end
|
|
if not smoked then
|
|
local distance = UTILS.MetersToNM( self.smokedistance )
|
|
self:_SendMessage( string.format( "Negative, need to be closer than %dnm to a zone!", distance ), 10, false, Group )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- User - Function to add/adjust unittype capabilities.
|
|
-- @param #CTLD self
|
|
-- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission.
|
|
-- @param #boolean Cancrates Unit can load crates. Default false.
|
|
-- @param #boolean Cantroops Unit can load troops. Default false.
|
|
-- @param #number Cratelimit Unit can carry number of crates. Default 0.
|
|
-- @param #number Trooplimit Unit can carry number of troops. Default 0.
|
|
-- @param #number Length Unit lenght (in mteres) for the load radius. Default 20.
|
|
function CTLD:UnitCapabilities( Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length )
|
|
self:T( self.lid .. " UnitCapabilities" )
|
|
local unittype = nil
|
|
local unit = nil
|
|
if type( Unittype ) == "string" then
|
|
unittype = Unittype
|
|
elseif type( Unittype ) == "table" then
|
|
unit = UNIT:FindByName( Unittype ) -- Wrapper.Unit#UNIT
|
|
unittype = unit:GetTypeName()
|
|
else
|
|
return self
|
|
end
|
|
-- set capabilities
|
|
local capabilities = {} -- #CTLD.UnitCapabilities
|
|
capabilities.type = unittype
|
|
capabilities.crates = Cancrates or false
|
|
capabilities.troops = Cantroops or false
|
|
capabilities.cratelimit = Cratelimit or 0
|
|
capabilities.trooplimit = Trooplimit or 0
|
|
capabilities.length = Length or 20
|
|
self.UnitTypes[unittype] = capabilities
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Check if a unit is hovering *in parameters*.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #boolean Outcome
|
|
function CTLD:IsCorrectHover( Unit )
|
|
self:T( self.lid .. " IsCorrectHover" )
|
|
local outcome = false
|
|
-- see if we are in air and within parameters.
|
|
if self:IsUnitInAir( Unit ) then
|
|
-- get speed and height
|
|
local uspeed = Unit:GetVelocityMPS()
|
|
local uheight = Unit:GetHeight()
|
|
local ucoord = Unit:GetCoordinate()
|
|
local gheight = ucoord:GetLandHeight()
|
|
local aheight = uheight - gheight -- height above ground
|
|
local maxh = self.maximumHoverHeight -- 15
|
|
local minh = self.minimumHoverHeight -- 5
|
|
local mspeed = 2 -- 2 m/s
|
|
if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then
|
|
-- yep within parameters
|
|
outcome = true
|
|
end
|
|
end
|
|
return outcome
|
|
end
|
|
|
|
--- (Internal) Check if a Hercules is flying *in parameters* for air drops.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #boolean Outcome
|
|
function CTLD:IsCorrectFlightParameters( Unit )
|
|
self:T( self.lid .. " IsCorrectFlightParameters" )
|
|
local outcome = false
|
|
-- see if we are in air and within parameters.
|
|
if self:IsUnitInAir( Unit ) then
|
|
-- get speed and height
|
|
local uspeed = Unit:GetVelocityMPS()
|
|
local uheight = Unit:GetHeight()
|
|
local ucoord = Unit:GetCoordinate()
|
|
local gheight = ucoord:GetLandHeight()
|
|
local aheight = uheight - gheight -- height above ground
|
|
local maxh = self.HercMinAngels -- 1500m
|
|
local minh = self.HercMaxAngels -- 5000m
|
|
local maxspeed = self.HercMaxSpeed -- 77 mps
|
|
-- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn
|
|
local kmspeed = uspeed * 3.6
|
|
local knspeed = kmspeed / 1.86
|
|
self:T( string.format( "%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn", self.lid, aheight, uspeed, kmspeed, knspeed ) )
|
|
if (aheight <= maxh) and (aheight >= minh) and (uspeed <= maxspeed) then
|
|
-- yep within parameters
|
|
outcome = true
|
|
end
|
|
end
|
|
return outcome
|
|
end
|
|
|
|
--- (Internal) List if a unit is hovering *in parameters*.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
function CTLD:_ShowHoverParams( Group, Unit )
|
|
local inhover = self:IsCorrectHover( Unit )
|
|
local htxt = "true"
|
|
if not inhover then
|
|
htxt = "false"
|
|
end
|
|
local text = ""
|
|
if _SETTINGS:IsMetric() then
|
|
text = string.format( "Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt )
|
|
else
|
|
local minheight = UTILS.MetersToFeet( self.minimumHoverHeight )
|
|
local maxheight = UTILS.MetersToFeet( self.maximumHoverHeight )
|
|
text = string.format( "Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt )
|
|
end
|
|
self:_SendMessage( text, 10, false, Group )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) List if a Herc unit is flying *in parameters*.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
function CTLD:_ShowFlightParams( Group, Unit )
|
|
local inhover = self:IsCorrectFlightParameters( Unit )
|
|
local htxt = "true"
|
|
if not inhover then
|
|
htxt = "false"
|
|
end
|
|
local text = ""
|
|
if _SETTINGS:IsImperial() then
|
|
local minheight = UTILS.MetersToFeet( self.HercMinAngels )
|
|
local maxheight = UTILS.MetersToFeet( self.HercMaxAngels )
|
|
text = string.format( "Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt )
|
|
else
|
|
local minheight = self.HercMinAngels
|
|
local maxheight = self.HercMaxAngels
|
|
text = string.format( "Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt )
|
|
end
|
|
self:_SendMessage( text, 10, false, Group )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Check if a unit is in a load zone and is hovering in parameters.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #boolean Outcome
|
|
function CTLD:CanHoverLoad( Unit )
|
|
self:T( self.lid .. " CanHoverLoad" )
|
|
if self:IsHercules( Unit ) then
|
|
return false
|
|
end
|
|
local outcome = self:IsUnitInZone( Unit, CTLD.CargoZoneType.LOAD ) and self:IsCorrectHover( Unit )
|
|
if not outcome then
|
|
outcome = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) -- and self:IsCorrectHover(Unit)
|
|
end
|
|
return outcome
|
|
end
|
|
|
|
--- (Internal) Check if a unit is above ground.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #boolean Outcome
|
|
function CTLD:IsUnitInAir( Unit )
|
|
-- get speed and height
|
|
local minheight = self.minimumHoverHeight
|
|
if self.enableHercules and Unit:GetTypeName() == "Hercules" then
|
|
minheight = 5.1 -- herc is 5m AGL on the ground
|
|
end
|
|
local uheight = Unit:GetHeight()
|
|
local ucoord = Unit:GetCoordinate()
|
|
local gheight = ucoord:GetLandHeight()
|
|
local aheight = uheight - gheight -- height above ground
|
|
if aheight >= minheight then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- (Internal) Autoload if we can do crates, have capacity free and are in a load zone.
|
|
-- @param #CTLD self
|
|
-- @param Wrapper.Unit#UNIT Unit
|
|
-- @return #CTLD self
|
|
function CTLD:AutoHoverLoad( Unit )
|
|
self:T( self.lid .. " AutoHoverLoad" )
|
|
-- get capabilities and current load
|
|
local unittype = Unit:GetTypeName()
|
|
local unitname = Unit:GetName()
|
|
local Group = Unit:GetGroup()
|
|
local capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities
|
|
local cancrates = capabilities.crates -- #boolean
|
|
local cratelimit = capabilities.cratelimit -- #number
|
|
if cancrates then
|
|
-- get load
|
|
local numberonboard = 0
|
|
local loaded = {}
|
|
if self.Loaded_Cargo[unitname] then
|
|
loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo
|
|
numberonboard = loaded.Cratesloaded or 0
|
|
end
|
|
local load = cratelimit - numberonboard
|
|
local canload = self:CanHoverLoad( Unit )
|
|
if canload and load > 0 then
|
|
self:_LoadCratesNearby( Group, Unit )
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Run through all pilots and see if we autoload.
|
|
-- @param #CTLD self
|
|
-- @return #CTLD self
|
|
function CTLD:CheckAutoHoverload()
|
|
if self.hoverautoloading then
|
|
for _, _pilot in pairs( self.CtldUnits ) do
|
|
local Unit = UNIT:FindByName( _pilot )
|
|
if self:CanHoverLoad( Unit ) then
|
|
self:AutoHoverLoad( Unit )
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Run through DroppedTroops and capture alive units
|
|
-- @param #CTLD self
|
|
-- @return #CTLD self
|
|
function CTLD:CleanDroppedTroops()
|
|
-- Troops
|
|
local troops = self.DroppedTroops
|
|
local newtable = {}
|
|
for _index, _group in pairs( troops ) do
|
|
self:T( { _group.ClassName } )
|
|
if _group and _group.ClassName == "GROUP" then
|
|
if _group:IsAlive() then
|
|
newtable[_index] = _group
|
|
end
|
|
end
|
|
end
|
|
self.DroppedTroops = newtable
|
|
-- Engineers
|
|
local engineers = self.EngineersInField
|
|
local engtable = {}
|
|
for _index, _group in pairs( engineers ) do
|
|
self:T( { _group.ClassName } )
|
|
if _group and _group:IsNotStatus( "Stopped" ) then
|
|
engtable[_index] = _group
|
|
end
|
|
end
|
|
self.EngineersInField = engtable
|
|
return self
|
|
end
|
|
|
|
--- User - function to add stock of a certain troops type
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name as defined in the generic cargo.
|
|
-- @param #number Number Number of units/groups to add.
|
|
-- @return #CTLD self
|
|
function CTLD:AddStockTroops( Name, Number )
|
|
local name = Name or "none"
|
|
local number = Number or 1
|
|
-- find right generic type
|
|
local gentroops = self.Cargo_Troops
|
|
for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO
|
|
if _troop.Name == name then
|
|
_troop:AddStock( number )
|
|
end
|
|
end
|
|
end
|
|
|
|
--- User - function to add stock of a certain crates type
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name as defined in the generic cargo.
|
|
-- @param #number Number Number of units/groups to add.
|
|
-- @return #CTLD self
|
|
function CTLD:AddStockCrates( Name, Number )
|
|
local name = Name or "none"
|
|
local number = Number or 1
|
|
-- find right generic type
|
|
local gentroops = self.Cargo_Crates
|
|
for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO
|
|
if _troop.Name == name then
|
|
_troop:AddStock( number )
|
|
end
|
|
end
|
|
end
|
|
|
|
--- User - function to remove stock of a certain troops type
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name as defined in the generic cargo.
|
|
-- @param #number Number Number of units/groups to add.
|
|
-- @return #CTLD self
|
|
function CTLD:RemoveStockTroops( Name, Number )
|
|
local name = Name or "none"
|
|
local number = Number or 1
|
|
-- find right generic type
|
|
local gentroops = self.Cargo_Troops
|
|
for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO
|
|
if _troop.Name == name then
|
|
_troop:RemoveStock( number )
|
|
end
|
|
end
|
|
end
|
|
|
|
--- User - function to remove stock of a certain crates type
|
|
-- @param #CTLD self
|
|
-- @param #string Name Name as defined in the generic cargo.
|
|
-- @param #number Number Number of units/groups to add.
|
|
-- @return #CTLD self
|
|
function CTLD:RemoveStockCrates( Name, Number )
|
|
local name = Name or "none"
|
|
local number = Number or 1
|
|
-- find right generic type
|
|
local gentroops = self.Cargo_Crates
|
|
for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO
|
|
if _troop.Name == name then
|
|
_troop:RemoveStock( number )
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) Check on engineering teams
|
|
-- @param #CTLD self
|
|
-- @return #CTLD self
|
|
function CTLD:_CheckEngineers()
|
|
self:T( self.lid .. " CheckEngineers" )
|
|
local engtable = self.EngineersInField
|
|
for _ind, _engineers in pairs( engtable ) do
|
|
local engineers = _engineers -- #CTLD_ENGINEERING
|
|
local wrenches = engineers.Group -- Wrapper.Group#GROUP
|
|
self:T( _engineers.lid .. _engineers:GetStatus() )
|
|
if wrenches and wrenches:IsAlive() then
|
|
if engineers:IsStatus( "Running" ) or engineers:IsStatus( "Searching" ) then
|
|
local crates, number = self:_FindCratesNearby( wrenches, nil, self.EngineerSearch ) -- #table
|
|
engineers:Search( crates, number )
|
|
elseif engineers:IsStatus( "Moving" ) then
|
|
engineers:Move()
|
|
elseif engineers:IsStatus( "Arrived" ) then
|
|
engineers:Build()
|
|
local unit = wrenches:GetUnit( 1 )
|
|
self:_BuildCrates( wrenches, unit, true )
|
|
self:_RepairCrates( wrenches, unit, true )
|
|
engineers:Done()
|
|
end
|
|
else
|
|
engineers:Stop()
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (User) Pre-populate troops in the field.
|
|
-- @param #CTLD self
|
|
-- @param Core.Zone#ZONE Zone The zone where to drop the troops.
|
|
-- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn.
|
|
-- @return #CTLD self
|
|
-- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone:
|
|
-- -- create a matching #CTLD_CARGO type
|
|
-- local InjectTroopsType = CTLD_CARGO:New(nil,"Infantry",{"Inf12"},CTLD_CARGO.Enum.TROOPS,true,true,12,nil,false,80)
|
|
-- -- get a #ZONE object
|
|
-- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE
|
|
-- -- and go:
|
|
-- my_ctld:InjectTroops(dropzone,InjectTroopsType)
|
|
function CTLD:InjectTroops( Zone, Cargo )
|
|
self:T( self.lid .. " InjectTroops" )
|
|
local cargo = Cargo -- #CTLD_CARGO
|
|
|
|
local function IsTroopsMatch( cargo )
|
|
local match = false
|
|
local cgotbl = self.Cargo_Troops
|
|
local name = cargo:GetName()
|
|
for _, _cgo in pairs( cgotbl ) do
|
|
local cname = _cgo:GetName()
|
|
if name == cname then
|
|
match = true
|
|
break
|
|
end
|
|
end
|
|
return match
|
|
end
|
|
|
|
if not IsTroopsMatch( cargo ) then
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
cargo.ID = self.CargoCounter
|
|
cargo.Stock = 1
|
|
table.insert( self.Cargo_Troops, cargo )
|
|
end
|
|
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then
|
|
-- unload
|
|
local name = cargo:GetName() or "none"
|
|
local temptable = cargo:GetTemplates() or {}
|
|
local factor = 1.5
|
|
local zone = Zone
|
|
|
|
local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2()
|
|
for _, _template in pairs( temptable ) do
|
|
self.TroopCounter = self.TroopCounter + 1
|
|
local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) )
|
|
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias )
|
|
:InitRandomizeUnits( true, 20, 2 )
|
|
:InitDelayOff()
|
|
:SpawnFromVec2( randomcoord )
|
|
if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then
|
|
self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] )
|
|
end
|
|
end -- template loop
|
|
cargo:SetWasDropped( true )
|
|
-- engineering group?
|
|
if type == CTLD_CARGO.Enum.ENGINEERS then
|
|
self.Engineers = self.Engineers + 1
|
|
local grpname = self.DroppedTroops[self.TroopCounter]:GetName()
|
|
self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New( name, grpname )
|
|
-- self:I(string.format("%s Injected Engineers %s into action!",self.lid, name))
|
|
else
|
|
-- self:I(string.format("%s Injected Troops %s into action!",self.lid, name))
|
|
end
|
|
if self.eventoninject then
|
|
self:__TroopsDeployed( 1, nil, nil, self.DroppedTroops[self.TroopCounter] )
|
|
end
|
|
end -- if type end
|
|
return self
|
|
end
|
|
|
|
--- (User) Pre-populate vehicles in the field.
|
|
-- @param #CTLD self
|
|
-- @param Core.Zone#ZONE Zone The zone where to drop the troops.
|
|
-- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn.
|
|
-- @return #CTLD self
|
|
-- @usage Use this function to pre-populate the field with Vehicles or FOB at a random coordinate in a zone:
|
|
-- -- create a matching #CTLD_CARGO type
|
|
-- local InjectVehicleType = CTLD_CARGO:New(nil,"Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000)
|
|
-- -- get a #ZONE object
|
|
-- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE
|
|
-- -- and go:
|
|
-- my_ctld:InjectVehicles(dropzone,InjectVehicleType)
|
|
function CTLD:InjectVehicles( Zone, Cargo )
|
|
self:T( self.lid .. " InjectVehicles" )
|
|
local cargo = Cargo -- #CTLD_CARGO
|
|
|
|
local function IsVehicMatch( cargo )
|
|
local match = false
|
|
local cgotbl = self.Cargo_Crates
|
|
local name = cargo:GetName()
|
|
for _, _cgo in pairs( cgotbl ) do
|
|
local cname = _cgo:GetName()
|
|
if name == cname then
|
|
match = true
|
|
break
|
|
end
|
|
end
|
|
return match
|
|
end
|
|
|
|
if not IsVehicMatch( cargo ) then
|
|
self.CargoCounter = self.CargoCounter + 1
|
|
cargo.ID = self.CargoCounter
|
|
cargo.Stock = 1
|
|
table.insert( self.Cargo_Crates, cargo )
|
|
end
|
|
|
|
local type = cargo:GetType() -- #CTLD_CARGO.Enum
|
|
if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then
|
|
-- unload
|
|
local name = cargo:GetName() or "none"
|
|
local temptable = cargo:GetTemplates() or {}
|
|
local factor = 1.5
|
|
local zone = Zone
|
|
local randomcoord = zone:GetRandomCoordinate( 10, 30 * factor ):GetVec2()
|
|
cargo:SetWasDropped( true )
|
|
local canmove = false
|
|
if type == CTLD_CARGO.Enum.VEHICLE then
|
|
canmove = true
|
|
end
|
|
for _, _template in pairs( temptable ) do
|
|
self.TroopCounter = self.TroopCounter + 1
|
|
local alias = string.format( "%s-%d", _template, math.random( 1, 100000 ) )
|
|
if canmove then
|
|
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias )
|
|
:InitRandomizeUnits( true, 20, 2 )
|
|
:InitDelayOff()
|
|
:SpawnFromVec2( randomcoord )
|
|
else -- don't random position of e.g. SAM units build as FOB
|
|
self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias )
|
|
:InitDelayOff()
|
|
:SpawnFromVec2( randomcoord )
|
|
end
|
|
if self.movetroopstowpzone and canmove then
|
|
self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] )
|
|
end
|
|
if self.eventoninject then
|
|
self:__CratesBuild( 1, nil, nil, self.DroppedTroops[self.TroopCounter] )
|
|
end
|
|
end -- end loop
|
|
end -- if type end
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------
|
|
-- FSM functions
|
|
-------------------------------------------------------------------
|
|
|
|
--- (Internal) FSM Function onafterStart.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @return #CTLD self
|
|
function CTLD:onafterStart( From, Event, To )
|
|
self:T( { From, Event, To } )
|
|
self:I( self.lid .. "Started (" .. self.version .. ")" )
|
|
if self.useprefix or self.enableHercules then
|
|
local prefix = self.prefixes
|
|
if self.enableHercules then
|
|
self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( prefix ):FilterStart()
|
|
else
|
|
self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterPrefixes( prefix ):FilterCategories( "helicopter" ):FilterStart()
|
|
end
|
|
else
|
|
self.PilotGroups = SET_GROUP:New():FilterCoalitions( self.coalitiontxt ):FilterCategories( "helicopter" ):FilterStart()
|
|
end
|
|
-- Events
|
|
self:HandleEvent( EVENTS.PlayerEnterAircraft, self._EventHandler )
|
|
self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventHandler )
|
|
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventHandler )
|
|
self:__Status( -5 )
|
|
|
|
-- AutoSave
|
|
if self.enableLoadSave then
|
|
local interval = self.saveinterval
|
|
local filename = self.filename
|
|
local filepath = self.filepath
|
|
self:__Save( interval, filepath, filename )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onbeforeStatus.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @return #CTLD self
|
|
function CTLD:onbeforeStatus( From, Event, To )
|
|
self:T( { From, Event, To } )
|
|
self:CleanDroppedTroops()
|
|
self:_RefreshF10Menus()
|
|
self:_RefreshRadioBeacons()
|
|
self:CheckAutoHoverload()
|
|
self:_CheckEngineers()
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onafterStatus.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @return #CTLD self
|
|
function CTLD:onafterStatus( From, Event, To )
|
|
self:T( { From, Event, To } )
|
|
-- gather some stats
|
|
-- pilots
|
|
local pilots = 0
|
|
for _, _pilot in pairs( self.CtldUnits ) do
|
|
pilots = pilots + 1
|
|
end
|
|
|
|
-- spawned cargo boxes curr in field
|
|
local boxes = 0
|
|
for _, _pilot in pairs( self.Spawned_Cargo ) do
|
|
boxes = boxes + 1
|
|
end
|
|
|
|
local cc = self.CargoCounter
|
|
local tc = self.TroopCounter
|
|
|
|
if self.debug or self.verbose > 0 then
|
|
local text = string.format( "%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc )
|
|
local m = MESSAGE:New( text, 10, "CTLD" ):ToAll()
|
|
if self.verbose > 0 then
|
|
self:I( self.lid .. "Cargo and Troops in Stock:" )
|
|
for _, _troop in pairs( self.Cargo_Crates ) do
|
|
local name = _troop:GetName()
|
|
local stock = _troop:GetStock()
|
|
self:I( string.format( "-- %s \t\t\t %d", name, stock ) )
|
|
end
|
|
for _, _troop in pairs( self.Cargo_Statics ) do
|
|
local name = _troop:GetName()
|
|
local stock = _troop:GetStock()
|
|
self:I( string.format( "-- %s \t\t\t %d", name, stock ) )
|
|
end
|
|
for _, _troop in pairs( self.Cargo_Troops ) do
|
|
local name = _troop:GetName()
|
|
local stock = _troop:GetStock()
|
|
self:I( string.format( "-- %s \t\t %d", name, stock ) )
|
|
end
|
|
end
|
|
end
|
|
self:__Status( -30 )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onafterStop.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @return #CTLD self
|
|
function CTLD:onafterStop( From, Event, To )
|
|
self:T( { From, Event, To } )
|
|
self:UnhandleEvent( EVENTS.PlayerEnterAircraft )
|
|
self:UnhandleEvent( EVENTS.PlayerEnterUnit )
|
|
self:UnhandleEvent( EVENTS.PlayerLeaveUnit )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onbeforeTroopsPickedUp.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param #CTLD_CARGO Cargo Cargo crate.
|
|
-- @return #CTLD self
|
|
function CTLD:onbeforeTroopsPickedUp( From, Event, To, Group, Unit, Cargo )
|
|
self:T( { From, Event, To } )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onbeforeCratesPickedUp.
|
|
-- @param #CTLD self
|
|
-- @param #string From State .
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param #CTLD_CARGO Cargo Cargo crate.
|
|
-- @return #CTLD self
|
|
function CTLD:onbeforeCratesPickedUp( From, Event, To, Group, Unit, Cargo )
|
|
self:T( { From, Event, To } )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onbeforeTroopsExtracted.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param Wrapper.Group#GROUP Troops Troops #GROUP Object.
|
|
-- @return #CTLD self
|
|
function CTLD:onbeforeTroopsExtracted( From, Event, To, Group, Unit, Troops )
|
|
self:T( { From, Event, To } )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onbeforeTroopsDeployed.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param Wrapper.Group#GROUP Troops Troops #GROUP Object.
|
|
-- @return #CTLD self
|
|
function CTLD:onbeforeTroopsDeployed( From, Event, To, Group, Unit, Troops )
|
|
self:T( { From, Event, To } )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onbeforeCratesDropped.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param #table Cargotable Table of #CTLD_CARGO objects dropped.
|
|
-- @return #CTLD self
|
|
function CTLD:onbeforeCratesDropped( From, Event, To, Group, Unit, Cargotable )
|
|
self:T( { From, Event, To } )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onbeforeCratesBuild.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build.
|
|
-- @return #CTLD self
|
|
function CTLD:onbeforeCratesBuild( From, Event, To, Group, Unit, Vehicle )
|
|
self:T( { From, Event, To } )
|
|
return self
|
|
end
|
|
|
|
--- (Internal) FSM Function onbeforeTroopsRTB.
|
|
-- @param #CTLD self
|
|
-- @param #string From State.
|
|
-- @param #string Event Trigger.
|
|
-- @param #string To State.
|
|
-- @param Wrapper.Group#GROUP Group Group Object.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit Object.
|
|
-- @return #CTLD self
|
|
function CTLD:onbeforeTroopsRTB( From, Event, To, Group, Unit )
|
|
self:T( { From, Event, To } )
|
|
return self
|
|
end
|
|
|
|
--- On before "Save" event. Checks if io and lfs are available.
|
|
-- @param #CTLD self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
|
-- @param #string filename (Optional) File name for saving. Default is "CTLD_<alias>_Persist.csv".
|
|
function CTLD:onbeforeSave( From, Event, To, path, filename )
|
|
self:T( { From, Event, To, path, filename } )
|
|
if not self.enableLoadSave then
|
|
return self
|
|
end
|
|
-- Thanks to @FunkyFranky
|
|
-- Check io module is available.
|
|
if not io then
|
|
self:E( self.lid .. "ERROR: io not desanitized. Can't save current state." )
|
|
return false
|
|
end
|
|
|
|
-- Check default path.
|
|
if path == nil and not lfs then
|
|
self:E( self.lid .. "WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder." )
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--- On after "Save" event. Player data is saved to file.
|
|
-- @param #CTLD self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized.
|
|
-- @param #string filename (Optional) File name for saving. Default is Default is "CTLD_<alias>_Persist.csv".
|
|
function CTLD:onafterSave( From, Event, To, path, filename )
|
|
self:T( { From, Event, To, path, filename } )
|
|
-- Thanks to @FunkyFranky
|
|
if not self.enableLoadSave then
|
|
return self
|
|
end
|
|
--- Function that saves data to file
|
|
local function _savefile( filename, data )
|
|
local f = assert( io.open( filename, "wb" ) )
|
|
f:write( data )
|
|
f:close()
|
|
end
|
|
|
|
-- Set path or default.
|
|
if lfs then
|
|
path = self.filepath or lfs.writedir()
|
|
end
|
|
|
|
-- Set file name.
|
|
filename = filename or self.filename
|
|
|
|
-- Set path.
|
|
if path ~= nil then
|
|
filename = path .. "\\" .. filename
|
|
end
|
|
|
|
local grouptable = self.DroppedTroops -- #table
|
|
local cgovehic = self.Cargo_Crates
|
|
local cgotable = self.Cargo_Troops
|
|
local stcstable = self.Spawned_Cargo
|
|
|
|
local statics = nil
|
|
local statics = {}
|
|
self:I( self.lid .. "Bulding Statics Table for Saving" )
|
|
for _, _cargo in pairs( stcstable ) do
|
|
local cargo = _cargo -- #CTLD_CARGO
|
|
local object = cargo:GetPositionable() -- Wrapper.Static#STATIC
|
|
if object and object:IsAlive() and cargo:WasDropped() then
|
|
self:I( { _cargo } )
|
|
statics[#statics + 1] = cargo
|
|
end
|
|
end
|
|
|
|
-- find matching cargo
|
|
local function FindCargoType( name, table )
|
|
-- name matching a template in the table
|
|
local match = false
|
|
local cargo = nil
|
|
for _ind, _cargo in pairs( table ) do
|
|
local thiscargo = _cargo -- #CTLD_CARGO
|
|
local template = thiscargo:GetTemplates()
|
|
if type( template ) == "string" then
|
|
template = { template }
|
|
end
|
|
for _, _name in pairs( template ) do
|
|
-- self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name))
|
|
if string.find( name, _name ) and _cargo:GetType() ~= CTLD_CARGO.Enum.REPAIR then
|
|
match = true
|
|
cargo = thiscargo
|
|
end
|
|
end
|
|
if match then
|
|
break
|
|
end
|
|
end
|
|
return match, cargo
|
|
end
|
|
|
|
-- local data = "LoadedData = {\n"
|
|
local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass\n"
|
|
local n = 0
|
|
for _, _grp in pairs( grouptable ) do
|
|
local group = _grp -- Wrapper.Group#GROUP
|
|
if group and group:IsAlive() then
|
|
-- get template name
|
|
local name = group:GetName()
|
|
local template = string.gsub( name, "-(.+)$", "" )
|
|
if string.find( template, "#" ) then
|
|
template = string.gsub( name, "#(%d+)$", "" )
|
|
end
|
|
|
|
local match, cargo = FindCargoType( template, cgotable )
|
|
if not match then
|
|
match, cargo = FindCargoType( template, cgovehic )
|
|
end
|
|
if match then
|
|
n = n + 1
|
|
local cargo = cargo -- #CTLD_CARGO
|
|
local cgoname = cargo.Name
|
|
local cgotemp = cargo.Templates
|
|
local cgotype = cargo.CargoType
|
|
local cgoneed = cargo.CratesNeeded
|
|
local cgomass = cargo.PerCrateMass
|
|
|
|
if type( cgotemp ) == "table" then
|
|
local templates = "{"
|
|
for _, _tmpl in pairs( cgotemp ) do
|
|
templates = templates .. _tmpl .. ";"
|
|
end
|
|
templates = templates .. "}"
|
|
cgotemp = templates
|
|
end
|
|
|
|
local location = group:GetVec3()
|
|
local txt = string.format( "%s,%d,%d,%d,%s,%s,%s,%d,%d\n", template, location.x, location.y, location.z, cgoname, cgotemp, cgotype, cgoneed, cgomass )
|
|
data = data .. txt
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, _cgo in pairs( statics ) do
|
|
local object = _cgo -- #CTLD_CARGO
|
|
local cgoname = object.Name
|
|
local cgotemp = object.Templates
|
|
|
|
if type( cgotemp ) == "table" then
|
|
local templates = "{"
|
|
for _, _tmpl in pairs( cgotemp ) do
|
|
templates = templates .. _tmpl .. ";"
|
|
end
|
|
templates = templates .. "}"
|
|
cgotemp = templates
|
|
end
|
|
|
|
local cgotype = object.CargoType
|
|
local cgoneed = object.CratesNeeded
|
|
local cgomass = object.PerCrateMass
|
|
local crateobj = object.Positionable
|
|
local location = crateobj:GetVec3()
|
|
local txt = string.format( "%s,%d,%d,%d,%s,%s,%s,%d,%d\n", "STATIC", location.x, location.y, location.z, cgoname, cgotemp, cgotype, cgoneed, cgomass )
|
|
data = data .. txt
|
|
end
|
|
|
|
_savefile( filename, data )
|
|
|
|
-- AutoSave
|
|
if self.enableLoadSave then
|
|
local interval = self.saveinterval
|
|
local filename = self.filename
|
|
local filepath = self.filepath
|
|
self:__Save( interval, filepath, filename )
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- On before "Load" event. Checks if io and lfs and the file are available.
|
|
-- @param #CTLD self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
|
-- @param #string filename (Optional) File name for loading. Default is "CTLD_<alias>_Persist.csv".
|
|
function CTLD:onbeforeLoad( From, Event, To, path, filename )
|
|
self:T( { From, Event, To, path, filename } )
|
|
if not self.enableLoadSave then
|
|
return self
|
|
end
|
|
--- Function that check if a file exists.
|
|
local function _fileexists( name )
|
|
local f = io.open( name, "r" )
|
|
if f ~= nil then
|
|
io.close( f )
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- Set file name and path
|
|
filename = filename or self.filename
|
|
path = path or self.filepath
|
|
|
|
-- Check io module is available.
|
|
if not io then
|
|
self:E( self.lid .. "WARNING: io not desanitized. Cannot load file." )
|
|
return false
|
|
end
|
|
|
|
-- Check default path.
|
|
if path == nil and not lfs then
|
|
self:E( self.lid .. "WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder." )
|
|
end
|
|
|
|
-- Set path or default.
|
|
if lfs then
|
|
path = path or lfs.writedir()
|
|
end
|
|
|
|
-- Set path.
|
|
if path ~= nil then
|
|
filename = path .. "\\" .. filename
|
|
end
|
|
|
|
-- Check if file exists.
|
|
local exists = _fileexists( filename )
|
|
|
|
if exists then
|
|
return true
|
|
else
|
|
self:E( self.lid .. string.format( "WARNING: State file %s might not exist.", filename ) )
|
|
return false
|
|
-- return self
|
|
end
|
|
|
|
end
|
|
|
|
--- On after "Load" event. Loads dropped units from file.
|
|
-- @param #CTLD self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
|
|
-- @param #string filename (Optional) File name for loading. Default is "CTLD_<alias>_Persist.csv".
|
|
function CTLD:onafterLoad( From, Event, To, path, filename )
|
|
self:T( { From, Event, To, path, filename } )
|
|
if not self.enableLoadSave then
|
|
return self
|
|
end
|
|
--- Function that loads data from a file.
|
|
local function _loadfile( filename )
|
|
local f = assert( io.open( filename, "rb" ) )
|
|
local data = f:read( "*all" )
|
|
f:close()
|
|
return data
|
|
end
|
|
|
|
-- Set file name and path
|
|
filename = filename or self.filename
|
|
path = path or self.filepath
|
|
|
|
-- Set path or default.
|
|
if lfs then
|
|
path = path or lfs.writedir()
|
|
end
|
|
|
|
-- Set path.
|
|
if path ~= nil then
|
|
filename = path .. "\\" .. filename
|
|
end
|
|
|
|
-- Info message.
|
|
local text = string.format( "Loading CTLD state from file %s", filename )
|
|
MESSAGE:New( text, 10 ):ToAllIf( self.Debug )
|
|
self:I( self.lid .. text )
|
|
|
|
local file = assert( io.open( filename, "rb" ) )
|
|
|
|
local loadeddata = {}
|
|
for line in file:lines() do
|
|
-- self:I({line=type(line)})
|
|
loadeddata[#loadeddata + 1] = line
|
|
end
|
|
file:close()
|
|
|
|
-- remove header
|
|
table.remove( loadeddata, 1 )
|
|
|
|
for _id, _entry in pairs( loadeddata ) do
|
|
local dataset = UTILS.Split( _entry, "," )
|
|
-- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass
|
|
local groupname = dataset[1]
|
|
local vec2 = {}
|
|
vec2.x = tonumber( dataset[2] )
|
|
vec2.y = tonumber( dataset[4] )
|
|
local cargoname = dataset[5]
|
|
local cargotype = dataset[7]
|
|
if type( groupname ) == "string" and groupname ~= "STATIC" then
|
|
local cargotemplates = dataset[6]
|
|
cargotemplates = string.gsub( cargotemplates, "{", "" )
|
|
cargotemplates = string.gsub( cargotemplates, "}", "" )
|
|
cargotemplates = UTILS.Split( cargotemplates, ";" )
|
|
local size = tonumber( dataset[8] )
|
|
local mass = tonumber( dataset[9] )
|
|
-- self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass})
|
|
-- inject at Vec2
|
|
local dropzone = ZONE_RADIUS:New( "DropZone", vec2, 20 )
|
|
if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then
|
|
local injectvehicle = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass )
|
|
self:InjectVehicles( dropzone, injectvehicle )
|
|
elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then
|
|
local injecttroops = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass )
|
|
self:InjectTroops( dropzone, injecttroops )
|
|
end
|
|
elseif (type( groupname ) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then
|
|
local cargotemplates = dataset[6]
|
|
local size = tonumber( dataset[8] )
|
|
local mass = tonumber( dataset[9] )
|
|
local dropzone = ZONE_RADIUS:New( "DropZone", vec2, 20 )
|
|
-- STATIC,-84037,154,834021,Humvee,{Humvee;},Vehicle,1,100
|
|
-- STATIC,-84036,154,834018,Ammunition-1,ammo_cargo,Static,1,500
|
|
local injectstatic = nil
|
|
if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then
|
|
cargotemplates = string.gsub( cargotemplates, "{", "" )
|
|
cargotemplates = string.gsub( cargotemplates, "}", "" )
|
|
cargotemplates = UTILS.Split( cargotemplates, ";" )
|
|
injectstatic = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass )
|
|
elseif cargotype == CTLD_CARGO.Enum.STATIC or cargotype == CTLD_CARGO.Enum.REPAIR then
|
|
injectstatic = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass )
|
|
end
|
|
if injectstatic then
|
|
self:InjectStatics( dropzone, injectstatic )
|
|
end
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
end -- end do
|
|
-------------------------------------------------------------------
|
|
-- End Ops.CTLD.lua
|
|
-------------------------------------------------------------------
|