mirror of
https://github.com/mehah/otclient
synced 2026-08-15 16:29:06 -04:00
1173 lines
49 KiB
Lua
1173 lines
49 KiB
Lua
containerSettings = nil
|
|
|
|
function init()
|
|
g_ui.importStyle('container')
|
|
|
|
-- Initialize container settings
|
|
containerSettings = g_settings.getNode('containers')
|
|
if not containerSettings then
|
|
containerSettings = {}
|
|
-- Set useManualSortMode as default
|
|
containerSettings['useManualSortMode'] = 1
|
|
-- Set default sorting mode to none
|
|
containerSettings['currentSortMode'] = 'none'
|
|
-- Initialize sorting options
|
|
containerSettings['sortContainersFirst'] = 0
|
|
containerSettings['sortNestedContainers'] = 0
|
|
g_settings.setNode('containers', containerSettings)
|
|
end
|
|
|
|
-- Ensure all required settings exist
|
|
if containerSettings['sortNestedContainers'] == nil then
|
|
containerSettings['sortNestedContainers'] = 0
|
|
end
|
|
if containerSettings['sortContainersFirst'] == nil then
|
|
containerSettings['sortContainersFirst'] = 0
|
|
end
|
|
|
|
connect(Container, {
|
|
onOpen = onContainerOpen,
|
|
onClose = onContainerClose,
|
|
onSizeChange = onContainerChangeSize,
|
|
onUpdateItem = onContainerUpdateItem
|
|
})
|
|
connect(Game, {
|
|
onGameEnd = clean()
|
|
})
|
|
|
|
reloadContainers()
|
|
end
|
|
|
|
function terminate()
|
|
disconnect(Container, {
|
|
onOpen = onContainerOpen,
|
|
onClose = onContainerClose,
|
|
onSizeChange = onContainerChangeSize,
|
|
onUpdateItem = onContainerUpdateItem
|
|
})
|
|
disconnect(Game, {
|
|
onGameEnd = clean()
|
|
})
|
|
end
|
|
|
|
function reloadContainers()
|
|
clean()
|
|
for _, container in pairs(g_game.getContainers()) do
|
|
onContainerOpen(container)
|
|
end
|
|
end
|
|
|
|
function clean()
|
|
for containerid, container in pairs(g_game.getContainers()) do
|
|
destroy(container)
|
|
end
|
|
end
|
|
|
|
function destroy(container)
|
|
if container.window then
|
|
container.window:destroy()
|
|
container.window = nil
|
|
container.itemsPanel = nil
|
|
end
|
|
end
|
|
|
|
function showContainersContextMenu(widget, mousePos, mouseButton)
|
|
local menu = g_ui.createWidget('ContainersSubMenu')
|
|
if not menu then
|
|
return false
|
|
end
|
|
menu:setGameMenu(true)
|
|
for _, choice in ipairs(menu:getChildren()) do
|
|
local choiceId = choice:getId()
|
|
if choiceId and choiceId ~= 'HorizontalSeparator' then
|
|
local widgetClass = choice:getClassName()
|
|
local isSortingAction = choiceId:find('sortAsc') or choiceId:find('sortDesc')
|
|
local isActionButton = isSortingAction or choiceId == 'moveToObtainContainers'
|
|
if isActionButton then
|
|
choice.onClick = function()
|
|
onContainersMenuAction(choiceId)
|
|
menu:destroy()
|
|
end
|
|
choice.onMouseRelease = function(widget, mousePos, mouseButton)
|
|
if mouseButton == MouseLeftButton then
|
|
onContainersMenuAction(choiceId)
|
|
menu:destroy()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
if isSortingAction then
|
|
local currentSortMode = containerSettings and containerSettings['currentSortMode'] or 'none'
|
|
local isActive = (currentSortMode == choiceId)
|
|
local isManualSortEnabled = containerSettings and containerSettings['useManualSortMode'] == 1
|
|
if isManualSortEnabled then
|
|
choice:setEnabled(false)
|
|
choice:setColor('#808080')
|
|
else
|
|
choice:setEnabled(true)
|
|
if isActive then
|
|
choice:setColor('#ffff00')
|
|
else
|
|
choice:setColor('#ffffff')
|
|
end
|
|
end
|
|
else
|
|
choice:setEnabled(true)
|
|
choice:setColor('#ffffff')
|
|
end
|
|
else
|
|
local currentState = getContainerOptionState(choiceId)
|
|
choice:setChecked(currentState)
|
|
local isManualSortEnabled = containerSettings and containerSettings['useManualSortMode'] == 1
|
|
if isManualSortEnabled and (choiceId == 'sortContainersFirst' or choiceId == 'sortNestedContainers') then
|
|
choice:setEnabled(false)
|
|
choice:setColor('#808080')
|
|
else
|
|
choice:setEnabled(true)
|
|
choice:setColor('#ffffff')
|
|
end
|
|
choice.onCheckChange = function()
|
|
onContainersMenuAction(choiceId)
|
|
menu:destroy()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local buttonPos = widget:getPosition()
|
|
local buttonSize = widget:getSize()
|
|
local menuWidth = menu:getWidth()
|
|
local buttonCenterX = buttonPos.x + buttonSize.width / 2
|
|
local buttonCenterY = buttonPos.y + buttonSize.height / 2
|
|
local menuX = buttonCenterX - menuWidth
|
|
local menuY = buttonCenterY
|
|
menu:display({x = menuX, y = menuY})
|
|
return true
|
|
end
|
|
|
|
function getContainerOptionState(optionId)
|
|
-- Return the current state of container options
|
|
-- These are stored in settings similar to skills module
|
|
if not containerSettings then
|
|
return false
|
|
end
|
|
|
|
if optionId == 'sortContainersFirst' then
|
|
return containerSettings['sortContainersFirst'] == 1
|
|
elseif optionId == 'sortNestedContainers' then
|
|
return containerSettings['sortNestedContainers'] == 1
|
|
elseif optionId == 'useManualSortMode' then
|
|
return containerSettings['useManualSortMode'] == 1
|
|
elseif optionId == 'moveNestedContainers' then
|
|
return containerSettings['moveNestedContainers'] == 1
|
|
end
|
|
return false
|
|
end
|
|
|
|
function sortContainerItems(container, sortMode)
|
|
if not container or not container.itemsPanel then
|
|
return
|
|
end
|
|
|
|
-- Don't sort if manual sort mode is enabled
|
|
local isManualSortEnabled = containerSettings and containerSettings['useManualSortMode'] == 1
|
|
if isManualSortEnabled then
|
|
return
|
|
end
|
|
|
|
-- Check if Sort Nested Containers is enabled when manual sort mode is disabled
|
|
local sortNestedContainers = containerSettings and containerSettings['sortNestedContainers'] == 1
|
|
if sortNestedContainers then
|
|
end
|
|
|
|
-- Get all items with their slot information (only non-nil items)
|
|
local items = {}
|
|
local totalCapacity = container:getCapacity()
|
|
|
|
for slot = 0, totalCapacity - 1 do
|
|
local item = container:getItem(slot)
|
|
if item and item:getId() > 0 then -- Check for valid item
|
|
table.insert(items, {
|
|
slot = slot,
|
|
item = item,
|
|
widget = container.itemsPanel:getChildById('item' .. slot)
|
|
})
|
|
end
|
|
end
|
|
|
|
-- Only sort if we have items
|
|
if #items == 0 then
|
|
return
|
|
end
|
|
|
|
-- Check if Sort Containers First and Sort Nested Containers are enabled
|
|
local sortContainersFirst = containerSettings and containerSettings['sortContainersFirst'] == 1
|
|
local sortNestedContainers = containerSettings and containerSettings['sortNestedContainers'] == 1
|
|
|
|
-- Helper function to check if an item is a container
|
|
local function isContainer(item)
|
|
local success, result = pcall(function()
|
|
if item.isContainer and type(item.isContainer) == "function" then
|
|
return item:isContainer()
|
|
end
|
|
return false
|
|
end)
|
|
return success and result
|
|
end
|
|
|
|
-- Helper function to check if a container has items inside (is nested)
|
|
local function isNestedContainer(item)
|
|
if not isContainer(item) then
|
|
return false
|
|
end
|
|
|
|
local success, result = pcall(function()
|
|
-- Try to get the container's item count
|
|
if item.getItemsCount and type(item.getItemsCount) == "function" then
|
|
local itemCount = item:getItemsCount()
|
|
return itemCount and itemCount > 0
|
|
end
|
|
|
|
-- Alternative: Check if container has capacity and items
|
|
if item.getCapacity and type(item.getCapacity) == "function" then
|
|
local capacity = item:getCapacity()
|
|
if capacity and capacity > 0 then
|
|
-- Check if any slot has an item
|
|
for slot = 0, capacity - 1 do
|
|
if item.getItem and type(item.getItem) == "function" then
|
|
local slotItem = item:getItem(slot)
|
|
if slotItem and slotItem:getId() > 0 then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end)
|
|
|
|
if not success then
|
|
return false
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
-- Helper function for container-first sorting with nested container support
|
|
local function sortWithContainersFirst(items, compareFn)
|
|
local sortContainersFirst = containerSettings and containerSettings['sortContainersFirst'] == 1
|
|
local sortNestedContainers = containerSettings and containerSettings['sortNestedContainers'] == 1
|
|
|
|
-- Sort Nested Containers feature:
|
|
-- When enabled, containers that have items inside them are prioritized at the top,
|
|
-- followed by empty containers (if Sort Containers First is also enabled),
|
|
-- and finally non-container items.
|
|
--
|
|
-- Priority order when both options are enabled:
|
|
-- 1. Nested containers (containers with items inside) - HIGHEST PRIORITY
|
|
-- 2. Empty containers (only if Sort Containers First is enabled)
|
|
-- 3. Non-container items - LOWEST PRIORITY
|
|
--
|
|
-- This feature can work together with Sort Containers First but not with Manual Sort Mode.
|
|
|
|
-- If neither option is enabled, just sort normally
|
|
if not sortContainersFirst and not sortNestedContainers then
|
|
table.sort(items, compareFn)
|
|
return
|
|
end
|
|
|
|
-- Separate items into categories
|
|
local nestedContainers = {} -- Containers with items inside
|
|
local emptyContainers = {} -- Empty containers
|
|
local nonContainers = {} -- Non-container items
|
|
|
|
for _, itemData in ipairs(items) do
|
|
if isContainer(itemData.item) then
|
|
if sortNestedContainers and isNestedContainer(itemData.item) then
|
|
table.insert(nestedContainers, itemData)
|
|
else
|
|
table.insert(emptyContainers, itemData)
|
|
end
|
|
else
|
|
table.insert(nonContainers, itemData)
|
|
end
|
|
end
|
|
|
|
-- Sort each group separately
|
|
table.sort(nestedContainers, compareFn)
|
|
table.sort(emptyContainers, compareFn)
|
|
table.sort(nonContainers, compareFn)
|
|
|
|
-- Merge groups based on enabled options with proper priority order
|
|
local sortedItems = {}
|
|
|
|
-- Priority 1: Nested containers (if Sort Nested Containers is enabled)
|
|
if sortNestedContainers then
|
|
for _, itemData in ipairs(nestedContainers) do
|
|
table.insert(sortedItems, itemData)
|
|
end
|
|
end
|
|
|
|
-- Priority 2: Empty containers (if Sort Containers First is enabled)
|
|
-- Note: This comes after nested containers even if both options are enabled
|
|
if sortContainersFirst then
|
|
for _, itemData in ipairs(emptyContainers) do
|
|
table.insert(sortedItems, itemData)
|
|
end
|
|
|
|
-- Add nested containers here only if sortNestedContainers is not enabled
|
|
if not sortNestedContainers then
|
|
for _, itemData in ipairs(nestedContainers) do
|
|
table.insert(sortedItems, itemData)
|
|
end
|
|
end
|
|
else
|
|
-- If sortContainersFirst is disabled but sortNestedContainers is disabled too,
|
|
-- add all containers together after nested containers
|
|
if not sortNestedContainers then
|
|
for _, itemData in ipairs(nestedContainers) do
|
|
table.insert(sortedItems, itemData)
|
|
end
|
|
end
|
|
|
|
-- Add empty containers without priority
|
|
for _, itemData in ipairs(emptyContainers) do
|
|
table.insert(sortedItems, itemData)
|
|
end
|
|
end
|
|
|
|
-- Priority 3 (Last): Non-container items
|
|
for _, itemData in ipairs(nonContainers) do
|
|
table.insert(sortedItems, itemData)
|
|
end
|
|
|
|
-- Replace original items array
|
|
for i = 1, #items do
|
|
items[i] = sortedItems[i]
|
|
end
|
|
end
|
|
|
|
-- Sort the items based on the sort mode
|
|
if sortMode == 'sortAscByName' then
|
|
sortWithContainersFirst(items, function(a, b)
|
|
if not a or not a.item or not b or not b.item then
|
|
return false
|
|
end
|
|
|
|
local nameA = ""
|
|
local nameB = ""
|
|
|
|
-- Use pcall for extra safety and try getName from the item
|
|
local success, result = pcall(function()
|
|
if a.item.getName and type(a.item.getName) == "function" then
|
|
return a.item:getName() or ""
|
|
end
|
|
-- Try getting name from the item type if direct getName fails
|
|
local itemType = g_things.getThingType(a.item:getId(), ThingCategoryItem)
|
|
if itemType and itemType.getName and type(itemType.getName) == "function" then
|
|
return itemType:getName() or ""
|
|
end
|
|
return ""
|
|
end)
|
|
if success then nameA = result end
|
|
|
|
success, result = pcall(function()
|
|
if b.item.getName and type(b.item.getName) == "function" then
|
|
return b.item:getName() or ""
|
|
end
|
|
-- Try getting name from the item type if direct getName fails
|
|
local itemType = g_things.getThingType(b.item:getId(), ThingCategoryItem)
|
|
if itemType and itemType.getName and type(itemType.getName) == "function" then
|
|
return itemType:getName() or ""
|
|
end
|
|
return ""
|
|
end)
|
|
if success then nameB = result end
|
|
|
|
return nameA:lower() < nameB:lower()
|
|
end)
|
|
elseif sortMode == 'sortDescByName' then
|
|
sortWithContainersFirst(items, function(a, b)
|
|
if not a or not a.item or not b or not b.item then
|
|
return false
|
|
end
|
|
|
|
local nameA = ""
|
|
local nameB = ""
|
|
|
|
-- Use pcall for extra safety and try getName from the item
|
|
local success, result = pcall(function()
|
|
if a.item.getName and type(a.item.getName) == "function" then
|
|
return a.item:getName() or ""
|
|
end
|
|
-- Try getting name from the item type if direct getName fails
|
|
local itemType = g_things.getThingType(a.item:getId(), ThingCategoryItem)
|
|
if itemType and itemType.getName and type(itemType.getName) == "function" then
|
|
return itemType:getName() or ""
|
|
end
|
|
return ""
|
|
end)
|
|
if success then nameA = result end
|
|
|
|
success, result = pcall(function()
|
|
if b.item.getName and type(b.item.getName) == "function" then
|
|
return b.item:getName() or ""
|
|
end
|
|
-- Try getting name from the item type if direct getName fails
|
|
local itemType = g_things.getThingType(b.item:getId(), ThingCategoryItem)
|
|
if itemType and itemType.getName and type(itemType.getName) == "function" then
|
|
return itemType:getName() or ""
|
|
end
|
|
return ""
|
|
end)
|
|
if success then nameB = result end
|
|
|
|
return nameA:lower() > nameB:lower()
|
|
end)
|
|
elseif sortMode == 'sortAscByWeight' then
|
|
-- TODO: Implement functionality to retrieve item weights and sort accordingly
|
|
return
|
|
elseif sortMode == 'sortDescByWeight' then
|
|
-- TODO: Implement weight-based sorting (descending)
|
|
return
|
|
elseif sortMode == 'sortAscByExpiry' then
|
|
sortWithContainersFirst(items, function(a, b)
|
|
if not a or not a.item or not b or not b.item then return false end
|
|
|
|
local aExpiry = 0
|
|
local bExpiry = 0
|
|
|
|
-- Use getDurationTime() for expiry - this exists on Item objects
|
|
local success, result = pcall(function()
|
|
if a.item.getDurationTime and type(a.item.getDurationTime) == "function" then
|
|
return a.item:getDurationTime() or 0
|
|
end
|
|
return 0
|
|
end)
|
|
if success then aExpiry = result end
|
|
|
|
success, result = pcall(function()
|
|
if b.item.getDurationTime and type(b.item.getDurationTime) == "function" then
|
|
return b.item:getDurationTime() or 0
|
|
end
|
|
return 0
|
|
end)
|
|
if success then bExpiry = result end
|
|
|
|
return aExpiry < bExpiry
|
|
end)
|
|
elseif sortMode == 'sortDescByExpiry' then
|
|
sortWithContainersFirst(items, function(a, b)
|
|
if not a or not a.item or not b or not b.item then return false end
|
|
|
|
local aExpiry = 0
|
|
local bExpiry = 0
|
|
|
|
-- Use getDurationTime() for expiry - this exists on Item objects
|
|
local success, result = pcall(function()
|
|
if a.item.getDurationTime and type(a.item.getDurationTime) == "function" then
|
|
return a.item:getDurationTime() or 0
|
|
end
|
|
return 0
|
|
end)
|
|
if success then aExpiry = result end
|
|
|
|
success, result = pcall(function()
|
|
if b.item.getDurationTime and type(b.item.getDurationTime) == "function" then
|
|
return b.item:getDurationTime() or 0
|
|
end
|
|
return 0
|
|
end)
|
|
if success then bExpiry = result end
|
|
|
|
return aExpiry > bExpiry
|
|
end)
|
|
elseif sortMode == 'sortAscByStackSize' then
|
|
sortWithContainersFirst(items, function(a, b)
|
|
if not a or not a.item or not b or not b.item then return false end
|
|
|
|
local countA = 1
|
|
local countB = 1
|
|
local chargesA = 0
|
|
local chargesB = 0
|
|
|
|
-- Use getCount() - this exists on Item objects
|
|
local success, result = pcall(function()
|
|
if a.item.getCount and type(a.item.getCount) == "function" then
|
|
return a.item:getCount() or 1
|
|
end
|
|
return 1
|
|
end)
|
|
if success then countA = result end
|
|
|
|
success, result = pcall(function()
|
|
if b.item.getCount and type(b.item.getCount) == "function" then
|
|
return b.item:getCount() or 1
|
|
end
|
|
return 1
|
|
end)
|
|
if success then countB = result end
|
|
|
|
-- Get charges
|
|
success, result = pcall(function()
|
|
if a.item.getCharges and type(a.item.getCharges) == "function" then
|
|
return a.item:getCharges() or 0
|
|
end
|
|
return 0
|
|
end)
|
|
if success then chargesA = result end
|
|
|
|
success, result = pcall(function()
|
|
if b.item.getCharges and type(b.item.getCharges) == "function" then
|
|
return b.item:getCharges() or 0
|
|
end
|
|
return 0
|
|
end)
|
|
if success then chargesB = result end
|
|
|
|
-- Use charges if available, otherwise use count
|
|
local valueA = chargesA > 0 and chargesA or countA
|
|
local valueB = chargesB > 0 and chargesB or countB
|
|
|
|
return valueA < valueB
|
|
end)
|
|
elseif sortMode == 'sortDescByStackSize' then
|
|
sortWithContainersFirst(items, function(a, b)
|
|
if not a or not a.item or not b or not b.item then return false end
|
|
|
|
local countA = 1
|
|
local countB = 1
|
|
local chargesA = 0
|
|
local chargesB = 0
|
|
|
|
-- Use getCount() - this exists on Item objects
|
|
local success, result = pcall(function()
|
|
if a.item.getCount and type(a.item.getCount) == "function" then
|
|
return a.item:getCount() or 1
|
|
end
|
|
return 1
|
|
end)
|
|
if success then countA = result end
|
|
|
|
success, result = pcall(function()
|
|
if b.item.getCount and type(b.item.getCount) == "function" then
|
|
return b.item:getCount() or 1
|
|
end
|
|
return 1
|
|
end)
|
|
if success then countB = result end
|
|
|
|
-- Get charges
|
|
success, result = pcall(function()
|
|
if a.item.getCharges and type(a.item.getCharges) == "function" then
|
|
return a.item:getCharges() or 0
|
|
end
|
|
return 0
|
|
end)
|
|
if success then chargesA = result end
|
|
|
|
success, result = pcall(function()
|
|
if b.item.getCharges and type(b.item.getCharges) == "function" then
|
|
return b.item:getCharges() or 0
|
|
end
|
|
return 0
|
|
end)
|
|
if success then chargesB = result end
|
|
|
|
-- Use charges if available, otherwise use count
|
|
local valueA = chargesA > 0 and chargesA or countA
|
|
local valueB = chargesB > 0 and chargesB or countB
|
|
|
|
return valueA > valueB
|
|
end)
|
|
else
|
|
-- No sorting or unknown mode
|
|
return
|
|
end
|
|
|
|
-- Clear all item widgets first
|
|
for slot = 0, totalCapacity - 1 do
|
|
local itemWidget = container.itemsPanel:getChildById('item' .. slot)
|
|
if itemWidget then
|
|
itemWidget:setItem(nil)
|
|
end
|
|
end
|
|
|
|
-- Place sorted items back into the container display starting from slot 0
|
|
for i, itemData in ipairs(items) do
|
|
local targetSlot = i - 1 -- Convert to 0-based index
|
|
|
|
if targetSlot < totalCapacity then
|
|
local itemWidget = container.itemsPanel:getChildById('item' .. targetSlot)
|
|
if itemWidget then
|
|
-- Set the visual item (this doesn't change the actual container data)
|
|
itemWidget:setItem(itemData.item)
|
|
-- Preserve the original slot position for drag-and-drop operations
|
|
itemWidget.position = container:getSlotPosition(itemData.slot)
|
|
|
|
ItemsDatabase.setRarityItem(itemWidget, itemData.item)
|
|
ItemsDatabase.setTier(itemWidget, itemData.item)
|
|
itemWidget:setShowDuration(g_game.getFeature(GameThingClock) and modules.client_options.getOption('showExpiryInContainers'))
|
|
itemWidget:setShowCharges(g_game.getFeature(GameThingCounter) and modules.client_options.getOption('showExpiryInContainers'))
|
|
|
|
local itemName = "unnamed"
|
|
local success, result = pcall(function()
|
|
if itemData.item.getName and type(itemData.item.getName) == "function" then
|
|
return itemData.item:getName() or "unnamed"
|
|
end
|
|
-- Try getting name from the item type if direct getName fails
|
|
local itemType = g_things.getThingType(itemData.item:getId(), ThingCategoryItem)
|
|
if itemType and itemType.getName and type(itemType.getName) == "function" then
|
|
return itemType:getName() or "unnamed"
|
|
end
|
|
return "unnamed"
|
|
end)
|
|
if success then itemName = result end
|
|
else
|
|
end
|
|
else
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Function to determine quick loot category for an item
|
|
|
|
function onContainersMenuAction(actionId)
|
|
local isToggleOption = actionId == 'sortContainersFirst' or actionId == 'sortNestedContainers' or
|
|
actionId == 'useManualSortMode' or actionId == 'moveNestedContainers'
|
|
local isActionButton = actionId == 'moveToObtainContainers' or actionId:find('sortAsc') or actionId:find('sortDesc')
|
|
if isActionButton then
|
|
-- Handle action buttons (non-toggle actions)
|
|
if actionId == 'moveToObtainContainers' then
|
|
-- TODO: Need to create function to use quickloot sorting.
|
|
return
|
|
elseif actionId:find('sortAsc') or actionId:find('sortDesc') then
|
|
-- When a sorting action is selected, automatically disable manual sort mode
|
|
local isManualSortEnabled = containerSettings and containerSettings['useManualSortMode'] == 1
|
|
if isManualSortEnabled then
|
|
containerSettings['useManualSortMode'] = 0
|
|
end
|
|
|
|
-- Sorting actions - these are mutually exclusive
|
|
containerSettings['currentSortMode'] = actionId
|
|
g_settings.setNode('containers', containerSettings)
|
|
|
|
-- Apply sorting to all open containers
|
|
for _, container in pairs(g_game.getContainers()) do
|
|
if container.window and container.window:isVisible() then
|
|
sortContainerItems(container, actionId)
|
|
end
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
if isToggleOption then
|
|
-- Toggle options
|
|
local currentState = getContainerOptionState(actionId)
|
|
local newState = not currentState
|
|
|
|
-- Special handling for useManualSortMode
|
|
if actionId == 'useManualSortMode' then
|
|
if newState then
|
|
-- When enabling manual sort mode, clear any active sorting mode and disable other sorting options
|
|
containerSettings['currentSortMode'] = 'none'
|
|
containerSettings['sortContainersFirst'] = 0
|
|
containerSettings['sortNestedContainers'] = 0
|
|
else
|
|
-- When disabling manual sort mode, sorting options become available again
|
|
end
|
|
elseif actionId == 'sortContainersFirst' then
|
|
-- Prevent enabling if manual sort mode is active
|
|
local isManualSortEnabled = containerSettings and containerSettings['useManualSortMode'] == 1
|
|
if newState and isManualSortEnabled then
|
|
return -- Don't change the setting
|
|
end
|
|
|
|
-- When Sort Containers First is toggled, re-apply current sorting if active
|
|
local currentSortMode = containerSettings and containerSettings['currentSortMode']
|
|
if currentSortMode and currentSortMode ~= 'none' then
|
|
-- Apply sorting to all open containers after toggling
|
|
scheduleEvent(function()
|
|
for _, container in pairs(g_game.getContainers()) do
|
|
if container.window and container.window:isVisible() then
|
|
sortContainerItems(container, currentSortMode)
|
|
end
|
|
end
|
|
end, 50) -- Small delay to ensure setting is saved first
|
|
end
|
|
elseif actionId == 'sortNestedContainers' then
|
|
-- Prevent enabling if manual sort mode is active
|
|
local isManualSortEnabled = containerSettings and containerSettings['useManualSortMode'] == 1
|
|
if newState and isManualSortEnabled then
|
|
return -- Don't change the setting
|
|
end
|
|
|
|
-- When Sort Nested Containers is toggled, re-apply current sorting if active
|
|
local currentSortMode = containerSettings and containerSettings['currentSortMode']
|
|
if currentSortMode and currentSortMode ~= 'none' then
|
|
-- Apply sorting to all open containers after toggling
|
|
scheduleEvent(function()
|
|
for _, container in pairs(g_game.getContainers()) do
|
|
if container.window and container.window:isVisible() then
|
|
sortContainerItems(container, currentSortMode)
|
|
end
|
|
end
|
|
end, 50) -- Small delay to ensure setting is saved first
|
|
end
|
|
elseif actionId == 'moveNestedContainers' then
|
|
-- TODO: Implement functionality to automatically move nested containers (containers with items inside)
|
|
-- to specific locations or positions within containers. This feature should work in conjunction
|
|
-- with container sorting and could help organize containers by moving filled containers to
|
|
-- preferred positions (e.g., top of container, specific slots, etc.).
|
|
-- Implementation should consider:
|
|
-- 1. Identify containers that have items inside (nested containers)
|
|
-- 2. Define target positions/rules for where to move them
|
|
-- 3. Handle conflicts with existing sorting modes
|
|
-- 4. Respect manual sort mode settings
|
|
-- 5. Provide user feedback on move operations
|
|
|
|
-- For now, just toggle the setting - actual move functionality to be implemented
|
|
end
|
|
|
|
-- Save the new state to settings
|
|
containerSettings[actionId] = newState and 1 or 0
|
|
g_settings.setNode('containers', containerSettings)
|
|
end
|
|
end
|
|
|
|
function refreshContainerItems(container)
|
|
-- Check if we should preserve sorting during refresh
|
|
local currentSortMode = containerSettings and containerSettings['currentSortMode']
|
|
local isManualSortEnabled = containerSettings and containerSettings['useManualSortMode'] == 1
|
|
local shouldSort = currentSortMode and currentSortMode ~= 'none' and not isManualSortEnabled
|
|
|
|
for slot = 0, container:getCapacity() - 1 do
|
|
local itemWidget = container.itemsPanel:getChildById('item' .. slot)
|
|
itemWidget:setItem(container:getItem(slot))
|
|
ItemsDatabase.setRarityItem(itemWidget, container:getItem(slot))
|
|
ItemsDatabase.setTier(itemWidget, container:getItem(slot))
|
|
itemWidget:setShowDuration(g_game.getFeature(GameThingClock) and modules.client_options.getOption('showExpiryInContainers'))
|
|
itemWidget:setShowCharges(g_game.getFeature(GameThingCounter) and modules.client_options.getOption('showExpiryInContainers'))
|
|
end
|
|
|
|
if container:hasPages() then
|
|
refreshContainerPages(container)
|
|
end
|
|
|
|
-- Apply current sorting if one is set and manual sort mode is disabled
|
|
if shouldSort then
|
|
sortContainerItems(container, currentSortMode)
|
|
end
|
|
end
|
|
|
|
function toggleContainerPages(containerWindow, pages)
|
|
local scrollbar = containerWindow:getChildById('miniwindowScrollBar')
|
|
local pagePanel = containerWindow:getChildById('pagePanel')
|
|
local separator = containerWindow:getChildById('separator')
|
|
local contentsPanel = containerWindow:getChildById('contentsPanel')
|
|
local upButton = containerWindow:getChildById('upButton')
|
|
local contextMenuButton = containerWindow:recursiveGetChildById('contextMenuButton')
|
|
local lockButton = containerWindow:recursiveGetChildById('lockButton')
|
|
local minimizeButton = containerWindow:recursiveGetChildById('minimizeButton')
|
|
|
|
if pages then
|
|
-- When pages are visible, anchor scrollbar to close button bottom and separator top
|
|
scrollbar:breakAnchors()
|
|
scrollbar:addAnchor(AnchorTop, 'closeButton', AnchorBottom)
|
|
scrollbar:addAnchor(AnchorRight, 'parent', AnchorRight)
|
|
scrollbar:addAnchor(AnchorBottom, 'separator', AnchorTop)
|
|
scrollbar:setMarginTop(2) -- Small margin from close button
|
|
scrollbar:setMarginRight(3)
|
|
scrollbar:setMarginBottom(2)
|
|
|
|
-- Content panel anchors to separator when pages are visible
|
|
contentsPanel:breakAnchors()
|
|
contentsPanel:addAnchor(AnchorTop, 'miniwindowTopBar', AnchorBottom)
|
|
contentsPanel:addAnchor(AnchorLeft, 'parent', AnchorLeft)
|
|
contentsPanel:addAnchor(AnchorRight, 'miniwindowScrollBar', AnchorLeft)
|
|
contentsPanel:addAnchor(AnchorBottom, 'separator', AnchorTop)
|
|
contentsPanel:setMarginLeft(3)
|
|
contentsPanel:setMarginBottom(1)
|
|
contentsPanel:setMarginTop(-2)
|
|
contentsPanel:setMarginRight(1)
|
|
|
|
-- When pages are active, move upButton to toggleFilterButton position if it's visible
|
|
if upButton and upButton:isVisible() and contextMenuButton and minimizeButton then
|
|
-- Position upButton where toggleFilterButton was
|
|
upButton:breakAnchors()
|
|
upButton:addAnchor(AnchorTop, minimizeButton:getId(), AnchorTop)
|
|
upButton:addAnchor(AnchorRight, minimizeButton:getId(), AnchorLeft)
|
|
upButton:setMarginRight(7)
|
|
upButton:setMarginTop(0)
|
|
|
|
-- Move contextMenuButton to the left of upButton
|
|
contextMenuButton:breakAnchors()
|
|
contextMenuButton:addAnchor(AnchorTop, upButton:getId(), AnchorTop)
|
|
contextMenuButton:addAnchor(AnchorRight, upButton:getId(), AnchorLeft)
|
|
contextMenuButton:setMarginRight(2)
|
|
contextMenuButton:setMarginTop(0)
|
|
|
|
-- Position lockButton to the left of contextMenu
|
|
if lockButton then
|
|
lockButton:breakAnchors()
|
|
lockButton:addAnchor(AnchorTop, contextMenuButton:getId(), AnchorTop)
|
|
lockButton:addAnchor(AnchorRight, contextMenuButton:getId(), AnchorLeft)
|
|
lockButton:setMarginRight(2)
|
|
lockButton:setMarginTop(0)
|
|
end
|
|
end
|
|
else
|
|
-- When pages are hidden, use normal bottom anchor
|
|
scrollbar:breakAnchors()
|
|
scrollbar:addAnchor(AnchorTop, 'parent', AnchorTop)
|
|
scrollbar:addAnchor(AnchorRight, 'parent', AnchorRight)
|
|
scrollbar:addAnchor(AnchorBottom, 'parent', AnchorBottom)
|
|
scrollbar:setMarginTop(16)
|
|
scrollbar:setMarginRight(3)
|
|
scrollbar:setMarginBottom(3)
|
|
|
|
-- Content panel extends to bottom when pages are hidden
|
|
contentsPanel:breakAnchors()
|
|
contentsPanel:addAnchor(AnchorTop, 'miniwindowTopBar', AnchorBottom)
|
|
contentsPanel:addAnchor(AnchorLeft, 'parent', AnchorLeft)
|
|
contentsPanel:addAnchor(AnchorRight, 'miniwindowScrollBar', AnchorLeft)
|
|
contentsPanel:addAnchor(AnchorBottom, 'parent', AnchorBottom)
|
|
contentsPanel:setMarginLeft(3)
|
|
contentsPanel:setMarginBottom(3)
|
|
contentsPanel:setMarginTop(-2)
|
|
contentsPanel:setMarginRight(1)
|
|
|
|
-- When pages are not active, reset button positions based on upButton visibility
|
|
if upButton and contextMenuButton and minimizeButton then
|
|
if upButton:isVisible() then
|
|
-- Reset upButton to original position
|
|
upButton:breakAnchors()
|
|
upButton:addAnchor(AnchorTop, minimizeButton:getId(), AnchorTop)
|
|
upButton:addAnchor(AnchorRight, minimizeButton:getId(), AnchorLeft)
|
|
upButton:setMarginRight(3)
|
|
upButton:setMarginTop(0)
|
|
|
|
-- Position contextMenuButton to the left of upButton
|
|
contextMenuButton:breakAnchors()
|
|
contextMenuButton:addAnchor(AnchorTop, upButton:getId(), AnchorTop)
|
|
contextMenuButton:addAnchor(AnchorRight, upButton:getId(), AnchorLeft)
|
|
contextMenuButton:setMarginRight(2)
|
|
contextMenuButton:setMarginTop(0)
|
|
else
|
|
-- Position contextMenuButton where toggleFilterButton was
|
|
contextMenuButton:breakAnchors()
|
|
contextMenuButton:addAnchor(AnchorTop, minimizeButton:getId(), AnchorTop)
|
|
contextMenuButton:addAnchor(AnchorRight, minimizeButton:getId(), AnchorLeft)
|
|
contextMenuButton:setMarginRight(7)
|
|
contextMenuButton:setMarginTop(0)
|
|
end
|
|
|
|
-- Position lockButton to the left of contextMenu
|
|
if lockButton then
|
|
lockButton:breakAnchors()
|
|
lockButton:addAnchor(AnchorTop, contextMenuButton:getId(), AnchorTop)
|
|
lockButton:addAnchor(AnchorRight, contextMenuButton:getId(), AnchorLeft)
|
|
lockButton:setMarginRight(2)
|
|
lockButton:setMarginTop(0)
|
|
end
|
|
end
|
|
end
|
|
|
|
pagePanel:setVisible(pages)
|
|
separator:setVisible(pages)
|
|
end
|
|
|
|
function refreshContainerPages(container)
|
|
local currentPage = 1 + math.floor(container:getFirstIndex() / container:getCapacity())
|
|
local pages = 1 + math.floor(math.max(0, (container:getSize() - 1)) / container:getCapacity())
|
|
container.window:recursiveGetChildById('pageLabel'):setText(string.format('Page %i of %i', currentPage, pages))
|
|
|
|
local prevPageButton = container.window:recursiveGetChildById('prevPageButton')
|
|
local nextPageButton = container.window:recursiveGetChildById('nextPageButton')
|
|
|
|
-- If there's only one page, hide both navigation buttons
|
|
if pages == 1 then
|
|
prevPageButton:setVisible(false)
|
|
nextPageButton:setVisible(false)
|
|
else
|
|
-- Multiple pages logic
|
|
if currentPage == 1 then
|
|
-- Hide the back button when on the first page of multiple pages
|
|
prevPageButton:setVisible(false)
|
|
else
|
|
prevPageButton:setVisible(true)
|
|
prevPageButton:setEnabled(true)
|
|
prevPageButton.onClick = function()
|
|
-- Store current height before page change
|
|
local currentHeight = container.window:getHeight()
|
|
container.window.preservedHeight = currentHeight
|
|
g_game.seekInContainer(container:getId(), container:getFirstIndex() - container:getCapacity())
|
|
end
|
|
end
|
|
|
|
if currentPage >= pages then
|
|
nextPageButton:setVisible(false)
|
|
else
|
|
nextPageButton:setVisible(true)
|
|
nextPageButton:setEnabled(true)
|
|
nextPageButton.onClick = function()
|
|
-- Store current height before page change
|
|
local currentHeight = container.window:getHeight()
|
|
container.window.preservedHeight = currentHeight
|
|
g_game.seekInContainer(container:getId(), container:getFirstIndex() + container:getCapacity())
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function onContainerOpen(container, previousContainer)
|
|
local containerWindow
|
|
if previousContainer then
|
|
containerWindow = previousContainer.window
|
|
previousContainer.window = nil
|
|
previousContainer.itemsPanel = nil
|
|
else
|
|
containerWindow = g_ui.createWidget('ContainerWindow')
|
|
end
|
|
containerWindow:setId('container' .. container:getId())
|
|
local containerPanel = containerWindow:getChildById('contentsPanel')
|
|
local containerItemWidget = containerWindow:getChildById('containerItemWidget')
|
|
containerWindow.onClose = function()
|
|
g_game.close(container)
|
|
containerWindow:hide()
|
|
end
|
|
|
|
-- this disables scrollbar auto hiding
|
|
local scrollbar = containerWindow:getChildById('miniwindowScrollBar')
|
|
scrollbar:mergeStyle({
|
|
['$!on'] = {}
|
|
})
|
|
|
|
-- Scrollbar positioning will be handled by toggleContainerPages function
|
|
|
|
local upButton = containerWindow:getChildById('upButton')
|
|
upButton.onClick = function()
|
|
g_game.openParent(container)
|
|
end
|
|
upButton:setVisible(container:hasParent())
|
|
|
|
-- Add minimize/maximize event handlers to manage pagePanel visibility
|
|
containerWindow.onMinimize = function()
|
|
local pagePanel = containerWindow:getChildById('pagePanel')
|
|
if pagePanel and pagePanel:isVisible() then
|
|
pagePanel.wasVisibleBeforeMinimize = true
|
|
pagePanel:setVisible(false)
|
|
end
|
|
end
|
|
|
|
containerWindow.onMaximize = function()
|
|
local pagePanel = containerWindow:getChildById('pagePanel')
|
|
if pagePanel and pagePanel.wasVisibleBeforeMinimize then
|
|
pagePanel:setVisible(true)
|
|
pagePanel.wasVisibleBeforeMinimize = nil
|
|
end
|
|
end
|
|
|
|
-- Hide toggleFilterButton and adjust button positioning
|
|
local toggleFilterButton = containerWindow:recursiveGetChildById('toggleFilterButton')
|
|
if toggleFilterButton then
|
|
toggleFilterButton:setVisible(false)
|
|
toggleFilterButton:setOn(false)
|
|
end
|
|
|
|
-- Hide newWindowButton
|
|
local newWindowButton = containerWindow:recursiveGetChildById('newWindowButton')
|
|
if newWindowButton then
|
|
newWindowButton:setVisible(false)
|
|
end
|
|
|
|
local contextMenuButton = containerWindow:recursiveGetChildById('contextMenuButton')
|
|
local lockButton = containerWindow:recursiveGetChildById('lockButton')
|
|
local minimizeButton = containerWindow:recursiveGetChildById('minimizeButton')
|
|
|
|
-- Make sure contextMenuButton is visible
|
|
if contextMenuButton then
|
|
contextMenuButton:setVisible(true)
|
|
end
|
|
|
|
if contextMenuButton and minimizeButton then
|
|
if container:hasParent() then
|
|
-- When upButton is visible, position contextMenuButton to its left
|
|
contextMenuButton:breakAnchors()
|
|
contextMenuButton:addAnchor(AnchorTop, upButton:getId(), AnchorTop)
|
|
contextMenuButton:addAnchor(AnchorRight, upButton:getId(), AnchorLeft)
|
|
contextMenuButton:setMarginRight(2)
|
|
contextMenuButton:setMarginTop(0)
|
|
else
|
|
-- When upButton is not visible, position contextMenuButton where toggleFilterButton was
|
|
contextMenuButton:breakAnchors()
|
|
contextMenuButton:addAnchor(AnchorTop, minimizeButton:getId(), AnchorTop)
|
|
contextMenuButton:addAnchor(AnchorRight, minimizeButton:getId(), AnchorLeft)
|
|
contextMenuButton:setMarginRight(7)
|
|
contextMenuButton:setMarginTop(0)
|
|
end
|
|
|
|
-- Position lockButton to the left of contextMenu
|
|
if lockButton then
|
|
lockButton:breakAnchors()
|
|
lockButton:addAnchor(AnchorTop, contextMenuButton:getId(), AnchorTop)
|
|
lockButton:addAnchor(AnchorRight, contextMenuButton:getId(), AnchorLeft)
|
|
lockButton:setMarginRight(2)
|
|
lockButton:setMarginTop(0)
|
|
end
|
|
|
|
-- Add onClick handler for context menu
|
|
contextMenuButton.onClick = function(widget, mousePos, mouseButton)
|
|
return showContainersContextMenu(widget, mousePos, mouseButton)
|
|
end
|
|
end
|
|
|
|
local name = container:getName()
|
|
name = name:sub(1, 1):upper() .. name:sub(2)
|
|
|
|
if name:len() > 14 then
|
|
name = name:sub(1, 14) .. "..."
|
|
end
|
|
|
|
-- Set the title in the new miniwindowTitle element
|
|
local titleWidget = containerWindow:getChildById('miniwindowTitle')
|
|
if titleWidget then
|
|
titleWidget:setText(name)
|
|
else
|
|
-- Fallback to old method if miniwindowTitle doesn't exist
|
|
containerWindow:setText(name)
|
|
end
|
|
|
|
containerItemWidget:setItem(container:getContainerItem())
|
|
containerItemWidget:setPhantom(true)
|
|
|
|
containerPanel:destroyChildren()
|
|
for slot = 0, container:getCapacity() - 1 do
|
|
local itemWidget = g_ui.createWidget('Item', containerPanel)
|
|
itemWidget:setId('item' .. slot)
|
|
itemWidget:setItem(container:getItem(slot))
|
|
ItemsDatabase.setRarityItem(itemWidget, container:getItem(slot))
|
|
ItemsDatabase.setTier(itemWidget, container:getItem(slot))
|
|
itemWidget:setShowDuration(g_game.getFeature(GameThingClock) and modules.client_options.getOption('showExpiryInContainers'))
|
|
itemWidget:setShowCharges(g_game.getFeature(GameThingCounter) and modules.client_options.getOption('showExpiryInContainers'))
|
|
itemWidget:setMargin(0)
|
|
itemWidget.position = container:getSlotPosition(slot)
|
|
|
|
if not container:isUnlocked() then
|
|
itemWidget:setBorderColor('red')
|
|
end
|
|
end
|
|
|
|
container.window = containerWindow
|
|
container.itemsPanel = containerPanel
|
|
|
|
toggleContainerPages(containerWindow, container:hasPages())
|
|
refreshContainerPages(container)
|
|
|
|
local layout = containerPanel:getLayout()
|
|
local cellSize = layout:getCellSize()
|
|
local step = cellSize.height + layout:getCellSpacing()
|
|
local numLines = math.max(layout:getNumLines(), 1)
|
|
local chromeHeight = container:hasPages() and 55 or 31
|
|
containerWindow:setContentMinimumHeight(cellSize.height)
|
|
|
|
local resizeBorder = containerWindow:getChildById('bottomResizeBorder')
|
|
if resizeBorder then
|
|
resizeBorder:setMinimum(step + chromeHeight)
|
|
resizeBorder:setMaximum(numLines * step + chromeHeight)
|
|
end
|
|
-- Enables dragging only when mouse press occurs within window bounds (with tolerance margins)
|
|
-- and not over the containerPanel child widget
|
|
-- On Drop: When an item is dropped, it is placed at the nearest valid parent location, such as in a grid.
|
|
local TOLERANCE_HORIZONTAL = 5
|
|
local TOLERANCE_VERTICAL = 2
|
|
containerWindow.onMousePress = function(widget, mousePos, mouseButton)
|
|
if mouseButton ~= MouseLeftButton then
|
|
return false
|
|
end
|
|
local winX, winY = containerWindow:getX(), containerWindow:getY()
|
|
local winW, winH = containerWindow:getWidth(), containerWindow:getHeight()
|
|
local inBounds = mousePos.x >= winX + TOLERANCE_HORIZONTAL and
|
|
mousePos.x <= winX + winW - TOLERANCE_HORIZONTAL and
|
|
mousePos.y >= winY + TOLERANCE_VERTICAL and
|
|
mousePos.y <= winY + winH - TOLERANCE_VERTICAL
|
|
containerWindow:setDraggable(inBounds and containerWindow:getChildByPos(mousePos) ~= containerPanel)
|
|
return inBounds
|
|
end
|
|
containerWindow.onMouseRelease = function(widget, mousePos, mouseButton)
|
|
containerWindow:setDraggable(true)
|
|
end
|
|
containerWindow.onDrop = function(container, widget, mousePos)
|
|
if containerPanel:getChildByPos(mousePos) then
|
|
return false
|
|
end
|
|
local child = containerPanel:getNearestChild(mousePos)
|
|
if child then
|
|
child:onDrop(widget, mousePos, true)
|
|
end
|
|
end
|
|
|
|
if not previousContainer then
|
|
local panel = modules.game_interface.findContentPanelAvailable(containerWindow, cellSize.height)
|
|
panel:addChild(containerWindow)
|
|
end
|
|
|
|
if not previousContainer or previousContainer:getCapacity() >= container:getCapacity() then
|
|
-- Always set the content height based on the current container's content, with a minimum of one row
|
|
local minRows = 1
|
|
if modules.client_options.getOption('openMaximized') then
|
|
local numLines = math.max(layout:getNumLines(), minRows)
|
|
containerWindow:setContentHeight(numLines * step + chromeHeight)
|
|
else
|
|
local numColumns = math.max(layout:getNumColumns(), 1)
|
|
local filledLines = math.max(math.ceil(container:getItemsCount() / numColumns), minRows)
|
|
containerWindow:setContentHeight(filledLines * step + chromeHeight)
|
|
end
|
|
end
|
|
|
|
containerWindow:setup()
|
|
|
|
-- Apply current sorting mode if one is active and manual sort mode is disabled
|
|
local currentSortMode = containerSettings and containerSettings['currentSortMode']
|
|
local isManualSortEnabled = containerSettings and containerSettings['useManualSortMode'] == 1
|
|
if currentSortMode and currentSortMode ~= 'none' and not isManualSortEnabled then
|
|
sortContainerItems(container, currentSortMode)
|
|
end
|
|
end
|
|
|
|
function onContainerClose(container)
|
|
destroy(container)
|
|
end
|
|
|
|
function onContainerChangeSize(container, size)
|
|
if not container.window then
|
|
return
|
|
end
|
|
|
|
-- Store the current height if one was preserved from page navigation
|
|
local preservedHeight = container.window.preservedHeight
|
|
|
|
refreshContainerItems(container)
|
|
|
|
-- Restore the preserved height if it exists (from page switching)
|
|
if preservedHeight then
|
|
container.window:setHeight(preservedHeight)
|
|
container.window.preservedHeight = nil -- Clear the preserved height
|
|
end
|
|
end
|
|
|
|
function onContainerUpdateItem(container, slot, item, oldItem)
|
|
if not container.window then
|
|
return
|
|
end
|
|
local itemWidget = container.itemsPanel:getChildById('item' .. slot)
|
|
itemWidget:setItem(item)
|
|
ItemsDatabase.setRarityItem(itemWidget, item)
|
|
ItemsDatabase.setTier(itemWidget, item)
|
|
itemWidget:setShowDuration(g_game.getFeature(GameThingClock) and modules.client_options.getOption('showExpiryInContainers'))
|
|
itemWidget:setShowCharges(g_game.getFeature(GameThingCounter) and modules.client_options.getOption('showExpiryInContainers'))
|
|
|
|
-- Note: Removed automatic re-sorting to prevent interference with manual item movement
|
|
-- Sorting should only happen when explicitly requested by the user
|
|
end
|