From cad8f15b616e25e65af707cd6f7c711457aa46a3 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Sun, 9 Feb 2025 23:41:16 +0100 Subject: [PATCH 1/5] Update CTLD.lua Reworked the logic of the menu. Now it will Show be shown : 1. Ammo truck 2. Humvee 3. Ammo truck 1/2 -- 1/2 due to incomplete set. And for the troops Squad 8 (2) -- 2 set of squad 8, Selecting this will only deploy 1 set. Squad 16 Changed so when loading troops, it will state Squad 8 boarded. same for the extraction. I have tested it and it works Heart8reaker also tested it and no issues so far. --- Moose Development/Moose/Ops/CTLD.lua | 630 +++++++++++++++------------ 1 file changed, 348 insertions(+), 282 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 067d28f37..fe760cc83 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2355,7 +2355,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:_SendMessage("Troops boarded!", 10, false, Group) + self:_SendMessage(string.format("%s boarded!", cgoname), 10, false, Group) self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group, Unit, Cargotype) self:_UpdateUnitCargoMass(Unit) @@ -2589,8 +2589,8 @@ end loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!", 10, false, Group) - self:_SendMessage("Troops boarding!", 10, false, Group) + self:ScheduleOnce(running, self._SendMessage, self, string.format("%s boarded!", Cargotype.Name), 10, false, Group) + self:_SendMessage(string.format("%s boarding!", Cargotype.Name), 10, false, Group) self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) local groupname = nearestGroup:GetName() @@ -3099,23 +3099,21 @@ 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 unit = Unit -- Wrapper.Unit#UNIT local unitname = unit:GetName() - -- see if this heli can load crates + -- see if this heli can load crates local unittype = unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities - --local capabilities = self.UnitTypeCapabilities[unittype] -- #CTLD.UnitTypeCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number local grounded = not self:IsUnitInAir(Unit) local canhoverload = self:CanHoverLoad(Unit) - + -- Door check if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) - if not self.debug then return self end + if not self.debug then return self end end - --- cases ------------------------------- -- Chopper can\'t do crates - bark & return -- Chopper can do crates - @@ -3123,77 +3121,97 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- --> hover or land if not forcedhover ----------------------------------------- if not cancrates then - self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + 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) + 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) + self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) else - -- have we loaded stuff already? + -- have we loaded stuff already? local numberonboard = 0 - local massonboard = 0 - local loaded = {} + local loaded = {} if self.Loaded_Cargo[unitname] then - loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo - numberonboard = loaded.Cratesloaded or 0 - massonboard = self:_GetUnitCargoMass(Unit) + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 else - loaded = {} -- #CTLD.LoadedCargo + loaded = {} loaded.Troopsloaded = 0 loaded.Cratesloaded = 0 loaded.Cargo = {} end + -- get nearby crates - local finddist = self.CrateDistance or 35 - local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist,false,false) -- #table + local finddist = self.CrateDistance or 35 + local nearcrates, number = self:_FindCratesNearby(Group,Unit,finddist,false,false) self:T(self.lid .. " Crates found: " .. number) + if number == 0 and self.hoverautoloading then - return self -- exit + return self elseif number == 0 then - self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!", 10, false, Group) - return self -- exit + self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!", 10, false, Group) + return self elseif numberonboard == cratelimit then - self:_SendMessage("Sorry, we are fully loaded!", 10, false, Group) - return self -- exit + self:_SendMessage("Sorry, we are fully loaded!", 10, false, Group) + return self else - -- go through crates and load local capacity = cratelimit - numberonboard local crateidsloaded = {} - local loops = 0 - while loaded.Cratesloaded < cratelimit and loops < number do - loops = loops + 1 - local crateind = 0 - -- get crate with largest index - for _ind,_crate in pairs (nearcrates) do - if self.allowcratepickupagain then - if _crate:GetID() > crateind and _crate.Positionable ~= nil then - crateind = _crate:GetID() - end + local crateMap = {} + + for _, cObj in pairs(nearcrates) do + if not cObj:HasMoved() or self.allowcratepickupagain then + local cName = cObj:GetName() or "Unknown" + crateMap[cName] = crateMap[cName] or {} + table.insert(crateMap[cName], cObj) + end + end + for cName, crateList in pairs(crateMap) do + if capacity <= 0 then break end + + table.sort(crateList, function(a, b) return a:GetID() > b:GetID() end) + local needed = crateList[1]:GetCratesNeeded() or 1 + local totalFound = #crateList + local loadedHere = 0 + + while loaded.Cratesloaded < cratelimit and loadedHere < totalFound do + loadedHere = loadedHere + 1 + local crate = crateList[loadedHere] + if crate and crate.Positionable then + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + crate:SetWasDropped(false) + table.insert(loaded.Cargo, crate) + table.insert(crateidsloaded, crate:GetID()) + -- destroy crate + crate:GetPositionable():Destroy(false) + crate.Positionable = nil else - if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then - crateind = _crate:GetID() - end + loadedHere = loadedHere - 1 + break end end - -- load one if we found one - if crateind > 0 then - local crate = nearcrates[crateind] -- #CTLD_CARGO - loaded.Cratesloaded = loaded.Cratesloaded + 1 - crate:SetHasMoved(true) - crate:SetWasDropped(false) - table.insert(loaded.Cargo, crate) - table.insert(crateidsloaded,crate:GetID()) - -- destroy crate - crate:GetPositionable():Destroy(false) - crate.Positionable = nil - self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) - table.remove(nearcrates,crate:GetID()) - self:__CratesPickedUp(1, Group, Unit, crate) + + capacity = cratelimit - loaded.Cratesloaded + if loadedHere > 0 then + local fullSets = math.floor(loadedHere / needed) + local leftover = loadedHere % needed + + if needed > 1 then + if fullSets > 0 and leftover == 0 then + self:_SendMessage(string.format("Loaded %d %s.", fullSets, cName), 10, false, Group) + elseif fullSets > 0 and leftover > 0 then + self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cName, leftover), 10, false, Group) + else + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cName), 15, false, Group) + end + else + self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cName), 10, false, Group) + end end end self.Loaded_Cargo[unitname] = loaded - self:_UpdateUnitCargoMass(Unit) - self:_RefreshDropCratesMenu(Group,Unit) + self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group, Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) end @@ -3201,6 +3219,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) return self end + --- (Internal) Function to clean up tracked cargo crates -- @param #CTLD self -- @param #list crateIdsToRemove Table of IDs @@ -3858,6 +3877,7 @@ end -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit + function CTLD:_PackCratesNearby(Group, Unit) self:T(self.lid .. " _PackCratesNearby") ----------------------------------------- @@ -4294,7 +4314,7 @@ function CTLD:_RefreshF10Menus() end for name, info in pairs(cargoByName) do local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) - MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrate, self, _group, _unit, name) + MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) end end @@ -4344,22 +4364,52 @@ end -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. -- @param Wrapper.Unit#UNIT Unit The calling unit. --- @param #string CrateName The name of the crate to unload +-- @param #string setIndex The name of the crate to unload -- @return #CTLD self -function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) +function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) + self:T(self.lid .. " _UnloadSingleCrateSet") + + -- Check if we are in a drop zone (unless we drop anywhere) if not self.dropcratesanywhere then 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 + if not self.debug then + return self end end end + + -- Check if doors must be open if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) if not self.debug then return self end end + + -- Check if the crate grouping data is available + local unitName = Unit:GetName() + if not self.CrateGroupList or not self.CrateGroupList[unitName] then + self:_SendMessage("No crate groups found for this unit!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Find the selected chunk/set by index + local chunk = self.CrateGroupList[unitName][setIndex] + if not chunk then + self:_SendMessage("No crate set found or index invalid!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Check if the chunk is empty + if #chunk == 0 then + self:_SendMessage("No crate found in that set!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Check hover/airdrop/landed logic local grounded = not self:IsUnitInAir(Unit) local hoverunload = self:IsCorrectHover(Unit) local isHerc = self:IsHercules(Unit) @@ -4373,64 +4423,51 @@ function CTLD:_UnloadSingleCrate(Group, Unit, CrateName) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end + if not self.debug then return self end return self end - local unitName = Unit:GetName() + + -- Get the first crate from this set + local crateObj = chunk[1] + if not crateObj then + self:_SendMessage("No crate found in that set!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Perform the actual "drop" spawn + local needed = crateObj:GetCratesNeeded() or 1 + self:_GetCrates(Group, Unit, crateObj, #chunk, true) + + -- Mark all crates in the chunk as dropped + for _, cObj in ipairs(chunk) do + cObj:SetWasDropped(true) + cObj:SetHasMoved(true) + end + + -- Rebuild the cargo list to remove the dropped crates local loadedData = self.Loaded_Cargo[unitName] - if not loadedData or not loadedData.Cargo then - self:_SendMessage("Nothing loaded!", 10, false, Group) - return self - end - local cargoList = loadedData.Cargo - local needed = 0 - for _, cObj in ipairs(cargoList) do - if cObj:GetName() == CrateName and not cObj:WasDropped() then - needed = cObj:GetCratesNeeded() or 1 - break - end - end - if needed < 1 then - self:_SendMessage(string.format("No %s crate found or already dropped!", CrateName), 10, false, Group) - return self - end - local matched = {} - for _, cObj in ipairs(cargoList) do - local t = cObj:GetType() - if t ~= CTLD_CARGO.Enum.TROOPS - and t ~= CTLD_CARGO.Enum.ENGINEERS - and t ~= CTLD_CARGO.Enum.GCLOADABLE - and (not cObj:WasDropped() or self.allowcratepickupagain) - and cObj:GetName() == CrateName - then - table.insert(matched, cObj) - end - end - local crateToUse = matched[1] - self:_GetCrates(Group, Unit, crateToUse, needed, true) - local used = 0 - for _, cObj in ipairs(matched) do - if used < needed then - used = used + 1 - cObj:SetWasDropped(true) - cObj:SetHasMoved(true) - end - end - local newList = {} - local newCratesCount = 0 - for _, cObj in ipairs(cargoList) do - if not cObj:WasDropped() then - table.insert(newList, cObj) - local ct = cObj:GetType() - if ct ~= CTLD_CARGO.Enum.TROOPS and ct ~= CTLD_CARGO.Enum.ENGINEERS then - newCratesCount = newCratesCount + 1 + if loadedData and loadedData.Cargo then + local newList = {} + local newCratesCount = 0 + for _, cObj in ipairs(loadedData.Cargo) do + if not cObj:WasDropped() then + table.insert(newList, cObj) + local ct = cObj:GetType() + if ct ~= CTLD_CARGO.Enum.TROOPS and ct ~= CTLD_CARGO.Enum.ENGINEERS then + newCratesCount = newCratesCount + 1 + end end end + loadedData.Cargo = newList + loadedData.Cratesloaded = newCratesCount + self.Loaded_Cargo[unitName] = loadedData end - loadedData.Cargo = newList - loadedData.Cratesloaded = newCratesCount - self.Loaded_Cargo[unitName] = loadedData + + -- Update cargo mass, refresh menu self:_UpdateUnitCargoMass(Unit) - self:_RefreshDropCratesMenu(Group,Unit) + self:_RefreshDropCratesMenu(Group, Unit) + return self end @@ -4445,29 +4482,67 @@ function CTLD:_RefreshDropCratesMenu(Group, Unit) if not theGroup.CTLDTopmenu then return end local topCrates = theGroup.MyTopCratesMenu if not topCrates then return end - if topCrates.DropCratesMenu then topCrates.DropCratesMenu:Remove() end + if topCrates.DropCratesMenu then + topCrates.DropCratesMenu:Remove() + end local dropCratesMenu = MENU_GROUP:New(theGroup, "Drop Crates", topCrates) topCrates.DropCratesMenu = dropCratesMenu MENU_GROUP_COMMAND:New(theGroup, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, theGroup, theUnit) + local loadedData = self.Loaded_Cargo[Unit:GetName()] - if loadedData and loadedData.Cargo then - local cargoByName = {} - for _, cargoObj in pairs(loadedData.Cargo) do - if cargoObj and not cargoObj:WasDropped() then - local ctype = cargoObj:GetType() - if ctype ~= CTLD_CARGO.Enum.TROOPS and ctype ~= CTLD_CARGO.Enum.ENGINEERS and ctype ~= CTLD_CARGO.Enum.GCLOADABLE then - local cName = cargoObj:GetName() - local needed = cargoObj:GetCratesNeeded() or 1 - if not cargoByName[cName] then - cargoByName[cName] = {count = 0, needed = needed} - end - cargoByName[cName].count = cargoByName[cName].count + 1 - end + if not loadedData or not loadedData.Cargo then return end + + local cargoByName = {} + for _, cObj in ipairs(loadedData.Cargo) do + if cObj and not cObj:WasDropped() then + local cType = cObj:GetType() + if cType ~= CTLD_CARGO.Enum.TROOPS and cType ~= CTLD_CARGO.Enum.ENGINEERS and cType ~= CTLD_CARGO.Enum.GCLOADABLE then + local name = cObj:GetName() or "Unknown" + cargoByName[name] = cargoByName[name] or {} + table.insert(cargoByName[name], cObj) end end - for name, info in pairs(cargoByName) do - local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) - MENU_GROUP_COMMAND:New(theGroup, line, dropCratesMenu, self._UnloadSingleCrate, self, theGroup, theUnit, name) + end + + self.CrateGroupList = self.CrateGroupList or {} + self.CrateGroupList[Unit:GetName()] = {} + + -- A single global line index for ALL crate names + local lineIndex = 1 + + for cName, list in pairs(cargoByName) do + local needed = list[1]:GetCratesNeeded() or 1 + table.sort(list, function(a,b) return a:GetID() < b:GetID() end) + + local i = 1 + while i <= #list do + local left = (#list - i + 1) + if left >= needed then + local chunk = {} + for n = i, i + needed - 1 do + table.insert(chunk, list[n]) + end + -- Now label uses the global lineIndex and increments after each chunk + local label = string.format("%d. %s", lineIndex, cName) + table.insert(self.CrateGroupList[Unit:GetName()], chunk) + local setIndex = #self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(theGroup, label, dropCratesMenu, self._UnloadSingleCrateSet, self, theGroup, theUnit, setIndex) + + i = i + needed + else + local chunk = {} + for n = i, #list do + table.insert(chunk, list[n]) + end + local label = string.format("%d. %s %d/%d", lineIndex, cName, left, needed) + table.insert(self.CrateGroupList[Unit:GetName()], chunk) + local setIndex = #self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(theGroup, label, dropCratesMenu, self._UnloadSingleCrateSet, self, theGroup, theUnit, setIndex) + + i = #list + 1 + end + + lineIndex = lineIndex + 1 end end end @@ -4476,21 +4551,12 @@ end -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. -- @param Wrapper.Unit#UNIT Unit The calling unit. --- @param #number cargoId the Cargo ID +-- @param #number chunkID the Cargo ID -- @return #CTLD self -function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) - self:T(self.lid .. " _UnloadSingleTroopByID for cargo ID " .. tostring(cargoID)) +function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) + self:T(self.lid .. " _UnloadSingleTroopByID chunkID=" .. tostring(chunkID)) - -- check if we are in LOAD zone local droppingatbase = false - local canunload = true - - -- Door check - if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) - if not self.debug then return self end - end - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.LOAD) if not inzone then inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP) @@ -4499,120 +4565,116 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) droppingatbase = true end - -- check for hover unload - local hoverunload = self:IsCorrectHover(Unit) -- if true we\'re hovering in parameters - local IsHerc = self:IsHercules(Unit) - local IsHook = self:IsHook(Unit) - if IsHerc and (not IsHook) then - -- no hover but airdrop here - hoverunload = self:IsCorrectFlightParameters(Unit) + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) + if not self.debug then return self end end - -- check if we\'re landed + local hoverunload = self:IsCorrectHover(Unit) + local isHerc = self:IsHercules(Unit) + local isHook = self:IsHook(Unit) + if isHerc and not isHook then + hoverunload = self:IsCorrectFlightParameters(Unit) + end local grounded = not self:IsUnitInAir(Unit) - -- Get what we have loaded - local unitname = Unit:GetName() + local unitName = Unit:GetName() - if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if self.Loaded_Cargo[unitName] and (grounded or hoverunload) then if not droppingatbase or self.debug then - - ------------------------------------------------------------------------ - -- (NEW CODE FOR SINGLE DROP) - -- Instead of dropping ALL troop cargo, we only drop the one matching cargoID. - ------------------------------------------------------------------------ - local loadedCargoData = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo - local cargoList = loadedCargoData.Cargo or {} - - local foundCargo = nil - for _, cargoObj in ipairs(cargoList) do - if (cargoObj:GetType() == CTLD_CARGO.Enum.TROOPS or cargoObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) - and not cargoObj:WasDropped() - and (cargoObj:GetID() == cargoID) - then - foundCargo = cargoObj - break - end + if not self.TroopsIDToChunk or not self.TroopsIDToChunk[chunkID] then + self:_SendMessage(string.format("No troop cargo chunk found for ID %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self end - if foundCargo then - local cType = foundCargo:GetType() - local name = foundCargo:GetName() or "none" - local temptable = foundCargo:GetTemplates() or {} - local zoneradius = self.troopdropzoneradius or 100 -- drop zone radius - local factor = 1 - if IsHerc then - factor = foundCargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping - zoneradius = Unit:GetVelocityMPS() or 100 + local chunk = self.TroopsIDToChunk[chunkID] + if not chunk or #chunk == 0 then + self:_SendMessage(string.format("Troop chunk is empty for ID %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self + end + + -- Drop ONLY the FIRST cargo in that chunk + local foundCargo = chunk[1] + if not foundCargo then + self:_SendMessage(string.format("No troop cargo at chunk %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self + end + + local cType = foundCargo:GetType() + local name = foundCargo:GetName() or "none" + local tmpl = foundCargo:GetTemplates() or {} + local zoneradius = self.troopdropzoneradius or 100 + local factor = 1 + if isHerc then + factor = foundCargo:GetCratesNeeded() or 1 + 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) + local heading = Group:GetHeading() or 0 + + if grounded or hoverunload then + randomcoord = Group:GetCoordinate() + local Angle = (heading + 270) % 360 + if isHerc or isHook then + Angle = (heading + 180) % 360 end - local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname), Group, zoneradius * factor) - local randomcoord = zone:GetRandomCoordinate(10, 30 * factor) - local heading = Group:GetHeading() or 0 - -- Spawn troops left from us, closer when hovering, further off when landed - if hoverunload or grounded then - randomcoord = Group:GetCoordinate() - -- slightly left from us - local Angle = (heading + 270) % 360 - if IsHerc or IsHook then Angle = (heading + 180) % 360 end - local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround - if IsHerc then offset = self.TroopUnloadDistGroundHerc or 25 end - if IsHook then - offset = self.TroopUnloadDistGroundHook or 15 - if hoverunload and self.TroopUnloadDistHoverHook then - offset = self.TroopUnloadDistHoverHook or 5 - end + local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround + if isHerc then + offset = self.TroopUnloadDistGroundHerc or 25 + end + if isHook then + offset = self.TroopUnloadDistGroundHook or 15 + if hoverunload and self.TroopUnloadDistHoverHook then + offset = self.TroopUnloadDistHoverHook or 5 end - randomcoord:Translate(offset, Angle, nil, true) end + randomcoord:Translate(offset, Angle, nil, true) + end - local tempcount = 0 - if IsHook then tempcount = self.ChinookTroopCircleRadius or 5 end -- 10m circle for the Chinook - for _, _template in pairs(temptable) do - self.TroopCounter = self.TroopCounter + 1 - tempcount = tempcount + 1 - local alias = string.format("%s-%d", _template, math.random(1,100000)) - local rad = 2.5 + (tempcount * 2) - local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template) - - self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) - :InitDelayOff() - :InitSetUnitAbsolutePositions(Positions) - :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) - :SpawnFromVec2(randomcoord:GetVec2()) - - self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) - end - foundCargo:SetWasDropped(true) - if cType == 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 + local tempcount = 0 + if isHook then + tempcount = self.ChinookTroopCircleRadius or 5 + end + for _, _template in pairs(tmpl) do + self.TroopCounter = self.TroopCounter + 1 + tempcount = tempcount + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + local rad = 2.5 + (tempcount * 2) + local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) + :InitDelayOff() + :InitSetUnitAbsolutePositions(Positions) + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) + :SpawnFromVec2(randomcoord:GetVec2()) + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) + end + + foundCargo:SetWasDropped(true) + if cType == CTLD_CARGO.Enum.ENGINEERS then + self.Engineers = self.Engineers + 1 + self:_SendMessage(string.format("Dropped Engineers %s into action!", name), 10, false, Group) else - -- We did not find any troop cargo with that ID - self:_SendMessage(string.format("No troop cargo with ID %d found or already dropped!", cargoID), 10, false, Group) + self:_SendMessage(string.format("Dropped Troops %s into action!", name), 10, false, Group) + end + + table.remove(chunk, 1) + if #chunk == 0 then + self.TroopsIDToChunk[chunkID] = nil end else - -- droppingatbase logic - self:_SendMessage("Troops have returned to base!", 10, false, Group) + -- Return to base logic, remove ONLY the first cargo + self:_SendMessage("Troops have returned to base!", 10, false, Group) self:__TroopsRTB(1, Group, Unit, zonename, zone) - -------------------------------------------------------------------- - -- (NEW CODE FOR SINGLE DROP AT BASE) - -- If you want to return only the single cargo item with cargoID to stock - -- instead of returning all, you can do something similar here: - -------------------------------------------------------------------- - local loadedCargoData = self.Loaded_Cargo[unitname] or {} - local cargoList = loadedCargoData.Cargo or {} - for _, cObj in ipairs(cargoList) do - if (cObj:GetType() == CTLD_CARGO.Enum.TROOPS or cObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) - and (cObj:GetID() == cargoID) - then - -- Return this one cargo to stock - local cName = cObj:GetName() + if self.TroopsIDToChunk and self.TroopsIDToChunk[chunkID] then + local chunk = self.TroopsIDToChunk[chunkID] + if #chunk > 0 then + local firstObj = chunk[1] + local cName = firstObj:GetName() local gentroops = self.Cargo_Troops for _id, _troop in pairs(gentroops) do if _troop.Name == cName then @@ -4622,53 +4684,42 @@ function CTLD:_UnloadSingleTroopByID(Group, Unit, cargoID) end end end - -- Mark it as dropped so we remove it from the loaded cargo - cObj:SetWasDropped(true) + firstObj:SetWasDropped(true) + table.remove(chunk, 1) + if #chunk == 0 then + self.TroopsIDToChunk[chunkID] = nil + end end end end - ------------------------------------------------------------------------ - -- cleanup load list - ------------------------------------------------------------------------ - local cargoList = self.Loaded_Cargo[unitname].Cargo - - -- 1) Remove all dropped cargo (iterate backward for table.remove) + local cargoList = self.Loaded_Cargo[unitName].Cargo for i = #cargoList, 1, -1 do if cargoList[i]:WasDropped() then table.remove(cargoList, i) end end - - -- 2) Recount local troopsLoaded = 0 local cratesLoaded = 0 for _, cargo in ipairs(cargoList) do - local cType = cargo:GetType() - if cType == CTLD_CARGO.Enum.TROOPS or cType == CTLD_CARGO.Enum.ENGINEERS then - -- If each cargo item represents just 1 group (or “1 load of troops”): + local cT = cargo:GetType() + if cT == CTLD_CARGO.Enum.TROOPS or cT == CTLD_CARGO.Enum.ENGINEERS then troopsLoaded = troopsLoaded + 1 - -- If you track “troops loaded” by `CratesNeeded()`, - -- then do: troopsLoaded = troopsLoaded + cargo:GetCratesNeeded() else cratesLoaded = cratesLoaded + 1 end end - - self.Loaded_Cargo[unitname].Troopsloaded = troopsLoaded - self.Loaded_Cargo[unitname].Cratesloaded = cratesLoaded - + self.Loaded_Cargo[unitName].Troopsloaded = troopsLoaded + self.Loaded_Cargo[unitName].Cratesloaded = cratesLoaded self:_RefreshDropTroopsMenu(Group, Unit) - - else - if IsHerc then + local isHerc = self:IsHercules(Unit) + if isHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) end end - return self end @@ -4678,31 +4729,46 @@ end -- @param Wrapper.Unit#UNIT Unit The requesting unit. -- @return #CTLD self function CTLD:_RefreshDropTroopsMenu(Group, Unit) - local theGroup=Group - local theUnit=Unit + local theGroup = Group + local theUnit = Unit if not theGroup.CTLDTopmenu then return end - local topTroops=theGroup.MyTopTroopsMenu + local topTroops = theGroup.MyTopTroopsMenu if not topTroops then return end - if topTroops.DropTroopsMenu then topTroops.DropTroopsMenu:Remove() end - local dropTroopsMenu=MENU_GROUP:New(theGroup,"Drop Troops",topTroops) - topTroops.DropTroopsMenu=dropTroopsMenu - MENU_GROUP_COMMAND:New(theGroup,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,theGroup,theUnit) - local loadedData=self.Loaded_Cargo[theUnit:GetName()] - if loadedData and loadedData.Cargo then - for i,cargoObj in ipairs(loadedData.Cargo) do - if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) - and not cargoObj:WasDropped() - then - local name=cargoObj:GetName()or"Unknown" - local size=cargoObj:GetCratesNeeded()or 1 - local cID=cargoObj:GetID() - local index = i - local line = string.format("Drop: %s (#%d)", name, index) - MENU_GROUP_COMMAND:New(theGroup,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,theGroup,theUnit,cID) - end + if topTroops.DropTroopsMenu then + topTroops.DropTroopsMenu:Remove() + end + local dropTroopsMenu = MENU_GROUP:New(theGroup, "Drop Troops", topTroops) + topTroops.DropTroopsMenu = dropTroopsMenu + MENU_GROUP_COMMAND:New(theGroup, "Drop ALL troops", dropTroopsMenu, self._UnloadTroops, self, theGroup, theUnit) + + local loadedData = self.Loaded_Cargo[theUnit:GetName()] + if not loadedData or not loadedData.Cargo then return end + + -- Gather troop cargo by name + local troopsByName = {} + for _, cargoObj in ipairs(loadedData.Cargo) do + if cargoObj + and (cargoObj:GetType() == CTLD_CARGO.Enum.TROOPS or cargoObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) + and not cargoObj:WasDropped() + then + local name = cargoObj:GetName() or "Unknown" + troopsByName[name] = troopsByName[name] or {} + table.insert(troopsByName[name], cargoObj) end end - return self + + self.TroopsIDToChunk = self.TroopsIDToChunk or {} + + for tName, objList in pairs(troopsByName) do + table.sort(objList, function(a,b) return a:GetID() < b:GetID() end) + local count = #objList + + local chunkID = objList[1]:GetID() + self.TroopsIDToChunk[chunkID] = objList + + local label = string.format("Drop %s (%d)", tName, count) + MENU_GROUP_COMMAND:New(theGroup, label, dropTroopsMenu, self._UnloadSingleTroopByID, self, theGroup, theUnit, chunkID) + end end --- [Internal] Function to check if a template exists in the mission. From f44db275651be4898705dbb9eb8756431b3403ed Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 10 Feb 2025 18:02:42 +0100 Subject: [PATCH 2/5] #ATC_GROUND_UNIVERSAL * Correct usage of airbase names if given * Exclude FARPs and Ships --- .../Moose/Functional/ATC_Ground.lua | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 4703559b9..9456fa270 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -18,7 +18,7 @@ -- ### Author: FlightControl - Framework Design & Programming -- ### Refactoring to use the Runway auto-detection: Applevangelist -- @date August 2022 --- Last Update Oct 2024 +-- Last Update Feb 2025 -- -- === -- @@ -416,7 +416,7 @@ end -- @field #ATC_GROUND_UNIVERSAL ATC_GROUND_UNIVERSAL = { ClassName = "ATC_GROUND_UNIVERSAL", - Version = "0.0.1", + Version = "0.0.2", SetClient = nil, Airbases = nil, AirbaseList = nil, @@ -441,17 +441,25 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) self:T( { self.ClassName } ) self.Airbases = {} - - for _name,_ in pairs(_DATABASE.AIRBASES) do - self.Airbases[_name]={} - end self.AirbaseList = AirbaseList if not self.AirbaseList then self.AirbaseList = {} - for _name,_ in pairs(_DATABASE.AIRBASES) do - self.AirbaseList[_name]=_name + for _name,_base in pairs(_DATABASE.AIRBASES) do + -- DONE exclude FARPS and Ships + if _base and _base.isAirdrome == true then + self.AirbaseList[_name]=_name + self.Airbases[_name]={} + end + end + else + for _,_name in pairs(AirbaseList) do + -- DONE exclude FARPS and Ships + local airbase = _DATABASE:FindAirbase(_name) + if airbase and airbase.isAirdrome == true then + self.Airbases[_name]={} + end end end @@ -1447,11 +1455,10 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end - - -- @type ATC_GROUND_MARIANAISLANDS +--- +-- @type ATC_GROUND_MARIANAISLANDS -- @extends #ATC_GROUND - --- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND} -- From 31aa604fc40c159c9d02cc08b0fd5d6676fbc913 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:20:23 +0100 Subject: [PATCH 3/5] Update CTLD.lua Added _LoadSingleCrateSet Function Added _refreshLoadCratesMenu Function Added Event for Takeoff, Land. Changes to the RefreshF10menus function the menus does not gets deleted, and rebuild, but only what is inside them. So they will remain at the same position. tested multiple times --- Moose Development/Moose/Ops/CTLD.lua | 354 ++++++++++++++++++++------- 1 file changed, 268 insertions(+), 86 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index fe760cc83..876f440fc 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -2021,6 +2021,13 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end return + elseif event.id == EVENTS.Land or event.id == EVENTS.Takeoff then + local unitname = event.IniUnitName + if self.CtldUnits[unitname] then + local _group = event.IniGroup + local _unit = event.IniUnit + self:_RefreshLoadCratesMenu(_group, _unit) + end elseif event.id == EVENTS.PlayerLeaveUnit or event.id == EVENTS.UnitLost then -- remove from pilot table local unitname = event.IniUnitName or "none" @@ -2845,7 +2852,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) text = string.format("Crates for %s have been dropped!",cratename) self:__CratesDropped(1, Group, Unit, droppedcargo) end - self:_SendMessage(text, 10, false, Group) + self:_SendMessage(text, 10, false, Group) + self:_RefreshLoadCratesMenu(Group, Unit) + return self end @@ -2973,31 +2982,32 @@ end -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self -function CTLD:_RemoveCratesNearby( _group, _unit) - self:T(self.lid .. " _RemoveCratesNearby") - local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(_group,_unit, finddist,true,true) -- #table - if number > 0 then - local text = REPORT:New("Removing Crates Found Nearby:") +function CTLD:_RemoveCratesNearby(_group, _unit) + self:T(self.lid.." _RemoveCratesNearby") + local finddist=self.CrateDistance or 35 + local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true,true) + if number>0 then + local removedIDs={} + local text=REPORT:New("Removing 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("Crate for %s, %dkg removed",name, entry.PerCrateMass)) - else - text:Add(string.format("Crate for %s, %dkg removed",name, entry.PerCrateMass)) + for _,_entry in pairs(crates)do + local entry=_entry + local name=entry:GetName()or"none" + text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) + if entry:GetPositionable()then + entry:GetPositionable():Destroy(false) end - entry:GetPositionable():Destroy(false) + table.insert(removedIDs,entry:GetID()) end - if text:GetCount() == 1 then - text:Add(" N O N E") + if text:GetCount()==1 then + text:Add(" N O N E") end text:Add("------------------------------------------------------------") - self:_SendMessage(text:Text(), 30, true, _group) + self:_SendMessage(text:Text(),30,true,_group) + self:_CleanupTrackedCrates(removedIDs) + self:_RefreshLoadCratesMenu(_group,_unit) else - self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end return self end @@ -3212,6 +3222,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group, Unit) + self:_RefreshLoadCratesMenu(Group, Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) end @@ -3900,6 +3911,7 @@ function CTLD:_PackCratesNearby(Group, Unit) if (_entry.Templates[1] == _Template.GroupName) then -- check if the #CTLD_CARGO matches the template name _Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player + self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu return self end end @@ -4235,17 +4247,10 @@ function CTLD:_RefreshF10Menus() end if cancrates then local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) - local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates) _group.MyTopCratesMenu = topcrates - -- Show "Load crates" on 1 line - MENU_GROUP_COMMAND:New(_group, "Load crates", topcrates, self._LoadCratesNearby, self, _group, _unit) - - MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) - local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) - MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) - -- Build the “Get Crates” sub-menu items + local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates) if self.usesubcats then local subcatmenus = {} for catName, _ in pairs(self.subcats) do @@ -4290,15 +4295,24 @@ function CTLD:_RefreshF10Menus() end end - MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) - + local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) + _group.MyLoadCratesMenu=loadCratesMenu + MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) + + local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates) + topcrates.DropCratesMenu=dropCratesMenu + if not self.nobuildmenu then - MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() end - -- Drop Crates sub-menu - local dropCratesMenu = MENU_GROUP:New(_group, "Drop Crates", topcrates) - MENU_GROUP_COMMAND:New(_group, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, _group, _unit) + + local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) + MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) + + MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) local uName = _unit:GetName() local loadedData = self.Loaded_Cargo[uName] @@ -4317,10 +4331,10 @@ function CTLD:_RefreshF10Menus() MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) end end - - end + + ----------------------------------------------------- -- Misc sub‐menus ----------------------------------------------------- @@ -4349,6 +4363,8 @@ function CTLD:_RefreshF10Menus() -- Mark we built the menu self.MenusDone[_unitName] = true + self:_RefreshLoadCratesMenu(_group, _unit) + self:_RefreshDropCratesMenu(_group,_unit) end -- if _group end -- if _unit @@ -4360,6 +4376,168 @@ function CTLD:_RefreshF10Menus() return self end +--- (Internal) Function to refresh the menu for load crates. Triggered from land/getcrate/pack and more +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @return #CTLD self +function CTLD:_RefreshLoadCratesMenu(Group, Unit) + if not Group.MyLoadCratesMenu then return end + Group.MyLoadCratesMenu:RemoveSubMenus() + + local d = self.CrateDistance or 35 + local nearby, n = self:_FindCratesNearby(Group, Unit, d, true, true) + if n == 0 then + MENU_GROUP_COMMAND:New(Group, "No crates found! Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) + return + end + MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) + MENU_GROUP_COMMAND:New(Group, "Load ALL", Group.MyLoadCratesMenu, self._LoadCratesNearby, self, Group, Unit) + local cargoByName = {} + for _, crate in pairs(nearby) do + local cName = crate:GetName() + cargoByName[cName] = cargoByName[cName] or {} + table.insert(cargoByName[cName], crate) + end + + for cName, cList in pairs(cargoByName) do + local needed = cList[1]:GetCratesNeeded() or 1 + local found = #cList + + local line + if found >= needed then + line = string.format("Load %s", cName) + else + line = string.format("Load %s (%d/%d)", cName, found, needed) + end + MENU_GROUP_COMMAND:New(Group, line, Group.MyLoadCratesMenu, self._LoadSingleCrateSet, self, Group, Unit, cName) + end +end + +--- +-- Loads exactly `CratesNeeded` crates for one cargoName in range. +-- If "Ammo Truck" needs 2 crates, we pick up 2 if available. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #string cargoName The cargo name, e.g. "Ammo Truck" +function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName) + self:T(self.lid .. " _LoadSingleCrateSet cargoName=" .. (cargoName or "nil")) + + -- 1) Must be landed or hovering + local grounded = not self:IsUnitInAir(Unit) + local hover = self:CanHoverLoad(Unit) + if not grounded and not hover then + self:_SendMessage("You must land or hover to load crates!", 10, false, Group) + return self + end + + -- 2) Check door if required + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) + return self + end + + -- 3) Find crates with `cargoName` in range + local finddist = self.CrateDistance or 35 + local cratesNearby, number = self:_FindCratesNearby(Group, Unit, finddist, false, false) + if number == 0 then + self:_SendMessage("No crates found in range!", 10, false, Group) + return self + end + + local matchingCrates = {} + local needed = nil + for _, crateObj in pairs(cratesNearby) do + if crateObj:GetName() == cargoName then + needed = needed or crateObj:GetCratesNeeded() + table.insert(matchingCrates, crateObj) + end + end + if not needed then + self:_SendMessage(string.format("No \"%s\" crates found in range!", cargoName), 10, false, Group) + return self + end + + local found = #matchingCrates + + -- 4) Check capacity + local unitName = Unit:GetName() + local loadedData = self.Loaded_Cargo[unitName] or { Troopsloaded=0, Cratesloaded=0, Cargo={} } + local capabilities = self:_GetUnitCapabilities(Unit) + local capacity = capabilities.cratelimit or 0 + if loadedData.Cratesloaded >= capacity then + self:_SendMessage("No more capacity to load crates!", 10, false, Group) + return self + end + + -- decide how many we can actually load + local spaceLeft = capacity - loadedData.Cratesloaded + local toLoad = math.min(found, needed, spaceLeft) + if toLoad < 1 then + self:_SendMessage("Cannot load crates: either none found or no capacity left.", 10, false, Group) + return self + end + + -- 5) Load exactly `toLoad` crates + local crateIDsLoaded = {} + for i = 1, toLoad do + local crate = matchingCrates[i] + crate:SetHasMoved(true) + crate:SetWasDropped(false) + table.insert(loadedData.Cargo, crate) + loadedData.Cratesloaded = loadedData.Cratesloaded + 1 + local stObj = crate:GetPositionable() + if stObj and stObj:IsAlive() then + stObj:Destroy(false) + end + table.insert(crateIDsLoaded, crate:GetID()) + end + self.Loaded_Cargo[unitName] = loadedData + self:_UpdateUnitCargoMass(Unit) + + -- 6) Remove them from self.Spawned_Cargo + local newSpawned = {} + for _, cObj in ipairs(self.Spawned_Cargo) do + local keep = true + for i=1, toLoad do + if matchingCrates[i] and cObj:GetID() == matchingCrates[i]:GetID() then + keep = false + break + end + end + if keep then + table.insert(newSpawned, cObj) + end + end + self.Spawned_Cargo = newSpawned + + -- 7) Show final message, including a special note if capacity is now reached + local loadedHere = toLoad + if loadedHere < needed and loadedData.Cratesloaded >= capacity then + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!", loadedHere, needed, cargoName), 10, false, Group) + else + local fullSets = math.floor(loadedHere / needed) + local leftover = loadedHere % needed + if needed > 1 then + if fullSets > 0 and leftover == 0 then + self:_SendMessage(string.format("Loaded %d %s.", fullSets, cargoName), 10, false, Group) + elseif fullSets > 0 and leftover > 0 then + self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cargoName, leftover), 10, false, Group) + else + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cargoName), 15, false, Group) + end + else + self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cargoName), 10, false, Group) + end + end + + self:_RefreshLoadCratesMenu(Group, Unit) + self:_RefreshDropCratesMenu(Group, Unit) + return self +end + + --- (Internal) Function to unload a single crate -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The calling group. @@ -4467,7 +4645,7 @@ function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) -- Update cargo mass, refresh menu self:_UpdateUnitCargoMass(Unit) self:_RefreshDropCratesMenu(Group, Unit) - + self:_RefreshLoadCratesMenu(Group, Unit) return self end @@ -4477,72 +4655,74 @@ end -- @param Wrapper.Unit#UNIT Unit The calling unit. -- @return #CTLD self function CTLD:_RefreshDropCratesMenu(Group, Unit) - local theGroup = Group - local theUnit = Unit - if not theGroup.CTLDTopmenu then return end - local topCrates = theGroup.MyTopCratesMenu + if not Group.CTLDTopmenu then return end + local topCrates = Group.MyTopCratesMenu if not topCrates then return end if topCrates.DropCratesMenu then - topCrates.DropCratesMenu:Remove() + topCrates.DropCratesMenu:RemoveSubMenus() + else + topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) end - local dropCratesMenu = MENU_GROUP:New(theGroup, "Drop Crates", topCrates) - topCrates.DropCratesMenu = dropCratesMenu - MENU_GROUP_COMMAND:New(theGroup, "Drop ALL crates", dropCratesMenu, self._UnloadCrates, self, theGroup, theUnit) + local dropCratesMenu = topCrates.DropCratesMenu local loadedData = self.Loaded_Cargo[Unit:GetName()] - if not loadedData or not loadedData.Cargo then return end + if not loadedData or not loadedData.Cargo then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end - local cargoByName = {} - for _, cObj in ipairs(loadedData.Cargo) do + local cargoByName={} + local dropableCrates=0 + for _,cObj in ipairs(loadedData.Cargo) do if cObj and not cObj:WasDropped() then - local cType = cObj:GetType() - if cType ~= CTLD_CARGO.Enum.TROOPS and cType ~= CTLD_CARGO.Enum.ENGINEERS and cType ~= CTLD_CARGO.Enum.GCLOADABLE then - local name = cObj:GetName() or "Unknown" - cargoByName[name] = cargoByName[name] or {} - table.insert(cargoByName[name], cObj) + local cType=cObj:GetType() + if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then + local name=cObj:GetName()or"Unknown" + cargoByName[name]=cargoByName[name]or{} + table.insert(cargoByName[name],cObj) + dropableCrates=dropableCrates+1 end end end - self.CrateGroupList = self.CrateGroupList or {} - self.CrateGroupList[Unit:GetName()] = {} + if dropableCrates==0 then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end - -- A single global line index for ALL crate names - local lineIndex = 1 + MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) + self.CrateGroupList=self.CrateGroupList or{} + self.CrateGroupList[Unit:GetName()]={} - for cName, list in pairs(cargoByName) do - local needed = list[1]:GetCratesNeeded() or 1 - table.sort(list, function(a,b) return a:GetID() < b:GetID() end) - - local i = 1 - while i <= #list do - local left = (#list - i + 1) - if left >= needed then - local chunk = {} - for n = i, i + needed - 1 do - table.insert(chunk, list[n]) + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + local chunk={} + for n=i,i+needed-1 do + table.insert(chunk,list[n]) end - -- Now label uses the global lineIndex and increments after each chunk - local label = string.format("%d. %s", lineIndex, cName) - table.insert(self.CrateGroupList[Unit:GetName()], chunk) - local setIndex = #self.CrateGroupList[Unit:GetName()] - MENU_GROUP_COMMAND:New(theGroup, label, dropCratesMenu, self._UnloadSingleCrateSet, self, theGroup, theUnit, setIndex) - - i = i + needed + local label=string.format("%d. %s",lineIndex,cName) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=i+needed else - local chunk = {} - for n = i, #list do - table.insert(chunk, list[n]) + local chunk={} + for n=i,#list do + table.insert(chunk,list[n]) end - local label = string.format("%d. %s %d/%d", lineIndex, cName, left, needed) - table.insert(self.CrateGroupList[Unit:GetName()], chunk) - local setIndex = #self.CrateGroupList[Unit:GetName()] - MENU_GROUP_COMMAND:New(theGroup, label, dropCratesMenu, self._UnloadSingleCrateSet, self, theGroup, theUnit, setIndex) - - i = #list + 1 + local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=#list+1 end - - lineIndex = lineIndex + 1 + lineIndex=lineIndex+1 end end end @@ -6562,6 +6742,8 @@ end self:HandleEvent(EVENTS.DynamicCargoLoaded, self._EventHandler) self:HandleEvent(EVENTS.DynamicCargoUnloaded, self._EventHandler) self:HandleEvent(EVENTS.DynamicCargoRemoved, self._EventHandler) + self:HandleEvent(EVENTS.Land, self._EventHandler) + self:HandleEvent(EVENTS.Takeoff, self._EventHandler) self:__Status(-5) -- AutoSave From fd2d8a511912059d446aeae243aaba5ae7545502 Mon Sep 17 00:00:00 2001 From: leka1986 <83298840+leka1986@users.noreply.github.com> Date: Sun, 16 Feb 2025 17:16:05 +0100 Subject: [PATCH 4/5] Update CTLD.lua Now in the listcargo, (cargo onboard) I will display CargoName and a number / number instead of listing each crate, 5 lines for 5 crates. Now 1 line for each CargoName and 2/2 or 3/3, etc. "Rescan" have been moved to only show up if it detects lesser then what it's needed when loading. --- Moose Development/Moose/Ops/CTLD.lua | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 876f440fc..9870a9d4a 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3333,6 +3333,7 @@ function CTLD:_ListCargo(Group, Unit) local maxloadable = self:_GetMaxLoadableMass(Unit) local finddist = self.CrateDistance or 35 --local _,_,loadedgc,loadedno = self:_FindCratesNearby(Group,Unit,finddist,true) + if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 local no_crates = loadedcargo.Cratesloaded or 0 @@ -3346,7 +3347,7 @@ function CTLD:_ListCargo(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) + report:Add(string.format("Troop: %s size %d", cargo:GetName(), cargo:GetCratesNeeded())) end end if report:GetCount() == 4 then @@ -3355,18 +3356,25 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add(" -- CRATES --") local cratecount = 0 + local accumCrates = {} for _,_cargo in pairs(cargotable or {}) 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 type ~= CTLD_CARGO.Enum.GCLOADABLE) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add(string.format("Crate: %s size 1",cargo:GetName())) - cratecount = cratecount + 1 + local cName = cargo:GetName() + local needed = cargo:GetCratesNeeded() or 1 + accumCrates[cName] = accumCrates[cName] or {count=0, needed=needed} + accumCrates[cName].count = accumCrates[cName].count + 1 end if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then - report:Add(string.format("GC loaded Crate: %s size 1",cargo:GetName())) + report:Add(string.format("GC loaded Crate: %s size 1", cargo:GetName())) cratecount = cratecount + 1 end end + for cName, data in pairs(accumCrates) do + cratecount = cratecount + data.count + report:Add(string.format("Crate: %s %d/%d", cName, data.count, data.needed)) + end if cratecount == 0 then report:Add(" N O N E") end @@ -3387,13 +3395,12 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add("Total Mass: ".. loadedmass .. " kg. Loadable: "..maxloadable.." kg.") local text = report:Text() - self:_SendMessage(text, 30, true, Group) + self:_SendMessage(text, 30, true, Group) else - self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",trooplimit,cratelimit,maxloadable), 10, false, Group) + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs", trooplimit, cratelimit, maxloadable), 10, false, Group) end return self end - --- (Internal) Function to list loaded cargo. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -4391,7 +4398,6 @@ function CTLD:_RefreshLoadCratesMenu(Group, Unit) MENU_GROUP_COMMAND:New(Group, "No crates found! Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) return end - MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) MENU_GROUP_COMMAND:New(Group, "Load ALL", Group.MyLoadCratesMenu, self._LoadCratesNearby, self, Group, Unit) local cargoByName = {} for _, crate in pairs(nearby) do @@ -4408,6 +4414,7 @@ function CTLD:_RefreshLoadCratesMenu(Group, Unit) if found >= needed then line = string.format("Load %s", cName) else + MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) line = string.format("Load %s (%d/%d)", cName, found, needed) end MENU_GROUP_COMMAND:New(Group, line, Group.MyLoadCratesMenu, self._LoadSingleCrateSet, self, Group, Unit, cName) From a49bd23a2abe741b07b59ce1f1cc58fe8096e3fd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 17 Feb 2025 08:35:25 +0100 Subject: [PATCH 5/5] #ATC_GROUND --- Moose Development/Moose/Functional/ATC_Ground.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 9456fa270..7d1669d51 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -457,7 +457,7 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) for _,_name in pairs(AirbaseList) do -- DONE exclude FARPS and Ships local airbase = _DATABASE:FindAirbase(_name) - if airbase and airbase.isAirdrome == true then + if airbase and (airbase.isAirdrome == true) then self.Airbases[_name]={} end end