feat: auto loot and clam opener

This commit is contained in:
Vati
2026-04-09 16:12:16 +04:00
parent 9fc5e085fd
commit 4e2e27aa8f
8 changed files with 409 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
-- Guda AutoLoot
-- TurtleWoW with SuperWoW removes the hardcoded shift-autoloot and exposes:
-- * SetAutoloot(0|1) — global client autoloot toggle
-- * LootSlot(slot, forceloot) — forceloot=1 is REQUIRED to actually loot
-- We prefer SetAutoloot when present (lets the client handle everything),
-- and also fall back to a LOOT_OPENED handler that calls LootSlot(slot, 1)
-- so this works on clients without SuperWoW too.
local addon = Guda
local AutoLoot = {}
addon.Modules.AutoLoot = AutoLoot
local function ApplyClientSetting()
local enabled = Guda.Modules.DB and Guda.Modules.DB:GetSetting("autoLoot")
if SetAutoloot then
SetAutoloot(enabled and 1 or 0)
elseif SetAutoLootDefault then
SetAutoLootDefault(enabled and true or false)
end
end
local function OnLootOpened()
if not (Guda.Modules.DB and Guda.Modules.DB:GetSetting("autoLoot")) then
return
end
-- If SuperWoW's SetAutoloot is present and enabled, the client has
-- already looted everything before LOOT_OPENED reaches us — this loop
-- is a no-op safety net for normal clients and any leftovers (BoP
-- confirms, etc.).
local n = GetNumLootItems()
if not n or n == 0 then return end
for slot = n, 1, -1 do
LootSlot(slot, 1) -- forceloot=1 for SuperWoW compatibility
if ConfirmLootSlot then ConfirmLootSlot(slot) end
end
end
function AutoLoot:Initialize()
addon.Modules.Events:Register("LOOT_OPENED", OnLootOpened, "AutoLoot")
ApplyClientSetting()
end
-- Called by the settings checkbox so the client toggle flips immediately.
function AutoLoot:Apply()
ApplyClientSetting()
end
+155
View File
@@ -0,0 +1,155 @@
-- Guda ClamOpener
-- Walks the player's bags and uses each clam in turn until none remain.
-- Triggered by /guda openclams. Stops on UI_ERROR_MESSAGE so a full inventory
-- (or any other error from UseContainerItem) cleanly aborts the run.
local addon = Guda
local L = Guda_L
local ClamOpener = {}
addon.Modules.ClamOpener = ClamOpener
-- Hardcoded vanilla clam item IDs
local CLAM_IDS = {
[5523] = true, -- Small Barnacled Clam
[5524] = true, -- Thick-shelled Clam
[7973] = true, -- Big-mouth Clam
[15874] = true, -- Soft-shelled Clam
}
local OPEN_DELAY = 0.3 -- seconds between opens; lets loot/bag updates settle
local running = false
local silentRun = false
-- Find the next clam in player bags. Returns bagID, slotID, itemLink or nil.
local function FindNextClam()
for bagID = 0, 4 do
local numSlots = GetContainerNumSlots(bagID)
if numSlots and numSlots > 0 then
for slotID = 1, numSlots do
local link = GetContainerItemLink(bagID, slotID)
if link then
local _, _, idStr = string.find(link, "item:(%d+)")
local itemID = idStr and tonumber(idStr)
if itemID and CLAM_IDS[itemID] then
return bagID, slotID
end
end
end
end
end
return nil
end
local function StopRun(reason)
if not running then return end
running = false
-- Don't unregister BAG_UPDATE — it's the auto-trigger. Only drop the
-- per-run UI_ERROR_MESSAGE listener.
addon.Modules.Events:UnregisterOwner("ClamOpener_Run")
if reason and not silentRun then
addon:Print(reason)
end
silentRun = false
end
local function OpenNext()
if not running then return end
if CursorHasItem() then
-- Cursor is busy with something else; try again shortly.
Guda_ScheduleTimer(OPEN_DELAY, OpenNext)
return
end
local bagID, slotID = FindNextClam()
if not bagID then
StopRun(L["No more clams to open."])
return
end
UseContainerItem(bagID, slotID)
Guda_ScheduleTimer(OPEN_DELAY, OpenNext)
end
-- Stop on UI_ERROR_MESSAGE (e.g. inventory full). UseContainerItem fires this
-- the same frame on failure, so any error after we start counts as an abort.
local function OnUIError()
if not running then return end
local msg = arg1
StopRun(string.format(L["Clam opener stopped: %s"], tostring(msg or "error")))
end
-- silent: true to suppress chat messages (used by auto-trigger).
function ClamOpener:Open(silent)
if running then
if not silent then
addon:Print(L["Clam opener is already running."])
end
return
end
-- Quick sanity check so we don't print "stopped" without ever starting.
if not FindNextClam() then
if not silent then
addon:Print(L["No clams found in your bags."])
end
return
end
running = true
silentRun = silent and true or false
addon.Modules.Events:Register("UI_ERROR_MESSAGE", OnUIError, "ClamOpener_Run")
if not silent then
addon:Print(L["Opening clams..."])
end
OpenNext()
end
-- Returns true if any blocking window (loot/mail/trade/merchant/bank/auction)
-- is currently open. We don't want to UseContainerItem while these are active —
-- it can race the open window or get queued and lost.
local function IsBlockingWindowOpen()
local frames = {
"LootFrame", "MailFrame", "TradeFrame", "MerchantFrame",
"BankFrame", "AuctionFrame",
-- Guda's own bank/mail frames too
"Guda_BankFrame", "Guda_MailboxFrame",
}
for _, name in ipairs(frames) do
local f = getglobal(name)
if f and f.IsShown and f:IsShown() then
return true
end
end
return false
end
-- Initialize auto-open. Triggers:
-- * BAG_UPDATE — catches clams that arrived without a blocking window.
-- * LOOT_CLOSED — the common case: clam looted from a corpse.
-- * MAIL_CLOSED — clam received via mail.
-- * TRADE_CLOSED — clam received via trade.
-- * BANKFRAME_CLOSED — clam withdrawn from bank.
-- Each trigger defers ~0.15s and then attempts a silent Open(); the running
-- guard, the FindNextClam check, and the IsBlockingWindowOpen guard absorb
-- the noise of multiple events firing in quick succession.
function ClamOpener:Initialize()
local function tryAutoOpen()
if running then return end
if not (Guda.Modules.DB and Guda.Modules.DB:GetSetting("autoOpenClams")) then
return
end
Guda_ScheduleTimer(0.15, function()
if running then return end
if IsBlockingWindowOpen() then return end
ClamOpener:Open(true)
end)
end
addon.Modules.Events:Register("BAG_UPDATE", tryAutoOpen, "ClamOpener")
addon.Modules.Events:Register("LOOT_CLOSED", tryAutoOpen, "ClamOpener")
addon.Modules.Events:Register("MAIL_CLOSED", tryAutoOpen, "ClamOpener")
addon.Modules.Events:Register("TRADE_CLOSED", tryAutoOpen, "ClamOpener")
addon.Modules.Events:Register("BANKFRAME_CLOSED", tryAutoOpen, "ClamOpener")
end
+6
View File
@@ -147,6 +147,12 @@ function DB:Initialize()
if Guda_CharDB.settings.autoLockSetItems == nil then
Guda_CharDB.settings.autoLockSetItems = true
end
if Guda_CharDB.settings.autoLoot == nil then
Guda_CharDB.settings.autoLoot = false
end
if Guda_CharDB.settings.autoOpenClams == nil then
Guda_CharDB.settings.autoOpenClams = false
end
-- Auto-detect pfUI on first load (theme not yet set)
if Guda_CharDB.settings.theme == nil then
+15
View File
@@ -82,6 +82,16 @@ function Main:Initialize()
addon:Error("Tooltip module not loaded!")
end
-- Initialize auto-loot handler (LOOT_OPENED listener)
if addon.Modules.AutoLoot and addon.Modules.AutoLoot.Initialize then
addon.Modules.AutoLoot:Initialize()
end
-- Initialize clam opener (registers BAG_UPDATE for auto-open)
if addon.Modules.ClamOpener and addon.Modules.ClamOpener.Initialize then
addon.Modules.ClamOpener:Initialize()
end
-- Setup slash commands
Main:SetupSlashCommands()
@@ -133,6 +143,10 @@ function Main:SetupSlashCommands()
-- Sort bank
addon.Modules.SortEngine:SortBank()
elseif msg == "openclams" or msg == "clams" then
-- Open all clams in bags
addon.Modules.ClamOpener:Open()
elseif msg == "debug" then
-- Toggle debug
addon.DEBUG = not addon.DEBUG
@@ -255,6 +269,7 @@ function Main:SetupSlashCommands()
addon:Print(L["/guda settings - Open settings"])
addon:Print(L["/guda sort - Sort bags"])
addon:Print(L["/guda sortbank - Sort bank"])
addon:Print(L["/guda openclams - Open all clams in bags"])
addon:Print(L["/guda track - Toggle item tracking"])
addon:Print(L["/guda debug - Toggle debug mode"])
addon:Print(L["/guda debugsort - Toggle sort debug output"])
+2
View File
@@ -21,6 +21,8 @@ Core\Utils.lua
Core\ItemDetection.lua
Core\CategoryManager.lua
Core\Tooltip.lua
Core\ClamOpener.lua
Core\AutoLoot.lua
Data\BagScanner.lua
Data\BankScanner.lua
+71
View File
@@ -58,6 +58,17 @@ L["/guda mail - Toggle mailbox"] = "/guda mail - Toggle mailbox"
L["/guda settings - Open settings"] = "/guda settings - Open settings"
L["/guda sort - Sort bags"] = "/guda sort - Sort bags"
L["/guda sortbank - Sort bank"] = "/guda sortbank - Sort bank"
L["/guda openclams - Open all clams in bags"] = "/guda openclams - Open all clams in bags"
L["Auto Loot"] = "Auto Loot"
L["Automatically loot all items when looting a corpse or container."] = "Automatically loot all items when looting a corpse or container."
L["Auto Loot requires the SuperWoW client mod. Install SuperWoW to enable this option."] = "Auto Loot requires the SuperWoW client mod. Install SuperWoW to enable this option, or apply the launcher's 'Always auto-loot' tweak (which makes the client autoloot independently of this addon)."
L["Auto Open Clams"] = "Auto Open Clams"
L["Automatically open clams in your bags when you loot one."] = "Automatically open clams in your bags when you loot one."
L["Opening clams..."] = "Opening clams..."
L["No clams found in your bags."] = "No clams found in your bags."
L["No more clams to open."] = "No more clams to open."
L["Clam opener is already running."] = "Clam opener is already running."
L["Clam opener stopped: %s"] = "Clam opener stopped: %s"
L["/guda track - Toggle item tracking"] = "/guda track - Toggle item tracking"
L["/guda debug - Toggle debug mode"] = "/guda debug - Toggle debug mode"
L["/guda debugsort - Toggle sort debug output"] = "/guda debugsort - Toggle sort debug output"
@@ -343,6 +354,18 @@ local locale = GetLocale and GetLocale() or "enUS"
if locale == "zhCN" then
-- Chinese (Simplified)
L["Auto Loot"] = "自动拾取"
L["Automatically loot all items when looting a corpse or container."] = "拾取尸体或容器时自动拾取所有物品。"
L["Auto Loot requires the SuperWoW client mod. Install SuperWoW to enable this option."] = "自动拾取需要 SuperWoW 客户端模组。安装 SuperWoW 以启用此选项,或应用启动器的 'Always auto-loot' 调整(客户端将独立于此插件自动拾取)。"
L["Auto Open Clams"] = "自动打开蚌壳"
L["Automatically open clams in your bags when you loot one."] = "拾取蚌壳后自动打开背包中的蚌壳。"
L["/guda openclams - Open all clams in bags"] = "/guda openclams - 打开背包中所有蚌壳"
L["Opening clams..."] = "正在打开蚌壳..."
L["No clams found in your bags."] = "背包中没有找到蚌壳。"
L["No more clams to open."] = "没有更多蚌壳可打开。"
L["Clam opener is already running."] = "蚌壳打开器已在运行。"
L["Clam opener stopped: %s"] = "蚌壳打开器已停止:%s"
-- Init / lifecycle
L["Initializing..."] = "正在初始化..."
L["Initializing UI..."] = "正在初始化界面..."
@@ -595,6 +618,18 @@ if locale == "zhCN" then
elseif locale == "esES" then
-- Spanish
L["Auto Loot"] = "Saqueo automático"
L["Automatically loot all items when looting a corpse or container."] = "Saquea automáticamente todos los objetos al saquear un cadáver o contenedor."
L["Auto Loot requires the SuperWoW client mod. Install SuperWoW to enable this option."] = "El saqueo automático requiere la modificación de cliente SuperWoW. Instala SuperWoW para habilitar esta opción, o aplica el ajuste 'Always auto-loot' del lanzador (el cliente saqueará automáticamente, independientemente de este addon)."
L["Auto Open Clams"] = "Abrir almejas automáticamente"
L["Automatically open clams in your bags when you loot one."] = "Abre automáticamente las almejas de tus bolsas cuando saqueas una."
L["/guda openclams - Open all clams in bags"] = "/guda openclams - Abrir todas las almejas de las bolsas"
L["Opening clams..."] = "Abriendo almejas..."
L["No clams found in your bags."] = "No se encontraron almejas en tus bolsas."
L["No more clams to open."] = "No hay más almejas para abrir."
L["Clam opener is already running."] = "El abridor de almejas ya está en ejecución."
L["Clam opener stopped: %s"] = "Abridor de almejas detenido: %s"
L["Initializing..."] = "Inicializando..."
L["Initializing UI..."] = "Inicializando interfaz..."
L["Ready! Type /guda to open bags"] = "¡Listo! Escribe /guda para abrir las bolsas"
@@ -832,6 +867,18 @@ elseif locale == "esES" then
elseif locale == "ptBR" then
-- Portuguese (Brazilian)
L["Auto Loot"] = "Saque automático"
L["Automatically loot all items when looting a corpse or container."] = "Saqueia automaticamente todos os itens ao saquear um cadáver ou recipiente."
L["Auto Loot requires the SuperWoW client mod. Install SuperWoW to enable this option."] = "O saque automático requer a modificação de cliente SuperWoW. Instale o SuperWoW para habilitar esta opção, ou aplique o ajuste 'Always auto-loot' do lançador (o cliente saqueará automaticamente, independentemente deste addon)."
L["Auto Open Clams"] = "Abrir mariscos automaticamente"
L["Automatically open clams in your bags when you loot one."] = "Abre automaticamente os mariscos nas suas bolsas quando você saqueia um."
L["/guda openclams - Open all clams in bags"] = "/guda openclams - Abrir todos os mariscos nas bolsas"
L["Opening clams..."] = "Abrindo mariscos..."
L["No clams found in your bags."] = "Nenhum marisco encontrado nas suas bolsas."
L["No more clams to open."] = "Não há mais mariscos para abrir."
L["Clam opener is already running."] = "O abridor de mariscos já está em execução."
L["Clam opener stopped: %s"] = "Abridor de mariscos parado: %s"
L["Initializing..."] = "Inicializando..."
L["Initializing UI..."] = "Inicializando interface..."
L["Ready! Type /guda to open bags"] = "Pronto! Digite /guda para abrir as bolsas"
@@ -1069,6 +1116,18 @@ elseif locale == "ptBR" then
elseif locale == "deDE" then
-- German
L["Auto Loot"] = "Automatisches Plündern"
L["Automatically loot all items when looting a corpse or container."] = "Plündere automatisch alle Gegenstände von Leichen und Behältern."
L["Auto Loot requires the SuperWoW client mod. Install SuperWoW to enable this option."] = "Automatisches Plündern erfordert die SuperWoW-Clienterweiterung. Installiere SuperWoW, um diese Option zu aktivieren, oder wende den Launcher-Tweak 'Always auto-loot' an (dann plündert der Client unabhängig von diesem Addon automatisch)."
L["Auto Open Clams"] = "Muscheln automatisch öffnen"
L["Automatically open clams in your bags when you loot one."] = "Öffnet automatisch Muscheln in deinen Taschen, wenn du eine erbeutest."
L["/guda openclams - Open all clams in bags"] = "/guda openclams - Alle Muscheln in den Taschen öffnen"
L["Opening clams..."] = "Öffne Muscheln..."
L["No clams found in your bags."] = "Keine Muscheln in deinen Taschen gefunden."
L["No more clams to open."] = "Keine weiteren Muscheln zu öffnen."
L["Clam opener is already running."] = "Der Muschelöffner läuft bereits."
L["Clam opener stopped: %s"] = "Muschelöffner gestoppt: %s"
L["Initializing..."] = "Initialisiere..."
L["Initializing UI..."] = "Initialisiere Oberfläche..."
L["Ready! Type /guda to open bags"] = "Bereit! Tippe /guda um die Taschen zu öffnen"
@@ -1306,6 +1365,18 @@ elseif locale == "deDE" then
elseif locale == "ruRU" then
-- Russian
L["Auto Loot"] = "Автосбор"
L["Automatically loot all items when looting a corpse or container."] = "Автоматически собирать все предметы при обыске трупов и контейнеров."
L["Auto Loot requires the SuperWoW client mod. Install SuperWoW to enable this option."] = "Автосбор требует клиентскую модификацию SuperWoW. Установите SuperWoW, чтобы включить эту опцию, или примените твик лаунчера 'Always auto-loot' (тогда клиент будет автоматически собирать добычу независимо от этого аддона)."
L["Auto Open Clams"] = "Автооткрытие моллюсков"
L["Automatically open clams in your bags when you loot one."] = "Автоматически открывать моллюсков в сумках, как только один из них получен."
L["/guda openclams - Open all clams in bags"] = "/guda openclams - Открыть всех моллюсков в сумках"
L["Opening clams..."] = "Открытие моллюсков..."
L["No clams found in your bags."] = "В ваших сумках нет моллюсков."
L["No more clams to open."] = "Больше моллюсков для открытия нет."
L["Clam opener is already running."] = "Открывалка моллюсков уже работает."
L["Clam opener stopped: %s"] = "Открывалка моллюсков остановлена: %s"
L["Initializing..."] = "Инициализация..."
L["Initializing UI..."] = "Инициализация интерфейса..."
L["Ready! Type /guda to open bags"] = "Готово! Введите /guda, чтобы открыть сумки"
+87
View File
@@ -341,6 +341,20 @@ function Guda_SettingsPopup_OnShow(self)
whiteItemsJunkCheckbox:SetChecked(whiteItemsJunk and 1 or 0)
end
-- Auto Loot checkbox
local autoLootCheckbox = getglobal("Guda_SettingsPopup_AutoLootCheckbox")
if autoLootCheckbox then
local autoLoot = Guda.Modules.DB:GetSetting("autoLoot") and true or false
autoLootCheckbox:SetChecked(autoLoot and 1 or 0)
end
-- Auto Open Clams checkbox
local autoOpenClamsCheckbox = getglobal("Guda_SettingsPopup_AutoOpenClamsCheckbox")
if autoOpenClamsCheckbox then
local autoOpenClams = Guda.Modules.DB:GetSetting("autoOpenClams") and true or false
autoOpenClamsCheckbox:SetChecked(autoOpenClams and 1 or 0)
end
if bagViewDropdown then
UIDropDownMenu_SetSelectedValue(bagViewDropdown, bagViewType)
UIDropDownMenu_SetText(bagViewType == "single" and "Single" or "Category", bagViewDropdown)
@@ -1425,6 +1439,79 @@ function Guda_SettingsPopup_ShowCategoryCountCheckbox_OnClick(self)
end
end
-- Auto Loot Checkbox OnLoad
function Guda_SettingsPopup_AutoLootCheckbox_OnLoad(self)
local text = getglobal(self:GetName().."Text")
if text then
text:SetText(Guda_L["Auto Loot"])
local font, _, flags = text:GetFont()
if font then text:SetFont(font, 13, flags) end
end
-- TurtleWoW requires SuperWoW for any addon-driven autoloot to work
-- (both SetAutoloot and LootSlot are gated). If it's not present, disable
-- the checkbox and explain why in the tooltip.
local hasSuperWoW = SetAutoloot ~= nil
if hasSuperWoW then
self.tooltipText = Guda_L["Automatically loot all items when looting a corpse or container."]
self:Enable()
if text then text:SetTextColor(1, 1, 1) end
else
self.tooltipText = Guda_L["Auto Loot requires the SuperWoW client mod. Install SuperWoW to enable this option."]
self:Disable()
if text then text:SetTextColor(0.5, 0.5, 0.5) end
end
local enabled = false
if Guda and Guda.Modules and Guda.Modules.DB then
enabled = Guda.Modules.DB:GetSetting("autoLoot") and true or false
end
self:SetChecked(enabled and 1 or 0)
end
-- Auto Loot Checkbox OnClick
function Guda_SettingsPopup_AutoLootCheckbox_OnClick(self)
local isChecked = self:GetChecked() == 1
if Guda and Guda.Modules and Guda.Modules.DB then
Guda.Modules.DB:SetSetting("autoLoot", isChecked)
end
-- Apply immediately to the client (SuperWoW's SetAutoloot or vanilla
-- SetAutoLootDefault) so the toggle takes effect on the next loot.
if Guda.Modules.AutoLoot and Guda.Modules.AutoLoot.Apply then
Guda.Modules.AutoLoot:Apply()
end
end
-- Auto Open Clams Checkbox OnLoad
function Guda_SettingsPopup_AutoOpenClamsCheckbox_OnLoad(self)
local text = getglobal(self:GetName().."Text")
if text then
text:SetText(Guda_L["Auto Open Clams"])
local font, _, flags = text:GetFont()
if font then text:SetFont(font, 13, flags) end
end
self.tooltipText = Guda_L["Automatically open clams in your bags when you loot one."]
local enabled = false
if Guda and Guda.Modules and Guda.Modules.DB then
enabled = Guda.Modules.DB:GetSetting("autoOpenClams") and true or false
end
self:SetChecked(enabled and 1 or 0)
end
-- Auto Open Clams Checkbox OnClick
function Guda_SettingsPopup_AutoOpenClamsCheckbox_OnClick(self)
local isChecked = self:GetChecked() == 1
if Guda and Guda.Modules and Guda.Modules.DB then
Guda.Modules.DB:SetSetting("autoOpenClams", isChecked)
end
-- If enabling, kick off an immediate run for any clams already in bags.
if isChecked and Guda.Modules.ClamOpener then
Guda.Modules.ClamOpener:Open(true)
end
end
-- Auto Vendor Junk Checkbox OnLoad
function Guda_SettingsPopup_AutoVendorJunkCheckbox_OnLoad(self)
local text = getglobal(self:GetName().."Text")
+26
View File
@@ -342,6 +342,32 @@
<OnClick>Guda_SettingsPopup_AutoVendorJunkCheckbox_OnClick(this)</OnClick>
</Scripts>
</CheckButton>
<!-- Auto Loot Checkbox -->
<CheckButton name="Guda_SettingsPopup_AutoLootCheckbox" inherits="OptionsCheckButtonTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeTo="Guda_SettingsPopup_AutoVendorJunkCheckbox">
<Offset><AbsDimension x="0" y="-8"/></Offset>
</Anchor>
</Anchors>
<Scripts>
<OnLoad>Guda_SettingsPopup_AutoLootCheckbox_OnLoad(this)</OnLoad>
<OnClick>Guda_SettingsPopup_AutoLootCheckbox_OnClick(this)</OnClick>
</Scripts>
</CheckButton>
<!-- Auto Open Clams Checkbox -->
<CheckButton name="Guda_SettingsPopup_AutoOpenClamsCheckbox" inherits="OptionsCheckButtonTemplate">
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT" relativeTo="Guda_SettingsPopup_AutoVendorJunkCheckbox">
<Offset><AbsDimension x="200" y="0"/></Offset>
</Anchor>
</Anchors>
<Scripts>
<OnLoad>Guda_SettingsPopup_AutoOpenClamsCheckbox_OnLoad(this)</OnLoad>
<OnClick>Guda_SettingsPopup_AutoOpenClamsCheckbox_OnClick(this)</OnClick>
</Scripts>
</CheckButton>
</Frames>
</Frame>