You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

326 lines
8.4 KiB

--[[
Description: Loot only what you want and keep your precious bag slots free of trash.
Author: Eliote
--]]
local ADDON_NAME, PRIVATE_TABLE = ...
local GetAddOnMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetadata
local MOD_VERSION = GetAddOnMetadata(ADDON_NAME, "Version")
---@class AutoLooter
AutoLooter = LibStub("AceAddon-3.0"):NewAddon("AutoLooter", "AceEvent-3.0")
local events = LibStub("CallbackHandler-1.0"):New(AutoLooter)
local L = LibStub("AceLocale-3.0"):GetLocale("AutoLooter")
---@type Util
local Util = PRIVATE_TABLE.Util
local Color = PRIVATE_TABLE.Color
AutoLooter.GetNumLootItems = GetNumLootItems
AutoLooter.GetLootSlotType = GetLootSlotType
AutoLooter.GetLootSlotInfo = GetLootSlotInfo
AutoLooter.GetLootSlotLink = GetLootSlotLink
AutoLooter.LootSlot = LootSlot
AutoLooter.ConfirmLootSlot = ConfirmLootSlot
local chatCache = {}
local function findChatFrame(name)
if (name == -1) then return DEFAULT_CHAT_FRAME end
local chat = chatCache[name]
if chat then return chat end
local found
for i = 1, NUM_CHAT_WINDOWS do
local chatName = GetChatWindowInfo(i)
if chatName then
local chatFrame = getglobal("ChatFrame" .. i)
chatCache[chatName] = chatFrame
if chatName == name then
found = chatFrame
end
end
end
return found
end
local function filterChatFrame(namesMap)
local frames = {}
for k, selected in pairs(namesMap) do
if selected then
local frame = findChatFrame(k)
if frame then
table.insert(frames, frame)
end
end
end
return frames
end
function AutoLooter.print(...)
local chatFrames = filterChatFrame(AutoLooter.db.char.chatFrameNames)
if #chatFrames == 0 then return end
local out = ""
for i = 1, select("#", ...) do
local s = tostring(select(i, ...))
out = out .. s
end
for i, chatFrame in ipairs(chatFrames) do
chatFrame:AddMessage(Color.WHITE .. "[" .. Color.BLUE .. "AL" .. Color.WHITE .. "]|r|r|r " .. out)
end
end
local function LoadState()
if (AutoLooter.db.profile.enable) then
AutoLooter:Enable()
else
AutoLooter:Disable()
end
end
function AutoLooter.Toggle(bool)
AutoLooter.db.profile.enable = Util.GetBoolean(bool, not AutoLooter.db.profile.enable)
LoadState()
end
function AutoLooter:ReloadOptions(onInit)
if onInit == "OnInitialization" then
self:SetEnabledState(self.db.profile.enable)
else
LoadState()
end
end
function AutoLooter:CreateProfile()
self.options = {
type = "group",
name = "AutoLooter",
args = {
config = {
type = "execute",
name = L["Show/Hide UI"],
func = function() LibStub("AceConfigDialog-3.0"):Open(ADDON_NAME) end,
hidden = true
},
general = {
order = 0,
name = L["General"],
type = "group",
args = {}
}
}
}
self.options.args.profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
self.options.args.profile.order = -1
for _, module in AutoLooter:IterateModules() do
if (module.GetOptions) then
Util.mergeTable(self.options.args, module:GetOptions())
end
end
local AceConfig = LibStub("AceConfig-3.0")
AceConfig:RegisterOptionsTable("AutoLooter", self.options, { "al", "autolooter", "atl" })
local AceDialog = LibStub("AceConfigDialog-3.0")
self.optionsFrame = AceDialog:AddToBlizOptions("AutoLooter")
end
local function registerResetCacheOn(module, func)
local original = module[func]
module[func] = function(...)
AutoLooter:ResetModulesCache()
if original then return original(...) end
end
end
function AutoLooter:OnInitialize()
local isDragonFlight = (LE_EXPANSION_LEVEL_CURRENT >= 9) -- DRAGONFLIGHT = 9
local defaults = {
profile = {
items = {},
ignore = {},
alert = {},
enable = true,
printout = true,
printoutIgnored = false,
printType = false,
lootAll = false,
lootQuest = true,
close = false,
rarity = 2,
price = 40000,
alertSound = "Sound/creature/Murloc/mMurlocAggroOld.ogg",
typeTable = {}, --CreateAHTable(),
ignoreGreys = false,
autoConfirmRoll = false,
minimap = { hide = false },
ignoreBop = false,
printoutReason = true,
lootEarly = not isDragonFlight,
printLoginCommands = false,
},
char = {
chatFrameNames = { [-1] = true }
}
}
self.db = LibStub("AceDB-3.0"):New("AutoLooterDB", defaults, "Default")
self.db.RegisterCallback(self, "OnProfileChanged", "ReloadOptions")
self.db.RegisterCallback(self, "OnProfileCopied", "ReloadOptions")
self.db.RegisterCallback(self, "OnProfileReset", "ReloadOptions")
for _, module in self:IterateModules() do
registerResetCacheOn(module, "OnEnable")
registerResetCacheOn(module, "OnDisable")
end
AutoLooter:ReloadOptions("OnInitialization")
AutoLooter:CreateProfile()
if (self.db.profile.printCommandsAtLogin) then
DEFAULT_CHAT_FRAME:AddMessage(Color.BLUE .. "AutoLooter" .. Color.WHITE .. " || v" .. MOD_VERSION .. " || " .. Color.YELLOW .. "/autolooter /al /atl|r")
end
end
function AutoLooter:OnEnable()
AutoLooter:RegisterEvent("LOOT_READY")
AutoLooter:RegisterEvent("LOOT_OPENED")
events:Fire("OnEnable")
end
function AutoLooter:OnDisable()
events:Fire("OnDisable")
end
local function Loot(index, itemName, itemLink)
AutoLooter.LootSlot(index)
AutoLooter.ConfirmLootSlot(index) -- In case it's a Bind on Pickup
events:Fire("OnLoot", index)
end
local trim = Util.trim
local function PrintReason(reason, contents)
local items = ""
for _, content in pairs(contents) do
items = items .. content .. " "
end
if (not reason or reason == "") then
AutoLooter.print(trim(items))
else
AutoLooter.print(reason, "|r: ", trim(items))
end
end
local modulesCache = {}
local resetMessage = {}
---@return fun(tbl: table<string, AutoLooterModule>):string, AutoLooterModule
function AutoLooter:SortedModulesIterator(lootOnly)
local cacheKey = lootOnly and "lootOnlyTrue" or "lootOnlyFalse"
if modulesCache[cacheKey] ~= nil then
modulesCache[cacheKey](resetMessage)
return modulesCache[cacheKey]
end
local function sort(o1, o2)
return (self:GetModule(o1).priority or 99999999) < (self:GetModule(o2).priority or 99999999)
end
local exclusion = function(key, module)
if module:IsEnabled() then
if lootOnly then return module.CanLoot end
return true
end
end
local function iterator() return self:IterateModules() end
modulesCache[cacheKey] = Util.orderedPairs(iterator, sort, exclusion, resetMessage)
return modulesCache[cacheKey]
end
function AutoLooter:ResetModulesCache()
modulesCache = {}
end
function AutoLooter:LOOT_OPENED(...)
if (not self.db.profile.lootEarly) then
self:Loot(...)
end
end
function AutoLooter:LOOT_READY(...)
if (self.db.profile.lootEarly) then
self:Loot(...)
end
end
function AutoLooter:Loot(_, autoloot)
if (autoloot) then return end
events:Fire("OnLootReadyStart")
local reasonMap = {}
local printReason = self.db.profile.printout
for nIndex = 1, self.GetNumLootItems() do
local slotType = self.GetLootSlotType(nIndex) or 0
if (slotType > 0) then
-- if the item is gone, there's nothing we can do.
local icon, title, quantity, currencyID, rarity, locked, isQuestItem, questId, isActive = self.GetLootSlotInfo(nIndex)
quantity = quantity or 0
local itemLink = self.GetLootSlotLink(nIndex)
for _, module in self:SortedModulesIterator(true) do
local loot, reason, reasonContent, forceBreak = module.CanLoot(
itemLink,
icon,
title,
quantity,
currencyID,
rarity,
locked,
isQuestItem,
questId,
isActive
)
if (printReason and reason and reasonContent and reasonContent ~= "") then
-- ignored items will still print the reason
if (loot and not self.db.profile.printoutReason and quantity > 0) then
reason = ""
end
if (loot or self.db.profile.printoutIgnored) then
reasonMap[reason] = reasonMap[reason] or {}
table.insert(reasonMap[reason], reasonContent)
end
end
if loot then
Loot(nIndex, title, itemLink)
break
end
if forceBreak then break end -- ignore other modules and go to the next item
end
end
end
for _, module in self:SortedModulesIterator() do
if (module.Finish) then module.Finish() end
end
for reason, contents in pairs(reasonMap) do
PrintReason(reason, contents)
end
events:Fire("OnLootReadyFinish")
end