otclient-redemption/modules/game_containers/containers.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