From 2694321256dbe14c0e0b3bf3211493a5641064eb Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Fri, 17 Dec 2021 12:07:24 +0400 Subject: [PATCH] Update CTLD.lua (#1666) Code formatting. Minor typos and text fixes. --- Moose Development/Moose/Ops/CTLD.lua | 6499 +++++++++++++------------- 1 file changed, 3274 insertions(+), 3225 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 37e203ec7..6822d3f55 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -25,34 +25,34 @@ -- 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, 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 @@ -60,26 +60,26 @@ CTLD_ENGINEERING = { -- @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}) - + 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.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.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.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. @@ -89,145 +89,145 @@ CTLD_ENGINEERING = { self:AddTransition("*", "Arrive", "Arrived") self:AddTransition("*", "Build", "Building") self:AddTransition("*", "Done", "Running") - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + self:__Start(5) --]] self:Start() - local parent = self:GetParent(self) + 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) + 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) + 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) + 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") + 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") + 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") + 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") + self:T( self.lid .. "Done" ) local grp = self.Group -- Wrapper.Group#GROUP - grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond") - self:SetStatus("Running") + 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") + 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 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 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 + 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 - _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 + 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)) + 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) + 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 @@ -235,101 +235,102 @@ CTLD_ENGINEERING = { self:Arrive() end else - self:T(self.lid.."No crates in reach!") + 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") + self:T( self.lid .. "Move" ) + self:SetStatus( "Moving" ) -- check if we arrived on target - --local COps = self.C_Ops -- Ops.CTLD#CTLD + -- 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)) + 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() + -- 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: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") + 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 + 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 + elseif distance2 and type( distance2 ) == "number" then return distance2 else - self:E("*****Cannot calculate distance!") - self:E({_point1,_point2}) + self:E( "*****Cannot calculate distance!" ) + self:E( { _point1, _point2 } ) return -1 end else - self:E("******Cannot calculate distance!") - self:E({_point1,_point2}) + 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, + ------------------------------------------------------ + --- **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. @@ -342,13 +343,13 @@ CTLD_CARGO = { ["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 Sorte Enumerator of Type. + -- @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. @@ -357,119 +358,119 @@ CTLD_CARGO = { -- @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, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock) + 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, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) - self.ID = ID or math.random(100000,1000000) + 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 = Sorte or "type" -- #CTLD_CARGO.Enum + 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.HasBeenDropped = Dropped or false -- #boolean self.PerCrateMass = PerCrateMass or 0 -- #number - self.Stock = Stock or nil --#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 + 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) + function CTLD_CARGO:SetHasMoved( moved ) self.HasBeenMoved = moved or false end - - --- Query if cargo has been loaded. + + --- 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 + return false + end end - + --- Set WasDropped. -- @param #CTLD_CARGO self -- @param #boolean dropped - function CTLD_CARGO:SetWasDropped(dropped) + function CTLD_CARGO:SetWasDropped( dropped ) self.HasBeenDropped = dropped or false end - + --- Get Stock. -- @param #CTLD_CARGO self -- @return #number Stock @@ -480,1043 +481,1048 @@ CTLD_CARGO = { 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) + 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) + 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 + 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 + 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 + if self.CargoType == "Static" then + return true + else + return false + end end - - function CTLD_CARGO:AddMark(Mark) + + function CTLD_CARGO:AddMark( Mark ) self.Mark = Mark return self end - - function CTLD_CARGO:GetMark(Mark) + + 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 + ------------------------------------------------------------------------- + --- **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) --- --- === --- --- ![Banner Image](OPS_CTLD.jpg) --- --- # 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 soundfile 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 + --- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there's POG stuff to be done.* (Mil Saying) + -- + -- === + -- + -- ![Banner Image](OPS_CTLD.jpg) + -- + -- # 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 = {}, -} + -- 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 ------------------------------- + ------------------------------ + -- 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 + --- 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 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", -} + --- 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). + --- 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. -} + --- 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" + --- 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 + --- 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:E("ERROR: Unknown coalition in CTLD!") + self.coalition = Coalition + self.coalitiontxt = string.lower( UTILS.GetCoalitionName( self.coalition ) ) 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" + + -- 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 - 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 + -- 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("*", "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. + -- tables + self.PilotGroups = {} + self.CtldUnits = {} - --- 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__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__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") + -- Beacons + self.FreeVHFFrequencies = {} self.FreeUHFFrequencies = {} - self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() - return self -end + self.FreeFMFrequencies = {} + self.UsedVHFFrequencies = {} + self.UsedUHFFrequencies = {} + self.UsedFMFrequencies = {} ---- (Internal) Function to generate valid FM Frequencies --- @param #CTLD self -function CTLD:_GenerateFMFrequencies() - self:T(self.lid .. " _GenerateFMrequencies") + -- 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__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__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) + --- (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 - -- 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") + --- (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 + 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}) + -- self:I({desc = desc.attributes}) if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then nearestGroup = v nearestGroupIndex = k @@ -1526,126 +1532,136 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) -- found one and matching distance? if nearestGroup == nil or nearestDistance > self.EngineerSearch then - self:_SendMessage("No unit close enough to repair!", 10, false, Group) + 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 function matchstring( String, Table ) local match = false - if type(Table) == "table" then - for _,_name in pairs (Table) do - if string.find(String,_name) then + 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 + if type( String ) == "string" then + if string.find( String, Table ) then + match = true + end 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 + 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) + -- 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 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") + 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) - + 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 + 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 + 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 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 @@ -1653,44 +1669,48 @@ end 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) + 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) + 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) + self:_SendMessage( "No units close enough to extract!", 10, false, Group ) return self end - + -- sort reference keys - table.sort(distancekeys) - + table.sort( distancekeys ) + local secondarygroups = {} - - for i=1,#distancekeys do + + for i = 1, #distancekeys do local nearestGroup = nearestList[distancekeys[i]] - -- find matching cargo type - local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") + -- find matching cargo type + local groupType = string.match( nearestGroup:GetName(), "(.+)-(.+)$" ) local Cargotype = nil - for k,v in pairs(self.Cargo_Troops) do + 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 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) + self:_SendMessage( "Can't onboard " .. groupType, 10, false, Group ) else - + local troopsize = Cargotype:GetCratesNeeded() -- #number -- have we loaded stuff already? local numberonboard = 0 @@ -1705,38 +1725,38 @@ end loaded.Cargo = {} end if troopsize + numberonboard > trooplimit then - self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) - --return self + 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}) + 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) + 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) - + 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) + -- 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) + nearestGroup:Destroy( false ) end end end -- clean up secondary groups - for _,_name in pairs(secondarygroups) do - for _,_group in pairs(nearestList) do + for _, _name in pairs( secondarygroups ) do + for _, _group in pairs( nearestList ) do if _group and _group:IsAlive() then - local groupname = string.match(_group:GetName(), "(.+)-(.+)$") + local groupname = string.match( _group:GetName(), "(.+)-(.+)$" ) if _name == groupname then - _group:Destroy(false) + _group:Destroy( false ) end end end @@ -1744,1712 +1764,1734 @@ 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 + --- (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 - local cratecoord = position:Translate(cratedistance,rheading) - local cratevec2 = cratecoord:GetVec2() - self.CrateCounter = self.CrateCounter + 1 + -- 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 - 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 + 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 - 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 + cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] + table.insert( self.Spawned_Cargo, cargotype ) 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 + --- (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)) + --- (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 - 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 + if text:GetCount() == 1 then + text:Add( " N O N E" ) + end + text:Add( "------------------------------------------------------------" ) + self:_SendMessage( text:Text(), 30, true, _group ) else - self:E("*****Cannot calculate distance!") - self:E({_point1,_point2}) + 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 - 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) + --- (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 - 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") + --- (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 + 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 - 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() + -- 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 - -- 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) + 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 - 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 + -- remove destroyed crates here too + local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates + if not static or not static:IsAlive() 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 + if keep then + table.insert( newexcrates, _crate ) end end + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + return self 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 + --- (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 - -- 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 + 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 - 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 + return loadedmass 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) + --- (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 - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - if not self.debug then - return self - end + inzone, zonename, zone, distance = self:IsUnitInZone( Unit, CTLD.CargoZoneType.SHIP ) 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 + if inzone then + droppingatbase = true 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 + -- check for hover unload + local hoverunload = self:IsCorrectHover( Unit ) -- if true we're hovering in parameters + local IsHerc = self:IsHercules( Unit ) 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 + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters( Unit ) 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 + -- 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 - buildables[name].Found = buildables[name].Found + 1 - foundbuilds = true + -- 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 - 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) + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass( Unit ) 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]) + if IsHerc then + self:_SendMessage( "Nothing loaded or not within airdrop parameters!", 10, false, Group ) else - self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + self:_SendMessage( "Nothing loaded or not hovering within parameters!", 10, false, Group ) 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 + return self 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() + --- (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 - local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + buildables[name].Found = buildables[name].Found + 1 + foundbuilds = true 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) + 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 - 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 + --- (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 - -- loop table - for _,_zone in pairs(table) do - local thiszone = _zone --#CTLD.CargoZone - if thiszone.name == Name then - thiszone.active = newstate - break + + --- (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 - 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 ---- 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) 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 = {} ---- (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 + -- 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 + 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 + --- (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 + 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 + 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 + --- (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 + -- 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 - 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 + --- 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" ) ---- (Internal) Function to refresh radio beacons --- @param #CTLD self -function CTLD:_RefreshRadioBeacons() - self:T(self.lid .. " _RefreshRadioBeacons") + 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 - 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 + if HasBeacon then + ctldzone.fmbeacon = self:_GetFMBeacon( Name ) + ctldzone.uhfbeacon = self:_GetUHFBeacon( Name ) + ctldzone.vhfbeacon = self:_GetVHFBeacon( Name ) else - zone = ZONE:FindByName(zonename) - zonecoord = zone:GetCoordinate() - zoneradius = zone:GetRadius() - zonewidth = zoneradius + ctldzone.fmbeacon = nil + ctldzone.uhfbeacon = nil + ctldzone.vhfbeacon = nil 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 + if Type == CTLD.CargoZoneType.SHIP then + ctldzone.shiplength = Shiplength or 100 + ctldzone.shipwidth = Shipwidth or 10 + 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) + 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 - 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 + if report:GetCount() == 1 then + report:Add( " N O N E" ) + end + report:Add( "------------------------------------------------------------" ) + self:_SendMessage( report:Text(), 30, true, Group ) + return self 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) + + --- (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 - return self -end --- User - Function to add/adjust unittype capabilities. -- @param #CTLD self @@ -3459,14 +3501,14 @@ end -- @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 + 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 + if type( Unittype ) == "string" then unittype = Unittype - elseif type(Unittype) == "table" then - unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT + elseif type( Unittype ) == "table" then + unit = UNIT:FindByName( Unittype ) -- Wrapper.Unit#UNIT unittype = unit:GetTypeName() else return self @@ -3476,22 +3518,22 @@ end capabilities.type = unittype capabilities.crates = Cancrates or false capabilities.troops = Cantroops or false - capabilities.cratelimit = Cratelimit or 0 + 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") + 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 + if self:IsUnitInAir( Unit ) then -- get speed and height local uspeed = Unit:GetVelocityMPS() local uheight = Unit:GetHeight() @@ -3499,108 +3541,113 @@ end local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground local maxh = self.maximumHoverHeight -- 15 - local minh = self.minimumHoverHeight -- 5 + local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s - if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then + 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. + + --- (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") + 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 + 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 + 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 + 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) + function CTLD:_ShowHoverParams( Group, Unit ) + local inhover = self:IsCorrectHover( Unit ) local htxt = "true" - if not inhover then htxt = "false" end + 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) + 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) + 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) + self:_SendMessage( text, 10, false, Group ) return self end - - --- (Internal) List if a Herc unit is flying *in parameters*. + + --- (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) + function CTLD:_ShowFlightParams( Group, Unit ) + local inhover = self:IsCorrectFlightParameters( Unit ) local htxt = "true" - if not inhover then htxt = "false" end + 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) + 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) + 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) + 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) + 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) + 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) + function CTLD:IsUnitInAir( Unit ) -- get speed and height local minheight = self.minimumHoverHeight if self.enableHercules and Unit:GetTypeName() == "Hercules" then @@ -3616,18 +3663,18 @@ end 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") + 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 capabilities = self:_GetUnitCapabilities( Unit ) -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then @@ -3639,27 +3686,29 @@ end numberonboard = loaded.Cratesloaded or 0 end local load = cratelimit - numberonboard - local canload = self:CanHoverLoad(Unit) + local canload = self:CanHoverLoad( Unit ) if canload and load > 0 then - self:_LoadCratesNearby(Group,Unit) + 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 + 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 @@ -3667,8 +3716,8 @@ end -- Troops local troops = self.DroppedTroops local newtable = {} - for _index, _group in pairs (troops) do - self:T({_group.ClassName}) + 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 @@ -3679,9 +3728,9 @@ end -- 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 + for _index, _group in pairs( engineers ) do + self:T( { _group.ClassName } ) + if _group and _group:IsNotStatus( "Stopped" ) then engtable[_index] = _group end end @@ -3694,91 +3743,91 @@ end -- @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) + 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 + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:AddStock(number) + _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) + 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 + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:AddStock(number) + _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) + 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 + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:RemoveStock(number) + _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) + 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 + for _id, _troop in pairs( gentroops ) do -- #number, #CTLD_CARGO if _troop.Name == name then - _troop:RemoveStock(number) + _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") + self:T( self.lid .. " CheckEngineers" ) local engtable = self.EngineersInField - for _ind,_engineers in pairs (engtable) do + 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()) + 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 + 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 + elseif engineers:IsStatus( "Arrived" ) then engineers:Build() - local unit = wrenches:GetUnit(1) - self:_BuildCrates(wrenches,unit,true) - self:_RepairCrates(wrenches,unit,true) + local unit = wrenches:GetUnit( 1 ) + self:_BuildCrates( wrenches, unit, true ) + self:_RepairCrates( wrenches, unit, true ) engineers:Done() end else @@ -3787,7 +3836,7 @@ 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. @@ -3800,15 +3849,15 @@ end -- 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") + function CTLD:InjectTroops( Zone, Cargo ) + self:T( self.lid .. " InjectTroops" ) local cargo = Cargo -- #CTLD_CARGO - - local function IsTroopsMatch(cargo) + + local function IsTroopsMatch( cargo ) local match = false local cgotbl = self.Cargo_Troops local name = cargo:GetName() - for _,_cgo in pairs (cgotbl) do + for _, _cgo in pairs( cgotbl ) do local cname = _cgo:GetName() if name == cname then match = true @@ -3817,14 +3866,14 @@ end end return match end - - if not IsTroopsMatch(cargo) then + + if not IsTroopsMatch( cargo ) then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert(self.Cargo_Troops,cargo) + 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 @@ -3832,37 +3881,37 @@ end 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 + + 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) + 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) + :SpawnFromVec2( randomcoord ) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then - self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) end end -- template loop - cargo:SetWasDropped(true) + 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)) + 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)) + -- 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]) + self:__TroopsDeployed( 1, nil, nil, self.DroppedTroops[self.TroopCounter] ) end end -- if type end return self end - - --- (User) Pre-populate vehicles in the field. + + --- (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. @@ -3874,15 +3923,15 @@ end -- 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") + function CTLD:InjectVehicles( Zone, Cargo ) + self:T( self.lid .. " InjectVehicles" ) local cargo = Cargo -- #CTLD_CARGO - - local function IsVehicMatch(cargo) + + local function IsVehicMatch( cargo ) local match = false local cgotbl = self.Cargo_Crates local name = cargo:GetName() - for _,_cgo in pairs (cgotbl) do + for _, _cgo in pairs( cgotbl ) do local cname = _cgo:GetName() if name == cname then match = true @@ -3891,14 +3940,14 @@ end end return match end - - if not IsVehicMatch(cargo) then + + if not IsVehicMatch( cargo ) then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter cargo.Stock = 1 - table.insert(self.Cargo_Crates,cargo) + 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 @@ -3906,37 +3955,39 @@ end local temptable = cargo:GetTemplates() or {} local factor = 1.5 local zone = Zone - local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() - cargo:SetWasDropped(true) + 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 + 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)) + 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) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) + :InitRandomizeUnits( true, 20, 2 ) :InitDelayOff() - :SpawnFromVec2(randomcoord) + :SpawnFromVec2( randomcoord ) else -- don't random position of e.g. SAM units build as FOB - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias( _template, alias ) :InitDelayOff() - :SpawnFromVec2(randomcoord) + :SpawnFromVec2( randomcoord ) end if self.movetroopstowpzone and canmove then - self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + self:_MoveGroupToZone( self.DroppedTroops[self.TroopCounter] ) end if self.eventoninject then - self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) + self:__CratesBuild( 1, nil, nil, self.DroppedTroops[self.TroopCounter] ) end end -- end loop end -- if type end return self end - -------------------------------------------------------------------- --- FSM functions -------------------------------------------------------------------- + + ------------------------------------------------------------------- + -- FSM functions + ------------------------------------------------------------------- --- (Internal) FSM Function onafterStart. -- @param #CTLD self @@ -3944,31 +3995,31 @@ end -- @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..")") + 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() + 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() + 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() + 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) - + 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) + self:__Save( interval, filepath, filename ) end return self end @@ -3979,8 +4030,8 @@ end -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self - function CTLD:onbeforeStatus(From, Event, To) - self:T({From, Event, To}) + function CTLD:onbeforeStatus( From, Event, To ) + self:T( { From, Event, To } ) self:CleanDroppedTroops() self:_RefreshF10Menus() self:_RefreshRadioBeacons() @@ -3988,71 +4039,71 @@ end 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 + 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 + 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 + for _, _pilot in pairs( self.Spawned_Cargo ) do + boxes = boxes + 1 end - - local cc = self.CargoCounter + + 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.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 + 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)) + self:I( string.format( "-- %s \t\t\t %d", name, stock ) ) end - for _,_troop in pairs (self.Cargo_Statics) do + 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)) + self:I( string.format( "-- %s \t\t\t %d", name, stock ) ) end - for _,_troop in pairs (self.Cargo_Troops) do + 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)) + self:I( string.format( "-- %s \t\t %d", name, stock ) ) end end end - self:__Status(-30) + 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) + 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. @@ -4062,12 +4113,12 @@ end -- @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}) + function CTLD:onbeforeTroopsPickedUp( From, Event, To, Group, Unit, Cargo ) + self:T( { From, Event, To } ) return self end - - --- (Internal) FSM Function onbeforeCratesPickedUp. + + --- (Internal) FSM Function onbeforeCratesPickedUp. -- @param #CTLD self -- @param #string From State . -- @param #string Event Trigger. @@ -4076,26 +4127,25 @@ end -- @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}) + 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 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. @@ -4105,11 +4155,11 @@ end -- @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}) + 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. @@ -4119,11 +4169,11 @@ end -- @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}) + 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. @@ -4133,11 +4183,11 @@ end -- @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}) + 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. @@ -4146,11 +4196,11 @@ end -- @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}) + 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. @@ -4158,26 +4208,26 @@ end -- @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__Persist.csv". - function CTLD:onbeforeSave(From, Event, To, path, filename) - self:T({From, Event, To, path, filename}) + 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.") + 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.") + 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. @@ -4185,89 +4235,90 @@ end -- @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__Persist.csv". - function CTLD:onafterSave(From, Event, To, path, filename) - self:T({From, Event, To, path, filename}) + 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) + 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() + path = self.filepath or lfs.writedir() end - + -- Set file name. - filename=filename or self.filename - + filename = filename or self.filename + -- Set path. - if path~=nil then - filename=path.."\\"..filename + 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 + 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 + self:I( { _cargo } ) + statics[#statics + 1] = cargo end end - + -- find matching cargo - local function FindCargoType(name,table) + 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 + for _ind, _cargo in pairs( table ) do local thiscargo = _cargo -- #CTLD_CARGO local template = thiscargo:GetTemplates() - if type(template) == "string" then + 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 + 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 + if match then + break + end end return match, cargo end - - - --local data = "LoadedData = {\n" + + -- 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 + 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+)$","") + local template = string.gsub( name, "-(.+)$", "" ) + if string.find( template, "#" ) then + template = string.gsub( name, "#(%d+)$", "" ) end - - local match, cargo = FindCargoType(template,cgotable) + + local match, cargo = FindCargoType( template, cgotable ) if not match then - match, cargo = FindCargoType(template,cgovehic) + match, cargo = FindCargoType( template, cgovehic ) end if match then n = n + 1 @@ -4277,56 +4328,54 @@ end local cgotype = cargo.CargoType local cgoneed = cargo.CratesNeeded local cgomass = cargo.PerCrateMass - - if type(cgotemp) == "table" then + + if type( cgotemp ) == "table" then local templates = "{" - for _,_tmpl in pairs(cgotemp) do + 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) + 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 + + for _, _cgo in pairs( statics ) do local object = _cgo -- #CTLD_CARGO local cgoname = object.Name local cgotemp = object.Templates - - if type(cgotemp) == "table" then + + if type( cgotemp ) == "table" then local templates = "{" - for _,_tmpl in pairs(cgotemp) do + 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) + 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) - + + _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) + self:__Save( interval, filepath, filename ) end return self end @@ -4338,58 +4387,58 @@ end -- @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__Persist.csv". - function CTLD:onbeforeLoad(From, Event, To, path, filename) - self:T({From, Event, To, path, filename}) + 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) + 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 + 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.") + 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.") + 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() + path = path or lfs.writedir() end - + -- Set path. - if path~=nil then - filename=path.."\\"..filename + if path ~= nil then + filename = path .. "\\" .. filename end - + -- Check if file exists. - local exists=_fileexists(filename) - + local exists = _fileexists( filename ) + if exists then return true else - self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename)) + self:E( self.lid .. string.format( "WARNING: State file %s might not exist.", filename ) ) return false - --return self + -- return self end - + end --- On after "Load" event. Loads dropped units from file. @@ -4399,98 +4448,98 @@ end -- @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__Persist.csv". - function CTLD:onafterLoad(From, Event, To, path, filename) - self:T({From, Event, To, path, filename}) + 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") + 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 + filename = filename or self.filename path = path or self.filepath - + -- Set path or default. if lfs then - path=path or lfs.writedir() + path = path or lfs.writedir() end - + -- Set path. - if path~=nil then - filename=path.."\\"..filename + 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 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 + -- 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,",") + 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]) + 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 + 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}) + 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) + 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) + 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) + 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 + 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) + 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) + 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) + injectstatic = CTLD_CARGO:New( nil, cargoname, cargotemplates, cargotype, true, true, size, nil, true, mass ) end if injectstatic then - self:InjectStatics(dropzone,injectstatic) + self:InjectStatics( dropzone, injectstatic ) end - end + end end - + return self end end -- end do