diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 1a6124eda..ca75e77db 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1514,7 +1514,7 @@ CTLD.UnitTypeCapabilities = { ["C-130J-30"] = {type="C-130J-30", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 35, cargoweightlimit = 21500}, -- 19t cargo, 64 paratroopers. --Actually it's longer, but the center coord is off-center of the model. ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats - ["UH-60L_DAP"] = {type="UH-60L_DAP", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 16, cargoweightlimit = 500}, -- UH-60L DAP is an attack helo but can do limited CSAR and CTLD + ["UH-60L_DAP"] = {type="UH-60L_DAP", crates=false, troops=true, cratelimit = 2, trooplimit = 2, length = 16, cargoweightlimit = 3000}, -- UH-60L DAP is an attack helo but can do limited CSAR and CTLD ["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo @@ -1647,6 +1647,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self.Cargo_Crates = {} self.Cargo_Troops = {} self.Cargo_Statics = {} + self._troopsByName = {} + self._crateOrStaticByName = {} + self._cargoByTemplate = {} self.Loaded_Cargo = {} self.Spawned_Crates = {} self.Spawned_Cargo = {} @@ -2419,9 +2422,9 @@ end --- (Internal) Function to check if a unit is a C-130J -- @param #CTLD self -function CTLD:IsC130J(Unit) +function CTLD:IsC130J(Unit, IgnoreUseC130Flag) if not Unit then return false end - if not self.UseC130LoadAndUnload then return false end + if not IgnoreUseC130Flag and not self.UseC130LoadAndUnload then return false end self.C130JUnits = self.C130JUnits or {} local unitname = Unit:GetName() or "none" return self.C130JUnits[unitname] == true @@ -2447,10 +2450,16 @@ end -- @return #CTLD_CARGO Cargo object, nil if it cannot be found function CTLD:_FindTroopsCargoObject(Name) self:T(self.lid .. " _FindTroopsCargoObject") + self._troopsByName = self._troopsByName or {} + local cached = self._troopsByName[Name] + if cached then + return cached + end local cargo = nil for _,_cargo in pairs(self.Cargo_Troops)do local cargo = _cargo -- #CTLD_CARGO if cargo.Name == Name then + self._troopsByName[Name] = cargo return cargo end end @@ -2463,16 +2472,23 @@ end -- @return #CTLD_CARGO Cargo object, nil if it cannot be found function CTLD:_FindCratesCargoObject(Name) self:T(self.lid .. " _FindCratesCargoObject") + self._crateOrStaticByName = self._crateOrStaticByName or {} + local cached = self._crateOrStaticByName[Name] + if cached then + return cached + end local cargo = nil for _,_cargo in pairs(self.Cargo_Crates)do local cargo = _cargo -- #CTLD_CARGO if cargo.Name == Name then + self._crateOrStaticByName[Name] = cargo return cargo end end for _,_cargo in pairs(self.Cargo_Statics)do local cargo = _cargo -- #CTLD_CARGO if cargo.Name == Name then + self._crateOrStaticByName[Name] = cargo return cargo end end @@ -2598,6 +2614,20 @@ function CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates) return self end +--- (User) Hook to allow mission-specific troop restrictions. +-- Override this in your mission to perform custom checks (e.g. warehouse stock, role limits) before troops are loaded. +-- Return true to allow the request, or false to block it. When blocked, _LoadTroops and _LoadTroopsQuantity exit silently. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group Requesting player group. +-- @param Wrapper.Unit#UNIT Unit Requesting unit. +-- @param #CTLD_CARGO Cargo Troop cargo type being requested. +-- @param #number quantity Number of troop sets requested. +-- @param #boolean Inject If true, this call originates from an inject/preload path. +-- @return #boolean Allow troop loading. +function CTLD:CanGetTroops(Group, Unit, Cargo, quantity, Inject) + return true +end + --- (Internal) Function to load troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -2668,6 +2698,9 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) self:_SendMessage("Sorry, that\'s too heavy to load!", 10, false, Group) return else + if not self:CanGetTroops(Group, Unit, Cargotype, 1, Inject) then + return self + end 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}) @@ -2987,6 +3020,8 @@ function CTLD:_LoadTroopsQuantity(Group, Unit, Cargo, quantity) if not self.debug then return self end end + if not self:CanGetTroops(Group, Unit, Cargo, n, false) then return self end + local prevSuppress = self.suppressmessages self.suppressmessages = true for i = 1, n do @@ -3141,26 +3176,40 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum maxQuantity = maxMassSets end end - if type(maxload)=="number"and maxload>0 and perCrateMass>0 then - maxMassCrates=math.floor(maxload/perCrateMass) + if type(maxload) == "number" and maxload > 0 and perCrateMass > 0 then + maxMassCrates = math.floor(maxload / perCrateMass) end end - self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) + self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." allowLoad "..tostring(allowLoad)) if maxQuantity < 1 then return self end if maxQuantity == 1 then self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText) - --parentMenu.MenuText - MENU_GROUP_COMMAND:New(Group, "Get", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) local canLoad = (allowLoad and (not capacitySets or capacitySets >= 1) and (not maxMassSets or maxMassSets >= 1)) local isHerc = self:IsC130J(Unit) - local isHook = self:IsHook(Unit) local cgotype = cargoObj:GetType() or nil - local suppressGetAndLoad = (self.enableChinookGCLoading == true) and isHook and (cgotype == CTLD_CARGO.Enum.STATIC) - local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) + local suppressGetAndLoad = (self.enableChinookGCLoading == true) and (cgotype == CTLD_CARGO.Enum.STATIC) + local canPartiallyLoad = ((not capacityCrates or capacityCrates >= 1) and (not maxMassCrates or maxMassCrates >= 1)) + + if suppressGetAndLoad or isHerc then + if canLoad then + MENU_GROUP_COMMAND:New(Group, "1", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) + else + local msg + if maxMassSets and (not capacitySets or capacitySets >= 1) and maxMassSets < 1 then + msg = "Weight limit reached" + else + msg = "Crate limit reached" + end + MENU_GROUP_COMMAND:New(Group, msg, parentMenu, self._SendMessage, self, msg, 10, false, Group) + end + return self + end + if canLoad and not isHerc and not suppressGetAndLoad then + MENU_GROUP_COMMAND:New(Group, "Get", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) MENU_GROUP_COMMAND:New(Group, "Get and Load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1) else local msg @@ -3171,13 +3220,14 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum msg = "Crate limit reached" end MENU_GROUP_COMMAND:New(Group, msg, parentMenu, self._SendMessage, self, msg, 10, false, Group) - if canPartiallyLoad and (cgotype ~= CTLD_CARGO.Enum.STATIC) and (not suppressGetAndLoad) then - MENU_GROUP_COMMAND:New(Group, "Partially load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1,true) + MENU_GROUP_COMMAND:New(Group, "Get anyway", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1) + + MENU_GROUP_COMMAND:New(Group, "Partially load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1, true) end end end - + return self end @@ -3185,29 +3235,17 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1") local label = tostring(quantity) self:T("_AddCrateQuantityMenus Label "..label) - local qMenu = MENU_GROUP:New(Group, label, parentMenu) - MENU_GROUP_COMMAND:New(Group, "Get", qMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity) local canLoad = (allowLoad and (not capacitySets or capacitySets >= quantity) and (not maxMassSets or maxMassSets >= quantity)) local isHerc = self:IsC130J(Unit) - local isHook = self:IsHook(Unit) local cgotype = cargoObj:GetType() or nil - local suppressGetAndLoad = (self.enableChinookGCLoading == true) and isHook and (cgotype == CTLD_CARGO.Enum.STATIC) - local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1)) - if canLoad and not isHerc and not suppressGetAndLoad then + local suppressGetAndLoad = (self.enableChinookGCLoading == true) and (cgotype == CTLD_CARGO.Enum.STATIC) + + if canLoad and not isHerc and not suppressGetAndLoad then + local qMenu = MENU_GROUP:New(Group, label, parentMenu) + MENU_GROUP_COMMAND:New(Group, "Get", qMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity) MENU_GROUP_COMMAND:New(Group, "Get and Load", qMenu, self._GetAndLoad, self, Group, Unit, cargoObj, quantity) else - local msg - if not isHerc and not suppressGetAndLoad then - if maxMassSets and (not capacitySets or capacitySets >= quantity) and maxMassSets < quantity then - msg = "Weight limit reached" - else - msg = "Crate limit reached" - end - MENU_GROUP_COMMAND:New(Group, msg, qMenu, self._SendMessage, self, msg, 10, false, Group) - if canPartiallyLoad and (cgotype ~= CTLD_CARGO.Enum.STATIC) and (not suppressGetAndLoad) then - MENU_GROUP_COMMAND:New(Group, "Partially load", qMenu, self._GetAndLoad, self, Group, Unit, cargoObj, quantity, true) - end - end + MENU_GROUP_COMMAND:New(Group, label, parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity) end end return self @@ -3374,7 +3412,6 @@ end function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppressGetEvent) self:T(self.lid .. " _GetCrates") - -- check if we have stock local perSet = Cargo:GetCratesNeeded() or 1 if perSet < 1 then perSet = 1 end local requestNumber = tonumber(number) @@ -3387,39 +3424,37 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress local requestedSets = math.floor((requestNumber + perSet - 1) / perSet) if requestedSets < 1 then requestedSets = 1 end if not drop and not pack then - local cgoname = self:_GetCargoDisplayName(Cargo) + local cgoname = Cargo:GetName() 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 false end end -- check if we are in LOAD zone - local inzone = false + local inzone = false local drop = drop or false local suppressGetEvent = suppressGetEvent or false local ship = nil local width = 20 local distance = nil local zone = nil - if not drop and not pack then - inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if not drop and not pack then + inzone = self:IsUnitInZone(Unit, CTLD.CargoZoneType.LOAD) if not inzone then ----@diagnostic disable-next-line: cast-local-type - inzone, ship, zone, distance, width = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) + inzone, ship, zone, distance, width = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP) end elseif drop and not pack then if self.dropcratesanywhere then -- #1570 inzone = true else - inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + inzone = self:IsUnitInZone(Unit, CTLD.CargoZoneType.DROP) end elseif pack and not drop then inzone = true 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 @@ -3442,7 +3477,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 35 - local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true,true,true) -- to ignore what's inside + local nearcrates, numbernearby = self:_FindCratesNearby(Group, Unit, loaddist, true, true, true) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) return false @@ -3454,6 +3489,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress -- spawn crates in front of helicopter local IsHerc = self:IsFixedWing(Unit) -- Herc, Bronco and Hook load from behind local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind + local IsHelo = Unit and Unit.IsHelicopter and Unit:IsHelicopter() or false local IsTruck = Unit:IsGround() local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO local number = requestNumber --#number @@ -3483,89 +3519,204 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress -- spawn behind the Herc addon = 180 end - heading = (heading+addon)%360 + heading = (heading + addon) % 360 local row = 1 local column = 1 - local initialdist = IsHerc and 16 or (capabilities.length+2) -- initial spacing of the first crates - local startpos = position:Translate(initialdist,heading) + local initialdist = IsHerc and 16 or (capabilities.length + 2) + local startpos = position:Translate(initialdist, heading) if self.placeCratesAhead == true then cratedistance = initialdist end - -- loop crates needed - local cratecoord = nil -- Core.Point#COORDINATE - for i=1,number do - local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000)) - if not self.placeCratesAhead or drop == true then - cratedistance = (i-1)*2.5 + capabilities.length - if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end - -- altered heading logic - -- DONE: right standard deviation? - if self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) then - rheading = math.random(20,60) - else - rheading = UTILS.RandomGaussian(0, 30, -90, 90, 100) - end - rheading=math.fmod((heading+rheading),360) - cratecoord = position:Translate(cratedistance,rheading) - else - cratedistance = (row-1)*6 - rheading = 90 - row = row+1 - cratecoord = startpos:Translate(cratedistance,rheading) - if row > 4 then - row = 1 - startpos:Translate(6,heading,nil,true) - end + local cratecoord = nil + local shipdist = nil + local shipoffset = nil + + local FW_STEP_BY_TYPE = { + cds_crate = 2.0, + cds_barrels = 2.0, + ammo_cargo = 1.0, + iso_container_small = 3.5, + iso_container = 3.5, + uh1h_cargo = 2.0, + container_cargo = 2.5, + } + local FW_WIDTH_BY_TYPE = { -- spacing between crates in the set. + cds_crate = 0.8, + cds_barrels = 0.8, + ammo_cargo = 0.4, + iso_container_small = 2.0, + iso_container = 2.0, + uh1h_cargo = 0.6, + container_cargo = 1.3, + } + local FW_ROW_GAP = 0.6 + local FW_BATCH_ANGLE_PATTERN = { 0, -20, 20, -40, 40, -60, 60, -80, 80 } + local fwBatchState = nil + local fwBatchIndex = 0 + local fwBatchKey = nil + if IsHerc or IsHelo then + self._fwBatchState = self._fwBatchState or {} + fwBatchKey = (Unit and Unit.GetName and Unit:GetName()) or "FW" + fwBatchState = self._fwBatchState[fwBatchKey] or { batch = 0 } + if (tonumber(numbernearby) or 0) <= 0 then + fwBatchState.batch = 0 end - - --local cratevec2 = cratecoord:GetVec2() - self.CrateCounter = self.CrateCounter + 1 + fwBatchIndex = fwBatchState.batch or 0 + end + + local fwZeroAngleSetHeading = nil + local fwNonZeroAngleSetHeading = nil + + for i = 1, number do + local currentAngleOffset = 0 + local cratealias = string.format("%s-%d", cratename, math.random(1, 100000)) local CCat, CType, CShape = Cargo:GetStaticTypeAndShape() local basetype = CType or self.basetype or "container_cargo" CCat = CCat or "Cargos" - if not isstatic and self:IsC130J(Unit) then + + if not isstatic and self:IsC130J(Unit, true) then if Cargo.C130TypeName then basetype = Cargo.C130TypeName elseif self.C130basetype and (not CType or CType == self.basetype) then basetype = self.C130basetype end end + + if not self.placeCratesAhead or drop == true then + local step = (IsHerc or IsHelo) and (FW_STEP_BY_TYPE[basetype] or 2.6) or 1.6 + if (IsHerc or IsHelo) and not drop then + local safeDistance = capabilities.length * 0.9 + local maxDist = self.CrateDistance or 35 + if safeDistance > maxDist then safeDistance = maxDist end + + local angleIndex = (fwBatchIndex % #FW_BATCH_ANGLE_PATTERN) + 1 + currentAngleOffset = FW_BATCH_ANGLE_PATTERN[angleIndex] or 0 + local centerHeading = math.fmod((heading + currentAngleOffset), 360) + if math.abs(currentAngleOffset) >= 0.01 and not fwNonZeroAngleSetHeading then + fwNonZeroAngleSetHeading = centerHeading + end + + local baseDistance = safeDistance + local crateWidth = FW_WIDTH_BY_TYPE[basetype] or step + local zeroAngle = math.abs(currentAngleOffset) < 0.01 + local lateral = nil + local lateralHeading = nil + if zeroAngle then + local totalRowWidth = (number * crateWidth) + (math.max(0, number - 1) * FW_ROW_GAP) + local leftEdge = -(totalRowWidth / 2) + lateral = leftEdge + ((i - 1) * (crateWidth + FW_ROW_GAP)) + (crateWidth / 2) + if lateral >= 0 then + lateralHeading = math.fmod(centerHeading + 90, 360) + else + lateral = -lateral + lateralHeading = math.fmod(centerHeading + 270, 360) + end + else + lateral = (i - 1) * (crateWidth + FW_ROW_GAP) + if currentAngleOffset < 0 then + lateralHeading = math.fmod(centerHeading + 270, 360) + else + lateralHeading = math.fmod(centerHeading + 90, 360) + end + end + + local maxLateralSq = (maxDist * maxDist) - (baseDistance * baseDistance) + if maxLateralSq < 0 then maxLateralSq = 0 end + local maxLateral = math.sqrt(maxLateralSq) + if lateral > maxLateral then + lateral = maxLateral + end + + local baseCoord = position:Translate(baseDistance, centerHeading) + cratecoord = baseCoord:Translate(lateral, lateralHeading) + cratedistance = baseDistance + rheading = centerHeading + else + cratedistance = (i - 1) * step + capabilities.length + if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end + rheading = UTILS.RandomGaussian(0, 18, -55, 55, 100) + rheading = math.fmod((heading + rheading), 360) + cratecoord = position:Translate(cratedistance, rheading) + end + else + cratedistance = (row - 1) * 6 + rheading = 90 + row = row + 1 + cratecoord = startpos:Translate(cratedistance, rheading) + if row > 4 then + row = 1 + startpos:Translate(6, heading, nil, true) + end + end + + self.CrateCounter = self.CrateCounter + 1 + local crateSpawnHeading = 270 + if (IsHerc or IsHelo) and not drop and cratecoord and type(ship) ~= "string" then + if math.abs(currentAngleOffset) < 0.01 then + if not fwZeroAngleSetHeading then + fwZeroAngleSetHeading = heading + end + crateSpawnHeading = fwZeroAngleSetHeading + else + crateSpawnHeading = fwNonZeroAngleSetHeading + end + end + if type(ship) == "string" then self:T("Spawning on ship "..ship) local Ship = UNIT:FindByName(ship) local shipcoord = Ship:GetCoordinate() local unitcoord = Unit:GetCoordinate() local dist = shipcoord:Get2DDistance(unitcoord) - dist = dist - (20 + math.random(1,10)) - local width = width / 2 - local Offy = math.random(-width,width) - local spawnstatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) - :InitCargoMass(cgomass) - :InitCargo(self.enableslingload) - :InitLinkToUnit(Ship,dist,Offy,0) + dist = dist - (20 + math.random(1, 10)) + local halfwidth = (width or 20) / 2 + local Offy = nil + if i == 1 or shipdist == nil or shipoffset == nil then + Offy = math.random(-halfwidth, halfwidth) + shipoffset = Offy + shipdist = dist + else + dist = shipdist + local step = math.max(1, math.min(3, halfwidth * 0.2)) + local slot = i - 1 + local ring = math.floor((slot + 1) / 2) + local sign = (slot % 2 == 1) and 1 or -1 + Offy = shipoffset + (sign * ring * step) + if Offy > halfwidth then + Offy = halfwidth + elseif Offy < -halfwidth then + Offy = -halfwidth + end + end + + local spawnstatic = SPAWNSTATIC:NewFromType(basetype, CCat, self.cratecountry) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) + :InitLinkToUnit(Ship, dist, Offy, 0) if CShape then spawnstatic:InitShape(CShape) - end + end if isstatic then - local map=cargotype:GetStaticResourceMap() + local map = cargotype:GetStaticResourceMap() spawnstatic.TemplateStaticUnit.resourcePayload = map end - self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) - else - local spawnstatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(crateSpawnHeading, cratealias) + else + local spawnstatic = SPAWNSTATIC:NewFromType(basetype, CCat, self.cratecountry) :InitCoordinate(cratecoord) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) if CShape then spawnstatic:InitShape(CShape) - end + end if isstatic then - local map=cargotype:GetStaticResourceMap() + local map = cargotype:GetStaticResourceMap() spawnstatic.TemplateStaticUnit.resourcePayload = map end - self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(crateSpawnHeading, cratealias) end + local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() local subcat = cargotype.Subcategory @@ -3573,43 +3724,50 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress local realcargo = nil if drop then --CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory) - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) -- #CTLD_CARGO + realcargo = CTLD_CARGO:New(self.CargoCounter, cratename, templ, sorte, true, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], true, cargotype.PerCrateMass, nil, subcat) realcargo:SetDisplayName(cargotype:GetDisplayName()) - local map=cargotype:GetStaticResourceMap() + local map = cargotype:GetStaticResourceMap() realcargo:SetStaticResourceMap(map) local CCat3, CType3, CShape3 = cargotype:GetStaticTypeAndShape() - realcargo:SetStaticTypeAndShape(CCat3,CType3,CShape3) + realcargo:SetStaticTypeAndShape(CCat3, CType3, CShape3) if cargotype.TypeNames then realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames) end - table.insert(droppedcargo,realcargo) + table.insert(droppedcargo, realcargo) else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) + realcargo = CTLD_CARGO:New(self.CargoCounter, cratename, templ, sorte, false, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], false, cargotype.PerCrateMass, nil, subcat) realcargo:SetDisplayName(cargotype:GetDisplayName()) - local map=cargotype:GetStaticResourceMap() - realcargo:SetStaticResourceMap(map) + local map = cargotype:GetStaticResourceMap() + realcargo:SetStaticResourceMap(map) if cargotype.TypeNames then realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames) end if self.UseC130LoadAndUnload and self:IsC130J(Unit) then - realcargo:SetWasDropped(true,true) -- we mark here that the crates was dropped even though we just got them because of the herc. + realcargo:SetWasDropped(true, true) end end + if not drop and not pack then table.insert(obtainedcargo, realcargo) end local CCat4, CType4, CShape4 = cargotype:GetStaticTypeAndShape() - realcargo:SetStaticTypeAndShape(CCat4,CType4,CShape4) + realcargo:SetStaticTypeAndShape(CCat4, CType4, CShape4) table.insert(self.Spawned_Cargo, realcargo) end + if (IsHerc or IsHelo) and fwBatchState and fwBatchKey and not drop then + local maxBatches = #FW_BATCH_ANGLE_PATTERN + fwBatchState.batch = (fwBatchIndex + 1) % maxBatches + self._fwBatchState[fwBatchKey] = fwBatchState + end + if not (drop or pack) then Cargo:RemoveStock(requestedSets) self:_RefreshCrateQuantityMenus(Group, Unit, Cargo) end - local text = string.format("%d crates for %s have been positioned near you!",number,cratedisplayname) + local text = string.format("%d crates for %s have been positioned near you!", number, cratedisplayname) if drop then - text = string.format("%d crates for %s have been dropped!",number,cratedisplayname) + text = string.format("%d crates for %s have been dropped!", number, cratedisplayname) self:__CratesDropped(1, Group, Unit, droppedcargo) else if not quiet then @@ -3930,11 +4088,11 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype end end self:T(self.lid..string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) - if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true and not hercInnerBlocked then - index = index + 1 - table.insert(found, staticid, cargo) - maxloadable = maxloadable - weight - end + if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true and not hercInnerBlocked then + index = index + 1 + found[#found+1] = cargo + maxloadable = maxloadable - weight + end end end @@ -4081,22 +4239,21 @@ end function CTLD:_CleanupTrackedCrates(crateIdsToRemove) local existingcrates = self.Spawned_Cargo -- #table local newexcrates = {} + local remove = {} + for _,_ID in pairs(crateIdsToRemove or {}) do + remove[_ID] = true + end for _,_crate in pairs(existingcrates) do local excrate = _crate -- #CTLD_CARGO local ID = excrate:GetID() - local keep = true - for _,_ID in pairs(crateIdsToRemove) do - if ID == _ID then - keep = false - end - end + local keep = not remove[ID] -- remove destroyed crates here too local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates if not static or not static:IsAlive() then keep = false end if keep then - table.insert(newexcrates,_crate) + newexcrates[#newexcrates+1] = _crate end end self.Spawned_Cargo = nil @@ -6819,6 +6976,16 @@ function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,Sub -- Troops are directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock, SubCategory) table.insert(self.Cargo_Troops,cargo) + self._troopsByName = self._troopsByName or {} + self._troopsByName[cargo.Name] = cargo + self._cargoByTemplate = self._cargoByTemplate or {} + local _template = cargo.Templates + if type(_template) == "table" then + _template = _template[1] + end + if type(_template) == "string" and _template ~= "" then + self._cargoByTemplate[_template] = cargo + end if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return self end @@ -6916,6 +7083,16 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub end cargo.C130TypeName = C130TypeName table.insert(self.Cargo_Crates,cargo) + self._crateOrStaticByName = self._crateOrStaticByName or {} + self._crateOrStaticByName[cargo.Name] = cargo + self._cargoByTemplate = self._cargoByTemplate or {} + local _template = cargo.Templates + if type(_template) == "table" then + _template = _template[1] + end + if type(_template) == "string" and _template ~= "" then + self._cargoByTemplate[_template] = cargo + end if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return self end @@ -6956,6 +7133,16 @@ function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Sto end cargo.C130TypeName = C130TypeName table.insert(self.Cargo_Crates,cargo) + self._crateOrStaticByName = self._crateOrStaticByName or {} + self._crateOrStaticByName[cargo.Name] = cargo + self._cargoByTemplate = self._cargoByTemplate or {} + local _template = cargo.Templates + if type(_template) == "table" then + _template = _template[1] + end + if type(_template) == "string" and _template ~= "" then + self._cargoByTemplate[_template] = cargo + end self.templateToCargoName = self.templateToCargoName or {} if type(Templates)=="table" then for _,t in pairs(Templates) do self.templateToCargoName[t] = Name end @@ -6982,7 +7169,7 @@ end function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,DisplayName) self:T(self.lid .. " AddStaticsCargo") self.CargoCounter = self.CargoCounter + 1 - local type = CTLD_CARGO.Enum.STATIC + local cargotype = CTLD_CARGO.Enum.STATIC local template = STATIC:FindByName(Name,true):GetTypeName() local unittemplate = _DATABASE:GetStaticUnitTemplate(Name) local ResourceMap = nil @@ -6990,13 +7177,23 @@ function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Locatio ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload) end -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,cargotype,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) if UnitTypes then cargo:AddUnitTypeName(UnitTypes) end cargo:SetDisplayName(DisplayName or Name) cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) + self._crateOrStaticByName = self._crateOrStaticByName or {} + self._crateOrStaticByName[cargo.Name] = cargo + self._cargoByTemplate = self._cargoByTemplate or {} + local _template = cargo.Templates + if type(_template) == "table" then + _template = _template[1] + end + if type(_template) == "string" and _template ~= "" then + self._cargoByTemplate[_template] = cargo + end if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return cargo end @@ -7020,9 +7217,9 @@ end function CTLD:AddStaticsCargoFromType(Name,TypeName,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,ShapeName,ResourceMap,DisplayName) self:T(self.lid .. " AddStaticsCargoFromType") self.CargoCounter = self.CargoCounter + 1 - local type = CTLD_CARGO.Enum.STATIC + local cargotype = CTLD_CARGO.Enum.STATIC local template = TypeName or self.basetype or "container_cargo" - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,cargotype,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) if UnitTypes then cargo:AddUnitTypeName(UnitTypes) end @@ -7033,6 +7230,16 @@ function CTLD:AddStaticsCargoFromType(Name,TypeName,Mass,Stock,SubCategory,DontS end cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) + self._crateOrStaticByName = self._crateOrStaticByName or {} + self._crateOrStaticByName[cargo.Name] = cargo + self._cargoByTemplate = self._cargoByTemplate or {} + local _template = cargo.Templates + if type(_template) == "table" then + _template = _template[1] + end + if type(_template) == "string" and _template ~= "" then + self._cargoByTemplate[_template] = cargo + end if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return cargo end @@ -7046,7 +7253,7 @@ end function CTLD:GetStaticsCargoFromTemplate(Name,Mass,DisplayName) self:T(self.lid .. " GetStaticsCargoFromTemplate") self.CargoCounter = self.CargoCounter + 1 - local type = CTLD_CARGO.Enum.STATIC + local cargotype = CTLD_CARGO.Enum.STATIC local template = STATIC:FindByName(Name,true):GetTypeName() local unittemplate = _DATABASE:GetStaticUnitTemplate(Name) local ResourceMap = nil @@ -7054,7 +7261,7 @@ function CTLD:GetStaticsCargoFromTemplate(Name,Mass,DisplayName) ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload) end -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,cargotype,false,false,1,nil,nil,Mass,1) cargo:SetDisplayName(DisplayName or Name) cargo:SetStaticResourceMap(ResourceMap) --table.insert(self.Cargo_Statics,cargo) @@ -7074,9 +7281,9 @@ end function CTLD:GetStaticsCargoFromType(Name,TypeName,Mass,Category,ShapeName,ResourceMap,DisplayName) self:T(self.lid .. " GetStaticsCargoFromType") self.CargoCounter = self.CargoCounter + 1 - local type = CTLD_CARGO.Enum.STATIC + local cargotype = CTLD_CARGO.Enum.STATIC local template = TypeName or self.basetype or "container_cargo" - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,cargotype,false,false,1,nil,nil,Mass,1) cargo:SetStaticTypeAndShape(Category or "Cargos", template, ShapeName) cargo:SetDisplayName(DisplayName or Name) if ResourceMap then @@ -7119,6 +7326,16 @@ function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,Su cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) end table.insert(self.Cargo_Crates,cargo) + self._crateOrStaticByName = self._crateOrStaticByName or {} + self._crateOrStaticByName[cargo.Name] = cargo + self._cargoByTemplate = self._cargoByTemplate or {} + local _template = cargo.Templates + if type(_template) == "table" then + _template = _template[1] + end + if type(_template) == "string" and _template ~= "" then + self._cargoByTemplate[_template] = cargo + end return self end @@ -8556,11 +8773,17 @@ end template = string.gsub(GroupName,"#(%d+)$","") end template = string.gsub(template,"-(%d+)$","") + self._cargoByTemplate = self._cargoByTemplate or {} + local cached = self._cargoByTemplate[template] + if cached and cached.CargoType ~= CTLD_CARGO.Enum.REPAIR then + return cached + end 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 == template then Cargotype = v + self._cargoByTemplate[template] = v break end end @@ -8570,6 +8793,7 @@ end if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end if comparison == template and v.CargoType ~= CTLD_CARGO.Enum.REPAIR then Cargotype = v + self._cargoByTemplate[template] = v break end end @@ -8994,23 +9218,20 @@ end -- @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 + -- gather stats only when logging is enabled + local pilots = 0 + for _,_pilot in pairs (self.CtldUnits) do + pilots = pilots + 1 + end + + local boxes = 0 + for _,_pilot in pairs (self.Spawned_Cargo) do + boxes = boxes + 1 + end + + local cc = self.CargoCounter + local tc = self.TroopCounter 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