--- **Ops** -- Combat Troops & Logistics Department. -- -- === -- -- **CTLD** - MOOSE based Helicopter CTLD Operations. -- -- === -- -- ## Missions: -- -- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CTLD) -- -- === -- -- **Main Features:** -- -- * MOOSE-based Helicopter CTLD Operations for Players. -- -- === -- -- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), bbirchnz (additional code!!) -- @module Ops.CTLD -- @image OPS_CTLD.jpg -- Date: Sep 2021 do ------------------------------------------------------ --- **CTLD_ENGINEERING** class, extends Core.Base#BASE -- @type CTLD_ENGINEERING -- @field #string ClassName -- @field #string lid -- @field #string Name -- @field Wrapper.Group#GROUP Group -- @field Wrapper.Unit#UNIT Unit -- @field Wrapper.Group#GROUP HeliGroup -- @field Wrapper.Unit#UNIT HeliUnit -- @field #string State -- @extends Core.Base#BASE CTLD_ENGINEERING = { ClassName = "CTLD_ENGINEERING", lid = "", Name = "none", Group = nil, Unit = nil, --C_Ops = nil, HeliGroup = nil, HeliUnit = nil, State = "", } --- CTLD_ENGINEERING class version. -- @field #string version CTLD_ENGINEERING.Version = "0.0.3" --- Create a new instance. -- @param #CTLD_ENGINEERING self -- @param #string Name -- @param #string GroupName Name of Engineering #GROUP object -- @param Wrapper.Group#GROUP HeliGroup HeliGroup -- @param Wrapper.Unit#UNIT HeliUnit HeliUnit -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:New(Name, GroupName, HeliGroup, HeliUnit) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD_ENGINEERING --BASE:I({Name, GroupName}) self.Name = Name or "Engineer Squad" -- #string self.Group = GROUP:FindByName(GroupName) -- Wrapper.Group#GROUP self.Unit = self.Group:GetUnit(1) -- Wrapper.Unit#UNIT --self.C_Ops = C_Ops -- Ops.CTLD#CTLD self.HeliGroup = HeliGroup -- Wrapper.Group#GROUP self.HeliUnit = HeliUnit -- Wrapper.Unit#UNIT --self.distance = Distance or UTILS.NMToMeters(1) self.currwpt = nil -- Core.Point#COORDINATE self.lid = string.format("%s (%s) | ",self.Name, self.Version) -- Start State. self.State = "Stopped" self.marktimer = 300 -- wait this many secs before trying a crate again --[[ Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") self:AddTransition("*", "Search", "Searching") self:AddTransition("*", "Move", "Moving") self:AddTransition("*", "Arrive", "Arrived") self:AddTransition("*", "Build", "Building") self:AddTransition("*", "Done", "Running") self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:__Start(5) --]] self:Start() local parent = self:GetParent(self) return self end --- (Internal) Set the status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:SetStatus(State) self.State = State return self end --- (Internal) Get the status -- @param #CTLD_ENGINEERING self -- @return #string State function CTLD_ENGINEERING:GetStatus() return self.State end --- (Internal) Check the status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #boolean Outcome function CTLD_ENGINEERING:IsStatus(State) return self.State == State end --- (Internal) Check the negative status -- @param #CTLD_ENGINEERING self -- @param #string State -- @return #boolean Outcome function CTLD_ENGINEERING:IsNotStatus(State) return self.State ~= State end --- (Internal) Set start status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Start() self:T(self.lid.."Start") self:SetStatus("Running") return self end --- (Internal) Set stop status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Stop() self:T(self.lid.."Stop") self:SetStatus("Stopped") return self end --- (Internal) Set build status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Build() self:T(self.lid.."Build") self:SetStatus("Building") return self end --- (Internal) Set done status. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Done() self:T(self.lid.."Done") local grp = self.Group -- Wrapper.Group#GROUP grp:RelocateGroundRandomInRadius(7,100,false,false,"Diamond") self:SetStatus("Running") return self end --- (Internal) Search for crates in reach. -- @param #CTLD_ENGINEERING self -- @param #table crates Table of found crate Ops.CTLD#CTLD_CARGO objects. -- @param #number number Number of crates found. -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Search(crates,number) self:T(self.lid.."Search") self:SetStatus("Searching") -- find crates close by --local COps = self.C_Ops -- Ops.CTLD#CTLD local dist = self.distance -- #number local group = self.Group -- Wrapper.Group#GROUP --local crates,number = COps:_FindCratesNearby(group,nil, dist) -- #table local ctable = {} local ind = 0 if number > 0 then -- get set of dropped only for _,_cargo in pairs (crates) do if _cargo:WasDropped() then local ok = false local chalk = _cargo:GetMark() if chalk == nil then ok = true else -- have we tried this cargo recently? local tag = chalk.tag or "none" local timestamp = chalk.timestamp or 0 --self:I({chalk}) -- enough time gone? local gone = timer.getAbsTime() - timestamp --self:I({time=gone}) if gone >= self.marktimer then ok = true _cargo:WipeMark() end -- end time check end -- end chalk if ok then local chalk = {} chalk.tag = "Engineers" chalk.timestamp = timer.getAbsTime() _cargo:AddMark(chalk) ind = ind + 1 table.insert(ctable,ind,_cargo) end end -- end dropped end -- end for end -- end number if ind > 0 then local crate = ctable[1] -- Ops.CTLD#CTLD_CARGO local static = crate:GetPositionable() -- Wrapper.Static#STATIC local crate_pos = static:GetCoordinate() -- Core.Point#COORDINATE local gpos = group:GetCoordinate() -- Core.Point#COORDINATE -- see how far we are from the crate local distance = self:_GetDistance(gpos,crate_pos) self:T(string.format("%s Distance to crate: %d", self.lid, distance)) -- move there if distance > 30 and distance ~= -1 and self:IsStatus("Searching") then group:RouteGroundTo(crate_pos,15,"Line abreast",1) self.currwpt = crate_pos -- Core.Point#COORDINATE self:Move() elseif distance <= 30 and distance ~= -1 then -- arrived self:Arrive() end else self:T(self.lid.."No crates in reach!") end return self end --- (Internal) Move towards crates in reach. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Move() self:T(self.lid.."Move") self:SetStatus("Moving") -- check if we arrived on target --local COps = self.C_Ops -- Ops.CTLD#CTLD local group = self.Group -- Wrapper.Group#GROUP local tgtpos = self.currwpt -- Core.Point#COORDINATE local gpos = group:GetCoordinate() -- Core.Point#COORDINATE -- see how far we are from the crate local distance = self:_GetDistance(gpos,tgtpos) self:T(string.format("%s Distance remaining: %d", self.lid, distance)) if distance <= 30 and distance ~= -1 then -- arrived self:Arrive() end return self end --- (Internal) Arrived at crates in reach. Stop group. -- @param #CTLD_ENGINEERING self -- @return #CTLD_ENGINEERING self function CTLD_ENGINEERING:Arrive() self:T(self.lid.."Arrive") self:SetStatus("Arrived") self.currwpt = nil local Grp = self.Group -- Wrapper.Group#GROUP Grp:RouteStop() return self end --- (Internal) Return distance in meters between two coordinates. -- @param #CTLD_ENGINEERING self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two -- @return #number Distance in meters or -1 function CTLD_ENGINEERING:_GetDistance(_point1, _point2) self:T(self.lid .. " _GetDistance") if _point1 and _point2 then local distance1 = _point1:Get2DDistance(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2) --self:I({dist1=distance1, dist2=distance2}) if distance1 and type(distance1) == "number" then return distance1 elseif distance2 and type(distance2) == "number" then return distance2 else self:E("*****Cannot calculate distance!") self:E({_point1,_point2}) return -1 end else self:E("******Cannot calculate distance!") self:E({_point1,_point2}) return -1 end end ------------------------------------------------------ --- **CTLD_CARGO** class, extends Core.Base#BASE -- @type CTLD_CARGO -- @field #number ID ID of this cargo. -- @field #string Name Name for menu. -- @field #table Templates Table of #POSITIONABLE objects. -- @field #CTLD_CARGO.Enum Type Enumerator of Type. -- @field #boolean HasBeenMoved Flag for moving. -- @field #boolean LoadDirectly Flag for direct loading. -- @field #number CratesNeeded Crates needed to build. -- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. -- @field #boolean HasBeenDropped True if dropped from heli. -- @field #number PerCrateMass Mass in kg -- @field #number Stock Number of builds available, -1 for unlimited -- @extends Core.Base#BASE CTLD_CARGO = { ClassName = "CTLD_CARGO", ID = 0, Name = "none", Templates = {}, CargoType = "none", HasBeenMoved = false, LoadDirectly = false, CratesNeeded = 0, Positionable = nil, HasBeenDropped = false, PerCrateMass = 0, Stock = nil, Mark = nil, } --- Define cargo types. -- @type CTLD_CARGO.Enum -- @field #string Type Type of Cargo. CTLD_CARGO.Enum = { ["VEHICLE"] = "Vehicle", -- #string vehicles ["TROOPS"] = "Troops", -- #string troops ["FOB"] = "FOB", -- #string FOB ["CRATE"] = "Crate", -- #string crate ["REPAIR"] = "Repair", -- #string repair ["ENGINEERS"] = "Engineers", -- #string engineers } --- 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 #boolean HasBeenMoved Flag for moving. -- @param #boolean LoadDirectly Flag for direct loading. -- @param #number CratesNeeded Crates needed to build. -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. -- @param #number PerCrateMass Mass in kg -- @param #number Stock Number of builds available, nil for unlimited -- @return #CTLD_CARGO self function CTLD_CARGO:New(ID, Name, Templates, Sorte, 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) self.Name = Name or "none" -- #string self.Templates = Templates or {} -- #table self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum self.HasBeenMoved = HasBeenMoved or false -- #boolean self.LoadDirectly = LoadDirectly or false -- #boolean self.CratesNeeded = CratesNeeded or 0 -- #number self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE self.HasBeenDropped = Dropped or false --#boolean self.PerCrateMass = PerCrateMass or 0 -- #number self.Stock = Stock or nil --#number self.Mark = nil return self end --- Query ID. -- @param #CTLD_CARGO self -- @return #number ID function CTLD_CARGO:GetID() return self.ID end --- Query Name. -- @param #CTLD_CARGO self -- @return #string Name function CTLD_CARGO:GetName() return self.Name end --- Query Templates. -- @param #CTLD_CARGO self -- @return #table Templates function CTLD_CARGO:GetTemplates() return self.Templates end --- Query has moved. -- @param #CTLD_CARGO self -- @return #boolean Has moved function CTLD_CARGO:HasMoved() return self.HasBeenMoved end --- Query was dropped. -- @param #CTLD_CARGO self -- @return #boolean Has been dropped. function CTLD_CARGO:WasDropped() return self.HasBeenDropped end --- Query directly loadable. -- @param #CTLD_CARGO self -- @return #boolean loadable function CTLD_CARGO:CanLoadDirectly() return self.LoadDirectly end --- Query number of crates or troopsize. -- @param #CTLD_CARGO self -- @return #number Crates or size of troops. function CTLD_CARGO:GetCratesNeeded() return self.CratesNeeded end --- Query type. -- @param #CTLD_CARGO self -- @return #CTLD_CARGO.Enum Type function CTLD_CARGO:GetType() return self.CargoType end --- Query type. -- @param #CTLD_CARGO self -- @return Wrapper.Positionable#POSITIONABLE Positionable function CTLD_CARGO:GetPositionable() return self.Positionable end --- Set HasMoved. -- @param #CTLD_CARGO self -- @param #boolean moved function CTLD_CARGO:SetHasMoved(moved) self.HasBeenMoved = moved or false end --- Query if cargo has been loaded. -- @param #CTLD_CARGO self -- @param #boolean loaded function CTLD_CARGO:Isloaded() if self.HasBeenMoved and not self.WasDropped() then return true else return false end end --- Set WasDropped. -- @param #CTLD_CARGO self -- @param #boolean dropped function CTLD_CARGO:SetWasDropped(dropped) self.HasBeenDropped = dropped or false end --- Get Stock. -- @param #CTLD_CARGO self -- @return #number Stock function CTLD_CARGO:GetStock() if self.Stock then return self.Stock else return -1 end end --- Add Stock. -- @param #CTLD_CARGO self -- @param #number Number to add, one if nil. -- @return #CTLD_CARGO self function CTLD_CARGO:AddStock(Number) if self.Stock then -- Stock nil? local number = Number or 1 self.Stock = self.Stock + number end return self end --- Remove Stock. -- @param #CTLD_CARGO self -- @param #number Number to reduce, one if nil. -- @return #CTLD_CARGO self function CTLD_CARGO:RemoveStock(Number) if self.Stock then -- Stock nil? local number = Number or 1 self.Stock = self.Stock - number if self.Stock < 0 then self.Stock = 0 end end return self end --- Query crate type for REPAIR -- @param #CTLD_CARGO self -- @param #boolean function CTLD_CARGO:IsRepair() if self.CargoType == "Repair" then return true else return false end end function CTLD_CARGO:AddMark(Mark) self.Mark = Mark return self end function CTLD_CARGO:GetMark(Mark) return self.Mark end function CTLD_CARGO:WipeMark() self.Mark = nil return self end end do ------------------------------------------------------------------------- --- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM -- @type CTLD -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. -- @extends Core.Fsm#FSM --- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there\'s POG stuff to be done.* (Mil Saying) -- -- === -- -- ![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 or crates, which have to be build on-location. -- -- ## 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 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) -- -- ## 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 smoke, flares, beacons don't work for this type of loadzone (yet). Also, 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 = 30 -- 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. Don't forget to change this fitting to your coalition! -- -- ## 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) -- -- Default unit type capabilities are: -- -- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, -- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, -- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, -- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, -- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, -- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, -- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, -- -- -- ### 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 -- 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 -- -- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- local points = 5 -- 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 -- -- ## 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 -- -- 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. -- -- ## 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 -- -- @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, dropOffZones = {}, 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 = 30, -- list crates in this radius debug = false, wpZones = {}, pickupZones = {}, dropOffZones = {}, } ------------------------------ -- 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: (WIP) Limit of troops, crates buildable? ------------------------------ --- Radio Beacons -- @type CTLD.ZoneBeacon -- @field #string name -- Name of zone for the coordinate -- @field #number frequency -- in mHz -- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM --- Zone Info. -- @type CTLD.CargoZone -- @field #string name Name of Zone. -- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. -- @field #boolean active Active or not. -- @field #string type Type of zone, i.e. load,drop,move,ship -- @field #boolean hasbeacon Create and run radio beacons if active. -- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon -- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon -- @field #number shiplength For ships - length of ship -- @field #number shipwidth For ships - width of ship --- Zone Type Info. -- @type CTLD.CargoZoneType CTLD.CargoZoneType = { LOAD = "load", DROP = "drop", MOVE = "move", SHIP = "ship", } --- Buildable table info. -- @type CTLD.Buildable -- @field #string Name Name of the object. -- @field #number Required Required crates. -- @field #number Found Found crates. -- @field #table Template Template names for this build. -- @field #boolean CanBuild Is buildable or not. -- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). --- Unit capabilities. -- @type CTLD.UnitCapabilities -- @field #string type Unit type. -- @field #boolean crates Can transport crate. -- @field #boolean troops Can transport troops. -- @field #number cratelimit Number of crates transportable. -- @field #number trooplimit Number of troop units transportable. CTLD.UnitTypes = { ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 12}, ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12}, ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15}, ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15}, ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15}, ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18}, ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25}, -- 19t cargo, 64 paratroopers. --Actually it's longer, but the center coord is off-center of the model. } --- CTLD class version. -- @field #string version CTLD.version="0.1.7a5" --- Instantiate a new CTLD. -- @param #CTLD self -- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL -- @param #table Prefixes Table of pilot prefixes. -- @param #string Alias Alias of this CTLD for logging. -- @return #CTLD self function CTLD:New(Coalition, Prefixes, Alias) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #CTLD BASE:T({Coalition, Prefixes, Alias}) --set Coalition if Coalition and type(Coalition)=="string" then if Coalition=="blue" then self.coalition=coalition.side.BLUE self.coalitiontxt = Coalition elseif Coalition=="red" then self.coalition=coalition.side.RED self.coalitiontxt = Coalition elseif Coalition=="neutral" then self.coalition=coalition.side.NEUTRAL self.coalitiontxt = Coalition else self:E("ERROR: Unknown coalition in CTLD!") end else self.coalition = Coalition self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) end -- Set alias. if Alias then self.alias=tostring(Alias) else self.alias="UNHCR" if self.coalition then if self.coalition==coalition.side.RED then self.alias="Red CTLD" elseif self.coalition==coalition.side.BLUE then self.alias="Blue CTLD" end end end -- Set some string id for output to DCS.log file. self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") -- Start State. self:SetStartState("Stopped") -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "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.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 -- country of crates spawned self.cratecountry = country.id.GERMANY 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. --- 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. return self end ------------------------------------------------------------------- -- Helper and User Functions ------------------------------------------------------------------- --- (Internal) Function to get capabilities of a chopper -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The unit -- @return #table Capabilities Table of caps function CTLD:_GetUnitCapabilities(Unit) self:T(self.lid .. " _GetUnitCapabilities") local _unit = Unit -- Wrapper.Unit#UNIT local unittype = _unit:GetTypeName() local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities if not capabilities or capabilities == {} then -- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, capabilities = {} capabilities.troops = false capabilities.crates = false capabilities.cratelimit = 0 capabilities.trooplimit = 0 capabilities.type = "generic" capabilities.length = 20 end return capabilities end --- (Internal) Function to generate valid UHF Frequencies -- @param #CTLD self function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() return self end --- (Internal) Function to generate valid FM Frequencies -- @param #CTLD self function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} self.FreeFMFrequencies = UTILS.GenerateFMFrequencies() return self end --- (Internal) Populate table with available VHF beacon frequencies. -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") self.FreeVHFFrequencies = {} self.UsedVHFFrequencies = {} self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() return self end --- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData function CTLD:_EventHandler(EventData) self:T(string.format("%s Event = %d",self.lid, EventData.id)) local event = EventData -- Core.Event#EVENTDATA if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then local _coalition = event.IniCoalition if _coalition ~= self.coalition then return --ignore! end -- check is Helicopter local _unit = event.IniUnit local _group = event.IniGroup if _unit:IsHelicopter() or _group:IsHelicopter() then local unitname = event.IniUnitName or "none" self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end -- Herc support --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end return elseif event.id == EVENTS.PlayerLeaveUnit then -- remove from pilot table local unitname = event.IniUnitName or "none" self.CtldUnits[unitname] = nil self.Loaded_Cargo[unitname] = nil end return self end --- (Internal) Function to message a group. -- @param #CTLD self -- @param #string Text The text to display. -- @param #number Time Number of seconds to display the message. -- @param #boolean Clearscreen Clear screen or not. -- @param Wrapper.Group#GROUP Group The group receiving the message. function CTLD:_SendMessage(Text, Time, Clearscreen, Group) self:T(self.lid .. " _SendMessage") if not self.suppressmessages then local m = MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) end return self end --- (Internal) Function to load troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #CTLD_CARGO Cargotype function CTLD:_LoadTroops(Group, Unit, Cargotype) self:T(self.lid .. " _LoadTroops") -- check if we have stock local instock = Cargotype:GetStock() local cgoname = Cargotype:GetName() local cgotype = Cargotype:GetType() if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then -- nothing left over self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) return self else -- remove one Cargotype:RemoveStock() end -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) local hoverload = self:CanHoverLoad(Unit) -- 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 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) end return self end function CTLD:_FindRepairNearby(Group, Unit, Repairtype) self:T(self.lid .. " _FindRepairNearby") local unitcoord = Unit:GetCoordinate() -- find nearest group of deployed groups local nearestGroup = nil local nearestGroupIndex = -1 local nearestDistance = 10000 for k,v in pairs(self.DroppedTroops) do local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) local unit = v:GetUnit(1) -- Wrapper.Unit#UNIT local desc = unit:GetDesc() or nil --self:I({desc = desc.attributes}) if distance < nearestDistance and distance ~= -1 and not desc.attributes.Infantry then nearestGroup = v nearestGroupIndex = k nearestDistance = distance end end -- found one and matching distance? if nearestGroup == nil or nearestDistance > self.EngineerSearch then self:_SendMessage("No unit close enough to repair!", 10, false, Group) return nil, nil end local groupname = nearestGroup:GetName() -- helper to find matching template local function matchstring(String,Table) local match = false if type(Table) == "table" then for _,_name in pairs (Table) do if string.find(String,_name) then match = true break end end else if type(String) == "string" then if string.find(String,Table) then match = true end end end return match end -- walk through generics and find matching type local Cargotype = nil for k,v in pairs(self.Cargo_Crates) do --self:I({groupname,v.Templates}) if matchstring(groupname,v.Templates) and matchstring(groupname,Repairtype) then Cargotype = v -- #CTLD_CARGO break end end if Cargotype == nil then --self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group) return nil, nil else return nearestGroup, Cargotype end end --- (Internal) Function to repair an object. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #table Crates Table of #CTLD_CARGO objects near the unit. -- @param #CTLD.Buildable Build Table build object. -- @param #number Number Number of objects in Crates (found) to limit search. -- @param #boolean Engineering If true it is an Engineering repair. function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number,Engineering) self:T(self.lid .. " _RepairObjectFromCrates") local build = Build -- -- #CTLD.Buildable --self:I({Build=Build}) local Repairtype = build.Template -- #string local NearestGroup, CargoType = self:_FindRepairNearby(Group,Unit,Repairtype) -- Wrapper.Group#GROUP, #CTLD_CARGO --self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) if NearestGroup ~= nil then if self.repairtime < 2 then self.repairtime = 30 end -- noob catch if not Engineering then self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) end -- now we can build .... --NearestGroup:Destroy(false) local name = CargoType:GetName() local required = CargoType:GetCratesNeeded() local template = CargoType:GetTemplates() local ctype = CargoType:GetType() local object = {} -- #CTLD.Buildable object.Name = CargoType:GetName() object.Required = required object.Found = required object.Template = template object.CanBuild = true object.Type = ctype -- #CTLD_CARGO.Enum self:_CleanUpCrates(Crates,Build,Number) local desttimer = TIMER:New(function() NearestGroup:Destroy(false) end, self) desttimer:Start(self.repairtime - 1) local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true,NearestGroup:GetCoordinate()) buildtimer:Start(self.repairtime) --self:_BuildObjectFromCrates(Group,Unit,object) else if not Engineering then self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) else self:T("Can't repair this unit with " .. build.Name) end end return self end --- (Internal) Function to extract (load from the field) troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit function CTLD:_ExtractTroops(Group, Unit) -- #1574 thanks to @bbirchnz! self:T(self.lid .. " _ExtractTroops") -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) local hoverload = self:CanHoverLoad(Unit) if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) if not self.debug then return self end end -- load troops into heli local unit = Unit -- Wrapper.Unit#UNIT local unitname = unit:GetName() -- see if this heli can load troops local unittype = unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) local cantroops = capabilities.troops -- #boolean local trooplimit = capabilities.trooplimit -- #number local unitcoord = unit:GetCoordinate() -- find nearest group of deployed troops local nearestGroup = nil local nearestGroupIndex = -1 local nearestDistance = 10000000 local nearestList = {} local distancekeys = {} local extractdistance = self.CrateDistance * self.ExtractFactor for k,v in pairs(self.DroppedTroops) do local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) if distance <= extractdistance and distance ~= -1 then nearestGroup = v nearestGroupIndex = k nearestDistance = distance table.insert(nearestList, math.floor(distance), v) distancekeys[#distancekeys+1] = math.floor(distance) end end if nearestGroup == nil or nearestDistance > extractdistance then self:_SendMessage("No units close enough to extract!", 10, false, Group) return self end -- sort reference keys table.sort(distancekeys) local secondarygroups = {} for i=1,#distancekeys do local nearestGroup = nearestList[distancekeys[i]] -- find matching cargo type local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") local Cargotype = nil for k,v in pairs(self.Cargo_Troops) do local comparison = "" if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end if comparison == groupType then Cargotype = v break end end if Cargotype == nil then self:_SendMessage("Can't onboard " .. groupType, 10, false, Group) else local troopsize = Cargotype:GetCratesNeeded() -- #number -- have we loaded stuff already? local numberonboard = 0 local loaded = {} if self.Loaded_Cargo[unitname] then loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo numberonboard = loaded.Troopsloaded or 0 else loaded = {} -- #CTLD.LoadedCargo loaded.Troopsloaded = 0 loaded.Cratesloaded = 0 loaded.Cargo = {} end if troopsize + numberonboard > trooplimit then self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) --return self else self.CargoCounter = self.CargoCounter + 1 local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, Cargotype.CargoType, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) self:T({cargotype=loadcargotype}) loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) self:_UpdateUnitCargoMass(Unit) self:__TroopsExtracted(1,Group, Unit, nearestGroup) -- clean up: --table.remove(self.DroppedTroops, nearestGroupIndex) if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then --self:I("*****This CargoType has multiple templates: "..Cargotype.Name) for _,_key in pairs (Cargotype.Templates) do table.insert(secondarygroups,_key) end end nearestGroup:Destroy(false) end end end -- clean up secondary groups for _,_name in pairs(secondarygroups) do for _,_group in pairs(nearestList) do if _group and _group:IsAlive() then local groupname = string.match(_group:GetName(), "(.+)-(.+)$") if _name == groupname then _group:Destroy(false) end end end end self:CleanDroppedTroops() return self end --- (Internal) Function to spawn crates in front of the heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #CTLD_CARGO Cargo -- @param #number number Number of crates to generate (for dropping) -- @param #boolean drop If true we\'re dropping from heli rather than loading. function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:T(self.lid .. " _GetCrates") if not drop then local cgoname = Cargo:GetName() -- check if we have stock local instock = Cargo:GetStock() if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then -- nothing left over self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group) return self else -- remove one Cargo:RemoveStock() 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 30 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 -- #CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number local cratename = cargotype:GetName() local cratetemplate = "Container"-- #string -- get position and heading of heli local position = Unit:GetCoordinate() local heading = Unit:GetHeading() + 1 local height = Unit:GetHeight() local droppedcargo = {} -- loop crates needed for i=1,number do local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) local cratedistance = (i-1)*2.5 + capabilities.length if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end local addon = 0 if IsHerc then -- spawn behind the Herc addon = 180 end -- altered heading logic -- TODO: right standard deviation? local randomheading = UTILS.RandomGaussian(0,30,-90,90,100) randomheading = math.fmod((heading + randomheading + addon), 360) local cratecoord = position:Translate(cratedistance,randomheading) local cratevec2 = cratecoord:GetVec2() self.CrateCounter = self.CrateCounter + 1 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("container_cargo","Cargos",self.cratecountry) --:InitCoordinate(cratecoord) :InitLinkToUnit(Ship,dist,Offy,0) :Spawn(270,cratealias) else self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",self.cratecountry) :InitCoordinate(cratecoord) --: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],nil,cargotype.PerCrateMass) 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) 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 30 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",name)) else text:Add(string.format("Crate for %s, %d Kg",name, entry.PerCrateMass)) end end if text:GetCount() == 1 then text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(), 30, true, _group) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) end return self end --- (Internal) Return distance in meters between two coordinates. -- @param #CTLD self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two -- @return #number Distance in meters function CTLD:_GetDistance(_point1, _point2) self:T(self.lid .. " _GetDistance") if _point1 and _point2 then local distance1 = _point1:Get2DDistance(_point2) local distance2 = _point1:DistanceFromPointVec2(_point2) --self:I({dist1=distance1, dist2=distance2}) if distance1 and type(distance1) == "number" then return distance1 elseif distance2 and type(distance2) == "number" then return distance2 else self:E("*****Cannot calculate distance!") self:E({_point1,_point2}) return -1 end else self:E("******Cannot calculate distance!") self:E({_point1,_point2}) return -1 end end --- (Internal) Function to find and return nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP _group Group -- @param Wrapper.Unit#UNIT _unit Unit -- @param #number _dist Distance -- @return #table Table of crates -- @return #number Number Number of crates found function CTLD:_FindCratesNearby( _group, _unit, _dist) self:T(self.lid .. " _FindCratesNearby") local finddist = _dist local location = _group:GetCoordinate() local existingcrates = self.Spawned_Cargo -- #table -- cycle local index = 0 local found = {} for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates local staticid = cargo:GetID() if static and static:IsAlive() then local staticpos = static:GetCoordinate() local distance = self:_GetDistance(location,staticpos) if distance <= finddist and static then index = index + 1 table.insert(found, staticid, cargo) end end end return found, index end --- (Internal) Function to get and load nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self function CTLD:_LoadCratesNearby(Group, Unit) self:T(self.lid .. " _LoadCratesNearby") -- load crates into heli local group = Group -- Wrapper.Group#GROUP local unit = Unit -- Wrapper.Unit#UNIT local unitname = unit:GetName() -- see if this heli can load crates local unittype = unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number local grounded = not self:IsUnitInAir(Unit) local canhoverload = self:CanHoverLoad(Unit) --- cases ------------------------------- -- Chopper can\'t do crates - bark & return -- Chopper can do crates - -- --> hover if forcedhover or bark and return -- --> hover or land if not forcedhover ----------------------------------------- if not cancrates then self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) elseif self.forcehoverload and not canhoverload then self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) elseif not grounded and not canhoverload then self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) else -- have we loaded stuff already? local numberonboard = 0 local massonboard = 0 local loaded = {} if self.Loaded_Cargo[unitname] then loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo numberonboard = loaded.Cratesloaded or 0 else loaded = {} -- #CTLD.LoadedCargo loaded.Troopsloaded = 0 loaded.Cratesloaded = 0 loaded.Cargo = {} end -- get nearby crates local finddist = self.CrateDistance or 30 local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table if number == 0 and self.hoverautoloading then return -- exit elseif number == 0 then self:_SendMessage("Sorry no loadable crates nearby!", 10, false, Group) return -- exit elseif numberonboard == cratelimit then self:_SendMessage("Sorry no fully loaded!", 10, false, Group) return -- 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 not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then crateind = _crate:GetID() 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) 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) self:__CratesPickedUp(1, Group, Unit, crate) end end self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) -- clean up real world crates local existingcrates = self.Spawned_Cargo -- #table local newexcrates = {} for _,_crate in pairs(existingcrates) do local excrate = _crate -- #CTLD_CARGO local ID = excrate:GetID() for _,_ID in pairs(crateidsloaded) do if ID ~= _ID then table.insert(newexcrates,_crate) end end end self.Spawned_Cargo = nil self.Spawned_Cargo = newexcrates end end return self end --- (Internal) Function to get current loaded mass -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #number mass in kgs function CTLD:_GetUnitCargoMass(Unit) self:T(self.lid .. " _GetUnitCargoMass") local unitname = Unit:GetName() local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo local loadedmass = 0 -- #number if self.Loaded_Cargo[unitname] then local cargotable = loadedcargo.Cargo or {} -- #table for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) end if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then loadedmass = loadedmass + cargo.PerCrateMass end end end return loadedmass end --- (Internal) Function to calculate and set Unit internal cargo mass -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit function CTLD:_UpdateUnitCargoMass(Unit) self:T(self.lid .. " _UpdateUnitCargoMass") local calculatedMass = self:_GetUnitCargoMass(Unit) Unit:SetUnitInternalCargo(calculatedMass) --local report = REPORT:New("Loadmaster report") --report:Add("Carrying " .. calculatedMass .. "Kg") --self:_SendMessage(report:Text(),10,false,Unit:GetGroup()) return self end --- (Internal) Function to list loaded cargo. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self function CTLD:_ListCargo(Group, Unit) self:T(self.lid .. " _ListCargo") local unitname = Unit:GetName() local unittype = Unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities local trooplimit = capabilities.trooplimit -- #boolean local cratelimit = capabilities.cratelimit -- #number local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo local loadedmass = self:_GetUnitCargoMass(Unit) -- #number if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 local no_crates = loadedcargo.Cratesloaded or 0 local cargotable = loadedcargo.Cargo or {} -- #table local report = REPORT:New("Transport Checkout Sheet") report:Add("------------------------------------------------------------") report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) report:Add("------------------------------------------------------------") report:Add(" -- TROOPS --") for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() 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() 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 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 inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) if not inzone then inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then droppingatbase = true end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters local IsHerc = self:IsHercules(Unit) if IsHerc then -- no hover but airdrop here hoverunload = self:IsCorrectFlightParameters(Unit) end -- check if we\'re landed local grounded = not self:IsUnitInAir(Unit) -- Get what we have loaded local unitname = Unit:GetName() if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then if not droppingatbase or self.debug then local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo -- looking for troops local cargotable = loadedcargo.Cargo for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then -- unload troops local name = cargo:GetName() or "none" local temptable = cargo:GetTemplates() or {} local position = Group:GetCoordinate() local zoneradius = 100 -- drop zone radius local factor = 1 if IsHerc then factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping zoneradius = Unit:GetVelocityMPS() or 100 end local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor) local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end end -- template loop cargo:SetWasDropped(true) -- engineering group? --self:I("Dropped Troop Type: "..type) if type == CTLD_CARGO.Enum.ENGINEERS then self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) self:_SendMessage(string.format("Dropped Engineers %s into action!",name), 10, false, Group) else self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) end self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) end -- if type end end -- cargotable loop else -- droppingatbase self:_SendMessage("Troops have returned to base!", 10, false, Group) self:__TroopsRTB(1, Group, Unit) end -- cleanup load list local loaded = {} -- #CTLD.LoadedCargo loaded.Troopsloaded = 0 loaded.Cratesloaded = 0 loaded.Cargo = {} local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo local cargotable = loadedcargo.Cargo or {} for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum local dropped = cargo:WasDropped() if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not dropped then table.insert(loaded.Cargo,_cargo) loaded.Cratesloaded = loaded.Cratesloaded + 1 else -- add troops back to stock if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and droppingatbase then -- find right generic type local name = cargo:GetName() local gentroops = self.Cargo_Troops for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then local stock = _troop:GetStock() -- avoid making unlimited stock limited if stock and tonumber(stock) >= 0 then _troop:AddStock() end end end end end end self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end end return self end --- (Internal) Function to unload crates from heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit function CTLD:_UnloadCrates(Group, Unit) self:T(self.lid .. " _UnloadCrates") if not self.dropcratesanywhere then -- #1570 -- check if we are in DROP zone local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) if not inzone then self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) if not self.debug then return self end end end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters local IsHerc = self:IsHercules(Unit) if IsHerc then -- no hover but airdrop here hoverunload = self:IsCorrectFlightParameters(Unit) end -- check if we\'re landed local grounded = not self:IsUnitInAir(Unit) -- Get what we have loaded local unitname = Unit:GetName() if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo -- looking for crate local cargotable = loadedcargo.Cargo for _,_cargo in pairs (cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() 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 30 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() then -- we can build these - maybe local name = Crate:GetName() local required = Crate:GetCratesNeeded() local template = Crate:GetTemplates() local ctype = Crate:GetType() if not buildables[name] then local object = {} -- #CTLD.Buildable object.Name = name object.Required = required object.Found = 1 object.Template = template object.CanBuild = false object.Type = ctype -- #CTLD_CARGO.Enum buildables[name] = object foundbuilds = true else buildables[name].Found = buildables[name].Found + 1 foundbuilds = true end if buildables[name].Found >= buildables[name].Required then buildables[name].CanBuild = true canbuild = true end self:T({buildables = buildables}) end -- end dropped end -- end crate loop -- ok let\'s list what we have local report = REPORT:New("Checklist Buildable Crates") report:Add("------------------------------------------------------------") for _,_build in pairs(buildables) do local build = _build -- Object table from above local name = build.Name local needed = build.Required local found = build.Found local txtok = "NO" if build.CanBuild then txtok = "YES" end local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) report:Add(text) end -- end list buildables if not foundbuilds then report:Add(" --- None Found ---") end report:Add("------------------------------------------------------------") local text = report:Text() if not Engineering then self:_SendMessage(text, 30, true, Group) else self:T(text) end -- let\'s get going if canbuild then -- loop again for _,_build in pairs(buildables) do local build = _build -- #CTLD.Buildable if build.CanBuild then self:_CleanUpCrates(crates,build,number) self:_BuildObjectFromCrates(Group,Unit,build) end end end else if not Engineering then self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) end end -- number > 0 return self end --- (Internal) Function to repair nearby vehicles / FOBs -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #boolean Engineering If true, this is an engineering role function CTLD:_RepairCrates(Group, Unit, Engineering) self:T(self.lid .. " _RepairCrates") -- get nearby crates local finddist = self.CrateDistance or 30 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() 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 type = Build.Type -- #CTLD_CARGO.Enum local canmove = false if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end local temptable = Build.Template or {} local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) local randomcoord = zone:GetRandomCoordinate(35):GetVec2() if Repair then randomcoord = RepairLocation:GetVec2() end for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) if canmove then self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() :SpawnFromVec2(randomcoord) end if self.movetroopstowpzone and canmove then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end if Repair then self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) else self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) end end -- template loop else self:T(self.lid.."Group KIA while building!") end return self end --- (Internal) Function to move group to WP zone. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The Group to move. function CTLD:_MoveGroupToZone(Group) self:T(self.lid .. " _MoveGroupToZone") local groupname = Group:GetName() or "none" local groupcoord = Group:GetCoordinate() -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) if (distance <= self.movetroopsdistance) and zone then -- yes, we can ;) local groupname = Group:GetName() local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local coordinate = zonecoord:GetVec2() Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) Group:OptionROEOpenFirePossible() Group:RouteToVec2(coordinate,5) end return self end --- (Internal) Housekeeping - Cleanup crates when build -- @param #CTLD self -- @param #table Crates Table of #CTLD_CARGO objects near the unit. -- @param #CTLD.Buildable Build Table build object. -- @param #number Number Number of objects in Crates (found) to limit search. function CTLD:_CleanUpCrates(Crates,Build,Number) self:T(self.lid .. " _CleanUpCrates") -- clean up real world crates local build = Build -- #CTLD.Buildable local existingcrates = self.Spawned_Cargo -- #table of exising crates local newexcrates = {} -- get right number of crates to destroy local numberdest = Build.Required local nametype = Build.Name local found = 0 local rounds = Number local destIDs = {} -- loop and find matching IDs in the set for _,_crate in pairs(Crates) do local nowcrate = _crate -- #CTLD_CARGO local name = nowcrate:GetName() local thisID = nowcrate:GetID() if name == nametype then -- matching crate type table.insert(destIDs,thisID) found = found + 1 nowcrate:GetPositionable():Destroy(false) nowcrate.Positionable = nil end if found == numberdest then break end -- got enough end -- loop and remove from real world representation for _,_crate in pairs(existingcrates) do local excrate = _crate -- #CTLD_CARGO local ID = excrate:GetID() for _,_ID in pairs(destIDs) do if ID ~= _ID then table.insert(newexcrates,_crate) end end end -- reset Spawned_Cargo self.Spawned_Cargo = nil self.Spawned_Cargo = newexcrates 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 topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, false) local smokemenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, true):Refresh() -- sub menus -- 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("Get crate for %s",entry.Name) 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 -- 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 local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) 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* 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.CargoZoneTyp 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.CargoZoneTyp ZoneType Type of zone this belongs to. function CTLD:DeactivateZone(Name,ZoneType) self:T(self.lid .. " AddZone") self:ActivateZone(Name,ZoneType,false) return self end --- (Internal) Function to obtain a valid FM frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. function CTLD:_GetFMBeacon(Name) self:T(self.lid .. " _GetFMBeacon") local beacon = {} -- #CTLD.ZoneBeacon if #self.FreeFMFrequencies <= 1 then self.FreeFMFrequencies = self.UsedFMFrequencies self.UsedFMFrequencies = {} end --random local FM = table.remove(self.FreeFMFrequencies, math.random(#self.FreeFMFrequencies)) table.insert(self.UsedFMFrequencies, FM) beacon.name = Name beacon.frequency = FM / 1000000 beacon.modulation = radio.modulation.FM return beacon end --- (Internal) Function to obtain a valid UHF frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. function CTLD:_GetUHFBeacon(Name) self:T(self.lid .. " _GetUHFBeacon") local beacon = {} -- #CTLD.ZoneBeacon if #self.FreeUHFFrequencies <= 1 then self.FreeUHFFrequencies = self.UsedUHFFrequencies self.UsedUHFFrequencies = {} end --random local UHF = table.remove(self.FreeUHFFrequencies, math.random(#self.FreeUHFFrequencies)) table.insert(self.UsedUHFFrequencies, UHF) beacon.name = Name beacon.frequency = UHF / 1000000 beacon.modulation = radio.modulation.AM return beacon end --- (Internal) Function to obtain a valid VHF frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. function CTLD:_GetVHFBeacon(Name) self:T(self.lid .. " _GetVHFBeacon") local beacon = {} -- #CTLD.ZoneBeacon if #self.FreeVHFFrequencies <= 3 then self.FreeVHFFrequencies = self.UsedVHFFrequencies self.UsedVHFFrequencies = {} end --get random local VHF = table.remove(self.FreeVHFFrequencies, math.random(#self.FreeVHFFrequencies)) table.insert(self.UsedVHFFrequencies, VHF) beacon.name = Name beacon.frequency = VHF / 1000000 beacon.modulation = radio.modulation.FM return beacon end --- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. -- Zones of type LOAD: Players load crates and troops here. -- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. -- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). -- @param #CTLD self -- @param #string Name Name of this zone, as in Mission Editor. -- @param #string Type Type of this zone, #CTLD.CargoZoneType -- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red -- @param #string Active Is this zone currently active? -- @param #string HasBeacon Does this zone have a beacon if it is active? -- @param #number Shiplength Length of Ship for shipzones -- @param #number Shipwidth Width of Ship for shipzones -- @return #CTLD self function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth) self:T(self.lid .. " AddCTLDZone") local ctldzone = {} -- #CTLD.CargoZone ctldzone.active = Active or false ctldzone.color = Color or SMOKECOLOR.Red ctldzone.name = Name or "NONE" ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType ctldzone.hasbeacon = HasBeacon or false if HasBeacon then ctldzone.fmbeacon = self:_GetFMBeacon(Name) ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) ctldzone.vhfbeacon = self:_GetVHFBeacon(Name) else ctldzone.fmbeacon = nil ctldzone.uhfbeacon = nil ctldzone.vhfbeacon = nil end if Type == CTLD.CargoZoneType.SHIP then ctldzone.shiplength = Shiplength or 100 ctldzone.shipwidth = Shipwidth or 10 end self:AddZone(ctldzone) return self end --- (Internal) Function to show list of radio beacons -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit function CTLD:_ListRadioBeacons(Group, Unit) self:T(self.lid .. " _ListRadioBeacons") local report = REPORT:New("Active Zone Beacons") report:Add("------------------------------------------------------------") local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} for i=1,3 do for index,cargozone in pairs(zones[i]) do -- Get Beacon object from zone local czone = cargozone -- #CTLD.CargoZone if czone.active and czone.hasbeacon then local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon local Name = czone.name local FM = FMbeacon.frequency -- MHz local VHF = VHFbeacon.frequency * 1000 -- KHz local UHF = UHFbeacon.frequency -- MHz report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF),"|") end end end if report:GetCount() == 1 then report:Add(" N O N E") end report:Add("------------------------------------------------------------") self:_SendMessage(report:Text(), 30, true, Group) return self end --- (Internal) Add radio beacon to zone. Runs 30 secs. -- @param #CTLD self -- @param #string Name Name of zone. -- @param #string Sound Name of soundfile. -- @param #number Mhz Frequency in Mhz. -- @param #number Modulation Modulation AM or FM. function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation) self:T(self.lid .. " _AddRadioBeacon") local Zone = ZONE:FindByName(Name) 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} for i=1,3 do 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) self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM) self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM) 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 - Start smoke 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} for i=1,3 do for index,cargozone in pairs(zones[i]) do local CZone = cargozone --#CTLD.CargoZone local zonename = CZone.name local zone = ZONE:FindByName(zonename) local zonecoord = zone:GetCoordinate() local active = CZone.active local color = CZone.color local distance = self:_GetDistance(zonecoord,unitcoord) if distance < smokedistance and active then -- smoke zone since we\'re nearby if not Flare then zonecoord:Smoke(color or SMOKECOLOR.White) else if color == SMOKECOLOR.Blue then color = FLARECOLOR.White end zonecoord:Flare(color or FLARECOLOR.White) end local txt = "smoking" if Flare then txt = "flaring" end self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) smoked = true end end end if not smoked then local distance = UTILS.MetersToNM(self.smokedistance) self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) end return self end --- User - Function to add/adjust unittype capabilities. -- @param #CTLD self -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. -- @param #boolean Cancrates Unit can load crates. Default false. -- @param #boolean Cantroops Unit can load troops. Default false. -- @param #number Cratelimit Unit can carry number of crates. Default 0. -- @param #number Trooplimit Unit can carry number of troops. Default 0. -- @param #number Length Unit lenght (in mteres) for the load radius. Default 20. function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit, Length) self:T(self.lid .. " UnitCapabilities") local unittype = nil local unit = nil if type(Unittype) == "string" then unittype = Unittype elseif type(Unittype) == "table" then unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT unittype = unit:GetTypeName() else return self end -- set capabilities local capabilities = {} -- #CTLD.UnitCapabilities capabilities.type = unittype capabilities.crates = Cancrates or false capabilities.troops = Cantroops or false capabilities.cratelimit = Cratelimit or 0 capabilities.trooplimit = Trooplimit or 0 capabilities.length = Length or 20 self.UnitTypes[unittype] = capabilities return self end --- (Internal) Check if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:IsCorrectHover(Unit) self:T(self.lid .. " IsCorrectHover") local outcome = false -- see if we are in air and within parameters. if self:IsUnitInAir(Unit) then -- get speed and height local uspeed = Unit:GetVelocityMPS() local uheight = Unit:GetHeight() local ucoord = Unit:GetCoordinate() local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground local maxh = self.maximumHoverHeight -- 15 local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true end end return outcome end --- (Internal) Check if a Hercules is flying *in parameters* for air drops. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:IsCorrectFlightParameters(Unit) self:T(self.lid .. " IsCorrectFlightParameters") local outcome = false -- see if we are in air and within parameters. if self:IsUnitInAir(Unit) then -- get speed and height local uspeed = Unit:GetVelocityMPS() local uheight = Unit:GetHeight() local ucoord = Unit:GetCoordinate() local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground local maxh = self.HercMinAngels-- 1500m local minh = self.HercMaxAngels -- 5000m local maxspeed = self.HercMaxSpeed -- 77 mps -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 local knspeed = kmspeed / 1.86 self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) if (aheight <= maxh) and (aheight >= minh) and (uspeed <= maxspeed) then -- yep within parameters outcome = true end end return outcome end --- (Internal) List if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit function CTLD:_ShowHoverParams(Group,Unit) local inhover = self:IsCorrectHover(Unit) local htxt = "true" if not inhover then htxt = "false" end local text = "" if _SETTINGS:IsMetric() then text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) else local minheight = UTILS.MetersToFeet(self.minimumHoverHeight) local maxheight = UTILS.MetersToFeet(self.maximumHoverHeight) text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) return self end --- (Internal) List if a Herc unit is flying *in parameters*. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit function CTLD:_ShowFlightParams(Group,Unit) local inhover = self:IsCorrectFlightParameters(Unit) local htxt = "true" if not inhover then htxt = "false" end local text = "" if _SETTINGS:IsImperial() then local minheight = UTILS.MetersToFeet(self.HercMinAngels) local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) else local minheight = self.HercMinAngels local maxheight = self.HercMaxAngels text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) return self end --- (Internal) Check if a unit is in a load zone and is hovering in parameters. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:CanHoverLoad(Unit) self:T(self.lid .. " CanHoverLoad") if self:IsHercules(Unit) then return false end local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) if not outcome then outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) --and self:IsCorrectHover(Unit) end return outcome end --- (Internal) Check if a unit is above ground. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:IsUnitInAir(Unit) -- get speed and height local minheight = self.minimumHoverHeight if self.enableHercules and Unit:GetTypeName() == "Hercules" then minheight = 5.1 -- herc is 5m AGL on the ground end local uheight = Unit:GetHeight() local ucoord = Unit:GetCoordinate() local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground if aheight >= minheight then return true else return false end end --- (Internal) Autoload if we can do crates, have capacity free and are in a load zone. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self function CTLD:AutoHoverLoad(Unit) self:T(self.lid .. " AutoHoverLoad") -- get capabilities and current load local unittype = Unit:GetTypeName() local unitname = Unit:GetName() local Group = Unit:GetGroup() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then -- get load local numberonboard = 0 local loaded = {} if self.Loaded_Cargo[unitname] then loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo numberonboard = loaded.Cratesloaded or 0 end local load = cratelimit - numberonboard local canload = self:CanHoverLoad(Unit) if canload and load > 0 then self:_LoadCratesNearby(Group,Unit) end end return self end --- (Internal) Run through all pilots and see if we autoload. -- @param #CTLD self -- @return #CTLD self function CTLD:CheckAutoHoverload() if self.hoverautoloading then for _,_pilot in pairs (self.CtldUnits) do local Unit = UNIT:FindByName(_pilot) if self:CanHoverLoad(Unit) then self:AutoHoverLoad(Unit) end end end return self end --- (Internal) Run through DroppedTroops and capture alive units -- @param #CTLD self -- @return #CTLD self function CTLD:CleanDroppedTroops() -- Troops local troops = self.DroppedTroops local newtable = {} for _index, _group in pairs (troops) do self:T({_group.ClassName}) if _group and _group.ClassName == "GROUP" then if _group:IsAlive() then newtable[_index] = _group end end end self.DroppedTroops = newtable -- Engineers local engineers = self.EngineersInField local engtable = {} for _index, _group in pairs (engineers) do self:T({_group.ClassName}) if _group and _group:IsNotStatus("Stopped") then engtable[_index] = _group end end self.EngineersInField = engtable return self end --- User - function to add stock of a certain troops type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self function CTLD:AddStockTroops(Name, Number) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Troops for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) end end end --- User - function to add stock of a certain crates type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self function CTLD:AddStockCrates(Name, Number) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Crates for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) end end end --- User - function to remove stock of a certain troops type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self function CTLD:RemoveStockTroops(Name, Number) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Troops for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:RemoveStock(number) end end end --- User - function to remove stock of a certain crates type -- @param #CTLD self -- @param #string Name Name as defined in the generic cargo. -- @param #number Number Number of units/groups to add. -- @return #CTLD self function CTLD:RemoveStockCrates(Name, Number) local name = Name or "none" local number = Number or 1 -- find right generic type local gentroops = self.Cargo_Crates for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:RemoveStock(number) end end return self end --- (Internal) Check on engineering teams -- @param #CTLD self -- @return #CTLD self function CTLD:_CheckEngineers() self:T(self.lid.." CheckEngineers") local engtable = self.EngineersInField for _ind,_engineers in pairs (engtable) do local engineers = _engineers -- #CTLD_ENGINEERING local wrenches = engineers.Group -- Wrapper.Group#GROUP self:T(_engineers.lid .. _engineers:GetStatus()) if wrenches and wrenches:IsAlive() then if engineers:IsStatus("Running") or engineers:IsStatus("Searching") then local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch) -- #table engineers:Search(crates,number) elseif engineers:IsStatus("Moving") then engineers:Move() elseif engineers:IsStatus("Arrived") then engineers:Build() local unit = wrenches:GetUnit(1) self:_BuildCrates(wrenches,unit,true) self:_RepairCrates(wrenches,unit,true) engineers:Done() end else engineers:Stop() end end return self end --- (User) Pre-populate troops in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. -- @return #CTLD self -- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type -- local InjectTroopsType = CTLD_CARGO:New(nil,"Injected Infantry",{"Inf12"},CTLD_CARGO.Enum.TROOPS,true,true,12,nil,false,80) -- -- get a #ZONE object -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectTroops(dropzone,InjectTroopsType) function CTLD:InjectTroops(Zone,Cargo) self:T(self.lid.." InjectTroops") local cargo = Cargo -- #CTLD_CARGO self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter table.insert(self.Cargo_Troops,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 factor = 1.5 local zone = Zone local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end end -- template loop cargo:SetWasDropped(true) -- engineering group? if type == CTLD_CARGO.Enum.ENGINEERS then self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) else self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) end end -- if type end return self end ------------------------------------------------------------------- -- FSM functions ------------------------------------------------------------------- --- (Internal) FSM Function onafterStart. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) self:I(self.lid .. "Started ("..self.version..")") if self.useprefix or self.enableHercules then local prefix = self.prefixes if self.enableHercules then self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() end else self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() end -- Events self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) self:__Status(-5) return self end --- (Internal) FSM Function onbeforeStatus. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self function CTLD:onbeforeStatus(From, Event, To) self:T({From, Event, To}) self:CleanDroppedTroops() self:_RefreshF10Menus() self:_RefreshRadioBeacons() self:CheckAutoHoverload() self:_CheckEngineers() return self end --- (Internal) FSM Function onafterStatus. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self function CTLD:onafterStatus(From, Event, To) self:T({From, Event, To}) -- gather some stats -- pilots local pilots = 0 for _,_pilot in pairs (self.CtldUnits) do pilots = pilots + 1 end -- spawned cargo boxes curr in field local boxes = 0 for _,_pilot in pairs (self.Spawned_Cargo) do boxes = boxes + 1 end local cc = self.CargoCounter local tc = self.TroopCounter if self.debug or self.verbose > 0 then local text = string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc) local m = MESSAGE:New(text,10,"CTLD"):ToAll() if self.verbose > 0 then self:I(self.lid.."Cargo and Troops in Stock:") for _,_troop in pairs (self.Cargo_Crates) do local name = _troop:GetName() local stock = _troop:GetStock() self:I(string.format("-- %s \t\t\t %d", name, stock)) end for _,_troop in pairs (self.Cargo_Troops) do local name = _troop:GetName() local stock = _troop:GetStock() self:I(string.format("-- %s \t\t %d", name, stock)) end end end self:__Status(-30) return self end --- (Internal) FSM Function onafterStop. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @return #CTLD self function CTLD:onafterStop(From, Event, To) self:T({From, Event, To}) self:UnhandleEvent(EVENTS.PlayerEnterAircraft) self:UnhandleEvent(EVENTS.PlayerEnterUnit) self:UnhandleEvent(EVENTS.PlayerLeaveUnit) return self end --- (Internal) FSM Function onbeforeTroopsPickedUp. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) self:T({From, Event, To}) return self end --- (Internal) FSM Function onbeforeCratesPickedUp. -- @param #CTLD self -- @param #string From State . -- @param #string Event Trigger. -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) self:T({From, Event, To}) return self end --- (Internal) FSM Function onbeforeTroopsExtracted. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. -- @return #CTLD self function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) self:T({From, Event, To}) return self end --- (Internal) FSM Function onbeforeTroopsDeployed. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. -- @return #CTLD self function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) self:T({From, Event, To}) return self end --- (Internal) FSM Function onbeforeCratesDropped. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. -- @return #CTLD self function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) self:T({From, Event, To}) return self end --- (Internal) FSM Function onbeforeCratesBuild. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) self:T({From, Event, To}) return self end --- (Internal) FSM Function onbeforeTroopsRTB. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @return #CTLD self function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) self:T({From, Event, To}) return self end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------