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
326 lines
8.4 KiB
|
1 year ago
|
--[[
|
||
|
|
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
|