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 diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 067d28f37..9870a9d4a 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" @@ -2355,7 +2362,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 +2596,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() @@ -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 @@ -3099,23 +3109,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 +3131,98 @@ 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) + self:_RefreshLoadCratesMenu(Group, Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) end @@ -3201,6 +3230,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 @@ -3303,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 @@ -3316,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 @@ -3325,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 @@ -3357,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 @@ -3858,6 +3895,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") ----------------------------------------- @@ -3880,6 +3918,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 @@ -4215,17 +4254,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 @@ -4270,15 +4302,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] @@ -4294,13 +4335,13 @@ 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 - - end + + ----------------------------------------------------- -- Misc sub‐menus ----------------------------------------------------- @@ -4329,6 +4370,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 @@ -4340,26 +4383,218 @@ 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, "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 + 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) + 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. -- @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 +4608,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) + self:_RefreshLoadCratesMenu(Group, Unit) return self end @@ -4440,34 +4662,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() 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) + if topCrates.DropCratesMenu then + topCrates.DropCratesMenu:RemoveSubMenus() + else + topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) + end + + local dropCratesMenu = topCrates.DropCratesMenu 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 + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end + + 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) + dropableCrates=dropableCrates+1 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 + + if dropableCrates==0 then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end + + MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) + self.CrateGroupList=self.CrateGroupList or{} + self.CrateGroupList[Unit:GetName()]={} + + 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 + 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]) + 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(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=#list+1 + end + lineIndex=lineIndex+1 end end end @@ -4476,21 +4738,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 +4752,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 +4871,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 +4916,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. @@ -6496,6 +6749,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