diff --git a/.luacov b/.luacov
index 8c59a9b..de65935 100644
--- a/.luacov
+++ b/.luacov
@@ -5,5 +5,8 @@ include = {
"^BlizzOverrides/", -- Include all files in BlizzOverrides/
"^config/", -- Include all files in config/
"^Features/", -- Include all files in Features/
+ "^GameTesting/", -- Include all files in GameTesting/
+ "^LootDisplay/", -- Include all files in LootDisplay/
+ "^utils/", -- Include all files in utils/
"^[^/]+$" -- Include all Lua files in the repo root
}
diff --git a/.pkgmeta b/.pkgmeta
index 08a8cb0..2e5aa62 100644
--- a/.pkgmeta
+++ b/.pkgmeta
@@ -2,6 +2,7 @@ externals:
Libs/LibStub: https://repos.wowace.com/wow/ace3/trunk/LibStub
Libs/CallbackHandler-1.0: https://repos.wowace.com/wow/ace3/trunk/CallbackHandler-1.0
Libs/AceAddon-3.0: https://repos.wowace.com/wow/ace3/trunk/AceAddon-3.0
+ Libs/AceBucket-3.0: https://repos.wowace.com/wow/ace3/trunk/AceBucket-3.0
Libs/AceEvent-3.0: https://repos.wowace.com/wow/ace3/trunk/AceEvent-3.0
Libs/AceDB-3.0: https://repos.wowace.com/wow/ace3/trunk/AceDB-3.0
Libs/AceConsole-3.0: https://repos.wowace.com/wow/ace3/trunk/AceConsole-3.0
@@ -19,8 +20,8 @@ ignore:
- spec/*
- Makefile
- trunk
- - *.ps1
- - *.md
- - *.rockspec
+ - "*.ps1"
+ - "*.md"
+ - "*.rockspec"
- requirements.txt
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..24ee5b1
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.13
diff --git a/.scripts/hardcode_string_check.py b/.scripts/hardcode_string_check.py
index 623f114..642d867 100644
--- a/.scripts/hardcode_string_check.py
+++ b/.scripts/hardcode_string_check.py
@@ -68,6 +68,7 @@ def scan_directory(directory, ignore_files=None, ignore_dirs=None):
def main():
ignore_files = [
+ "IntegrationTest.lua",
"SmokeTest.lua",
]
ignore_dirs = [
diff --git a/BlizzOverrides/BossBanner.lua b/BlizzOverrides/BossBanner.lua
index 2c52634..8288532 100644
--- a/BlizzOverrides/BossBanner.lua
+++ b/BlizzOverrides/BossBanner.lua
@@ -1,28 +1,25 @@
local addonName, G_RLF = ...
-local RLF = G_RLF.RLF
+local BossBannerOverride = G_RLF.RLF:NewModule("BossBanner", "AceEvent-3.0", "AceHook-3.0", "AceTimer-3.0")
-local bossBannerAttempts = 0
+function BossBannerOverride:OnInitialize()
+ self:RegisterEvent("PLAYER_ENTERING_WORLD", "BossBannerHook")
+end
-function RLF:BossBannerHook()
+local bossBannerAttempts = 0
+function BossBannerOverride:BossBannerHook()
if self:IsHooked(BossBanner, "OnEvent") then
return
end
if BossBanner then
self:RawHookScript(BossBanner, "OnEvent", "InterceptBossBannerAlert", true)
+ self:UnregisterEvent("PLAYER_ENTERING_WORLD")
else
- if bossBannerAttempts <= 30 then
- bossBannerAttempts = bossBannerAttempts + 1
- -- Keep checking until it's available
- self:ScheduleTimer("BossBannerHook", 1)
- else
- self:Print(G_RLF.L["BossBannerAlertUnavailable"])
- self:Print(G_RLF.L["Issues"])
- end
+ bossBannerAttempts = G_RLF.retryHook(self, "BossBannerHook", bossBannerAttempts, "BossBannerAlertUnavailable")
end
end
-function RLF:InterceptBossBannerAlert(s, event, ...)
+function BossBannerOverride:InterceptBossBannerAlert(s, event, ...)
if G_RLF.db.global.bossBannerConfig == G_RLF.DisableBossBanner.FULLY_DISABLE then
return
end
@@ -55,3 +52,5 @@ function RLF:InterceptBossBannerAlert(s, event, ...)
-- Call the original AddAlert function if not blocked
self.hooks[BossBanner].OnEvent(s, event, ...)
end
+
+return BossBannerOverride
diff --git a/BlizzOverrides/LootToasts.lua b/BlizzOverrides/LootToasts.lua
index 478e52f..4c9f635 100644
--- a/BlizzOverrides/LootToasts.lua
+++ b/BlizzOverrides/LootToasts.lua
@@ -1,30 +1,30 @@
local addonName, G_RLF = ...
-local RLF = G_RLF.RLF
+local LootToastOverride = G_RLF.RLF:NewModule("LootToasts", "AceEvent-3.0", "AceHook-3.0", "AceTimer-3.0")
+
+function LootToastOverride:OnInitialize()
+ self:RegisterEvent("PLAYER_ENTERING_WORLD", "LootToastHook")
+end
local lootAlertAttempts = 0
-function RLF:LootToastHook()
- if RLF:IsHooked(LootAlertSystem, "AddAlert") then
+function LootToastOverride:LootToastHook()
+ if self:IsHooked(LootAlertSystem, "AddAlert") then
return
end
if LootAlertSystem and LootAlertSystem.AddAlert then
- RLF:RawHook(LootAlertSystem, "AddAlert", "InterceptAddAlert", true)
+ self:RawHook(LootAlertSystem, "AddAlert", "InterceptAddAlert", true)
+ self:UnregisterEvent("PLAYER_ENTERING_WORLD")
else
- if lootAlertAttempts <= 30 then
- lootAlertAttempts = lootAlertAttempts + 1
- -- Keep checking until it's available
- RLF:ScheduleTimer("LootToastHook", 1)
- else
- RLF:Print(G_RLF.L["AddLootAlertUnavailable"])
- RLF:Print(G_RLF.L["Issues"])
- end
+ lootAlertAttempts = G_RLF.retryHook(self, "LootToastHook", lootAlertAttempts, "AddLootAlertUnavailable")
end
end
-function RLF:InterceptAddAlert(frame, ...)
+function LootToastOverride:InterceptAddAlert(frame, ...)
if G_RLF.db.global.disableBlizzLootToasts then
return
end
-- Call the original AddAlert function if not blocked
- RLF.hooks[LootAlertSystem].AddAlert(frame, ...)
+ self.hooks[LootAlertSystem].AddAlert(frame, ...)
end
+
+return LootToastOverride
diff --git a/BlizzOverrides/MoneyAlerts.lua b/BlizzOverrides/MoneyAlerts.lua
index 03b5d49..29fa4b5 100644
--- a/BlizzOverrides/MoneyAlerts.lua
+++ b/BlizzOverrides/MoneyAlerts.lua
@@ -1,30 +1,30 @@
local addonName, G_RLF = ...
-local RLF = G_RLF.RLF
+local MoneyAlertOverride = G_RLF.RLF:NewModule("MoneyAlerts", "AceEvent-3.0", "AceHook-3.0", "AceTimer-3.0")
+
+function MoneyAlertOverride:OnInitialize()
+ self:RegisterEvent("PLAYER_ENTERING_WORLD", "MoneyAlertHook")
+end
local moneyAlertAttempts = 0
-function RLF:MoneyAlertHook()
- if RLF:IsHooked(MoneyWonAlertSystem, "AddAlert") then
+function MoneyAlertOverride:MoneyAlertHook()
+ if self:IsHooked(MoneyWonAlertSystem, "AddAlert") then
return
end
if MoneyWonAlertSystem and MoneyWonAlertSystem.AddAlert then
- RLF:RawHook(MoneyWonAlertSystem, "AddAlert", "InterceptMoneyAddAlert", true)
+ self:RawHook(MoneyWonAlertSystem, "AddAlert", "InterceptMoneyAddAlert", true)
+ self:UnregisterEvent("PLAYER_ENTERING_WORLD")
else
- if moneyAlertAttempts <= 30 then
- moneyAlertAttempts = moneyAlertAttempts + 1
- -- Keep checking until it's available
- RLF:ScheduleTimer("MoneyAlertHook", 1)
- else
- RLF:Print(G_RLF.L["AddMoneyAlertUnavailable"])
- RLF:Print(G_RLF.L["Issues"])
- end
+ moneyAlertAttempts = G_RLF.retryHook(self, "MoneyAlertHook", moneyAlertAttempts, "AddMoneyAlertUnavailable")
end
end
-function RLF:InterceptMoneyAddAlert(frame, ...)
+function MoneyAlertOverride:InterceptMoneyAddAlert(frame, ...)
if G_RLF.db.global.disableBlizzMoneyAlerts then
return
end
-- Call the original AddAlert function if not blocked
- RLF.hooks[MoneyWonAlertSystem].AddAlert(frame, ...)
+ self.hooks[MoneyWonAlertSystem].AddAlert(frame, ...)
end
+
+return MoneyAlertOverride
diff --git a/BlizzOverrides/overrides.xml b/BlizzOverrides/overrides.xml
index 6ba0096..36804bd 100644
--- a/BlizzOverrides/overrides.xml
+++ b/BlizzOverrides/overrides.xml
@@ -2,6 +2,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
+
diff --git a/BlizzOverrides/retryHook.lua b/BlizzOverrides/retryHook.lua
new file mode 100644
index 0000000..1292ac9
--- /dev/null
+++ b/BlizzOverrides/retryHook.lua
@@ -0,0 +1,16 @@
+local addonName, G_RLF = ...
+
+G_RLF.retryHook = function(module, previousAttempts, hookFunctionName, localeKey)
+ local attempts = previousAttempts
+
+ if attempts < 30 then
+ attempts = attempts + 1
+ -- Keep checking until it's available
+ module:ScheduleTimer(hookFunctionName, 1)
+ else
+ G_RLF:Print(G_RLF.L[localeKey])
+ G_RLF:Print(G_RLF.L["Issues"])
+ end
+
+ return attempts
+end
diff --git a/Core.lua b/Core.lua
index 7e73bd4..de5c50f 100644
--- a/Core.lua
+++ b/Core.lua
@@ -1,105 +1,114 @@
local addonName, G_RLF = ...
--- Define the global scope early so that the whole addon can use it
-local dbName = addonName .. "DB"
-local localeName = addonName .. "Locale"
-
-local xpcall = xpcall
-
-local function errorhandler(err)
- local suffix = "\n\n==== Addon Info " .. addonName .. " " .. G_RLF.addonVersion .. " ====\n\n"
- suffix = suffix .. G_RLF.L["Issues"] .. "\n\n"
-
- return geterrorhandler()(err .. suffix)
-end
-
-function G_RLF:fn(func, ...)
- -- Borrowed from AceAddon-3.0
- if type(func) == "function" then
- return xpcall(func, errorhandler, ...)
- end
-end
+G_RLF.addonVersion = "@project-version@-@project-revision@-@project-abbreviated-hash@"
-G_RLF.RLF = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0", "AceTimer-3.0")
-G_RLF.RLF:SetDefaultModuleState(true)
-G_RLF.RLF:SetDefaultModulePrototype({
- getLogger = function(self)
- return G_RLF.RLF:GetModule("Logger")
- end,
+local RLF = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0", "AceTimer-3.0")
+RLF:SetDefaultModuleState(true)
+RLF:SetDefaultModulePrototype({
fn = function(s, func, ...)
- local function errorhandler(err)
- local suffix = "\n\n==== Addon Info " .. addonName .. " " .. G_RLF.addonVersion .. " ====\n\n"
- local status, trace = pcall(function()
- return s:getLogger():Trace(s.moduleName)
- end)
- if status then
- suffix = suffix .. "Log traces related to " .. s.moduleName .. "\n"
- suffix = suffix .. "-------------------------------------------------\n"
- suffix = suffix .. trace
- suffix = suffix .. "-------------------------------------------------\n\n"
- end
- suffix = suffix .. G_RLF.L["Issues"] .. "\n\n"
-
- return geterrorhandler()(err .. suffix)
- end
-
- -- Borrowed from AceAddon-3.0
- if type(func) == "function" then
- return xpcall(func, errorhandler, ...)
- end
+ return G_RLF.fn(s, func, ...)
end,
})
-G_RLF.dbName = dbName
-G_RLF.localeName = localeName
-G_RLF.addonVersion = "@project-version@-@project-revision@-@project-abbreviated-hash@"
-G_RLF.DisableBossBanner = {
- ENABLED = 0,
- FULLY_DISABLE = 1,
- DISABLE_LOOT = 2,
- DISABLE_MY_LOOT = 3,
- DISABLE_GROUP_LOOT = 4,
-}
+G_RLF.localeName = addonName .. "Locale"
G_RLF.lsm = LibStub("LibSharedMedia-3.0")
G_RLF.Masque = LibStub and LibStub("Masque", true)
G_RLF.iconGroup = Masque and Masque:Group(addonName)
+local dbName = addonName .. "DB"
+local acd = LibStub("AceConfigDialog-3.0")
+local TestMode
+function RLF:OnInitialize()
+ G_RLF.db = LibStub("AceDB-3.0"):New(dbName, G_RLF.defaults, true)
+ LibStub("AceConfig-3.0"):RegisterOptionsTable(addonName, G_RLF.options)
+ local lsm = G_RLF.lsm
+ lsm:Register(lsm.MediaType.FONT, "BAR SADY Regular", "Interface\\AddOns\\RPGLootFeed\\Fonts\\BAR_SADY_Variable.ttf")
+ self:Hook(acd, "Open", "OnOptionsOpen")
+ self:RegisterEvent("PLAYER_ENTERING_WORLD")
+ self:RegisterChatCommand("rlf", "SlashCommand")
+ self:RegisterChatCommand("RLF", "SlashCommand")
+ self:RegisterChatCommand("rpglootfeed", "SlashCommand")
+ self:RegisterChatCommand("rpgLootFeed", "SlashCommand")
+
+ if EditModeManagerFrame then
+ EventRegistry:RegisterCallback("EditMode.Enter", function()
+ G_RLF.LootDisplay:SetBoundingBoxVisibility(true)
+ end)
+ EventRegistry:RegisterCallback("EditMode.Exit", function()
+ G_RLF.LootDisplay:SetBoundingBoxVisibility(false)
+ end)
+ end
-local acr = LibStub("AceConfigRegistry-3.0")
-
-function G_RLF:NotifyChange(...)
- acr:NotifyChange(...)
-end
-
-function G_RLF:SendMessage(...)
- G_RLF.RLF:SendMessage(...)
+ TestMode = self:GetModule("TestMode")
end
-function G_RLF:Print(...)
- G_RLF.RLF:Print(...)
+function RLF:SlashCommand(msg, editBox)
+ G_RLF:fn(function()
+ if msg == "test" then
+ TestMode:ToggleTestMode()
+ --@alpha@
+ elseif msg == "i" then
+ TestMode:IntegrationTest()
+ --@end-alpha@
+ elseif msg == "clear" then
+ G_RLF.LootDisplay:HideLoot()
+ elseif msg == "log" then
+ self:GetModule("Logger"):Show()
+ else
+ acd:Open(addonName)
+ end
+ end)
end
-function G_RLF:RGBAToHexFormat(r, g, b, a)
- local red = string.format("%02X", math.floor(r * 255))
- local green = string.format("%02X", math.floor(g * 255))
- local blue = string.format("%02X", math.floor(b * 255))
- local alpha = string.format("%02X", math.floor((a or 1) * 255)) -- Default alpha to 1 if not provided
+local currentVersion = "@project-version@"
+function RLF:PLAYER_ENTERING_WORLD(event, isLogin, isReload)
+ if self.optionsFrame == nil then
+ self.optionsFrame = acd:AddToBlizOptions(addonName, addonName)
+ end
- -- Return in WoW format with |c prefix
- return "|c" .. alpha .. red .. green .. blue
+ local isNewVersion = currentVersion ~= G_RLF.db.global.lastVersionLoaded
+ if isLogin and isReload == false and isNewVersion then
+ G_RLF.db.global.lastVersionLoaded = currentVersion
+ self:Print(G_RLF.L["Welcome"] .. " (" .. currentVersion .. ")")
+ if G_RLF.db.global.enableAutoLoot then
+ C_CVar.SetCVar("autoLootDefault", "1")
+ end
+ end
+ G_RLF.AuctionIntegrations:Init()
end
---@alpha@
-function G_RLF:ProfileFunction(func, funcName)
- return function(...)
- local startTime = debugprofilestop()
- local result = { func(...) }
- local endTime = debugprofilestop()
- local duration = endTime - startTime
- if duration > 0.3 then
- G_RLF:Print(string.format("%s took %.2f ms", funcName, endTime - startTime))
+local optionsFrame
+local isOpen = false
+function RLF:OnOptionsOpen(...)
+ local _, name, container, path = ...
+ G_RLF:fn(function()
+ if container then
+ return
end
+ if name == addonName and not isOpen then
+ isOpen = true
+ G_RLF.LootDisplay:SetBoundingBoxVisibility(true)
+ self:ScheduleTimer(function()
+ optionsFrame = acd.OpenFrames[name]
+ if self:IsHooked(optionsFrame, "Hide") then
+ self:Unhook(optionsFrame, "Hide")
+ end
+ if optionsFrame and optionsFrame.Hide then
+ self:Hook(optionsFrame, "Hide", "OnOptionsClose", true)
+ end
+ end, 0.25)
+ end
+ end)
+end
- return unpack(result)
- end
+function RLF:OnOptionsClose(...)
+ G_RLF:fn(function()
+ isOpen = false
+ G_RLF.LootDisplay:SetBoundingBoxVisibility(false)
+ self:Unhook(optionsFrame, "Hide")
+ optionsFrame = nil
+ end)
end
---@end-alpha@
+
+G_RLF.RLF = RLF
+
+return RLF
diff --git a/Features/Currency.lua b/Features/Currency.lua
index 558cb54..9a9478b 100644
--- a/Features/Currency.lua
+++ b/Features/Currency.lua
@@ -16,7 +16,8 @@ function Currency.Element:new(...)
element.isLink = true
local t
- element.key, t, element.icon, element.quantity = ...
+ element.key, t, element.icon, element.quantity, element.totalCount, element.quality, element.totalEarned, element.cappedQuantity =
+ ...
element.textFn = function(existingQuantity, truncatedLink)
if not truncatedLink then
@@ -25,28 +26,15 @@ function Currency.Element:new(...)
return truncatedLink .. " x" .. ((existingQuantity or 0) + element.quantity)
end
- local info = C_CurrencyInfo.GetCurrencyInfo(element.key)
-
- element.quality = info.quality
- element.currentTotal = info.quantity
- element.totalEarned = info.totalEarned
- element.cappedQuantity = info.maxQuantity
-
element.secondaryTextFn = function(...)
- if element.currentTotal == 0 then
- return ""
- end
-
- local str = " |cFFBABABA" .. element.currentTotal .. "|r"
-
- if element.cappedQuantity > 0 then
+ if element.cappedQuantity and element.cappedQuantity > 0 then
local percentage, numerator
if element.totalEarned > 0 then
numerator = element.totalEarned
percentage = element.totalEarned / element.cappedQuantity
else
- numerator = element.currentTotal
- percentage = element.currentTotal / element.cappedQuantity
+ numerator = element.totalCount
+ percentage = element.totalCount / element.cappedQuantity
end
local color
if percentage < 0.7 then
@@ -57,10 +45,10 @@ function Currency.Element:new(...)
color = "|cFFFF0000"
end
- str = str .. " " .. color .. "(" .. numerator .. " / " .. element.cappedQuantity .. ")|r"
+ return " " .. color .. numerator .. " / " .. element.cappedQuantity .. "|r"
end
- return str
+ return ""
end
return element
@@ -91,10 +79,10 @@ function Currency:OnEnable()
end
function Currency:Process(eventName, currencyType, quantityChange)
- self:getLogger():Info(eventName, "WOWEVENT", self.moduleName, currencyType, eventName, quantityChange)
+ G_RLF:LogInfo(eventName, "WOWEVENT", self.moduleName, currencyType, eventName, quantityChange)
if currencyType == nil or not quantityChange or quantityChange <= 0 then
- self:getLogger():Debug(
+ G_RLF:LogDebug(
"Skip showing currency",
addonName,
self.moduleName,
@@ -106,7 +94,7 @@ function Currency:Process(eventName, currencyType, quantityChange)
end
if isHiddenCurrency(currencyType) then
- self:getLogger():Debug(
+ G_RLF:LogDebug(
"Skip showing currency",
addonName,
self.moduleName,
@@ -119,7 +107,7 @@ function Currency:Process(eventName, currencyType, quantityChange)
local info = C_CurrencyInfo.GetCurrencyInfo(currencyType)
if info == nil or info.description == "" or info.iconFileID == nil then
- self:getLogger():Debug(
+ G_RLF:LogDebug(
"Skip showing currency",
addonName,
self.moduleName,
@@ -136,7 +124,11 @@ function Currency:Process(eventName, currencyType, quantityChange)
info.currencyID,
C_CurrencyInfo.GetCurrencyLink(currencyType),
info.iconFileID,
- basicInfo.displayAmount
+ basicInfo.displayAmount,
+ info.quantity,
+ info.quality,
+ info.totalEarned,
+ info.maxQuantity
)
e:Show()
end)
@@ -149,11 +141,17 @@ function Currency:CURRENCY_DISPLAY_UPDATE(eventName, ...)
end
function Currency:PERKS_PROGRAM_CURRENCY_AWARDED(eventName, quantityChange)
- local currencyType = 2032 -- https://www.wowhead.com/currency=2032/traders-tender
+ local currencyType = Constants.CurrencyConsts.CURRENCY_ID_PERKS_PROGRAM_DISPLAY_INFO
self:Process(eventName, currencyType, quantityChange)
end
+-- Handle Lifetime Honor
+-- LIFETIME_HONOR
+-- Constants.CurrencyConsts.ACCOUNT_WIDE_HONOR_CURRENCY_ID
+-- print(UnitHonorLevel("player"))
+-- print(UnitHonor("player") .. "/" .. UnitHonorMax("player"))
+
hiddenCurrencies = {
[2918] = true,
[2919] = true,
diff --git a/Features/Experience.lua b/Features/Experience.lua
index 149efee..93300e6 100644
--- a/Features/Experience.lua
+++ b/Features/Experience.lua
@@ -20,6 +20,7 @@ function Xp.Element:new(...)
element.textFn = function(existingXP)
return "+" .. ((existingXP or 0) + element.quantity) .. " " .. G_RLF.L["XP"]
end
+ element.currentLevel = currentLevel
element.secondaryTextFn = function()
if not currentXP then
@@ -28,14 +29,9 @@ function Xp.Element:new(...)
if not currentMaxXP then
return ""
end
- local color = G_RLF:RGBAToHexFormat(1, 1, 1, 1)
+ local color = G_RLF:RGBAToHexFormat(element.r, element.g, element.b, element.a)
- return " "
- .. color
- .. currentLevel
- .. "|r "
- .. math.floor((currentXP / currentMaxXP) * 10000) / 100
- .. "%"
+ return " " .. color .. math.floor((currentXP / currentMaxXP) * 10000) / 100 .. "%|r"
end
return element
@@ -69,19 +65,19 @@ function Xp:OnEnable()
end
function Xp:PLAYER_ENTERING_WORLD(eventName)
- self:getLogger():Info(eventName, "WOWEVENT", self.moduleName)
+ G_RLF:LogInfo(eventName, "WOWEVENT", self.moduleName)
self:fn(initXpValues)
end
function Xp:PLAYER_XP_UPDATE(eventName, unitTarget)
- self:getLogger():Info(eventName, "WOWEVENT", self.moduleName, unitTarget)
+ G_RLF:LogInfo(eventName, "WOWEVENT", self.moduleName, unitTarget)
self:fn(function()
if unitTarget == "player" then
local newLevel = UnitLevel(unitTarget)
local newCurrentXP = UnitXP(unitTarget)
local delta = 0
if newLevel == nil then
- self:getLogger():Warn("Could not get player level", addonName, self.moduleName)
+ G_RLF:LogWarn("Could not get player level", addonName, self.moduleName)
return
end
currentLevel = currentLevel or newLevel
@@ -97,7 +93,7 @@ function Xp:PLAYER_XP_UPDATE(eventName, unitTarget)
local e = self.Element:new(delta)
e:Show()
else
- self:getLogger():Warn(eventName .. " fired but delta was not positive", addonName, self.moduleName)
+ G_RLF:LogWarn(eventName .. " fired but delta was not positive", addonName, self.moduleName)
end
end
end)
diff --git a/Features/ItemLoot.lua b/Features/ItemLoot.lua
deleted file mode 100644
index 5d80a61..0000000
--- a/Features/ItemLoot.lua
+++ /dev/null
@@ -1,264 +0,0 @@
-local addonName, G_RLF = ...
-
-local ItemLoot = G_RLF.RLF:NewModule("ItemLoot", "AceEvent-3.0")
-
-ItemLoot.SecondaryTextOption = {
- ["None"] = "None",
- ["SellPrice"] = "Sell Price",
- ["iLvl"] = "Item Level",
-}
-
--- local equipLocToSlotID = {
--- ["INVTYPE_HEAD"] = INVSLOT_HEAD,
--- ["INVTYPE_NECK"] = INVSLOT_NECK,
--- ["INVTYPE_SHOULDER"] = INVSLOT_SHOULDER,
--- ["INVTYPE_CHEST"] = INVSLOT_CHEST,
--- ["INVTYPE_WAIST"] = INVSLOT_WAIST,
--- ["INVTYPE_LEGS"] = INVSLOT_LEGS,
--- ["INVTYPE_FEET"] = INVSLOT_FEET,
--- ["INVTYPE_WRIST"] = INVSLOT_WRIST,
--- ["INVTYPE_HAND"] = INVSLOT_HAND,
--- ["INVSLOT_FINGER1"] = INVSLOT_FINGER1,
--- ["INVSLOT_FINGER2"] = INVSLOT_FINGER2,
--- ["INVSLOT_TRINKET1"] = INVSLOT_TRINKET1,
--- ["INVSLOT_TRINKET2"] = INVSLOT_TRINKET2,
--- ["INVTYPE_BACK"] = INVSLOT_BACK,
--- ["INVTYPE_MAINHAND"] = INVSLOT_MAINHAND,
--- ["INVTYPE_OFFHAND"] = INVSLOT_OFFHAND,
--- ["INVTYPE_RANGED"] = INVSLOT_RANGED,
--- ["INVTYPE_WEAPON"] = INVSLOT_MAINHAND, -- Generally used for one-handed weapons
--- ["INVTYPE_2HWEAPON"] = INVSLOT_MAINHAND, -- Two-handed weapons
--- ["INVTYPE_RANGEDRIGHT"] = INVSLOT_RANGED, -- Ranged weapons
--- }
-
-ItemLoot.Element = {}
-
-local function itemQualityName(enumValue)
- for k, v in pairs(Enum.ItemQuality) do
- if v == enumValue then
- return k
- end
- end
- return nil
-end
-
-local nameUnitMap = {}
-
-local function setNameUnitMap()
- local units = {}
- if IsInRaid() then
- for i = 1, MEMBERS_PER_RAID_GROUP do
- table.insert(units, "raid" .. i)
- end
- else
- table.insert(units, "player")
-
- for i = 2, MEMBERS_PER_RAID_GROUP do
- table.insert(units, "party" .. (i - 1))
- end
- end
-
- nameUnitMap = {}
- for _, unit in ipairs(units) do
- local name, server = UnitName(unit)
- if name then
- nameUnitMap[name] = unit
- end
- end
-end
-
-function ItemLoot.Element:new(...)
- local element = {}
- G_RLF.InitializeLootDisplayProperties(element)
-
- element.type = "ItemLoot"
- element.IsEnabled = function()
- return ItemLoot:IsEnabled()
- end
-
- element.isLink = true
-
- local t
- element.key, t, element.icon, element.quantity, element.sellPrice, element.unit = ...
-
- if not G_RLF.db.global.enablePartyLoot then
- element.unit = nil
- end
-
- function element:isPassingFilter(itemName, itemQuality)
- if not G_RLF.db.global.itemQualityFilter[itemQuality] then
- element:getLogger():Debug(
- itemName .. " ignored by quality: " .. itemQualityName(itemQuality),
- addonName,
- "ItemLoot",
- "",
- nil,
- self.quantity
- )
- return false
- end
-
- -- if G_RLF.db.global.onlyBetterThanEquipped and itemEquipLoc then
- -- local equippedLink = GetInventoryItemLink("player", equipLocToSlotID[itemEquipLoc])
- -- if equippedLink then
- -- local _, _, _, equippediLvl, _, _, equippedSubType = C_Item.GetItemInfo(equippedLink)
- -- if equippediLvl > itemLevel then
- -- return
- -- elseif equippedSubType ~= itemSubType then
- -- return
- -- end
- -- end
- -- end
-
- return true
- end
-
- element.textFn = function(existingQuantity, truncatedLink)
- if not truncatedLink then
- return t
- end
- return truncatedLink .. " x" .. ((existingQuantity or 0) + element.quantity)
- end
-
- element.secondaryTextFn = function(...)
- if element.unit then
- local name, server = UnitName(element.unit)
- if server then
- return " " .. name .. "-" .. server
- end
- return " " .. name
- end
- local quantity = ...
- if not element.sellPrice or element.sellPrice == 0 then
- return ""
- end
- return " " .. C_CurrencyInfo.GetCoinTextureString(element.sellPrice * (quantity or 1))
- end
-
- return element
-end
-
-local logger
-function ItemLoot:OnInitialize()
- if G_RLF.db.global.itemLootFeed then
- self:Enable()
- else
- self:Disable()
- end
-end
-
-function ItemLoot:OnDisable()
- self:UnregisterEvent("CHAT_MSG_LOOT")
- self:UnregisterEvent("GET_ITEM_INFO_RECEIVED")
- self:UnregisterEvent("GROUP_ROSTER_UPDATE")
-end
-
-function ItemLoot:OnEnable()
- self:RegisterEvent("CHAT_MSG_LOOT")
- self:RegisterEvent("GET_ITEM_INFO_RECEIVED")
- self:RegisterEvent("GROUP_ROSTER_UPDATE")
- setNameUnitMap()
-end
-
-local pendingItemRequests = {}
-local function onItemReadyToShow(itemId, itemLink, itemTexture, amount, itemName, itemQuality, sellPrice)
- pendingItemRequests[itemId] = nil
- local e = ItemLoot.Element:new(itemId, itemLink, itemTexture, amount, sellPrice, false)
- e:Show(itemName, itemQuality)
-end
-
-local pendingPartyRequests = {}
-local function onPartyReadyToShow(itemId, itemLink, itemTexture, amount, itemName, itemQuality, sellPrice, unit)
- pendingPartyRequests[itemId] = nil
- local e = ItemLoot.Element:new(itemId, itemLink, itemTexture, amount, sellPrice, unit)
- e:Show(itemName, itemQuality)
-end
-
-function ItemLoot:GET_ITEM_INFO_RECEIVED(eventName, itemID, success)
- if pendingItemRequests[itemID] then
- local itemLink, amount = unpack(pendingItemRequests[itemID])
-
- if not success then
- error("Failed to load item: " .. itemID .. " " .. itemLink .. " x" .. amount)
- else
- local itemName, _, itemQuality, _, _, _, _, _, _, itemTexture, sellPrice, _, _, _, _, _, _ =
- C_Item.GetItemInfo(itemLink)
- onItemReadyToShow(itemID, itemLink, itemTexture, amount, itemName, itemQuality, sellPrice)
- end
- return
- end
-
- if pendingPartyRequests[itemID] then
- local itemLink, amount, unit = unpack(pendingPartyRequests[itemID])
-
- if not success then
- error("Failed to load item: " .. itemID .. " " .. itemLink .. " x" .. amount .. " for " .. unit)
- else
- local itemName, _, itemQuality, _, _, _, _, _, _, itemTexture, sellPrice, _, _, _, _, _, _ =
- C_Item.GetItemInfo(itemLink)
- onPartyReadyToShow(itemID, itemLink, itemTexture, amount, itemName, itemQuality, sellPrice, unit)
- end
- return
- end
-end
-
-local function showPartyLoot(msg, itemLink, unit)
- local amount = tonumber(msg:match("r ?x(%d+)") or 1)
- local itemId = itemLink:match("Hitem:(%d+)")
- pendingPartyRequests[itemId] = { itemLink, amount, unit }
- local itemName, _, itemQuality, _, _, _, _, _, _, itemTexture, sellPrice, _, _, _, _, _, _ =
- C_Item.GetItemInfo(itemLink)
- if itemName ~= nil then
- onPartyReadyToShow(itemId, itemLink, itemTexture, amount, itemName, itemQuality, sellPrice, unit)
- end
-end
-
-local function showItemLoot(msg, itemLink)
- local amount = tonumber(msg:match("r ?x(%d+)") or 1)
- local itemId = itemLink:match("Hitem:(%d+)")
- pendingItemRequests[itemId] = { itemLink, amount }
- local itemName, _, itemQuality, _, _, _, _, _, _, itemTexture, sellPrice, _, _, _, _, _, _ =
- C_Item.GetItemInfo(itemLink)
- if itemName ~= nil then
- onItemReadyToShow(itemId, itemLink, itemTexture, amount, itemName, itemQuality, sellPrice)
- end
-end
-
-function ItemLoot:CHAT_MSG_LOOT(eventName, ...)
- local msg, playerName, _, _, playerName2, _, _, _, _, _, _, guid = ...
- local raidLoot = msg:match("HlootHistory:")
- self:getLogger():Info(eventName, "WOWEVENT", self.moduleName, nil, eventName .. " " .. msg)
- if raidLoot then
- -- Ignore this message as it's a raid loot message
- self:getLogger():Debug("Raid Loot Ignored", "WOWEVENT", self.moduleName, "", msg)
- return
- end
-
- local me = guid == GetPlayerGuid()
- if not me then
- if not G_RLF.db.global.enablePartyLoot then
- self:getLogger():Debug("Party Loot Ignored", "WOWEVENT", self.moduleName, "", msg)
- return
- end
- local sanitizedPlayerName = (playerName or playerName2):gsub("%-.+", "")
- local unit = nameUnitMap[sanitizedPlayerName]
- local itemLink = msg:match("|c%x+|Hitem:.-|h%[.-%]|h|r")
- if itemLink then
- self:fn(showPartyLoot, msg, itemLink, unit)
- end
- return
- end
-
- local itemLink = msg:match("|c%x+|Hitem:.-|h%[.-%]|h|r")
- if itemLink then
- self:fn(showItemLoot, msg, itemLink)
- end
-end
-
-function ItemLoot:GROUP_ROSTER_UPDATE(eventName, ...)
- self:getLogger():Info(eventName, "WOWEVENT", self.moduleName, nil, eventName)
-
- setNameUnitMap()
-end
-
-return ItemLoot
diff --git a/Features/ItemLoot/AuctionIntegrations.lua b/Features/ItemLoot/AuctionIntegrations.lua
new file mode 100644
index 0000000..0d8e651
--- /dev/null
+++ b/Features/ItemLoot/AuctionIntegrations.lua
@@ -0,0 +1,115 @@
+local addonName, G_RLF = ...
+
+local AuctionIntegrations = {}
+local Integ_Auctionator = {}
+local Integ_TSM = {}
+local Integ_Nil = {}
+
+function AuctionIntegrations:Init()
+ if self.initialized then
+ return
+ end
+
+ self.initialized = true
+ local possibleIntegrations = { Integ_Auctionator, Integ_TSM }
+ self.nilIntegration = Integ_Nil
+ self.activeIntegrations = {}
+
+ self.numActiveIntegrations = 0
+ for _, integration in ipairs(possibleIntegrations) do
+ if integration:Init() then
+ self.activeIntegrations[integration:ToString()] = integration
+ self.numActiveIntegrations = self.numActiveIntegrations + 1
+ end
+ end
+
+ G_RLF:LogDebug("Active AH integrations: " .. self.numActiveIntegrations)
+
+ if self.numActiveIntegrations == 1 then
+ for _, integration in pairs(self.activeIntegrations) do
+ self.activeIntegration = integration
+ end
+ elseif G_RLF.db.global.auctionHouseSource then
+ if G_RLF.db.global.auctionHouseSource == Integ_Nil:ToString() then
+ self.activeIntegration = self.nilIntegration
+ end
+ self.activeIntegration = self.activeIntegrations[G_RLF.db.global.auctionHouseSource]
+ if not self.activeIntegration then
+ self.activeIntegration = self.nilIntegration
+ end
+ end
+
+ if G_RLF.db.global.auctionHouseSource ~= self.activeIntegration:ToString() then
+ G_RLF.db.global.auctionHouseSource = self.activeIntegration:ToString()
+ end
+end
+
+function AuctionIntegrations:GetAHPrice(itemLink)
+ if self.activeIntegration then
+ return self.activeIntegration:GetAHPrice(itemLink)
+ end
+
+ return nil
+end
+
+function Integ_Nil:Init()
+ return true
+end
+
+function Integ_Nil:ToString()
+ return G_RLF.L["None"]
+end
+
+function Integ_Nil:GetAHPrice(itemLink)
+ return nil
+end
+
+function Integ_Auctionator:Init()
+ if Auctionator and Auctionator.API and Auctionator.API.v1 and Auctionator.API.v1.GetAuctionPriceByItemLink then
+ Auctionator.API.v1.RegisterForDBUpdate(addonName, function()
+ G_RLF:LogDebug("Auctionator DB updated")
+ end)
+ return true
+ end
+
+ return false
+end
+
+function Integ_Auctionator:ToString()
+ return G_RLF.L["Auctionator"]
+end
+
+function Integ_Auctionator:GetAHPrice(itemLink)
+ local price = Auctionator.API.v1.GetAuctionPriceByItemLink(addonName, itemLink)
+ if price then
+ return price
+ end
+
+ return nil
+end
+
+function Integ_TSM:Init()
+ return TSM_API ~= nil
+end
+
+function Integ_TSM:ToString()
+ return G_RLF.L["TSM"]
+end
+
+function Integ_TSM:GetAHPrice(itemLink)
+ if TSM_API and TSM_API.ToItemString and TSM_API.GetCustomPriceValue then
+ local itemString = TSM_API.Item:ToItemString(itemLink)
+ if itemString then
+ local marketValue = TSM_API:GetCustomPriceValue("DBMarket", itemString)
+ if marketValue then
+ return marketValue
+ end
+ end
+ end
+
+ return nil
+end
+
+G_RLF.AuctionIntegrations = AuctionIntegrations
+
+return AuctionIntegrations
diff --git a/Features/ItemLoot/ItemLoot.lua b/Features/ItemLoot/ItemLoot.lua
new file mode 100644
index 0000000..b9326ca
--- /dev/null
+++ b/Features/ItemLoot/ItemLoot.lua
@@ -0,0 +1,338 @@
+local addonName, G_RLF = ...
+
+local ItemLoot = G_RLF.RLF:NewModule("ItemLoot", "AceEvent-3.0")
+
+ItemLoot.SecondaryTextOption = {
+ ["None"] = "None",
+ ["SellPrice"] = "Sell Price",
+ ["iLvl"] = "Item Level",
+}
+
+local cachedArmorClass = nil
+local onlyEpicPartyLoot = false
+
+ItemLoot.Element = {}
+
+local ItemInfo = G_RLF.ItemInfo
+
+function ItemLoot:ItemQualityName(enumValue)
+ for k, v in pairs(Enum.ItemQuality) do
+ if v == enumValue then
+ return k
+ end
+ end
+ return nil
+end
+
+function ItemLoot:SetNameUnitMap()
+ local units = {}
+ local groupMembers = GetNumGroupMembers()
+ if IsInRaid() then
+ for i = 1, groupMembers do
+ table.insert(units, "raid" .. i)
+ end
+ else
+ table.insert(units, "player")
+
+ for i = 2, groupMembers do
+ table.insert(units, "party" .. (i - 1))
+ end
+ end
+
+ self.nameUnitMap = {}
+ for _, unit in ipairs(units) do
+ local name, server = UnitName(unit)
+ if name then
+ self.nameUnitMap[name] = unit
+ end
+ end
+end
+
+function ItemLoot:SetPartyLootFilters()
+ if IsInRaid() then
+ onlyEpicPartyLoot = true
+ return
+ end
+
+ if IsInInstance() then
+ onlyEpicPartyLoot = true
+ return
+ end
+
+ onlyEpicPartyLoot = false
+end
+
+local function IsMount(info)
+ if G_RLF.db.global.itemHighlights.mounts then
+ return info:IsMount()
+ end
+end
+
+local function IsLegendary(info)
+ if G_RLF.db.global.itemHighlights.legendaries then
+ return info:IsLegendary()
+ end
+end
+
+local function IsBetterThanEquipped(info)
+ -- Highlight Better Than Equipped
+ if G_RLF.db.global.itemHighlights.betterThanEquipped then
+ local equippedLink
+ if type(slot) == "table" then
+ for _, s in ipairs(slot) do
+ equippedLink = GetInventoryItemLink("player", s)
+ if equippedLink then
+ break
+ end
+ end
+ else
+ equippedLink = GetInventoryItemLink("player", slot)
+ end
+
+ if not equippedLink then
+ return
+ end
+
+ local equippedId = C_Item.GetItemIDForItemInfo(equippedLink)
+ local equippedInfo = ItemInfo:new(equippedId, C_Item.GetItemInfo(equippedLink))
+ if not equippedInfo then
+ return
+ end
+
+ if equippedInfo.itemLevel and equippedInfo.itemLevel < info.itemLevel then
+ self.highlight = true
+ return
+ elseif equippedInfo.itemLevel == info.itemLevel then
+ local statDelta = C_Item.GetItemStatDelta(equippedLink, info.itemLink)
+ for k, v in pairs(statDelta) do
+ -- Has a Tertiary Stat
+ if k:find("ITEM_MOD_CR_") and v > 0 then
+ self.highlight = true
+ return
+ end
+ -- Has a Gem Socket
+ if k:find("EMPTY_SOCKET_") and v > 0 then
+ self.highlight = true
+ return
+ end
+ end
+ end
+ end
+end
+
+function ItemLoot.Element:new(...)
+ local element = {}
+ G_RLF.InitializeLootDisplayProperties(element)
+
+ element.type = "ItemLoot"
+ element.IsEnabled = function()
+ return ItemLoot:IsEnabled()
+ end
+
+ element.isLink = true
+
+ local itemLink, info
+ info, element.quantity, element.unit = ...
+ itemLink = info.itemLink
+
+ element.key = info.itemId
+ element.icon = info.itemTexture
+ element.sellPrice = info.sellPrice
+
+ if not G_RLF.db.global.enablePartyLoot then
+ element.unit = nil
+ end
+
+ function element:isPassingFilter(itemName, itemQuality)
+ if not G_RLF.db.global.itemQualityFilter[itemQuality] then
+ G_RLF:LogDebug(
+ itemName .. " ignored by quality: " .. ItemLoot:ItemQualityName(itemQuality),
+ addonName,
+ "ItemLoot",
+ "",
+ nil,
+ self.quantity
+ )
+ return false
+ end
+
+ return true
+ end
+
+ element.textFn = function(existingQuantity, truncatedLink)
+ if not truncatedLink then
+ return itemLink
+ end
+ return truncatedLink .. " x" .. ((existingQuantity or 0) + element.quantity)
+ end
+
+ element.secondaryTextFn = function(...)
+ if element.unit then
+ local name, server = UnitName(element.unit)
+ if not name then
+ return "A former party member"
+ end
+ if server then
+ return " " .. name .. "-" .. server
+ end
+ return " " .. name
+ end
+ local quantity = ...
+ local atlasIconSize = G_RLF.db.global.fontSize * 1.5
+ if G_RLF.db.global.pricesForSellableItems == G_RLF.PricesEnum.Vendor then
+ if not element.sellPrice or element.sellPrice == 0 then
+ return ""
+ end
+ local sellAtlasStr = "|A:spellicon-256x256-selljunk:" .. atlasIconSize .. ":" .. atlasIconSize .. ":0:0|a "
+ return " " .. sellAtlasStr .. C_CurrencyInfo.GetCoinTextureString(element.sellPrice * (quantity or 1))
+ elseif G_RLF.db.global.pricesForSellableItems == G_RLF.PricesEnum.AH then
+ local marketPrice = G_RLF.AuctionIntegrations.activeIntegration:GetAHPrice(itemLink)
+ if not marketPrice or marketPrice == 0 then
+ return ""
+ end
+ local ahAtlasStr = "|A:auctioneer:" .. atlasIconSize .. ":" .. atlasIconSize .. ":0:0|a "
+ return " " .. ahAtlasStr .. C_CurrencyInfo.GetCoinTextureString(marketPrice * (quantity or 1))
+ end
+
+ return ""
+ end
+
+ function element:SetHighlight()
+ self.highlight = IsMount(info) or IsLegendary(info) or IsBetterThanEquipped(info)
+ end
+
+ return element
+end
+
+local logger
+function ItemLoot:OnInitialize()
+ self.pendingItemRequests = {}
+ self.pendingPartyRequests = {}
+ self.nameUnitMap = {}
+ if G_RLF.db.global.itemLootFeed then
+ self:Enable()
+ else
+ self:Disable()
+ end
+end
+
+function ItemLoot:OnDisable()
+ self:UnregisterEvent("CHAT_MSG_LOOT")
+ self:UnregisterEvent("GET_ITEM_INFO_RECEIVED")
+ self:UnregisterEvent("GROUP_ROSTER_UPDATE")
+end
+
+function ItemLoot:OnEnable()
+ self:RegisterEvent("CHAT_MSG_LOOT")
+ self:RegisterEvent("GET_ITEM_INFO_RECEIVED")
+ self:RegisterEvent("GROUP_ROSTER_UPDATE")
+ self:SetNameUnitMap()
+end
+
+function ItemLoot:OnItemReadyToShow(info, amount)
+ self.pendingItemRequests[info.itemId] = nil
+ local e = ItemLoot.Element:new(info, amount, false)
+ e:SetHighlight()
+ e:Show(info.itemName, info.itemQuality)
+end
+
+function ItemLoot:OnPartyReadyToShow(info, amount, unit)
+ if not unit then
+ return
+ end
+ if onlyEpicPartyLoot and info.itemQuality < Enum.ItemQuality.Epic then
+ return
+ end
+ self.pendingPartyRequests[info.itemId] = nil
+ local e = ItemLoot.Element:new(info, amount, unit)
+ e:Show(info.itemName, info.itemQuality)
+end
+
+function ItemLoot:GET_ITEM_INFO_RECEIVED(eventName, itemID, success)
+ if self.pendingItemRequests[itemID] then
+ local itemLink, amount = unpack(self.pendingItemRequests[itemID])
+
+ if not success then
+ error("Failed to load item: " .. itemID .. " " .. itemLink .. " x" .. amount)
+ else
+ local info = ItemInfo:new(itemID, C_Item.GetItemInfo(itemLink))
+ self:OnItemReadyToShow(info, amount)
+ end
+ return
+ end
+
+ if self.pendingPartyRequests[itemID] then
+ local itemLink, amount, unit = unpack(self.pendingPartyRequests[itemID])
+
+ if not success then
+ error("Failed to load item: " .. itemID .. " " .. itemLink .. " x" .. amount .. " for " .. unit)
+ else
+ local info = ItemInfo:new(itemID, C_Item.GetItemInfo(itemLink))
+ self:OnPartyReadyToShow(info, amount, unit)
+ end
+ return
+ end
+end
+
+function ItemLoot:ShowPartyLoot(msg, itemLink, unit)
+ local amount = tonumber(msg:match("r ?x(%d+)") or 1)
+ local itemId = itemLink:match("Hitem:(%d+)")
+ self.pendingPartyRequests[itemId] = { itemLink, amount, unit }
+ local info = ItemInfo:new(itemId, C_Item.GetItemInfo(itemLink))
+ if info ~= nil then
+ self:OnPartyReadyToShow(info, amount, unit)
+ end
+end
+
+function ItemLoot:ShowItemLoot(msg, itemLink)
+ local amount = tonumber(msg:match("r ?x(%d+)") or 1)
+ local itemId = C_Item.GetItemIDForItemInfo(itemLink)
+ self.pendingItemRequests[itemId] = { itemLink, amount }
+ local info = ItemInfo:new(itemId, C_Item.GetItemInfo(itemLink))
+ if info ~= nil then
+ self:OnItemReadyToShow(info, amount)
+ end
+end
+
+function ItemLoot:CHAT_MSG_LOOT(eventName, ...)
+ local msg, playerName, _, _, playerName2, _, _, _, _, _, _, guid = ...
+ local raidLoot = msg:match("HlootHistory:")
+ G_RLF:LogInfo(eventName, "WOWEVENT", self.moduleName, nil, eventName .. " " .. msg)
+ if raidLoot then
+ -- Ignore this message as it's a raid loot message
+ G_RLF:LogDebug("Raid Loot Ignored", "WOWEVENT", self.moduleName, "", msg)
+ return
+ end
+
+ local me = guid == GetPlayerGuid()
+ if not me then
+ if not G_RLF.db.global.enablePartyLoot then
+ G_RLF:LogDebug("Party Loot Ignored", "WOWEVENT", self.moduleName, "", msg)
+ return
+ end
+ local sanitizedPlayerName = (playerName or playerName2):gsub("%-.+", "")
+ local unit = self.nameUnitMap[sanitizedPlayerName]
+ if not unit then
+ return
+ end
+ local itemLink = msg:match("|c%x+|Hitem:.-|h%[.-%]|h|r")
+ if itemLink then
+ self:fn(self.ShowPartyLoot, self, msg, itemLink, unit)
+ end
+ return
+ end
+
+ local itemLink = msg:match("|c%x+|Hitem:.-|h%[.-%]|h|r")
+ if itemLink then
+ self:fn(self.ShowItemLoot, self, msg, itemLink)
+ end
+end
+
+function ItemLoot:GROUP_ROSTER_UPDATE(eventName, ...)
+ G_RLF:LogInfo(eventName, "WOWEVENT", self.moduleName, nil, eventName)
+
+ self:SetNameUnitMap()
+ self:SetPartyLootFilters()
+end
+
+return ItemLoot
diff --git a/Features/ItemLoot/ItemLoot.xml b/Features/ItemLoot/ItemLoot.xml
new file mode 100644
index 0000000..816756f
--- /dev/null
+++ b/Features/ItemLoot/ItemLoot.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/Features/LootDisplayProperties.lua b/Features/LootDisplayProperties.lua
index 9b3fa8f..9f5c364 100644
--- a/Features/LootDisplayProperties.lua
+++ b/Features/LootDisplayProperties.lua
@@ -10,6 +10,7 @@ G_RLF.LootDisplayProperties = {
"icon",
"quantity",
"quality",
+ "totalCount",
"r",
"g",
"b",
@@ -24,17 +25,13 @@ function G_RLF.InitializeLootDisplayProperties(self)
self.isLink = false
- self.getLogger = function()
- return G_RLF.RLF:GetModule("Logger")
- end
-
self.isPassingFilter = function()
return true
end
- self.Show = function(_, itemName, itemQuality)
+ self.Show = function(element, itemName, itemQuality)
if self:isPassingFilter(itemName, itemQuality) then
- G_RLF.LootDisplay:ShowLoot(self)
+ G_RLF:SendMessage("RLF_NEW_LOOT", self)
end
end
@@ -48,6 +45,8 @@ function G_RLF.InitializeLootDisplayProperties(self)
amountLogText = format("%s (diff: %s%s)", amount, sign, math.abs(self.quantity))
end
- self:getLogger():Info(self.type .. "Shown", addonName, self.type, self.key, text, amountLogText, new)
+ G_RLF:LogInfo(self.type .. "Shown", addonName, self.type, self.key, text, amountLogText, new)
end
end
+
+return G_RLF.LootDisplayProperties
diff --git a/Features/Money.lua b/Features/Money.lua
index e108bab..2da882e 100644
--- a/Features/Money.lua
+++ b/Features/Money.lua
@@ -4,8 +4,6 @@ local Money = G_RLF.RLF:NewModule("Money", "AceEvent-3.0")
Money.Element = {}
-local startingMoney
-
function Money.Element:new(...)
local element = {}
G_RLF.InitializeLootDisplayProperties(element)
@@ -41,6 +39,7 @@ function Money.Element:new(...)
end
function Money:OnInitialize()
+ self.startingMoney = 0
if G_RLF.db.global.moneyFeed then
self:Enable()
else
@@ -56,20 +55,20 @@ end
function Money:OnEnable()
self:RegisterEvent("PLAYER_MONEY")
self:RegisterEvent("PLAYER_ENTERING_WORLD")
- startingMoney = GetMoney()
+ self.startingMoney = GetMoney()
end
function Money:PLAYER_ENTERING_WORLD(eventName)
- self:getLogger():Info(eventName, "WOWEVENT", self.moduleName)
- startingMoney = GetMoney()
+ G_RLF:LogInfo(eventName, "WOWEVENT", self.moduleName)
+ self.startingMoney = GetMoney()
end
function Money:PLAYER_MONEY(eventName)
- self:getLogger():Info(eventName, "WOWEVENT", self.moduleName)
+ G_RLF:LogInfo(eventName, "WOWEVENT", self.moduleName)
self:fn(function()
local newMoney = GetMoney()
- local amountInCopper = newMoney - startingMoney
- startingMoney = newMoney
+ local amountInCopper = newMoney - self.startingMoney
+ self.startingMoney = newMoney
local e = self.Element:new(amountInCopper)
e:Show()
end)
diff --git a/Features/Professions.lua b/Features/Professions.lua
new file mode 100644
index 0000000..a6b2541
--- /dev/null
+++ b/Features/Professions.lua
@@ -0,0 +1,119 @@
+local addonName, G_RLF = ...
+
+local Professions = G_RLF.RLF:NewModule("Professions", "AceEvent-3.0")
+
+Professions.Element = {}
+
+local color = "|cFF5555FF"
+
+function Professions.Element:new(...)
+ local element = {}
+ G_RLF.InitializeLootDisplayProperties(element)
+
+ element.type = "Professions"
+ element.IsEnabled = function()
+ return Professions:IsEnabled()
+ end
+
+ local keyPrefix = "PROF_"
+
+ local key
+ key, element.name, element.icon, element.level, element.maxLevel, element.quantity = ...
+ element.quality = Enum.ItemQuality.Rare
+
+ element.key = keyPrefix .. key
+
+ element.textFn = function()
+ return color .. element.name .. " " .. element.level .. "|r"
+ end
+
+ element.secondaryTextFn = function()
+ return ""
+ end
+
+ return element
+end
+
+local segments
+local localeString = _G.SKILL_RANK_UP
+function Professions:OnInitialize()
+ self.professions = {}
+ self.profNameIconMap = {}
+ self.profLocaleBaseNames = {}
+ if G_RLF.db.global.profFeed then
+ self:Enable()
+ else
+ self:Disable()
+ end
+ segments = G_RLF:CreatePatternSegmentsForStringNumber(localeString)
+end
+
+function Professions:OnDisable()
+ self:UnregisterEvent("PLAYER_ENTERING_WORLD")
+ self:UnregisterEvent("CHAT_MSG_SKILL")
+end
+
+function Professions:OnEnable()
+ self:RegisterEvent("PLAYER_ENTERING_WORLD")
+ self:RegisterEvent("CHAT_MSG_SKILL")
+end
+
+function Professions:InitializeProfessions()
+ local primaryId, secondaryId, archId, fishingId, cookingId = GetProfessions()
+ local profs = { primaryId, secondaryId, archId, fishingId, cookingId }
+ for i = 1, #profs do
+ if profs[i] then
+ local name, icon, skillLevel, maxSkillLevel, numAbilities, spellOffset, skillLine, skillModifier, specializationIndex, specializationOffset, a, b =
+ GetProfessionInfo(profs[i])
+ if name and icon then
+ self.profNameIconMap[name] = icon
+ end
+ end
+ end
+
+ for k, v in pairs(self.profNameIconMap) do
+ table.insert(self.profLocaleBaseNames, k)
+ end
+end
+
+function Professions:PLAYER_ENTERING_WORLD()
+ Professions:InitializeProfessions()
+end
+
+function Professions:CHAT_MSG_SKILL(event, message)
+ G_RLF:LogInfo(event, "WOWEVENT", self.moduleName, nil, message)
+
+ local skillName, skillLevel = G_RLF:ExtractDynamicsFromPattern(message, segments)
+ if skillName and skillLevel then
+ if not self.professions[skillName] then
+ self.professions[skillName] = {
+ name = skillName,
+ lastSkillLevel = skillLevel,
+ }
+ end
+ local icon
+ if self.profNameIconMap[skillName] then
+ icon = self.profNameIconMap[skillName]
+ else
+ for i = 1, #self.profLocaleBaseNames do
+ if skillName:find(self.profLocaleBaseNames[i]) then
+ icon = self.profNameIconMap[self.profLocaleBaseNames[i]]
+ self.profNameIconMap[skillName] = icon
+ break
+ end
+ end
+ end
+ local e = self.Element:new(
+ skillName,
+ skillName,
+ icon,
+ skillLevel,
+ nil,
+ skillLevel - self.professions[skillName].lastSkillLevel
+ )
+ e:Show()
+ self.professions[skillName].lastSkillLevel = skillLevel
+ end
+end
+
+return Professions
diff --git a/Features/Reputation.lua b/Features/Reputation.lua
index 8558eae..d7c1d2e 100644
--- a/Features/Reputation.lua
+++ b/Features/Reputation.lua
@@ -8,18 +8,16 @@ local RepType = {
MajorFaction = 1,
Paragon = 2,
BaseFaction = 3,
+ DelveCompanion = 4,
+ Friendship = 5,
}
-- Precompute pattern segments to optimize runtime message parsing
local function precomputePatternSegments(patterns)
local computedPatterns = {}
for _, pattern in ipairs(patterns) do
- local preStart, preEnd = string.find(pattern, "%%s")
- local prePattern = string.sub(pattern, 1, preStart - 1)
- local midStart, midEnd = string.find(pattern, "%%d", preEnd + 1)
- local midPattern = string.sub(pattern, preEnd + 1, midStart - 1)
- local postPattern = string.sub(pattern, midEnd + 1)
- table.insert(computedPatterns, { prePattern, midPattern, postPattern })
+ local segments = G_RLF:CreatePatternSegmentsForStringNumber(pattern)
+ table.insert(computedPatterns, segments)
end
return computedPatterns
end
@@ -37,11 +35,12 @@ local function countMappedFactions()
end
local function buildFactionLocaleMap(findName)
- local numFactions = C_Reputation.GetNumFactions()
local mappedFactions = countMappedFactions()
- if mappedFactions >= numFactions and not findName then
+ local hasMoreFactions = C_Reputation.GetFactionDataByIndex(mappedFactions + 1) ~= nil
+ if not hasMoreFactions and not findName then
return
end
+ local numFactions = mappedFactions + 5
if not findName then
local buckets = math.ceil(numFactions / 10) + 1
@@ -63,9 +62,6 @@ local function buildFactionLocaleMap(findName)
return
end
- -- If we are searching for a specific faction, we need to expand all headers to ensure we find it
- C_Reputation.ExpandAllFactionHeaders()
-
for i = 1, numFactions do
local factionData = C_Reputation.GetFactionDataByIndex(i)
if factionData then
@@ -88,8 +84,8 @@ function Rep.Element:new(...)
return Rep:IsEnabled()
end
- local factionName, rL, gL, bL
- element.quantity, factionName, rL, gL, bL, element.factionId, element.repType, element.isDelveCompanion = ...
+ local factionName, factionData, rL, gL, bL
+ element.quantity, factionName, rL, gL, bL, element.factionId, factionData, element.repType = ...
element.r, element.g, element.b = rL or 0.5, gL or 0.5, bL or 1
element.a = 1
element.key = "REP_" .. factionName
@@ -102,56 +98,70 @@ function Rep.Element:new(...)
return sign .. math.abs(rep) .. " " .. factionName
end
+ element.repLevel = nil
+ if factionData then
+ if factionData.renownLevel then
+ element.repLevel = factionData.renownLevel
+ elseif element.repType == RepType.DelveCompanion then
+ element.repLevel = factionData.currentLevel
+ end
+ end
+
element.secondaryTextFn = function()
local str = ""
- if not element.factionId or element.isDelveCompanion then
+ if not element.factionId or not factionData then
return str
end
local color = G_RLF:RGBAToHexFormat(element.r, element.g, element.b, 0.7)
- local function normalRep()
- local factionData = C_Reputation.GetFactionDataByID(element.factionId)
- if factionData.currentStanding >= 0 and factionData.currentReactionThreshold > 0 then
- str = str .. factionData.currentStanding .. "/" .. factionData.currentReactionThreshold
- end
+ if element.repType == RepType.DelveCompanion and factionData then
+ str = math.floor((factionData.currentXp / factionData.nextLevelAt) * 10000) / 100 .. "%"
+ return " " .. color .. str .. "|r"
end
if element.repType == RepType.MajorFaction then
- local factionData = C_MajorFactions.GetMajorFactionRenownInfo(element.factionId)
- if factionData.renownLevel ~= nil and factionData.renownLevel > 0 then
- str = str .. factionData.renownLevel
- end
if
factionData.renownReputationEarned ~= nil
and factionData.renownLevelThreshold ~= nil
and factionData.renownReputationEarned > 0
and factionData.renownLevelThreshold > 0
then
- str = str
- .. " ("
- .. factionData.renownReputationEarned
- .. "/"
- .. factionData.renownLevelThreshold
- .. ")"
+ str = str .. factionData.renownReputationEarned .. "/" .. factionData.renownLevelThreshold
end
elseif element.repType == RepType.Paragon then
- local currentValue, threshold, _, hasRewardPending, tooLowLevelForParagon =
- C_Reputation.GetFactionParagonInfo(element.factionId)
-
- if hasRewardPending then
+ if factionData.hasRewardPending then
local bagSize = G_RLF.db.global.fontSize
str = str .. "|A:ParagonReputation_Bag:" .. bagSize .. ":" .. bagSize .. ":0:0|a "
end
- if currentValue ~= nil and currentValue > 0 then
- str = str .. currentValue
+ if
+ factionData.currentValue ~= nil
+ and factionData.currentValue > 0
+ and factionData.threshold ~= nil
+ and factionData.threshold > 0
+ then
+ str = str .. (factionData.currentValue - factionData.threshold)
+ str = str .. "/" .. factionData.threshold
end
- if threshold ~= nil and threshold > 0 then
- str = str .. "/" .. threshold
+ elseif element.repType == RepType.Friendship then
+ if factionData.repNumerator ~= nil and factionData.repNumerator > 0 then
+ str = str .. factionData.repNumerator
+ if factionData.repDenominator ~= nil and factionData.repDenominator > 0 then
+ str = str .. "/" .. factionData.repDenominator
+ end
end
else
- normalRep()
+ if
+ factionData.currentStanding ~= 0
+ or factionData.currentReactionThreshold ~= 0
+ or factionData.nextReactionThreshold ~= 0
+ then
+ str = str
+ .. (factionData.currentStanding - factionData.currentReactionThreshold)
+ .. "/"
+ .. (factionData.nextReactionThreshold - factionData.currentReactionThreshold)
+ end
end
if str ~= "" then
@@ -164,7 +174,6 @@ function Rep.Element:new(...)
return element
end
-local season, companionFactionId, companionFactionName
local increasePatterns, decreasePatterns
function Rep:OnInitialize()
locale = GetLocale()
@@ -172,11 +181,10 @@ function Rep:OnInitialize()
G_RLF.db.global.factionMaps = G_RLF.db.global.factionMaps or {}
G_RLF.db.global.factionMaps[locale] = G_RLF.db.global.factionMaps[locale] or {}
- season = C_DelvesUI.GetCurrentDelvesSeasonNumber()
- companionFactionId = C_DelvesUI.GetFactionForCompanion(season)
- local factionData = C_Reputation.GetFactionDataByID(companionFactionId)
+ self.companionFactionId = C_DelvesUI.GetFactionForCompanion(BRANN_COMPANION_INFO_ID)
+ local factionData = C_Reputation.GetFactionDataByID(self.companionFactionId)
if factionData then
- companionFactionName = factionData.name
+ self.companionFactionName = factionData.name
end
increasePatterns = precomputePatternSegments({
@@ -207,30 +215,15 @@ end
-- Function to extract faction and reputation change using precomputed patterns
local function extractFactionAndRep(message, patterns)
for _, segments in ipairs(patterns) do
- local prePattern, midPattern, postPattern = unpack(segments)
- local preMatchStart, preMatchEnd = string.find(message, prePattern, 1, true)
- if preMatchStart then
- local msgLoop = message:sub(preMatchEnd + 1)
- local midMatchStart, midMatchEnd = string.find(msgLoop, midPattern, 1, true)
- if midMatchStart then
- local postMatchStart, postMatchEnd = string.find(msgLoop, postPattern, midMatchEnd, true)
- if postMatchStart then
- local faction = msgLoop:sub(1, midMatchStart - 1)
- local rep
- if midMatchEnd == postMatchStart then
- rep = msgLoop:sub(midMatchEnd + 1)
- else
- rep = msgLoop:sub(midMatchEnd + 1, postMatchStart - 1)
- end
- return faction, tonumber(rep)
- end
- end
+ local faction, rep = G_RLF:ExtractDynamicsFromPattern(message, segments)
+ if faction and rep then
+ return faction, rep
end
end
return nil, nil
end
-local function extractFactionAndRepForDelves(message)
+local function extractFactionAndRepForDelves(message, companionFactionName)
if not companionFactionName then
return nil, nil
end
@@ -265,7 +258,7 @@ function Rep:ParseFactionChangeMessage(message)
end
end
if not faction then
- faction, repChange = extractFactionAndRepForDelves(message)
+ faction, repChange = extractFactionAndRepForDelves(message, self.companionFactionName)
if faction then
isDelveCompanion = true
end
@@ -274,12 +267,12 @@ function Rep:ParseFactionChangeMessage(message)
end
function Rep:CHAT_MSG_COMBAT_FACTION_CHANGE(eventName, message)
- self:getLogger():Info(eventName .. " " .. message, "WOWEVENT", self.moduleName)
+ G_RLF:LogInfo(eventName .. " " .. message, "WOWEVENT", self.moduleName)
return self:fn(function()
local faction, repChange, isDelveCompanion = self:ParseFactionChangeMessage(message)
if not faction or not repChange then
- self:getLogger():Error(
+ G_RLF:LogError(
"Could not determine faction and/or rep change from message",
addonName,
self.moduleName,
@@ -293,35 +286,69 @@ function Rep:CHAT_MSG_COMBAT_FACTION_CHANGE(eventName, message)
local r, g, b, color
if G_RLF.db.global.factionMaps[locale][faction] == nil then
-- attempt to find the missing faction's ID
- self:getLogger():Debug(faction .. " not cached for " .. locale, addonName, self.moduleName)
+ G_RLF:LogDebug(faction .. " not cached for " .. locale, addonName, self.moduleName)
buildFactionLocaleMap(faction)
end
- local type, fId
+ local repType, fId, factionData
if G_RLF.db.global.factionMaps[locale][faction] then
fId = G_RLF.db.global.factionMaps[locale][faction]
if C_Reputation.IsMajorFaction(fId) then
color = ACCOUNT_WIDE_FONT_COLOR
- type = RepType.MajorFaction
- elseif C_Reputation.IsFactionParagon(fId) then
- color = FACTION_GREEN_COLOR
- type = RepType.Paragon
+ repType = RepType.MajorFaction
+ factionData = C_MajorFactions.GetMajorFactionRenownInfo(fId)
+ elseif isDelveCompanion then
+ factionData = C_Reputation.GetFactionDataByID(fId)
+ local ranks = C_GossipInfo.GetFriendshipReputationRanks(fId)
+ local info = C_GossipInfo.GetFriendshipReputation(fId)
+ if factionData.reaction then
+ color = FACTION_BAR_COLORS[factionData.reaction]
+ end
+ factionData.currentLevel = ranks and ranks.currentLevel or 0
+ factionData.maxLevel = ranks and ranks.maxLevel or 0
+ factionData.currentXp = info.standing - info.reactionThreshold
+ factionData.nextLevelAt = info.nextThreshold - info.reactionThreshold
+ repType = RepType.DelveCompanion
else
- local factionData = C_Reputation.GetFactionDataByID(fId)
+ local friendInfo = C_GossipInfo.GetFriendshipReputation(fId)
+ factionData = C_Reputation.GetFactionDataByID(fId)
if factionData.reaction then
color = FACTION_BAR_COLORS[factionData.reaction]
end
- type = RepType.BaseFaction
+ if
+ friendInfo
+ and friendInfo.friendshipFactionID
+ and friendInfo.friendshipFactionID > 0
+ and friendInfo.nextThreshold
+ and friendInfo.nextThreshold > 1
+ then
+ local ranks = C_GossipInfo.GetFriendshipReputationRanks(fId)
+ factionData.currentLevel = ranks and ranks.currentLevel or 0
+ factionData.maxLevel = ranks and ranks.maxLevel or 0
+ factionData.repNumerator = friendInfo.standing - friendInfo.reactionThreshold
+ factionData.repDenominator = friendInfo.nextThreshold - friendInfo.reactionThreshold
+ repType = RepType.Friendship
+ else
+ repType = RepType.BaseFaction
+ end
+ end
+
+ if C_Reputation.IsFactionParagon(fId) then
+ color = color or FACTION_GREEN_COLOR
+ repType = RepType.Paragon
+ factionData = factionData or {}
+ factionData.currentValue, factionData.threshold, factionData.rewardQuestId, factionData.hasRewardPending, factionData.tooLowLevelForParagon =
+ C_Reputation.GetFactionParagonInfo(fId)
end
else
- self:getLogger():Warn(faction .. " is STILL not cached for " .. locale, addonName, self.moduleName)
+ G_RLF:LogWarn(faction .. " is STILL not cached for " .. locale, addonName, self.moduleName)
end
if color then
r, g, b = color.r, color.g, color.b
end
- local e = self.Element:new(repChange, faction, r, g, b, fId, type, isDelveCompanion)
+ local e = self.Element:new(repChange, faction, r, g, b, fId, factionData, repType)
e:Show()
end)
end
diff --git a/Features/features.xml b/Features/features.xml
index 466a0cb..1a5a184 100644
--- a/Features/features.xml
+++ b/Features/features.xml
@@ -3,9 +3,10 @@
xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
-
+
+
\ No newline at end of file
diff --git a/GameTesting/IntegrationTest.lua b/GameTesting/IntegrationTest.lua
new file mode 100644
index 0000000..90827d2
--- /dev/null
+++ b/GameTesting/IntegrationTest.lua
@@ -0,0 +1,155 @@
+local addonName, G_RLF = ...
+
+--@alpha@
+-- trunk-ignore-begin(no-invalid-prints/invalid-print)
+local TestMode = G_RLF.RLF:GetModule("TestMode")
+local tests = {}
+local prints = ""
+local successCount = 0
+local failureCount = 0
+
+local function assertEqual(actual, expected, testName, err)
+ tests[testName] = {
+ result = actual == expected,
+ expected = expected,
+ actual = actual,
+ err = err,
+ }
+ if actual == expected then
+ prints = prints .. "|cff00ff00•|r"
+ successCount = successCount + 1
+ else
+ prints = prints .. "|cffff0000x|r"
+ failureCount = failureCount + 1
+ end
+end
+
+local function runTestSafely(testFunction, testName, ...)
+ local success, err = pcall(testFunction, ...)
+ assertEqual(success, true, testName, err)
+end
+
+local function displayResults()
+ G_RLF:Print("Integration Test")
+ print(prints)
+ print("|cff00ff00Successes: " .. successCount .. "|r")
+ if failureCount > 0 then
+ print("|cffff0000Failures: " .. failureCount .. "|r")
+ end
+
+ local msg = ""
+ for testName, testData in pairs(tests) do
+ if not testData.result then
+ msg = msg
+ .. "|cffff0000Failure: "
+ .. testName
+ .. " failed: expected "
+ .. tostring(testData.expected)
+ .. ", got "
+ .. tostring(testData.actual)
+ if testData.err then
+ msg = msg .. " Error: " .. testData.err
+ end
+ msg = msg .. "|r|n\n"
+ end
+ end
+
+ if failureCount > 0 then
+ error(msg)
+ end
+end
+
+local function runExperienceIntegrationTest()
+ local module = G_RLF.RLF:GetModule("Experience")
+ local e = module.Element:new(1337)
+ runTestSafely(e.Show, "LootDisplay: Experience")
+end
+
+local function runMoneyIntegrationTest()
+ local module = G_RLF.RLF:GetModule("Money")
+ local e = module.Element:new(12345)
+ runTestSafely(e.Show, "LootDisplay: Money")
+end
+
+local function runItemLootIntegrationTest()
+ local module = G_RLF.RLF:GetModule("ItemLoot")
+ local info = TestMode.testItems[2]
+ local amountLooted = 1
+ local e = module.Element:new(info, amountLooted, false)
+ if info.itemName == nil then
+ G_RLF:Print("Item not cached, skipping ItemLoot test")
+ else
+ runTestSafely(e.Show, "LootDisplay: Item", e, info.itemName, info.itemQuality)
+ e = module.Element:new(info, amountLooted, false)
+ e.highlight = true
+ runTestSafely(e.Show, "LootDisplay: Item Quantity Update", e, info.itemName, info.itemQuality)
+ e = module.Element:new(info, amountLooted, "player")
+ runTestSafely(e.Show, "LootDisplay: Item Unit", e, info.itemName, info.itemQuality)
+ end
+end
+
+local function runCurrencyIntegrationTest()
+ local module = G_RLF.RLF:GetModule("Currency")
+ local testObj = TestMode.testCurrencies[2]
+ local amountLooted = 1
+ local e = module.Element:new(
+ testObj.id,
+ testObj.link,
+ testObj.icon,
+ amountLooted,
+ testObj.quantity,
+ testObj.quality,
+ testObj.totalEarned,
+ testObj.maxQuantity
+ )
+ runTestSafely(e.Show, "LootDisplay: Currency")
+ e = module.Element:new(
+ testObj.id,
+ testObj.link,
+ testObj.icon,
+ amountLooted,
+ testObj.quantity,
+ testObj.quality,
+ testObj.totalEarned,
+ testObj.maxQuantity
+ )
+ runTestSafely(e.Show, "LootDisplay: Currency Quantity Update")
+end
+
+local function runReputationIntegrationTest()
+ local module = G_RLF.RLF:GetModule("Reputation")
+ local testObj = TestMode.testFactions[2]
+ local amountLooted = 664
+ local e = module.Element:new(amountLooted, testObj)
+ runTestSafely(e.Show, "LootDisplay: Reputation")
+ e = module.Element:new(amountLooted, testObj)
+ runTestSafely(e.Show, "LootDisplay: Reputation Quantity Update")
+end
+
+function TestMode:IntegrationTest()
+ if not self.integrationTestReady then
+ G_RLF:Print("Integration test not ready")
+ return
+ end
+
+ tests = {}
+ prints = ""
+ successCount = 0
+ failureCount = 0
+
+ runExperienceIntegrationTest()
+ runMoneyIntegrationTest()
+ runItemLootIntegrationTest()
+ runCurrencyIntegrationTest()
+ runReputationIntegrationTest()
+
+ local frame = LootDisplayFrame
+ assertEqual(frame ~= nil, true, "LootDisplayFrame")
+ C_Timer.After(G_RLF.db.global.fadeOutDelay + 3, function()
+ assertEqual(#frame.rowHistory, 6, "LootDisplayFrame: rowHistory")
+ displayResults()
+ end)
+end
+
+-- trunk-ignore-end(no-invalid-prints/invalid-print)
+--@end-alpha@
diff --git a/LootDisplayProfiler.lua b/GameTesting/Profiling.lua
similarity index 100%
rename from LootDisplayProfiler.lua
rename to GameTesting/Profiling.lua
diff --git a/SmokeTest.lua b/GameTesting/SmokeTest.lua
similarity index 73%
rename from SmokeTest.lua
rename to GameTesting/SmokeTest.lua
index b3c551a..72feb7d 100644
--- a/SmokeTest.lua
+++ b/GameTesting/SmokeTest.lua
@@ -3,7 +3,6 @@ local addonName, G_RLF = ...
--@alpha@
-- trunk-ignore-begin(no-invalid-prints/invalid-print)
local TestMode = G_RLF.RLF:GetModule("TestMode")
-local testItems, testCurrencies, testFactions, testItem
local tests = {}
local prints = ""
@@ -27,7 +26,6 @@ local function assertEqual(actual, expected, testName, err)
end
local function testGetItemInfo(id)
- -- Get the id of the last element in testItems
local itemName, itemLink, itemQuality, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture, sellPrice, classID, subclassID, bindType, expansionID, _, isCraftingReagent =
C_Item.GetItemInfo(id)
assertEqual(itemName ~= nil, true, "Global: C_Item.GetItemInfo(" .. id .. ").itemName")
@@ -84,12 +82,13 @@ local function testWoWGlobals()
assertEqual(type(UnitLevel), "function", "Global: UnitLevel")
assertEqual(type(GetPlayerGuid), "function", "Global: GetPlayerGuid")
assertEqual(type(C_Item.GetItemInfo), "function", "Global: C_Item.GetItemInfo")
- local id = testItems[#testItems].id
- local isCached = C_Item.GetItemInfo(id) ~= nil
+ assertEqual(type(GetInventoryItemLink), "function", "Global: GetInventoryItemLink")
+ local link = GetInventoryItemLink("player", G_RLF.equipSlotMap["INVTYPE_CHEST"])
+ local isCached = C_Item.GetItemInfo(link) ~= nil
if not isCached then
G_RLF:Print("Item not cached, skipping GetItemInfo test")
else
- testGetItemInfo(id)
+ testGetItemInfo(link)
end
assertEqual(type(GetMoney), "function", "Global: GetMoney")
assertEqual(type(C_CurrencyInfo.GetCoinTextureString), "function", "Global: C_CurrencyInfo.GetCoinTextureString")
@@ -126,59 +125,6 @@ local function testWoWGlobals()
assertEqual(type(FACTION_BAR_COLORS), "table", "Global: FACTION_BAR_COLORS")
end
-local function runTestSafely(testFunction, testName, ...)
- local success, err = pcall(testFunction, ...)
- assertEqual(success, true, testName, err)
-end
-
-local function runExperienceSmokeTest()
- local module = G_RLF.RLF:GetModule("Experience")
- local e = module.Element:new(1337)
- runTestSafely(e.Show, "LootDisplay: Experience")
-end
-
-local function runMoneySmokeTest()
- local module = G_RLF.RLF:GetModule("Money")
- local e = module.Element:new(12345)
- runTestSafely(e.Show, "LootDisplay: Money")
-end
-
-local function runItemLootSmokeTest()
- local module = G_RLF.RLF:GetModule("ItemLoot")
- local testObj = testItems[2]
- local amountLooted = 1
- local e = module.Element:new(testObj.id, testObj.link, testObj.icon, amountLooted, testObj.sellPrice)
- if testObj.name == nil then
- G_RLF:Print("Item not cached, skipping ItemLoot test")
- else
- runTestSafely(e.Show, "LootDisplay: Item", e, testObj.name, testObj.quality)
- e = module.Element:new(testObj.id, testObj.link, testObj.icon, amountLooted, testObj.sellPrice)
- runTestSafely(e.Show, "LootDisplay: Item Quantity Update", e, testObj.name, testObj.quality)
- e = module.Element:new(testObj.id, testObj.link, testObj.icon, amountLooted, nil, "player")
- runTestSafely(e.Show, "LootDisplay: Item Unit", e, testObj.name, testObj.quality)
- end
-end
-
-local function runCurrencySmokeTest()
- local module = G_RLF.RLF:GetModule("Currency")
- local testObj = testCurrencies[2]
- local amountLooted = 1
- local e = module.Element:new(testObj.id, testObj.link, testObj.icon, amountLooted)
- runTestSafely(e.Show, "LootDisplay: Currency")
- e = module.Element:new(testObj.id, testObj.link, testObj.icon, amountLooted)
- runTestSafely(e.Show, "LootDisplay: Currency Quantity Update")
-end
-
-local function runReputationSmokeTest()
- local module = G_RLF.RLF:GetModule("Reputation")
- local testObj = testFactions[2]
- local amountLooted = 664
- local e = module.Element:new(amountLooted, testObj)
- runTestSafely(e.Show, "LootDisplay: Reputation")
- e = module.Element:new(amountLooted, testObj)
- runTestSafely(e.Show, "LootDisplay: Reputation Quantity Update")
-end
-
local function displayResults()
G_RLF:Print("Smoke Test")
print(prints)
@@ -209,30 +155,13 @@ local function displayResults()
end
end
-local function testLootDisplay()
- runExperienceSmokeTest()
- runMoneySmokeTest()
- runItemLootSmokeTest()
- runCurrencySmokeTest()
- runReputationSmokeTest()
-
- local frame = LootDisplayFrame
- assertEqual(frame ~= nil, true, "LootDisplayFrame")
- C_Timer.After(G_RLF.db.global.fadeOutDelay + 3, function()
- assertEqual(#frame.rowHistory, 6, true, "LootDisplayFrame: rowHistory")
- displayResults()
- end)
-end
-
-function TestMode:SmokeTest(...)
- testItems, testCurrencies, testFactions, testItem = ...
-
+function TestMode:SmokeTest()
tests = {}
prints = ""
successCount = 0
failureCount = 0
testWoWGlobals()
- testLootDisplay()
+ displayResults()
end
-- trunk-ignore-end(no-invalid-prints/invalid-print)
diff --git a/TestMode.lua b/GameTesting/TestMode.lua
similarity index 59%
rename from TestMode.lua
rename to GameTesting/TestMode.lua
index 32c10ae..d143892 100644
--- a/TestMode.lua
+++ b/GameTesting/TestMode.lua
@@ -4,19 +4,20 @@ local TestMode = G_RLF.RLF:NewModule("TestMode", "AceEvent-3.0")
local logger
local allItemsInitialized = false
+local allCurrenciesInitialized = false
+local allFactionsInitialized = false
local isLootDisplayReady = false
local pendingRequests = {}
-local testItems = {}
-local testCurrencies = {}
-local testFactions = {
- "Undercity",
- "Thunder Bluff",
- "Orgrimmar",
-}
+TestMode.testItems = {}
+TestMode.testCurrencies = {}
+TestMode.testFactions = {}
local function idExistsInTable(id, table)
for _, item in pairs(table) do
- if item.id == id then
+ if item.id and item.id == id then
+ return true
+ end
+ if item.itemId and item.itemId == id then
return true
end
end
@@ -32,28 +33,28 @@ local function anyPendingRequests()
return false
end
-local function runSmokeTestIfReady()
- if allItemsInitialized and isLootDisplayReady then
+local function signalIntegrationTestReady()
+ if
+ not TestMode.integrationTestReady
+ and allItemsInitialized
+ and isLootDisplayReady
+ and allCurrenciesInitialized
+ and allFactionsInitialized
+ then
--@alpha@
- TestMode:SmokeTest(testItems, testCurrencies, testFactions)
+ TestMode:IntegrationTestReady()
--@end-alpha@
end
end
local function getItem(id)
- local name, link, quality, _, _, _, _, _, _, icon, sellPrice = C_Item.GetItemInfo(id)
- local isCached = name ~= nil
+ local name, link, quality, icon, sellPrice, _
+ local info = G_RLF.ItemInfo:new(id, C_Item.GetItemInfo(id))
+ local isCached = info ~= nil
if isCached then
- if name and link and quality and icon and not idExistsInTable(id, testItems) then
+ if not idExistsInTable(id, TestMode.testItems) then
pendingRequests[id] = nil
- table.insert(testItems, {
- id = id,
- link = link,
- icon = icon,
- name = name,
- quality = quality,
- sellPrice = sellPrice,
- })
+ table.insert(TestMode.testItems, info)
end
else
pendingRequests[id] = true
@@ -66,9 +67,9 @@ local function initializeTestItems()
getItem(id)
end
- if #testItems == #testItemIds then
+ if #TestMode.testItems == #testItemIds then
allItemsInitialized = true
- runSmokeTestIfReady()
+ signalIntegrationTestReady()
return
end
end
@@ -76,32 +77,64 @@ end
local testCurrencyIds = { 2245, 1191, 1828, 1792, 1755, 1580, 1273, 1166, 515, 241, 1813, 2778, 3089, 1101, 1704 }
local function initializeTestCurrencies()
for _, id in pairs(testCurrencyIds) do
- if not idExistsInTable(id, testCurrencies) then
+ if not idExistsInTable(id, TestMode.testCurrencies) then
local info = C_CurrencyInfo.GetCurrencyInfo(id)
local link = C_CurrencyInfo.GetCurrencyLink(id)
+ local basicInfo = C_CurrencyInfo.GetBasicCurrencyInfo(id, 100)
if info and link and info.currencyID and info.iconFileID then
- table.insert(testCurrencies, {
+ table.insert(TestMode.testCurrencies, {
id = info.currencyID,
link = link,
icon = info.iconFileID,
+ quantity = info.quantity,
+ quality = info.quality,
+ totalEarned = info.totalEarned,
+ maxQuantity = info.maxQuantity,
})
end
end
end
+
+ allCurrenciesInitialized = true
+ signalIntegrationTestReady()
+end
+
+local numTestFactions = 3
+local function initializeTestFactions()
+ for i = 1, numTestFactions do
+ local factionInfo = C_Reputation.GetFactionDataByIndex(i)
+ if factionInfo and factionInfo.name then
+ table.insert(TestMode.testFactions, factionInfo.name)
+ end
+ end
+ allFactionsInitialized = true
+ signalIntegrationTestReady()
end
function TestMode:OnInitialize()
isLootDisplayReady = false
allItemsInitialized = false
- testCurrencies = {}
- testItems = {}
+ self.testCurrencies = {}
+ self.testItems = {}
+ self.testFactions = {}
self:RegisterEvent("GET_ITEM_INFO_RECEIVED")
- self:InitializeTestData()
+ RunNextFrame(function()
+ self:InitializeTestData()
+ end)
+ --@alpha@
+ RunNextFrame(function()
+ self:SmokeTest()
+ end)
+ --@end-alpha@
+end
+
+function TestMode:IntegrationTestReady()
+ self.integrationTestReady = true
end
function TestMode:OnLootDisplayReady()
isLootDisplayReady = true
- runSmokeTestIfReady()
+ signalIntegrationTestReady()
end
local failedRetrievals = {}
@@ -119,20 +152,22 @@ function TestMode:GET_ITEM_INFO_RECEIVED(eventName, itemID, success)
return
end
end
- getItem(itemID)
- if #testItems == #testItemIds and not anyPendingRequests() then
+ G_RLF:ProfileFunction(getItem, "getItem")(itemID)
+ -- getItem(itemID)
+
+ if #self.testItems == #testItemIds and not anyPendingRequests() then
allItemsInitialized = true
- runSmokeTestIfReady()
+ signalIntegrationTestReady()
end
end
local function generateRandomLoot()
- if #testItems ~= #testItemIds then
+ if #TestMode.testItems ~= #testItemIds then
initializeTestItems()
end
- if #testCurrencies ~= #testCurrencyIds then
+ if #TestMode.testCurrencies ~= #testCurrencyIds then
initializeTestCurrencies()
end
-- Randomly decide whether to generate an item or currency
@@ -156,32 +191,41 @@ local function generateRandomLoot()
-- 50% chance to show items
if rng > 0.2 and rng <= 0.7 then
- local item = testItems[math.random(#testItems)]
+ local info = TestMode.testItems[math.random(#TestMode.testItems)]
local amountLooted = math.random(1, 5)
local module = G_RLF.RLF:GetModule("ItemLoot")
- local e = module.Element:new(item.id, item.link, item.icon, amountLooted, item.sellPrice)
- e:Show(item.name, item.quality)
+ local e = module.Element:new(info, amountLooted, false)
+ e:Show(info.itemName, info.itemQuality)
-- 10% chance of iitem loot to show up as a party member
if rng < 0.3 then
local unit = "player"
local module = G_RLF.RLF:GetModule("ItemLoot")
- local e = module.Element:new(item.id, item.link, item.icon, amountLooted, item.sellPrice, unit)
- e:Show(item.name, item.quality)
+ local e = module.Element:new(info, amountLooted, unit)
+ e:Show(info.itemName, info.itemQuality)
end
-- 15% chance to show currency
elseif rng > 0.7 and rng <= 0.85 then
- local currency = testCurrencies[math.random(#testCurrencies)]
+ local currency = TestMode.testCurrencies[math.random(#TestMode.testCurrencies)]
local amountLooted = math.random(1, 500)
local module = G_RLF.RLF:GetModule("Currency")
- local e = module.Element:new(currency.id, currency.link, currency.icon, amountLooted)
+ local e = module.Element:new(
+ currency.id,
+ currency.link,
+ currency.icon,
+ amountLooted,
+ currency.quantity,
+ currency.quality,
+ currency.totalEarned,
+ currency.maxQuantity
+ )
e:Show()
-- 10% chance to show reputation (least frequent)
elseif rng > 0.85 then
local reputationGained = math.random(10, 100)
- local factionName = testFactions[math.random(#testFactions)]
+ local factionName = TestMode.testFactions[math.random(#TestMode.testFactions)]
local module = G_RLF.RLF:GetModule("Reputation")
local e = module.Element:new(reputationGained, factionName)
e:Show()
@@ -192,21 +236,7 @@ end
function TestMode:InitializeTestData()
G_RLF:fn(initializeTestItems)
G_RLF:fn(initializeTestCurrencies)
-end
-
-function dump(o)
- if type(o) == "table" then
- local s = "{ "
- for k, v in pairs(o) do
- if type(k) ~= "number" then
- k = '"' .. k .. '"'
- end
- s = s .. "[" .. k .. "] = " .. dump(v) .. ","
- end
- return s .. "} "
- else
- return tostring(o)
- end
+ G_RLF:fn(initializeTestFactions)
end
function TestMode:ToggleTestMode()
@@ -224,12 +254,12 @@ function TestMode:ToggleTestMode()
self.testTimer = nil
end
G_RLF:Print(G_RLF.L["Test Mode Disabled"])
- logger:Debug("Test Mode Disabled", addonName)
+ G_RLF:LogDebug("Test Mode Disabled", addonName)
else
-- Start test mode
self.testMode = true
G_RLF:Print(G_RLF.L["Test Mode Enabled"])
- logger:Debug("Test Mode Enabled", addonName)
+ G_RLF:LogDebug("Test Mode Enabled", addonName)
self.testTimer = C_Timer.NewTicker(1.5, function()
G_RLF:fn(generateRandomLoot)
end)
diff --git a/GameTesting/testing.xml b/GameTesting/testing.xml
new file mode 100644
index 0000000..0194d6f
--- /dev/null
+++ b/GameTesting/testing.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/LootDisplay/LootDisplay.lua b/LootDisplay/LootDisplay.lua
index fdf4172..73a54c6 100644
--- a/LootDisplay/LootDisplay.lua
+++ b/LootDisplay/LootDisplay.lua
@@ -1,12 +1,11 @@
local addonName, G_RLF = ...
-local LootDisplay = G_RLF.RLF:NewModule("LootDisplay", "AceEvent-3.0")
+local LootDisplay = G_RLF.RLF:NewModule("LootDisplay", "AceBucket-3.0", "AceEvent-3.0")
local lsm = G_RLF.lsm
-- Private method declaration
local processFromQueue
-local debounceProcessFromQueue
local getTextWidth
local truncateItemLink
@@ -51,17 +50,18 @@ function LootDisplay:OnInitialize()
tempFontString = UIParent:CreateFontString(nil, "ARTWORK")
tempFontString:Hide() -- Prevent it from showing up
- frame.OnRowRelease = function()
- if elementQueue:size() > 0 then
- debounceProcessFromQueue()
- end
- end
- RunNextFrame(function()
- G_RLF.RLF:GetModule("TestMode"):OnLootDisplayReady()
- end)
+end
+function LootDisplay:OnEnable()
self:RegisterEvent("PLAYER_REGEN_DISABLED", "OnPlayerCombatChange")
self:RegisterEvent("PLAYER_REGEN_ENABLED", "OnPlayerCombatChange")
+ self:RegisterBucketEvent("BAG_UPDATE_DELAYED", 0.5, "BAG_UPDATE_DELAYED")
+ self:RegisterMessage("RLF_NEW_LOOT", "OnLootReady")
+ self:RegisterBucketMessage("RLF_ROW_RETURNED", 0.3, "OnRowReturn")
+
+ RunNextFrame(function()
+ G_RLF.RLF:GetModule("TestMode"):OnLootDisplayReady()
+ end)
end
function LootDisplay:OnPlayerCombatChange()
@@ -108,6 +108,12 @@ function LootDisplay:UpdateFadeDelay()
frame:UpdateFadeDelay()
end
+function LootDisplay:BAG_UPDATE_DELAYED()
+ G_RLF:LogInfo("BAG_UPDATE_DELAYED", "WOWEVENT", self.moduleName, nil, "BAG_UPDATE_DELAYED")
+
+ frame:UpdateRowItemCounts()
+end
+
local function processRow(element)
if not element:IsEnabled() then
return
@@ -125,6 +131,8 @@ local function processRow(element)
local logFn = element.logFn
local isLink = element.isLink
local unit = element.unit
+ local itemCount = element.itemCount
+ local highlight = element.highlight
if unit then
key = unit .. "_" .. key
@@ -153,10 +161,17 @@ local function processRow(element)
row.unit = unit
end
+ row.id = element.key
row.amount = quantity
+ row.type = element.type
if isLink then
- local extraWidth = getTextWidth(" x" .. row.amount)
+ local extraWidthStr = " x" .. row.amount
+ if element.itemCount then
+ extraWidthStr = extraWidthStr .. " (" .. element.itemCount .. ")"
+ end
+
+ local extraWidth = getTextWidth(extraWidthStr)
if row.unit then
local portraitSize = G_RLF.db.global.iconSize * 0.8
extraWidth = extraWidth + portraitSize - (portraitSize / 2)
@@ -164,14 +179,15 @@ local function processRow(element)
row.link = truncateItemLink(textFn(), extraWidth)
row.quality = quality
text = textFn(0, row.link)
-
- row:UpdateIcon(key, icon, quality)
-
row:SetupTooltip()
else
text = textFn()
end
+ if icon then
+ row:UpdateIcon(key, icon, quality)
+ end
+
row:UpdateSecondaryText(secondaryTextFn)
row:UpdateStyles()
end
@@ -180,65 +196,77 @@ local function processRow(element)
row:UpdateSecondaryText(secondaryTextFn)
end
+ if element.type == "ItemLoot" and not element.unit then
+ RunNextFrame(function()
+ local itemCount = C_Item.GetItemCount(element.key, true, false, true, true)
+ row:ShowItemCountText(itemCount, { wrapChar = G_RLF.WrapCharEnum.PARENTHESIS })
+ end)
+ end
+
+ if element.type == "Currency" then
+ row:ShowItemCountText(element.totalCount, { wrapChar = G_RLF.WrapCharEnum.PARENTHESIS })
+ end
+
+ if element.type == "Reputation" and element.repLevel then
+ row:ShowItemCountText(
+ element.repLevel,
+ { color = G_RLF:RGBAToHexFormat(0.5, 0.5, 1, 1), wrapChar = G_RLF.WrapCharEnum.ANGLE }
+ )
+ end
+
+ if element.type == "Experience" and element.currentLevel then
+ row:ShowItemCountText(
+ element.currentLevel,
+ { color = G_RLF:RGBAToHexFormat(0.749, 0.737, 0.012, 1), wrapChar = G_RLF.WrapCharEnum.ANGLE }
+ )
+ end
+
+ if element.type == "Professions" then
+ row:ShowItemCountText(
+ row.amount,
+ { color = "|cFF5555FF", wrapChar = G_RLF.WrapCharEnum.BRACKET, showSign = true }
+ )
+ end
+
row:ShowText(text, r, g, b, a)
+ if highlight then
+ RunNextFrame(function()
+ row:HighlightIcon()
+ end)
+ end
+
logFn(text, row.amount, new)
row:ResetFadeOut()
end
-function LootDisplay:ShowLoot(element)
- elementQueue:enqueue(element)
- debounceProcessFromQueue()
+function LootDisplay:OnLootReady(_, element)
+ RunNextFrame(function()
+ processRow(element)
+ end)
+end
+
+function LootDisplay:OnRowReturn()
+ RunNextFrame(function()
+ processFromQueue()
+ end)
end
processFromQueue = function()
local snapshotQueueSize = elementQueue:size()
if snapshotQueueSize > 0 then
local rowsToProcess = math.min(snapshotQueueSize, G_RLF.db.global.maxRows)
- LootDisplay:getLogger():Debug("Processing " .. rowsToProcess .. " items from element queue")
+ G_RLF:LogDebug("Processing " .. rowsToProcess .. " items from element queue")
for i = 1, rowsToProcess do
if elementQueue:isEmpty() then
return
end
local e = elementQueue:dequeue()
- processRow(e)
- end
- end
-end
-
-local debounceTimer = nil
-local maxWaitTimer = nil
-local debounceDelay = 0.15 -- 150 milliseconds
-local maxWaitTime = debounceDelay * 2
-debounceProcessFromQueue = function()
- if debounceTimer then
- debounceTimer:Cancel()
- debounceTimer = nil
- end
-
- debounceTimer = C_Timer.NewTimer(debounceDelay, function()
- LootDisplay:getLogger():Debug("Debounce Timer fired", addonName)
- if maxWaitTimer then
- maxWaitTimer:Cancel()
- maxWaitTimer = nil
+ RunNextFrame(function()
+ processRow(e)
+ end)
end
- debounceTimer:Cancel()
- debounceTimer = nil
- G_RLF:fn(processFromQueue)
- end)
-
- if not maxWaitTimer then
- maxWaitTimer = C_Timer.NewTimer(maxWaitTime, function()
- LootDisplay:getLogger():Debug("Max Wait Timer fired", addonName)
- if debounceTimer then
- debounceTimer:Cancel()
- debounceTimer = nil
- end
- maxWaitTimer:Cancel()
- maxWaitTimer = nil
- G_RLF:fn(processFromQueue)
- end)
end
end
diff --git a/LootDisplay/LootDisplayFrame/LootDisplayFrame.lua b/LootDisplay/LootDisplayFrame/LootDisplayFrame.lua
index 80e4680..305a9d9 100644
--- a/LootDisplay/LootDisplayFrame/LootDisplayFrame.lua
+++ b/LootDisplay/LootDisplayFrame/LootDisplayFrame.lua
@@ -255,7 +255,7 @@ function LootDisplayFrameMixin:ReleaseRow(row)
row:SetParent(nil)
self.rowFramePool:Release(row)
- self:OnRowRelease()
+ G_RLF:SendMessage("RLF_ROW_RETURNED")
self:UpdateTabVisibility()
end
@@ -398,3 +398,11 @@ function LootDisplayFrameMixin:HideHistoryFrame()
self.historyFrame:SetVerticalScroll(0)
end
end
+
+function LootDisplayFrameMixin:UpdateRowItemCounts()
+ for row in rows:iterate() do
+ if row.id and row.type == "ItemLoot" and not row.unit then
+ row:UpdateItemCount()
+ end
+ end
+end
diff --git a/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRow.lua b/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRow.lua
index b45672d..a678425 100644
--- a/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRow.lua
+++ b/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRow.lua
@@ -61,7 +61,11 @@ end
local function rowUnitPortrait(row)
if row.unit then
- SetPortraitTexture(row.UnitPortrait, row.unit)
+ RunNextFrame(function()
+ if row.unit then
+ SetPortraitTexture(row.UnitPortrait, row.unit)
+ end
+ end)
local portraitSize = G_RLF.db.global.iconSize * 0.8
row.UnitPortrait:SetSize(portraitSize, portraitSize)
row.UnitPortrait:ClearAllPoints()
@@ -100,10 +104,12 @@ local function rowText(row, icon)
if fontChanged then
if G_RLF.db.global.useFontObjects or not G_RLF.db.global.fontFace then
row.PrimaryText:SetFontObject(G_RLF.db.global.font)
+ row.ItemCountText:SetFontObject(G_RLF.db.global.font)
row.SecondaryText:SetFontObject(G_RLF.db.global.font)
else
local fontPath = G_RLF.lsm:Fetch(G_RLF.lsm.MediaType.FONT, G_RLF.db.global.fontFace)
row.PrimaryText:SetFont(fontPath, G_RLF.db.global.fontSize, G_RLF.defaults.global.fontFlags)
+ row.ItemCountText:SetFont(fontPath, G_RLF.db.global.fontSize, G_RLF.defaults.global.fontFlags)
row.SecondaryText:SetFont(fontPath, G_RLF.db.global.secondaryFontSize, G_RLF.defaults.global.fontFlags)
end
end
@@ -132,6 +138,7 @@ local function rowText(row, icon)
xOffset = xOffset * -1
end
row.PrimaryText:ClearAllPoints()
+ row.ItemCountText:ClearAllPoints()
row.PrimaryText:SetJustifyH(anchor)
if icon then
if row.unit then
@@ -160,8 +167,9 @@ local function rowText(row, icon)
row.SecondaryText:SetPoint("TOP", row, "CENTER", 0, -padding)
row.SecondaryText:SetShown(true)
end
+
+ row.ItemCountText:SetPoint(anchor, row.PrimaryText, iconAnchor, xOffset, 0)
end
- -- Adjust the text position dynamically based on leftAlign or other conditions
end
local function updateBorderPositions(row)
@@ -260,6 +268,11 @@ local function rowFadeOutAnimation(row)
row.FadeOutAnimation.fadeOut:SetFromAlpha(1)
row.FadeOutAnimation.fadeOut:SetToAlpha(0)
row.FadeOutAnimation.fadeOut:SetDuration(1)
+ row.FadeOutAnimation.fadeOut:SetScript("OnUpdate", function()
+ if row.glowTexture and row.glowTexture:IsShown() then
+ row.glowTexture:SetAlpha(0.75 * (1 - row.FadeOutAnimation.fadeOut:GetProgress()))
+ end
+ end)
row.FadeOutAnimation.fadeOut:SetScript("OnFinished", function()
row:Hide()
local frame = LootDisplayFrame
@@ -270,10 +283,57 @@ local function rowFadeOutAnimation(row)
row.FadeOutAnimation.fadeOut:SetStartDelay(G_RLF.db.global.fadeOutDelay)
end
+local function rowHighlightIcon(row)
+ if not row.glowTexture then
+ -- Create the glow texture
+ row.glowTexture = row.Icon:CreateTexture(nil, "OVERLAY")
+ row.glowTexture:SetDrawLayer("OVERLAY", 7)
+ row.glowTexture:SetTexture("Interface\\SpellActivationOverlay\\IconAlert")
+ row.glowTexture:SetPoint("CENTER", row.Icon, "CENTER", 0, 0)
+ row.glowTexture:SetSize(row.Icon:GetWidth() * 1.75, row.Icon:GetHeight() * 1.75)
+ row.glowTexture:SetBlendMode("ADD") -- "ADD" is often better for glow effects
+ row.glowTexture:SetAlpha(0.75)
+ row.glowTexture:SetTexCoord(0.00781250, 0.50781250, 0.27734375, 0.52734375)
+ end
+
+ row.glowTexture:Hide()
+
+ -- Create the animation group if it doesn't exist
+ if not row.glowAnimationGroup then
+ row.glowAnimationGroup = row.glowTexture:CreateAnimationGroup()
+
+ -- Add a scale animation for pulsing
+ local scaleUp = row.glowAnimationGroup:CreateAnimation("Scale")
+ scaleUp:SetScale(1.25, 1.25) -- Slightly increase size
+ scaleUp:SetDuration(0.5) -- Half a second to scale up
+ scaleUp:SetOrder(1)
+ scaleUp:SetSmoothing("IN_OUT") -- Smooth scaling in and out
+
+ local scaleDown = row.glowAnimationGroup:CreateAnimation("Scale")
+ scaleDown:SetScale(0.8, 0.8) -- Slightly decrease size back
+ scaleDown:SetDuration(0.5) -- Half a second to scale down
+ scaleDown:SetOrder(2)
+ scaleDown:SetSmoothing("IN_OUT")
+
+ -- Optional: Add a subtle alpha fade during the pulse
+ local alphaPulse = row.glowAnimationGroup:CreateAnimation("Alpha")
+ alphaPulse:SetFromAlpha(0.75)
+ alphaPulse:SetToAlpha(1)
+ alphaPulse:SetDuration(0.5)
+ alphaPulse:SetOrder(1)
+ alphaPulse:SetSmoothing("IN_OUT")
+
+ row.glowAnimationGroup:SetLooping("REPEAT")
+ end
+end
+
local function rowStyles(row)
row:SetSize(G_RLF.db.global.feedWidth, G_RLF.db.global.rowHeight)
rowBackground(row)
rowIcon(row, row.icon)
+ RunNextFrame(function()
+ rowHighlightIcon(row)
+ end)
rowUnitPortrait(row)
rowText(row, row.icon)
rowHighlightBorder(row)
@@ -295,12 +355,14 @@ function LootDisplayRowMixin:Reset()
self:ClearAllPoints()
-- Reset row-specific data
+ self.id = nil
self.key = nil
self.amount = nil
self.icon = nil
self.link = nil
self.secondaryText = nil
self.unit = nil
+ self.type = nil
-- Reset UI elements that were part of the template
self.TopBorder:SetAlpha(0)
@@ -310,8 +372,17 @@ function LootDisplayRowMixin:Reset()
self.Icon:Reset()
+ if self.glowAnimationGroup then
+ self.glowAnimationGroup:Stop()
+ end
+ if self.glowTexture then
+ self.glowTexture:Hide()
+ end
+
self.UnitPortrait:SetTexture(nil)
self.SecondaryText:SetText(nil)
+ self.ItemCountText:SetText(nil)
+ self.ItemCountText:Hide()
-- Reset amount text behavior
self.PrimaryText:SetScript("OnEnter", nil)
@@ -441,6 +512,52 @@ function LootDisplayRowMixin:Dump()
)
end
+function LootDisplayRowMixin:UpdateItemCount()
+ RunNextFrame(function()
+ if self.id then
+ local itemCount = C_Item.GetItemCount(self.id, true, false, true, true)
+
+ if itemCount then
+ self:ShowItemCountText(itemCount, { wrapChar = G_RLF.WrapCharEnum.PARENTHESIS })
+ end
+ end
+ end)
+end
+
+function LootDisplayRowMixin:ShowItemCountText(itemCount, options)
+ local WrapChar = G_RLF.WrapCharEnum
+ options = options or {}
+ local color = options.color or "|cFFBCBCBC"
+ local wrapChar = options.wrapChar or WrapChar.DEFAULT
+ local showSign = options.showSign or false
+
+ local sChar, eChar
+ if wrapChar == WrapChar.SPACE then
+ sChar, eChar = " ", ""
+ elseif wrapChar == WrapChar.PARENTHESIS then
+ sChar, eChar = "(", ")"
+ elseif wrapChar == WrapChar.BRACKET then
+ sChar, eChar = "[", "]"
+ elseif wrapChar == WrapChar.BRACE then
+ sChar, eChar = "{", "}"
+ elseif wrapChar == WrapChar.ANGLE then
+ sChar, eChar = "<", ">"
+ else
+ sChar, eChar = "", ""
+ end
+
+ if itemCount and (itemCount > 1 or (showSign and itemCount >= 1)) then
+ local sign = ""
+ if showSign then
+ sign = "+"
+ end
+ self.ItemCountText:SetText(color .. sChar .. sign .. itemCount .. eChar .. "|r")
+ self.ItemCountText:Show()
+ else
+ self.ItemCountText:Hide()
+ end
+end
+
function LootDisplayRowMixin:ShowText(text, r, g, b, a)
if a == nil then
a = 1
@@ -497,6 +614,14 @@ function LootDisplayRowMixin:UpdateIcon(key, icon, quality)
end
end
+function LootDisplayRowMixin:HighlightIcon()
+ RunNextFrame(function()
+ -- Show the glow texture and play the animation
+ self.glowTexture:Show()
+ self.glowAnimationGroup:Play()
+ end)
+end
+
function LootDisplayRowMixin:ResetFadeOut()
RunNextFrame(function()
self.FadeOutAnimation:Stop()
diff --git a/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRowTemplate.xml b/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRowTemplate.xml
index 2a660f9..2ae9913 100644
--- a/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRowTemplate.xml
+++ b/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRowTemplate.xml
@@ -17,6 +17,7 @@
+
diff --git a/RPGLootFeed.lua b/RPGLootFeed.lua
deleted file mode 100644
index a9668b7..0000000
--- a/RPGLootFeed.lua
+++ /dev/null
@@ -1,95 +0,0 @@
-local addonName, G_RLF = ...
-local acd = LibStub("AceConfigDialog-3.0")
-local lsm = G_RLF.lsm
-local RLF = G_RLF.RLF
-local TestMode
-
-function RLF:OnInitialize()
- G_RLF.db = LibStub("AceDB-3.0"):New(G_RLF.dbName, G_RLF.defaults, true)
- LibStub("AceConfig-3.0"):RegisterOptionsTable(addonName, G_RLF.options)
- lsm:Register(lsm.MediaType.FONT, "BAR SADY Regular", "Interface\\AddOns\\RPGLootFeed\\Fonts\\BAR_SADY_Variable.ttf")
- self:Hook(acd, "Open", "OnOptionsOpen")
- self:RegisterEvent("PLAYER_ENTERING_WORLD")
- self:RegisterChatCommand("rlf", "SlashCommand")
- self:RegisterChatCommand("RLF", "SlashCommand")
- self:RegisterChatCommand("rpglootfeed", "SlashCommand")
- self:RegisterChatCommand("rpgLootFeed", "SlashCommand")
-
- if EditModeManagerFrame then
- EventRegistry:RegisterCallback("EditMode.Enter", function()
- G_RLF.LootDisplay:SetBoundingBoxVisibility(true)
- end)
- EventRegistry:RegisterCallback("EditMode.Exit", function()
- G_RLF.LootDisplay:SetBoundingBoxVisibility(false)
- end)
- end
-
- TestMode = self:GetModule("TestMode")
-end
-
-function RLF:SlashCommand(msg, editBox)
- G_RLF:fn(function()
- if msg == "test" then
- TestMode:ToggleTestMode()
- elseif msg == "clear" then
- G_RLF.LootDisplay:HideLoot()
- elseif msg == "log" then
- self:GetModule("Logger"):Show()
- else
- acd:Open(addonName)
- end
- end)
-end
-
-local currentVersion = "@project-version@"
-function RLF:PLAYER_ENTERING_WORLD(event, isLogin, isReload)
- if self.optionsFrame == nil then
- self.optionsFrame = acd:AddToBlizOptions(addonName, addonName)
- end
- G_RLF:fn(function()
- self:BossBannerHook()
- self:LootToastHook()
- self:MoneyAlertHook()
- end)
- local isNewVersion = currentVersion ~= G_RLF.db.global.lastVersionLoaded
- if isLogin and isReload == false and isNewVersion then
- G_RLF.db.global.lastVersionLoaded = currentVersion
- self:Print(G_RLF.L["Welcome"] .. " (" .. currentVersion .. ")")
- if G_RLF.db.global.enableAutoLoot then
- C_CVar.SetCVar("autoLootDefault", "1")
- end
- end
-end
-
-local optionsFrame
-local isOpen = false
-function RLF:OnOptionsOpen(...)
- local _, name, container, path = ...
- G_RLF:fn(function()
- if container then
- return
- end
- if name == addonName and not isOpen then
- isOpen = true
- G_RLF.LootDisplay:SetBoundingBoxVisibility(true)
- self:ScheduleTimer(function()
- optionsFrame = acd.OpenFrames[name]
- if self:IsHooked(optionsFrame, "Hide") then
- self:Unhook(optionsFrame, "Hide")
- end
- if optionsFrame and optionsFrame.Hide then
- self:Hook(optionsFrame, "Hide", "OnOptionsClose", true)
- end
- end, 0.25)
- end
- end)
-end
-
-function RLF:OnOptionsClose(...)
- G_RLF:fn(function()
- isOpen = false
- G_RLF.LootDisplay:SetBoundingBoxVisibility(false)
- self:Unhook(optionsFrame, "Hide")
- optionsFrame = nil
- end)
-end
diff --git a/RPGLootFeed.toc b/RPGLootFeed.toc
index 0ea9dc4..ca94397 100644
--- a/RPGLootFeed.toc
+++ b/RPGLootFeed.toc
@@ -14,23 +14,10 @@
embeds.xml
Core.lua
-DoubleLinkedList.lua
-Queue.lua
-
+utils/utils.xml
locale/locales.xml
-
-BlizzOverrides/overrides.xml
-
config/config.xml
-RPGLootFeed.lua
-Logger.lua
-
+BlizzOverrides/overrides.xml
Features/features.xml
-TestMode.lua
-#@alpha@
-SmokeTest.lua
-#@end-alpha@
LootDisplay/LootDisplay.xml
-#@alpha@
-LootDisplayProfiler.lua
-#@end-alpha@
+GameTesting/testing.xml
diff --git a/config/Features.lua b/config/Features/Features.lua
similarity index 80%
rename from config/Features.lua
rename to config/Features/Features.lua
index 27e0a39..45bbd30 100644
--- a/config/Features.lua
+++ b/config/Features/Features.lua
@@ -6,22 +6,13 @@ G_RLF.defaults.global.lootHistoryEnabled = true
G_RLF.defaults.global.historyLimit = 100
G_RLF.defaults.global.enablePartyLoot = false
G_RLF.defaults.global.itemLootFeed = true
-G_RLF.defaults.global.itemQualityFilter = {
- [Enum.ItemQuality.Poor] = true,
- [Enum.ItemQuality.Common] = true,
- [Enum.ItemQuality.Uncommon] = true,
- [Enum.ItemQuality.Rare] = true,
- [Enum.ItemQuality.Epic] = true,
- [Enum.ItemQuality.Legendary] = true,
- [Enum.ItemQuality.Artifact] = true,
- [Enum.ItemQuality.Heirloom] = true,
-}
G_RLF.defaults.global.currencyFeed = true
G_RLF.defaults.global.tooltip = true
G_RLF.defaults.global.tooltipOnShift = false
G_RLF.defaults.global.moneyFeed = true
G_RLF.defaults.global.xpFeed = true
G_RLF.defaults.global.repFeed = true
+G_RLF.defaults.global.profFeed = true
G_RLF.options.args.features = {
type = "group",
@@ -78,34 +69,6 @@ G_RLF.options.args.features = {
set = "SetItemLootStatus",
order = 2,
},
- itemLootConfig = {
- type = "group",
- disabled = "ItemLootDisabled",
- name = G_RLF.L["Item Loot Config"],
- inline = true,
- order = 2.1,
- args = {
- itemQualityFilter = {
- type = "multiselect",
- name = G_RLF.L["Item Quality Filter"],
- desc = G_RLF.L["ItemQualityFilterDesc"],
- values = {
- [Enum.ItemQuality.Poor] = G_RLF.L["Poor"],
- [Enum.ItemQuality.Common] = G_RLF.L["Common"],
- [Enum.ItemQuality.Uncommon] = G_RLF.L["Uncommon"],
- [Enum.ItemQuality.Rare] = G_RLF.L["Rare"],
- [Enum.ItemQuality.Epic] = G_RLF.L["Epic"],
- [Enum.ItemQuality.Legendary] = G_RLF.L["Legendary"],
- [Enum.ItemQuality.Artifact] = G_RLF.L["Artifact"],
- [Enum.ItemQuality.Heirloom] = G_RLF.L["Heirloom"],
- },
- width = "double",
- get = "GetItemQualityFilter",
- set = "SetItemQualityFilter",
- order = 1,
- },
- },
- },
enableCurrency = {
type = "toggle",
name = G_RLF.L["Enable Currency in Feed"],
@@ -169,6 +132,15 @@ G_RLF.options.args.features = {
set = "SetRepStatus",
order = 7,
},
+ enableProf = {
+ type = "toggle",
+ name = G_RLF.L["Enable Professions in Feed"],
+ desc = G_RLF.L["EnableProfDesc"],
+ width = "double",
+ get = "GetProfStatus",
+ set = "SetProfStatus",
+ order = 8,
+ },
},
}
@@ -227,18 +199,6 @@ function Features:SetItemLootStatus(info, value)
end
end
-function Features:ItemLootDisabled()
- return not G_RLF.db.global.itemLootFeed
-end
-
-function Features:GetItemQualityFilter(info, quality)
- return G_RLF.db.global.itemQualityFilter[quality]
-end
-
-function Features:SetItemQualityFilter(info, quality, value)
- G_RLF.db.global.itemQualityFilter[quality] = value
-end
-
function Features:GetCurrencyStatus(info, value)
return G_RLF.db.global.currencyFeed
end
@@ -310,3 +270,16 @@ function Features:SetRepStatus(info, value)
G_RLF.RLF:DisableModule("Reputation")
end
end
+
+function Features:GetProfStatus(info, value)
+ return G_RLF.db.global.profFeed
+end
+
+function Features:SetProfStatus(info, value)
+ G_RLF.db.global.profFeed = value
+ if value then
+ G_RLF.RLF:EnableModule("Professions")
+ else
+ G_RLF.RLF:DisableModule("Professions")
+ end
+end
diff --git a/config/Features/ItemConfig.lua b/config/Features/ItemConfig.lua
new file mode 100644
index 0000000..58b9e76
--- /dev/null
+++ b/config/Features/ItemConfig.lua
@@ -0,0 +1,299 @@
+local addonName, G_RLF = ...
+
+local ItemConfig = {}
+
+local PricesEnum = G_RLF.PricesEnum
+
+G_RLF.defaults.global.itemQualityFilter = {
+ [Enum.ItemQuality.Poor] = true,
+ [Enum.ItemQuality.Common] = true,
+ [Enum.ItemQuality.Uncommon] = true,
+ [Enum.ItemQuality.Rare] = true,
+ [Enum.ItemQuality.Epic] = true,
+ [Enum.ItemQuality.Legendary] = true,
+ [Enum.ItemQuality.Artifact] = true,
+ [Enum.ItemQuality.Heirloom] = true,
+}
+G_RLF.defaults.global.auctionHouseSource = G_RLF.L["None"]
+G_RLF.defaults.global.pricesForSellableItems = PricesEnum.Vendor
+G_RLF.defaults.global.itemHighlights = {
+ boe = false,
+ bop = false,
+ quest = false,
+ transmog = false,
+ mounts = true,
+ legendary = true,
+ betterThanEquipped = true,
+}
+
+G_RLF.options.args.features.args.itemLootConfig = {
+ type = "group",
+ handler = ItemConfig,
+ disabled = "ItemLootDisabled",
+ name = G_RLF.L["Item Loot Config"],
+ order = 2.1,
+ args = {
+ enableItemLoot = {
+ type = "toggle",
+ name = G_RLF.L["Enable Item Loot in Feed"],
+ desc = G_RLF.L["EnableItemLootDesc"],
+ width = "double",
+ get = "GetItemLootStatus",
+ set = "SetItemLootStatus",
+ order = 1,
+ },
+ enablePartyLoot = {
+ type = "toggle",
+ name = G_RLF.L["Enable Party Loot in Feed"],
+ desc = G_RLF.L["EnablePartyLootDesc"],
+ width = "double",
+ get = "GetPartyLootStatus",
+ set = "SetPartyLootStatus",
+ order = 1.1,
+ },
+ itemSecondaryTextOptions = {
+ type = "group",
+ name = G_RLF.L["Item Secondary Text Options"],
+ inline = true,
+ order = 1.2,
+ args = {
+ pricesForSellableItems = {
+ type = "select",
+ name = G_RLF.L["Prices for Sellable Items"],
+ desc = G_RLF.L["PricesForSellableItemsDesc"],
+ values = function()
+ local values = {
+ [PricesEnum.None] = G_RLF.L["None"],
+ [PricesEnum.Vendor] = G_RLF.L["Vendor Price"],
+ }
+
+ if G_RLF.AuctionIntegrations.numActiveIntegrations > 0 then
+ values[PricesEnum.AH] = G_RLF.L["Auction Price"]
+ end
+
+ return values
+ end,
+ sorting = {
+ PricesEnum.None,
+ PricesEnum.Vendor,
+ PricesEnum.AH,
+ },
+ get = function(info)
+ if
+ G_RLF.db.global.pricesForSellableItems == PricesEnum.AH
+ and G_RLF.AuctionIntegrations.numActiveIntegrations == 0
+ then
+ G_RLF.db.global.pricesForSellableItems = PricesEnum.Vendor
+ end
+ return G_RLF.db.global.pricesForSellableItems
+ end,
+ set = function(info, value)
+ G_RLF.db.global.pricesForSellableItems = value
+ end,
+ order = 1,
+ },
+ auctionHouseSource = {
+ type = "select",
+ name = G_RLF.L["Auction House Source"],
+ desc = G_RLF.L["AuctionHouseSourceDesc"],
+ values = function()
+ local values = {}
+ values[G_RLF.AuctionIntegrations.nilIntegration:ToString()] =
+ G_RLF.AuctionIntegrations.nilIntegration:ToString()
+
+ local activeIntegrations = G_RLF.AuctionIntegrations.activeIntegrations
+ local numActiveIntegrations = G_RLF.AuctionIntegrations.numActiveIntegrations
+ if activeIntegrations and numActiveIntegrations >= 1 then
+ for k, _ in pairs(activeIntegrations) do
+ values[k] = k
+ end
+ end
+ return values
+ end,
+ sorting = function()
+ local values = {}
+ values[1] = G_RLF.AuctionIntegrations.nilIntegration:ToString()
+
+ local activeIntegrations = G_RLF.AuctionIntegrations.activeIntegrations
+ local numActiveIntegrations = G_RLF.AuctionIntegrations.numActiveIntegrations
+ if activeIntegrations and numActiveIntegrations >= 1 then
+ local i = 2
+ for k, _ in pairs(activeIntegrations) do
+ values[i] = k
+ i = i + 1
+ end
+ end
+ return values
+ end,
+ hidden = function()
+ local activeIntegrations = G_RLF.AuctionIntegrations.activeIntegrations
+ local numActiveIntegrations = G_RLF.AuctionIntegrations.numActiveIntegrations
+ local hide = not activeIntegrations or numActiveIntegrations == 0
+ if hide then
+ G_RLF.db.global.auctionHouseSource = G_RLF.AuctionIntegrations.nilIntegration:ToString()
+ end
+ return hide
+ end,
+ get = function(info)
+ local activeIntegrations = G_RLF.AuctionIntegrations.activeIntegrations
+ local numActiveIntegrations = G_RLF.AuctionIntegrations.numActiveIntegrations
+ if
+ not activeIntegrations
+ or not activeIntegrations[G_RLF.db.global.auctionHouseSource]
+ or numActiveIntegrations == 0
+ then
+ G_RLF.db.global.auctionHouseSource = G_RLF.AuctionIntegrations.nilIntegration:ToString()
+ end
+ return G_RLF.db.global.auctionHouseSource
+ end,
+ set = function(info, value)
+ G_RLF.db.global.auctionHouseSource = value
+ if value ~= G_RLF.AuctionIntegrations.nilIntegration:ToString() then
+ G_RLF.AuctionIntegrations.activeIntegration =
+ G_RLF.AuctionIntegrations.activeIntegrations[value]
+ else
+ G_RLF.AuctionIntegrations.activeIntegration = G_RLF.AuctionIntegrations.nilIntegration
+ end
+ end,
+ order = 2,
+ },
+ },
+ },
+ itemQualityFilter = {
+ type = "multiselect",
+ name = G_RLF.L["Item Quality Filter"],
+ desc = G_RLF.L["ItemQualityFilterDesc"],
+ values = {
+ [Enum.ItemQuality.Poor] = G_RLF.L["Poor"],
+ [Enum.ItemQuality.Common] = G_RLF.L["Common"],
+ [Enum.ItemQuality.Uncommon] = G_RLF.L["Uncommon"],
+ [Enum.ItemQuality.Rare] = G_RLF.L["Rare"],
+ [Enum.ItemQuality.Epic] = G_RLF.L["Epic"],
+ [Enum.ItemQuality.Legendary] = G_RLF.L["Legendary"],
+ [Enum.ItemQuality.Artifact] = G_RLF.L["Artifact"],
+ [Enum.ItemQuality.Heirloom] = G_RLF.L["Heirloom"],
+ },
+ width = "double",
+ get = "GetItemQualityFilter",
+ set = "SetItemQualityFilter",
+ order = 2,
+ },
+ itemHighlights = {
+ type = "group",
+ name = G_RLF.L["Item Highlights"],
+ desc = G_RLF.L["ItemHighlightsDesc"],
+ inline = true,
+ order = 3,
+ args = {
+ highlightMount = {
+ type = "toggle",
+ name = G_RLF.L["Highlight Mounts"],
+ desc = G_RLF.L["HighlightMountsDesc"],
+ width = "double",
+ get = function(info)
+ return G_RLF.db.global.itemHighlights.mounts
+ end,
+ set = function(info, value)
+ G_RLF.db.global.itemHighlights.mounts = value
+ end,
+ order = 1,
+ },
+ highlightLegendary = {
+ type = "toggle",
+ name = G_RLF.L["Highlight Legendary Items"],
+ desc = G_RLF.L["HighlightLegendaryDesc"],
+ width = "double",
+ get = function(info)
+ return G_RLF.db.global.itemHighlights.legendary
+ end,
+ set = function(info, value)
+ G_RLF.db.global.itemHighlights.legendary = value
+ end,
+ order = 2,
+ },
+ highlightBetterThanEquipped = {
+ type = "toggle",
+ name = G_RLF.L["Highlight Items Better Than Equipped"],
+ desc = G_RLF.L["HighlightBetterThanEquippedDesc"],
+ width = "double",
+ get = function(info)
+ return G_RLF.db.global.itemHighlights.betterThanEquipped
+ end,
+ set = function(info, value)
+ G_RLF.db.global.itemHighlights.betterThanEquipped = value
+ end,
+ order = 3,
+ },
+ -- highlightBoE = {
+ -- type = "toggle",
+ -- name = G_RLF.L["Highlight BoE Items"],
+ -- desc = G_RLF.L["HighlightBoEDesc"],
+ -- width = "double",
+ -- get = function(info) return G_RLF.db.global.itemHighlights.boe end,
+ -- set = function(info, value) G_RLF.db.global.itemHighlights.boe = value end,
+ -- order = 3,
+ -- },
+ -- highlightBoP = {
+ -- type = "toggle",
+ -- name = G_RLF.L["Highlight BoP Items"],
+ -- desc = G_RLF.L["HighlightBoPDesc"],
+ -- width = "double",
+ -- get = function(info) return G_RLF.db.global.itemHighlights.bop end,
+ -- set = function(info, value) G_RLF.db.global.itemHighlights.bop = value end,
+ -- order = 4,
+ -- },
+ -- highlightQuest = {
+ -- type = "toggle",
+ -- name = G_RLF.L["Highlight Quest Items"],
+ -- desc = G_RLF.L["HighlightQuestDesc"],
+ -- width = "double",
+ -- get = function(info) return G_RLF.db.global.itemHighlights.quest end,
+ -- set = function(info, value) G_RLF.db.global.itemHighlights.quest = value end,
+ -- order = 5,
+ -- },
+ -- highlightTransmog = {
+ -- type = "toggle",
+ -- name = G_RLF.L["Highlight Transmog Items"],
+ -- desc = G_RLF.L["HighlightTransmogDesc"],
+ -- width = "double",
+ -- get = function(info) return G_RLF.db.global.itemHighlights.transmog end,
+ -- set = function(info, value) G_RLF.db.global.itemHighlights.transmog = value end,
+ -- order = 6,
+ -- },
+ },
+ },
+ },
+}
+
+function ItemConfig:GetItemLootStatus(info, value)
+ return G_RLF.db.global.itemLootFeed
+end
+
+function ItemConfig:SetItemLootStatus(info, value)
+ G_RLF.db.global.itemLootFeed = value
+ if value then
+ G_RLF.RLF:EnableModule("ItemLoot")
+ else
+ G_RLF.RLF:DisableModule("ItemLoot")
+ end
+end
+
+function ItemConfig:GetPartyLootStatus()
+ return G_RLF.db.global.enablePartyLoot
+end
+
+function ItemConfig:SetPartyLootStatus(info, value)
+ G_RLF.db.global.enablePartyLoot = value
+end
+
+function ItemConfig:ItemLootDisabled()
+ return not G_RLF.db.global.itemLootFeed
+end
+
+function ItemConfig:GetItemQualityFilter(info, quality)
+ return G_RLF.db.global.itemQualityFilter[quality]
+end
+
+function ItemConfig:SetItemQualityFilter(info, quality, value)
+ G_RLF.db.global.itemQualityFilter[quality] = value
+end
diff --git a/config/Features/features.xml b/config/Features/features.xml
new file mode 100644
index 0000000..b5f9865
--- /dev/null
+++ b/config/Features/features.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/config.xml b/config/config.xml
index 343d309..7a2f7f6 100644
--- a/config/config.xml
+++ b/config/config.xml
@@ -3,7 +3,7 @@
xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\FrameXML\UI.xsd">
-
+
diff --git a/embeds.xml b/embeds.xml
index 9cd2637..08b58d7 100644
--- a/embeds.xml
+++ b/embeds.xml
@@ -2,6 +2,7 @@
+
diff --git a/locale/enUS.lua b/locale/enUS.lua
index e604477..cdd5980 100644
--- a/locale/enUS.lua
+++ b/locale/enUS.lua
@@ -42,6 +42,16 @@ L["EnablePartyLootDesc"] = "Show party/raid looted items in the Loot Feed"
L["Enable Item Loot in Feed"] = true
L["EnableItemLootDesc"] = "Show looted items in the Loot Feed"
L["Item Loot Config"] = true
+L["Item Secondary Text Options"] = true
+L["Prices for Sellable Items"] = true
+L["PricesForSellableItemsDesc"] = "Select which price to show for sellable items in the Loot Feed"
+L["None"] = true
+L["Vendor Price"] = true
+L["Auctionator"] = true
+L["TSM"] = true
+L["Auction House Source"] = true
+L["AuctionHouseSourceDesc"] = "Select the source addon for Auction House pricing information"
+L["Auction Price"] = true
L["Item Quality Filter"] = true
L["ItemQualityFilterDesc"] = "Check which qualities you would like to show in the Loot Feed."
L["Poor"] = true
@@ -52,6 +62,14 @@ L["Epic"] = true
L["Legendary"] = true
L["Artifact"] = true
L["Heirloom"] = true
+L["Item Highlights"] = true
+L["ItemHighlightsDesc"] = "Highlight items in the Loot Feed based on certain criteria"
+L["Highlight Mounts"] = true
+L["HighlightMountsDesc"] = "Highlight Mounts in the Loot Feed"
+L["Highlight Legendary Items"] = true
+L["HighlightLegendaryDesc"] = "Highlight Legendary items in the Loot Feed"
+L["Highlight Items Better Than Equipped"] = true
+L["HighlightBetterThanEquippedDesc"] = "Highlight items that are better than what you have equipped in the Loot Feed"
L["Enable Currency in Feed"] = true
L["EnableCurrencyDesc"] = "Show currency such as Flightstones, Honor, Drake's Awakened Crest, etc. in the Loot Feed"
L["Enable Item/Currency Tooltips"] = true
@@ -65,6 +83,8 @@ L["Enable Experience in Feed"] = true
L["EnableXPDesc"] = "Show experience gains in the Loot Feed"
L["Enable Reputation in Feed"] = true
L["EnableRepDesc"] = "Show reputation gains in the Loot Feed"
+L["Enable Professions in Feed"] = true
+L["EnableProfDesc"] = "Show profession skill gains in the Loot Feed"
-- ConfigOptions - Positioning Group
L["Toggle Test Mode"] = true
diff --git a/spec/BlizzOverrides/BossBanner_spec.lua b/spec/BlizzOverrides/BossBanner_spec.lua
index 46a5d96..10d0358 100644
--- a/spec/BlizzOverrides/BossBanner_spec.lua
+++ b/spec/BlizzOverrides/BossBanner_spec.lua
@@ -1,15 +1,110 @@
+local common_stubs = require("spec/common_stubs")
+
describe("BossBanner module", function()
+ local ns, BossBannerOverride
before_each(function()
- -- Define the global G_RLF
- local ns = {
- RLF = {},
+ ns = ns or common_stubs.setup_G_RLF(spy)
+ ns.DisableBossBanner = {
+ ENABLED = 0,
+ FULLY_DISABLE = 1,
+ DISABLE_LOOT = 2,
+ DISABLE_MY_LOOT = 3,
+ DISABLE_GROUP_LOOT = 4,
+ }
+ _G.BossBanner = {
+ OnEvent = function() end,
}
- -- Load the list module before each test
- assert(loadfile("BlizzOverrides/BossBanner.lua"))("TestAddon", ns)
+ BossBannerOverride = assert(loadfile("BlizzOverrides/BossBanner.lua"))("TestAddon", ns)
+ end)
+
+ describe("OnInitialize", function()
+ it("registers PLAYER_ENTERING_WORLD event", function()
+ spy.on(BossBannerOverride, "RegisterEvent")
+ BossBannerOverride:OnInitialize()
+ assert
+ .spy(BossBannerOverride.RegisterEvent).was
+ .called_with(BossBannerOverride, "PLAYER_ENTERING_WORLD", "BossBannerHook")
+ end)
+ end)
+
+ it("hooks BossBanner OnEvent when available", function()
+ _G.BossBanner = {}
+ spy.on(BossBannerOverride, "RawHookScript")
+ BossBannerOverride:BossBannerHook()
+ assert
+ .spy(BossBannerOverride.RawHookScript).was
+ .called_with(BossBannerOverride, BossBanner, "OnEvent", "InterceptBossBannerAlert", true)
+ end)
+
+ it("does not hook BossBanner OnEvent if already hooked", function()
+ _G.BossBanner = {}
+ spy.on(BossBannerOverride, "IsHooked")
+ spy.on(BossBannerOverride, "RawHookScript")
+ BossBannerOverride.IsHooked = function()
+ return true
+ end
+ BossBannerOverride:BossBannerHook()
+ assert.spy(BossBannerOverride.RawHookScript).was_not_called()
end)
- it("TODO", function()
- assert.are.equal(true, true)
+ describe("InterceptBossBannerAlert", function()
+ before_each(function()
+ BossBannerOverride.hooks = { [BossBanner] = { OnEvent = function() end } }
+ spy.on(BossBannerOverride.hooks[BossBanner], "OnEvent")
+ end)
+
+ it("completely skips BossBanner alert if fully disabled", function()
+ local event = "ANYTHING"
+ ns.db.global.bossBannerConfig = ns.DisableBossBanner.FULLY_DISABLE
+ BossBannerOverride:InterceptBossBannerAlert(nil, event, nil, nil, nil, nil, nil, nil)
+ assert.spy(BossBannerOverride.hooks[BossBanner].OnEvent).was_not_called()
+ end)
+
+ it("does not show any loot if loot is disabled", function()
+ local event = "ENCOUNTER_LOOT_RECEIVED"
+ local myName = "MyPlayer"
+ local playerName = "TestPlayer"
+ ns.db.global.bossBannerConfig = ns.DisableBossBanner.DISABLE_LOOT
+ BossBannerOverride:InterceptBossBannerAlert(nil, event, nil, nil, nil, nil, playerName, nil)
+ BossBannerOverride:InterceptBossBannerAlert(nil, event, nil, nil, nil, nil, myName, nil)
+ assert.spy(BossBannerOverride.hooks[BossBanner].OnEvent).was_not_called()
+ end)
+
+ it("does not show my loot if my loot is disabled", function()
+ local event = "ENCOUNTER_LOOT_RECEIVED"
+ local playerName = "TestPlayer"
+ local myName = "MyPlayer"
+ local myGuid = "Player-1234-5678"
+ _G.GetPlayerGuid = function()
+ return myGuid
+ end
+ _G.GetNameAndServerNameFromGUID = function()
+ return myName, nil
+ end
+
+ ns.db.global.bossBannerConfig = ns.DisableBossBanner.DISABLE_MY_LOOT
+ BossBannerOverride:InterceptBossBannerAlert(nil, event, nil, nil, nil, nil, myName, nil)
+ assert.spy(BossBannerOverride.hooks[BossBanner].OnEvent).was_not_called()
+ end)
+
+ it("does not show group loot if group loot is disabled", function()
+ local event = "ENCOUNTER_LOOT_RECEIVED"
+ local playerName = "TestPlayer"
+ local myName = "MyPlayer"
+ local myGuid = "Player-1234-5678"
+ _G.GetPlayerGuid = function()
+ return myGuid
+ end
+ _G.GetNameAndServerNameFromGUID = function()
+ return myName, nil
+ end
+
+ ns.db.global.bossBannerConfig = ns.DisableBossBanner.DISABLE_GROUP_LOOT
+ BossBannerOverride:InterceptBossBannerAlert(nil, event, nil, nil, nil, nil, playerName, nil)
+ assert.spy(BossBannerOverride.hooks[BossBanner].OnEvent).was_not_called()
+ BossBannerOverride:InterceptBossBannerAlert(nil, event, nil, nil, nil, nil, myName, nil)
+ assert.spy(BossBannerOverride.hooks[BossBanner].OnEvent).was_called()
+ end)
end)
end)
diff --git a/spec/BlizzOverrides/LootToasts_spec.lua b/spec/BlizzOverrides/LootToasts_spec.lua
index 1ac0d38..57960d3 100644
--- a/spec/BlizzOverrides/LootToasts_spec.lua
+++ b/spec/BlizzOverrides/LootToasts_spec.lua
@@ -1,14 +1,60 @@
+local common_stubs = require("spec/common_stubs")
+
describe("LootToasts module", function()
+ local ns, LootToastOverride
before_each(function()
- -- Define the global G_RLF
- local ns = {
- RLF = {},
+ ns = ns or common_stubs.setup_G_RLF(spy)
+ _G.LootAlertSystem = {
+ AddAlert = function() end,
}
- -- Load the list module before each test
- assert(loadfile("BlizzOverrides/LootToasts.lua"))("TestAddon", ns)
+
+ LootToastOverride = assert(loadfile("BlizzOverrides/LootToasts.lua"))("TestAddon", ns)
+ end)
+
+ describe("OnInitialize", function()
+ it("registers PLAYER_ENTERING_WORLD event", function()
+ spy.on(LootToastOverride, "RegisterEvent")
+ LootToastOverride:OnInitialize()
+ assert
+ .spy(LootToastOverride.RegisterEvent).was
+ .called_with(LootToastOverride, "PLAYER_ENTERING_WORLD", "LootToastHook")
+ end)
+ end)
+
+ it("hooks LootAlertSystem AddAlert when available", function()
+ spy.on(LootToastOverride, "RawHook")
+ LootToastOverride:LootToastHook()
+ assert
+ .spy(LootToastOverride.RawHook).was
+ .called_with(LootToastOverride, LootAlertSystem, "AddAlert", "InterceptAddAlert", true)
+ end)
+
+ it("does not hook LootAlertSystem AddAlert if already hooked", function()
+ spy.on(LootToastOverride, "IsHooked")
+ spy.on(LootToastOverride, "RawHook")
+ LootToastOverride.IsHooked = function()
+ return true
+ end
+ LootToastOverride:LootToastHook()
+ assert.spy(LootToastOverride.RawHook).was_not_called()
end)
- it("TODO", function()
- assert.are.equal(true, true)
+ describe("InterceptAddAlert", function()
+ before_each(function()
+ LootToastOverride.hooks = { [LootAlertSystem] = { AddAlert = function() end } }
+ spy.on(LootToastOverride.hooks[LootAlertSystem], "AddAlert")
+ end)
+
+ it("completely skips LootAlertSystem alert if disabled", function()
+ ns.db.global.disableBlizzLootToasts = true
+ LootToastOverride:InterceptAddAlert(nil)
+ assert.spy(LootToastOverride.hooks[LootAlertSystem].AddAlert).was_not_called()
+ end)
+
+ it("calls the original AddAlert function if not disabled", function()
+ ns.db.global.disableBlizzLootToasts = false
+ LootToastOverride:InterceptAddAlert(nil)
+ assert.spy(LootToastOverride.hooks[LootAlertSystem].AddAlert).was_called()
+ end)
end)
end)
diff --git a/spec/BlizzOverrides/MoneyAlerts_spec.lua b/spec/BlizzOverrides/MoneyAlerts_spec.lua
new file mode 100644
index 0000000..e24bb79
--- /dev/null
+++ b/spec/BlizzOverrides/MoneyAlerts_spec.lua
@@ -0,0 +1,60 @@
+local common_stubs = require("spec/common_stubs")
+
+describe("MoneyAlerts module", function()
+ local ns, MoneyAlertOverride
+ before_each(function()
+ ns = ns or common_stubs.setup_G_RLF(spy)
+ _G.MoneyWonAlertSystem = {
+ AddAlert = function() end,
+ }
+
+ MoneyAlertOverride = assert(loadfile("BlizzOverrides/MoneyAlerts.lua"))("TestAddon", ns)
+ end)
+
+ describe("OnInitialize", function()
+ it("registers PLAYER_ENTERING_WORLD event", function()
+ spy.on(MoneyAlertOverride, "RegisterEvent")
+ MoneyAlertOverride:OnInitialize()
+ assert
+ .spy(MoneyAlertOverride.RegisterEvent).was
+ .called_with(MoneyAlertOverride, "PLAYER_ENTERING_WORLD", "MoneyAlertHook")
+ end)
+ end)
+
+ it("hooks MoneyWonAlertSystem AddAlert when available", function()
+ spy.on(MoneyAlertOverride, "RawHook")
+ MoneyAlertOverride:MoneyAlertHook()
+ assert
+ .spy(MoneyAlertOverride.RawHook).was
+ .called_with(MoneyAlertOverride, MoneyWonAlertSystem, "AddAlert", "InterceptMoneyAddAlert", true)
+ end)
+
+ it("does not hook MoneyWonAlertSystem AddAlert if already hooked", function()
+ spy.on(MoneyAlertOverride, "IsHooked")
+ spy.on(MoneyAlertOverride, "RawHook")
+ MoneyAlertOverride.IsHooked = function()
+ return true
+ end
+ MoneyAlertOverride:MoneyAlertHook()
+ assert.spy(MoneyAlertOverride.RawHook).was_not_called()
+ end)
+
+ describe("InterceptMoneyAddAlert", function()
+ before_each(function()
+ MoneyAlertOverride.hooks = { [MoneyWonAlertSystem] = { AddAlert = function() end } }
+ spy.on(MoneyAlertOverride.hooks[MoneyWonAlertSystem], "AddAlert")
+ end)
+
+ it("completely skips MoneyWonAlertSystem alert if disabled", function()
+ ns.db.global.disableBlizzMoneyAlerts = true
+ MoneyAlertOverride:InterceptMoneyAddAlert(nil)
+ assert.spy(MoneyAlertOverride.hooks[MoneyWonAlertSystem].AddAlert).was_not_called()
+ end)
+
+ it("calls the original AddAlert function if not disabled", function()
+ ns.db.global.disableBlizzMoneyAlerts = false
+ MoneyAlertOverride:InterceptMoneyAddAlert(nil)
+ assert.spy(MoneyAlertOverride.hooks[MoneyWonAlertSystem].AddAlert).was_called()
+ end)
+ end)
+end)
diff --git a/spec/BlizzOverrides/retryHook_spec.lua b/spec/BlizzOverrides/retryHook_spec.lua
new file mode 100644
index 0000000..5de9e08
--- /dev/null
+++ b/spec/BlizzOverrides/retryHook_spec.lua
@@ -0,0 +1,35 @@
+describe("retryHook function", function()
+ local module, ns
+
+ before_each(function()
+ module = {
+ ScheduleTimer = function() end,
+ }
+ ns = {
+ Print = function() end,
+ L = {
+ ["Issues"] = "There are issues.",
+ ["TestLocaleKey"] = "Test locale message.",
+ },
+ }
+ assert(loadfile("BlizzOverrides/retryHook.lua"))("TestAddon", ns)
+ end)
+
+ it("should schedule the hook function if attempts are less than or equal to 30", function()
+ spy.on(module, "ScheduleTimer")
+ local attempts = ns.retryHook(module, 0, "hookFunctionName", "TestLocaleKey")
+ assert.are.equal(attempts, 1)
+ assert.spy(module.ScheduleTimer).was.called_with(module, "hookFunctionName", 1)
+ end)
+
+ it("should print an error message if attempts exceed 30", function()
+ spy.on(module, "ScheduleTimer")
+ spy.on(ns, "Print")
+ local attempts = ns.retryHook(module, 30, "hookFunctionName", "TestLocaleKey")
+ assert.are.equal(attempts, 30)
+ assert.spy(module.ScheduleTimer).was_not_called()
+ assert.spy(ns.Print).was.called(2)
+ -- assert.spy(ns.Print).was.called_with(ns.L["TestLocaleKey"])
+ -- assert.spy(ns.Print).was.called_with(ns.L["Issues"])
+ end)
+end)
diff --git a/spec/Core_spec.lua b/spec/Core_spec.lua
index 0a789bd..9f8d39c 100644
--- a/spec/Core_spec.lua
+++ b/spec/Core_spec.lua
@@ -1,22 +1,99 @@
+local common_stubs = require("spec/common_stubs")
+
describe("Core module", function()
+ local ns, RLF
+ local _ = match._
+
before_each(function()
- _G.LibStub = function()
- return {
- NewAddon = function()
- return {
- SetDefaultModuleState = function() end,
- SetDefaultModulePrototype = function() end,
- }
- end,
- }
+ ns = ns or common_stubs.setup_G_RLF(spy)
+ ns.L = {
+ Welcome = "Welcome",
+ }
+ ns.LootDisplay = {
+ SetBoundingBoxVisibility = function() end,
+ HideLoot = function() end,
+ }
+ RLF = assert(loadfile("Core.lua"))("TestAddon", ns)
+ RLF.GetModule = function(_, moduleName)
+ if moduleName == "TestMode" then
+ return {}
+ end
end
+ RLF.Hook = spy.new()
+ RLF.Unhook = spy.new()
+ RLF.RegisterEvent = spy.new()
+ RLF.UnregisterEvent = spy.new()
+ RLF.RegisterChatCommand = spy.new()
+ RLF.Print = spy.new()
+ RLF.ScheduleTimer = function() end
+ end)
- local ns = {}
- -- Load the list module before each test
- assert(loadfile("Core.lua"))("TestAddon", ns)
+ describe("addon initialization", function()
+ it("should initialize correctly", function()
+ spy.on(RLF, "OnInitialize")
+ RLF:OnInitialize()
+ assert.spy(RLF.OnInitialize).was.called()
+ end)
end)
- it("TODO", function()
- assert.are.equal(true, true)
+ describe("OnSlashCommand", function()
+ it("should handle test mode command correctly", function()
+ local TestMode = {
+ ToggleTestMode = function() end,
+ }
+ spy.on(TestMode, "ToggleTestMode")
+ RLF.GetModule = function(_, moduleName)
+ if moduleName == "TestMode" then
+ return TestMode
+ end
+ end
+
+ RLF:OnInitialize()
+ RLF:SlashCommand("test")
+ assert.spy(TestMode.ToggleTestMode).was.called()
+ end)
+
+ it("should handle unknown command correctly", function()
+ local acd = ns.LibStubReturn["AceConfigDialog-3.0"]
+ spy.on(acd, "Open")
+ RLF:OnInitialize()
+ RLF:SlashCommand("unknown")
+ assert.spy(acd.Open).was.called_with(_, "TestAddon")
+ end)
+ end)
+
+ describe("PLAYER_ENTERING_WORLD", function()
+ it("should handle PLAYER_ENTERING_WORLD event correctly", function()
+ ns.db.global.enableAutoLoot = true
+ spy.on(RLF, "PLAYER_ENTERING_WORLD")
+ RLF:PLAYER_ENTERING_WORLD("PLAYER_ENTERING_WORLD", true, false)
+ assert.spy(RLF.PLAYER_ENTERING_WORLD).was.called()
+ end)
+ end)
+
+ describe("OnOptionsOpen/OnOptionsClose", function()
+ it("shows the bounding box when the options are opened", function()
+ spy.on(RLF, "ScheduleTimer")
+ spy.on(ns.LootDisplay, "SetBoundingBoxVisibility")
+ RLF:OnOptionsOpen(nil, "TestAddon", nil, nil)
+ assert.spy(RLF.ScheduleTimer).was.called()
+ assert.spy(ns.LootDisplay.SetBoundingBoxVisibility).was_called_with(_, true)
+ end)
+
+ it("does nothing if the options are already open", function()
+ spy.on(RLF, "ScheduleTimer")
+ spy.on(ns.LootDisplay, "SetBoundingBoxVisibility")
+ RLF:OnOptionsOpen(nil, "TestAddon", nil, nil)
+ RLF:OnOptionsOpen(nil, "TestAddon", nil, nil)
+ assert.spy(RLF.ScheduleTimer).was.called(1)
+ assert.spy(ns.LootDisplay.SetBoundingBoxVisibility).was_called(1)
+ end)
+
+ it("hides the bounding box when the options are closed", function()
+ spy.on(ns.LootDisplay, "SetBoundingBoxVisibility")
+ RLF:OnOptionsOpen(nil, "TestAddon", nil, nil)
+ RLF:OnOptionsClose(nil, "TestAddon", nil, nil)
+ assert.spy(ns.LootDisplay.SetBoundingBoxVisibility).was_called_with(_, false)
+ end)
end)
end)
diff --git a/spec/Features/Currency_spec.lua b/spec/Features/Currency_spec.lua
index 9f8028d..d0c1bbb 100644
--- a/spec/Features/Currency_spec.lua
+++ b/spec/Features/Currency_spec.lua
@@ -24,7 +24,7 @@ describe("Currency module", function()
CurrencyModule:CURRENCY_DISPLAY_UPDATE(_, nil)
- assert.stub(ns.LootDisplay.ShowLoot).was.not_called()
+ assert.stub(ns.SendMessage).was.not_called()
end)
it("does not show loot if the quantityChange is nil", function()
@@ -32,7 +32,7 @@ describe("Currency module", function()
CurrencyModule:CURRENCY_DISPLAY_UPDATE(_, 123, nil, nil)
- assert.stub(ns.LootDisplay.ShowLoot).was.not_called()
+ assert.stub(ns.SendMessage).was.not_called()
end)
it("does not show loot if the quantityChange is lte 0", function()
@@ -40,7 +40,7 @@ describe("Currency module", function()
CurrencyModule:CURRENCY_DISPLAY_UPDATE(_, 123, nil, -1)
- assert.stub(ns.LootDisplay.ShowLoot).was.not_called()
+ assert.stub(ns.SendMessage).was.not_called()
end)
it("does not show loot if the currency info cannot be found", function()
@@ -51,7 +51,7 @@ describe("Currency module", function()
CurrencyModule:CURRENCY_DISPLAY_UPDATE(_, 123, 1, 1)
- assert.stub(ns.LootDisplay.ShowLoot).was.not_called()
+ assert.stub(ns.SendMessage).was.not_called()
end)
it("does not show loot if the currency has an empty description", function()
@@ -66,7 +66,7 @@ describe("Currency module", function()
CurrencyModule:CURRENCY_DISPLAY_UPDATE(_, 123, 5, 2)
- assert.stub(ns.LootDisplay.ShowLoot).was.not_called()
+ assert.stub(ns.SendMessage).was.not_called()
end)
it("shows loot if the currency info is valid", function()
@@ -76,6 +76,8 @@ describe("Currency module", function()
currencyID = 123,
description = "An awesome currency",
iconFileID = 123456,
+ quantity = 5,
+ quality = 2,
}
end
@@ -83,7 +85,7 @@ describe("Currency module", function()
CurrencyModule:CURRENCY_DISPLAY_UPDATE(_, 123, 5, 2)
- assert.spy(newElement).was.called_with(_, 123, "|c12345678|Hcurrency:123|r", 123456, 2)
- assert.stub(ns.LootDisplay.ShowLoot).was.called()
+ assert.spy(newElement).was.called_with(_, 123, "|c12345678|Hcurrency:123|r", 123456, 2, 5, 2, nil, nil)
+ assert.stub(ns.SendMessage).was.called()
end)
end)
diff --git a/spec/Features/Experience_spec.lua b/spec/Features/Experience_spec.lua
index b7c4aef..2d5d303 100644
--- a/spec/Features/Experience_spec.lua
+++ b/spec/Features/Experience_spec.lua
@@ -24,7 +24,7 @@ describe("Experience module", function()
XpModule:PLAYER_XP_UPDATE("PLAYER_XP_UPDATE", "target")
- assert.stub(ns.LootDisplay.ShowLoot).was_not_called()
+ assert.stub(ns.SendMessage).was_not_called()
end)
it("does not show xp if the calculated delta is 0", function()
@@ -34,7 +34,7 @@ describe("Experience module", function()
XpModule:PLAYER_XP_UPDATE("PLAYER_XP_UPDATE", "player")
- assert.stub(ns.LootDisplay.ShowLoot).was_not_called()
+ assert.stub(ns.SendMessage).was_not_called()
end)
it("show xp if the player levels up", function()
@@ -58,6 +58,6 @@ describe("Experience module", function()
XpModule:PLAYER_XP_UPDATE("PLAYER_XP_UPDATE", "player")
assert.spy(newElement).was.called_with(_, 50)
- assert.stub(ns.LootDisplay.ShowLoot).was.called()
+ assert.stub(ns.SendMessage).was.called()
end)
end)
diff --git a/spec/Features/ItemLoot_spec.lua b/spec/Features/ItemLoot_spec.lua
index 45e8c13..ab00f89 100644
--- a/spec/Features/ItemLoot_spec.lua
+++ b/spec/Features/ItemLoot_spec.lua
@@ -1,16 +1,108 @@
local common_stubs = require("spec/common_stubs")
describe("ItemLoot module", function()
- local LootModule, ns
+ local _ = match._
+ local LootModule, ns, showSpy
before_each(function()
-- Define the global G_RLF
+ common_stubs.stub_C_Item()
+ showSpy = spy.new()
ns = ns or common_stubs.setup_G_RLF(spy)
+ ns.InitializeLootDisplayProperties = function(element)
+ element.Show = function(...)
+ showSpy(...)
+ end
+ end
-- Load the list module before each test
- LootModule = assert(loadfile("Features/ItemLoot.lua"))("TestAddon", ns)
+ LootModule = assert(loadfile("Features/ItemLoot/ItemLoot.lua"))("TestAddon", ns)
+ LootModule:OnInitialize()
end)
- it("LootModule is not nil", function()
- assert.is_not_nil(LootModule)
+ it("should initialize correctly", function()
+ assert.is_function(LootModule.OnInitialize)
+ assert.is_function(LootModule.OnEnable)
+ assert.is_function(LootModule.OnDisable)
+ end)
+
+ it("should enable and disable correctly", function()
+ spy.on(LootModule, "RegisterEvent")
+ spy.on(LootModule, "UnregisterEvent")
+
+ LootModule:OnEnable()
+ assert.spy(LootModule.RegisterEvent).was.called_with(_, "CHAT_MSG_LOOT")
+ assert.spy(LootModule.RegisterEvent).was.called_with(_, "GET_ITEM_INFO_RECEIVED")
+ assert.spy(LootModule.RegisterEvent).was.called_with(_, "GROUP_ROSTER_UPDATE")
+
+ LootModule:OnDisable()
+ assert.spy(LootModule.UnregisterEvent).was.called_with(_, "CHAT_MSG_LOOT")
+ assert.spy(LootModule.UnregisterEvent).was.called_with(_, "GET_ITEM_INFO_RECEIVED")
+ assert.spy(LootModule.UnregisterEvent).was.called_with(_, "GROUP_ROSTER_UPDATE")
+ end)
+
+ it("should handle CHAT_MSG_LOOT event", function()
+ local msg = "You received |cffa335ee|Hitem:18803::::::::60:::::|h[Finkle's Lava Dredger]|h|r"
+ local playerName = "Player"
+ local guid = UnitGUID("player")
+
+ LootModule:CHAT_MSG_LOOT("CHAT_MSG_LOOT", msg, playerName, nil, nil, nil, nil, nil, nil, nil, nil, nil, guid)
+ assert.spy(showSpy).was.called()
+ end)
+
+ it("should handle GET_ITEM_INFO_RECEIVED event", function()
+ local itemID = 18803
+ local success = true
+ local itemLink = "|cffa335ee|Hitem:18803::::::::60:::::|h[Finkle's Lava Dredger]|h|r"
+ local amount = 1
+
+ LootModule.pendingItemRequests[itemID] = { itemLink, amount }
+ LootModule:GET_ITEM_INFO_RECEIVED("GET_ITEM_INFO_RECEIVED", itemID, success)
+ assert.is_nil(LootModule.pendingItemRequests[itemID])
+ assert.spy(showSpy).was.called()
+ end)
+
+ it("should handle GROUP_ROSTER_UPDATE event", function()
+ LootModule:GROUP_ROSTER_UPDATE("GROUP_ROSTER_UPDATE")
+ assert.spy(ns.LogInfo).was.called()
+ end)
+
+ it("should show party loot", function()
+ local msg = "PartyMember received |cffa335ee|Hitem:18803::::::::60:::::|h[Finkle's Lava Dredger]|h|r"
+ local itemLink = "|cffa335ee|Hitem:18803::::::::60:::::|h[Finkle's Lava Dredger]|h|r"
+ local playerName = "PartyMember"
+ local amount = 1
+ local itemId = 18803
+ ns.db.global.enablePartyLoot = true
+ LootModule.nameUnitMap = { PartyMember = "party1" }
+
+ LootModule:CHAT_MSG_LOOT(
+ "CHAT_MSG_LOOT",
+ msg,
+ playerName,
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ nil,
+ "Party1"
+ )
+ assert.spy(showSpy).was.called()
+ end)
+
+ it("handles GET_ITEM_INFO_RECEIVED event for party loot", function()
+ local itemID = 18803
+ local success = true
+ local itemLink = "|cffa335ee|Hitem:18803::::::::60:::::|h[Finkle's Lava Dredger]|h|r"
+ local amount = 1
+ local unit = "Party1"
+
+ LootModule.pendingPartyRequests[itemID] = { itemLink, amount, unit }
+ LootModule:GET_ITEM_INFO_RECEIVED("GET_ITEM_INFO_RECEIVED", itemID, success)
+ assert.is_nil(LootModule.pendingPartyRequests[itemID])
+ assert.spy(showSpy).was.called()
end)
end)
diff --git a/spec/Features/LootDisplayProperties_spec.lua b/spec/Features/LootDisplayProperties_spec.lua
new file mode 100644
index 0000000..5f5f78b
--- /dev/null
+++ b/spec/Features/LootDisplayProperties_spec.lua
@@ -0,0 +1,16 @@
+local common_stubs = require("spec/common_stubs")
+
+describe("LootDisplayProperties module", function()
+ local LootModule, ns
+
+ before_each(function()
+ -- Define the global G_RLF
+ ns = ns or common_stubs.setup_G_RLF(spy)
+ -- Load the list module before each test
+ LootModule = assert(loadfile("Features/LootDisplayProperties.lua"))("TestAddon", ns)
+ end)
+
+ it("LootModule is not nil", function()
+ assert.is_not_nil(LootModule)
+ end)
+end)
diff --git a/spec/Features/Money_spec.lua b/spec/Features/Money_spec.lua
index dc377e9..a2bf70d 100644
--- a/spec/Features/Money_spec.lua
+++ b/spec/Features/Money_spec.lua
@@ -1,6 +1,7 @@
local common_stubs = require("spec/common_stubs")
describe("Money module", function()
+ local _ = match._
local MoneyModule, ns
before_each(function()
@@ -10,7 +11,64 @@ describe("Money module", function()
MoneyModule = assert(loadfile("Features/Money.lua"))("TestAddon", ns)
end)
- it("MoneyModule is not nil", function()
- assert.is_not_nil(MoneyModule)
+ it("Money:OnInitialize enables or disables the module based on global moneyFeed", function()
+ ns.db.global.moneyFeed = true
+ spy.on(MoneyModule, "Enable")
+ MoneyModule:OnInitialize()
+ assert.spy(MoneyModule.Enable).was.called()
+
+ ns.db.global.moneyFeed = false
+ spy.on(MoneyModule, "Disable")
+ MoneyModule:OnInitialize()
+ assert.spy(MoneyModule.Disable).was.called()
+ end)
+
+ it("Money:OnEnable registers events and sets startingMoney", function()
+ stub(MoneyModule, "RegisterEvent")
+ stub(_G, "GetMoney").returns(1000)
+
+ MoneyModule:OnEnable()
+
+ assert.stub(MoneyModule.RegisterEvent).was_called_with(_, "PLAYER_MONEY")
+ assert.stub(MoneyModule.RegisterEvent).was_called_with(_, "PLAYER_ENTERING_WORLD")
+ assert.equals(MoneyModule.startingMoney, 1000)
+
+ MoneyModule.RegisterEvent:revert()
+ _G.GetMoney:revert()
+ end)
+
+ it("Money:OnDisable unregisters events", function()
+ stub(MoneyModule, "UnregisterEvent")
+
+ MoneyModule:OnDisable()
+
+ assert.stub(MoneyModule.UnregisterEvent).was_called_with(_, "PLAYER_MONEY")
+ assert.stub(MoneyModule.UnregisterEvent).was_called_with(_, "PLAYER_ENTERING_WORLD")
+
+ MoneyModule.UnregisterEvent:revert()
+ end)
+
+ it("Money:PLAYER_ENTERING_WORLD sets startingMoney", function()
+ stub(_G, "GetMoney").returns(2000)
+
+ MoneyModule:PLAYER_ENTERING_WORLD("PLAYER_ENTERING_WORLD")
+
+ assert.equals(MoneyModule.startingMoney, 2000)
+
+ _G.GetMoney:revert()
+ end)
+
+ it("Money:PLAYER_MONEY updates startingMoney and creates a new element", function()
+ stub(_G, "GetMoney").returns(3000)
+ stub(MoneyModule.Element, "new").returns({ Show = function() end })
+
+ MoneyModule.startingMoney = 1000
+ MoneyModule:PLAYER_MONEY("PLAYER_MONEY")
+
+ assert.equals(MoneyModule.startingMoney, 3000)
+ assert.stub(MoneyModule.Element.new).was_called_with(MoneyModule.Element, 2000)
+
+ MoneyModule.Element.new:revert()
+ _G.GetMoney:revert()
end)
end)
diff --git a/spec/Features/Professions_spec.lua b/spec/Features/Professions_spec.lua
new file mode 100644
index 0000000..6ff8205
--- /dev/null
+++ b/spec/Features/Professions_spec.lua
@@ -0,0 +1,43 @@
+local common_stubs = require("spec/common_stubs")
+
+describe("Professions Module", function()
+ local _ = match._
+ local Professions, ns
+
+ before_each(function()
+ ns = ns or common_stubs.setup_G_RLF(spy)
+ _G.Enum = { ItemQuality = { Rare = 3 } }
+ _G.GetProfessions = function()
+ return 1, 2, 3, 4, 5
+ end
+ _G.GetProfessionInfo = function(id)
+ return "Profession" .. id, "icon" .. id, id * 10, id * 20, nil, nil, nil, nil, nil, nil, "Expansion" .. id
+ end
+
+ Professions = assert(loadfile("Features/Professions.lua"))("TestAddon", ns)
+ Professions:OnInitialize()
+ end)
+
+ it("should initialize professions correctly", function()
+ Professions:InitializeProfessions()
+ assert.are.same(Professions.profNameIconMap["Profession1"], "icon1")
+ end)
+
+ it("should handle PLAYER_ENTERING_WORLD event", function()
+ spy.on(Professions, "RegisterEvent")
+ Professions:PLAYER_ENTERING_WORLD()
+ assert.equal(#Professions.profLocaleBaseNames, 5)
+ end)
+
+ describe("Element", function()
+ it("creates a new element correctly", function()
+ local element = Professions.Element:new(1, "Expansion1", "icon1", 10, 20, 5)
+ assert.are.same(element.name, "Expansion1")
+ assert.are.same(element.icon, "icon1")
+ assert.are.same(element.level, 10)
+ assert.are.same(element.maxLevel, 20)
+ assert.are.same(element.quantity, 5)
+ assert.are.same(element.key, "PROF_1")
+ end)
+ end)
+end)
diff --git a/spec/Features/Reputation_spec.lua b/spec/Features/Reputation_spec.lua
index 5bdb2ff..efc7758 100644
--- a/spec/Features/Reputation_spec.lua
+++ b/spec/Features/Reputation_spec.lua
@@ -26,42 +26,54 @@ describe("Reputation module", function()
end)
it("does not show rep if the faction and/or repChange can't be determined from the message", function()
+ ns.ExtractDynamicsFromPattern = function()
+ return nil, nil
+ end
local success =
RepModule:CHAT_MSG_COMBAT_FACTION_CHANGE("CHAT_MSG_COMBAT_FACTION_CHANGE", "10x Reputation with Faction A")
assert.is_true(success)
- assert.stub(ns.LootDisplay.ShowLoot).was.not_called()
+ assert.spy(ns.SendMessage).was.not_called()
end)
it("handles rep increases", function()
local newElement = spy.on(RepModule.Element, "new")
+ ns.ExtractDynamicsFromPattern = function()
+ return "Faction A", 10
+ end
local success =
RepModule:CHAT_MSG_COMBAT_FACTION_CHANGE("CHAT_MSG_COMBAT_FACTION_CHANGE", "Rep with Faction A inc by 10.")
assert.is_true(success)
- assert.spy(newElement).was.called_with(_, 10, "Faction A", 1, 0, 0, 1, 3, false)
- assert.stub(ns.LootDisplay.ShowLoot).was.called()
+ assert.spy(newElement).was.called_with(_, 10, "Faction A", 1, 0, 0, 1, _, 3)
+ assert.spy(ns.SendMessage).was.called()
-- Successfully populates the locale cache
assert.equal(ns.db.global.factionMaps.enUS["Faction A"], 1)
end)
it("handles rep increases despite locale cache miss", function()
local newElement = spy.on(RepModule.Element, "new")
+ ns.ExtractDynamicsFromPattern = function()
+ return "Faction B", 100
+ end
local success =
RepModule:CHAT_MSG_COMBAT_FACTION_CHANGE("CHAT_MSG_COMBAT_FACTION_CHANGE", "Rep with Faction B inc by 100.")
assert.is_true(success)
- assert.spy(newElement).was.called_with(_, 100, "Faction B", nil, nil, nil, nil, nil, false)
- assert.stub(ns.LootDisplay.ShowLoot).was.called()
- assert.spy(RepModule:getLogger().Warn).was.called()
- assert.spy(RepModule:getLogger().Warn).was.called_with(_, "Faction B is STILL not cached for enUS", _, _)
+ assert.spy(newElement).was.called_with(_, 100, "Faction B", nil, nil, nil, nil, nil, nil)
+ assert.spy(ns.SendMessage).was.called()
+ assert.spy(ns.LogWarn).was.called()
+ assert.spy(ns.LogWarn).was.called_with(_, "Faction B is STILL not cached for enUS", _, _)
end)
it("handles delve companion experience gains", function()
local newElement = spy.on(RepModule.Element, "new")
+ ns.ExtractDynamicsFromPattern = function()
+ return nil, nil
+ end
local success = RepModule:CHAT_MSG_COMBAT_FACTION_CHANGE(
"CHAT_MSG_COMBAT_FACTION_CHANGE",
"Brann Bronzebeard has gained 313 experience."
@@ -69,29 +81,29 @@ describe("Reputation module", function()
assert.is_true(success)
- assert.spy(newElement).was.called_with(_, 313, "Brann Bronzebeard", 0, 1, 0, 2640, 3, true)
- assert.stub(ns.LootDisplay.ShowLoot).was.called()
+ assert.spy(newElement).was.called_with(_, 313, "Brann Bronzebeard", 0, 1, 0, 2640, _, 4)
+ assert.spy(ns.SendMessage).was.called()
-- Successfully populates the locale cache
assert.equal(ns.db.global.factionMaps.enUS["Brann Bronzebeard"], 2640)
end)
describe("element.textFn", function()
it("handles positive rep gains", function()
- local element = RepModule.Element:new(10, "Faction A", 1, 0, 0, 1, 3, false)
+ local element = RepModule.Element:new(10, "Faction A", 1, 0, 0, 1, _, 3)
local text = element.textFn()
assert.equal(text, "+10 Faction A")
end)
it("handles negative rep gains", function()
- local element = RepModule.Element:new(-10, "Faction A", 1, 0, 0, 1, 3, false)
+ local element = RepModule.Element:new(-10, "Faction A", 1, 0, 0, 1, _, 3)
local text = element.textFn()
assert.equal(text, "-10 Faction A")
end)
it("handles updated rep values", function()
- local element = RepModule.Element:new(10, "Faction A", 1, 0, 0, 1, 3, false)
+ local element = RepModule.Element:new(10, "Faction A", 1, 0, 0, 1, _, 3)
local text = element.textFn(20)
assert.equal(text, "+30 Faction A")
@@ -100,22 +112,37 @@ describe("Reputation module", function()
describe("element.secondaryTextFn", function()
it("does not continue if factionId is missing", function()
- local element = RepModule.Element:new(10, "Faction A", 1, 0, 0, nil, 3, false)
+ local element = RepModule.Element:new(10, "Faction A", 1, 0, 0, nil, _, 3)
local text = element.secondaryTextFn()
assert.equal(text, "")
end)
it("does not continue if this is a delve companion experience gain", function()
- local element = RepModule.Element:new(10, "Brann Bronzebeard", 1, 0, 0, 2640, 3, true)
+ local factionData = {
+ factionId = 2640,
+ factionName = "Brann Bronzebeard",
+ currentLevel = 1,
+ maxLevel = 10,
+ currentXp = 23,
+ nextLevelAt = 100,
+ }
+ local element = RepModule.Element:new(10, "Brann Bronzebeard", 1, 0, 0, 2640, factionData, 4)
local text = element.secondaryTextFn()
- assert.equal(text, "")
+ assert.is_not_nil(string.match(text, "23.0%%"))
end)
describe("normal factions", function()
it("shows current standing and progress to next standing", function()
- local element = RepModule.Element:new(10, "Faction A", 1, 0, 0, 1, 3, false)
+ local factionData = {
+ factionId = 1,
+ factionName = "Faction A",
+ currentStanding = 20,
+ currentReactionThreshold = 0,
+ nextReactionThreshold = 3000,
+ }
+ local element = RepModule.Element:new(10, "Faction A", 1, 0, 0, 1, factionData, 3)
local text = element.secondaryTextFn()
-- assert that text containns "20/3000"
diff --git a/spec/GameTesting/TestMode_spec.lua b/spec/GameTesting/TestMode_spec.lua
new file mode 100644
index 0000000..6e4dc30
--- /dev/null
+++ b/spec/GameTesting/TestMode_spec.lua
@@ -0,0 +1,21 @@
+local common_stubs = require("spec/common_stubs")
+
+describe("TestMode module", function()
+ local ns
+ before_each(function()
+ _G.LibStub = function()
+ return {
+ GetLocale = function() end,
+ }
+ end
+ -- Define the global G_RLF
+ ns = ns or common_stubs.setup_G_RLF(spy)
+
+ -- Load the module before each test
+ assert(loadfile("GameTesting/TestMode.lua"))("TestAddon", ns)
+ end)
+
+ it("TODO", function()
+ assert.are.equal(true, true)
+ end)
+end)
diff --git a/spec/LootDisplay/LootDisplayFrame/LootDisplayFrame_spec.lua b/spec/LootDisplay/LootDisplayFrame/LootDisplayFrame_spec.lua
new file mode 100644
index 0000000..daa9596
--- /dev/null
+++ b/spec/LootDisplay/LootDisplayFrame/LootDisplayFrame_spec.lua
@@ -0,0 +1,21 @@
+local common_stubs = require("spec/common_stubs")
+
+describe("LootDisplayFrameMixin", function()
+ local ns
+ before_each(function()
+ _G.LibStub = function()
+ return {
+ GetLocale = function() end,
+ }
+ end
+ -- Define the global G_RLF
+ ns = ns or common_stubs.setup_G_RLF(spy)
+
+ -- Load the module before each test
+ assert(loadfile("LootDisplay/LootDisplayFrame/LootDisplayFrame.lua"))("TestAddon", ns)
+ end)
+
+ it("TODO", function()
+ assert.is_not_nil(_G.LootDisplayFrameMixin)
+ end)
+end)
diff --git a/spec/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRow_spec.lua b/spec/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRow_spec.lua
new file mode 100644
index 0000000..8c0691f
--- /dev/null
+++ b/spec/LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRow_spec.lua
@@ -0,0 +1,21 @@
+local common_stubs = require("spec/common_stubs")
+
+describe("LootDisplayRowMixin", function()
+ local ns
+ before_each(function()
+ _G.LibStub = function()
+ return {
+ GetLocale = function() end,
+ }
+ end
+ -- Define the global G_RLF
+ ns = ns or common_stubs.setup_G_RLF(spy)
+
+ -- Load the module before each test
+ assert(loadfile("LootDisplay/LootDisplayFrame/LootDisplayRow/LootDisplayRow.lua"))("TestAddon", ns)
+ end)
+
+ it("TODO", function()
+ assert.is_not_nil(_G.LootDisplayRowMixin)
+ end)
+end)
diff --git a/spec/RPGLootFeed_spec.lua b/spec/RPGLootFeed_spec.lua
deleted file mode 100644
index 2548e98..0000000
--- a/spec/RPGLootFeed_spec.lua
+++ /dev/null
@@ -1,19 +0,0 @@
-describe("RPGLootFeed module", function()
- before_each(function()
- _G.LibStub = function()
- return {
- GetLocale = function() end,
- }
- end
- -- Define the global G_RLF
- local ns = {
- RLF = {},
- }
- -- Load the list module before each test
- assert(loadfile("RPGLootFeed.lua"))("TestAddon", ns)
- end)
-
- it("TODO", function()
- assert.are.equal(true, true)
- end)
-end)
diff --git a/spec/common_stubs.lua b/spec/common_stubs.lua
index 45d8091..37174e7 100644
--- a/spec/common_stubs.lua
+++ b/spec/common_stubs.lua
@@ -1,20 +1,38 @@
-- common_stubs.lua
local common_stubs = {}
-function common_stubs.setup_G_RLF(spy)
- _G.unpack = table.unpack
- _G.handledError = function(err)
- print("\n")
- print(err)
- print("The above error was thrown during a test and caught by xpcall")
- print("This is usually indicative of an issue, or an improperly mocked test")
- print("\n")
- return false
+local function embedLibs(addonOrModule, ...)
+ for _, lib in ipairs({ ... }) do
+ if lib == "AceBucket-3.0" then
+ addonOrModule.RegisterBucketMessage = function(self, bucket, delay, handler) end
+ addonOrModule.UnregisterBucket = function(self, bucket) end
+ end
+ if lib == "AceEvent-3.0" then
+ addonOrModule.RegisterEvent = function(self, event, handler) end
+ addonOrModule.UnregisterEvent = function(self, event) end
+ addonOrModule.RegisterMessage = function(self, message, handler) end
+ addonOrModule.SendMessage = function(self, message, ...) end
+ addonOrModule.UnregisterMessage = function(self, message) end
+ end
+ if lib == "AceHook-3.0" then
+ addonOrModule.Hook = function(self, object, method, handler, hookSecure) end
+ addonOrModule.HookScript = function(self, frame, script, handler) end
+ addonOrModule.IsHooked = function(self, obj, method)
+ return false
+ end
+ addonOrModule.RawHook = function(self, object, method, handler, hookSecure) end
+ addonOrModule.RawHookScript = function(self, frame, script, handler) end
+ addonOrModule.SecureHook = function(self, object, method, handler) end
+ addonOrModule.SecureHookScript = function(self, frame, script, handler) end
+ addonOrModule.Unhook = function(self, object, method) end
+ addonOrModule.UnhookAll = function(self) end
+ addonOrModule.hooks = {}
+ end
end
+end
- _G.RunNextFrame = function(func)
- func()
- end
+function common_stubs.setup_G_RLF(spy)
+ common_stubs.stub_WoWGlobals(spy)
local logger = {
Debug = spy.new(),
@@ -23,15 +41,21 @@ function common_stubs.setup_G_RLF(spy)
Error = spy.new(),
}
local ns = {
+ addonVersion = "1.0.0",
db = {
global = {
currencyFeed = true,
factionMaps = {},
+ itemHighlights = {
+ mounts = true,
+ legendary = true,
+ },
},
},
- LootDisplay = {
- ShowLoot = function() end,
+ L = {
+ Issues = "Issues",
},
+ LootDisplay = {},
list = function()
return {}
end,
@@ -40,13 +64,13 @@ function common_stubs.setup_G_RLF(spy)
return {}
end,
},
+ AuctionIntegrations = {
+ Init = spy.new(),
+ },
RLF = {
- NewModule = function(_, name, libs)
- return {
+ NewModule = function(_, name, ...)
+ local module = {
moduleName = name,
- getLogger = function(self)
- return logger
- end,
Enable = function() end,
Disable = function() end,
fn = function(s, func, ...)
@@ -55,28 +79,255 @@ function common_stubs.setup_G_RLF(spy)
end
end,
}
+ embedLibs(module, ...)
+ return module
+ end,
+ GetModule = function(_, name)
+ local module = {
+ Enable = spy.new(),
+ Disable = spy.new(),
+ }
+ if name == "Logger" then
+ module.Trace = function()
+ return "Trace"
+ end
+ end
+
+ return module
end,
},
+ SendMessage = spy.new(),
fn = function(_, func, ...)
return func(...)
end,
Print = function(msg) end,
+ ProfileFunction = function(_, name, func)
+ return func
+ end,
+ CreatePatternSegmentsForStringNumber = spy.new(function()
+ return { 1, 2, 3 }
+ end),
+ ExtractDynamicsFromPattern = spy.new(function()
+ return "Test", 3
+ end),
RGBAToHexFormat = function(_, r, g, b, a)
local f = math.floor
return string.format("|c%02x%02x%02x%02x", f(a * 255), f(r * 255), f(g * 255), f(b * 255))
end,
+ InitializeLootDisplayProperties = function(element)
+ element.Show = spy.new()
+ end,
+ ItemInfo = {
+ new = function()
+ return {
+ itemId = 18803,
+ itemName = "Finkle's Lava Dredger",
+ itemQuality = 2,
+ IsMount = function()
+ return true
+ end,
+ IsLegendary = function()
+ return true
+ end,
+ IsEligibleEquipment = function()
+ return true
+ end,
+ }
+ end,
+ },
+ FeatureModule = {
+ ItemLoot = "ItemLoot",
+ Currency = "Currency",
+ Money = "Money",
+ Reputation = "Reputation",
+ Experience = "Experience",
+ Profession = "Profession",
+ },
+ LogEventSource = {
+ ADDON = "TestAddon",
+ WOWEVENT = "WOWEVENT",
+ },
+ LogLevel = {
+ debug = "DEBUG",
+ info = "INFO",
+ warn = "WARN",
+ error = "ERROR",
+ },
+ LogDebug = spy.new(),
+ LogInfo = spy.new(),
+ LogWarn = spy.new(),
+ LogError = spy.new(),
}
- _G.GetLocale = function()
- return "enUS"
+ ns.LibStubReturn = {}
+ _G.LibStub = function(lib)
+ ns.LibStubReturn[lib] = {}
+ if lib == "AceAddon-3.0" then
+ ns.LibStubReturn[lib] = {
+ NewAddon = function(...)
+ local addon = {}
+ embedLibs(addon, ...)
+ addon.SetDefaultModuleState = spy.new()
+ addon.SetDefaultModulePrototype = spy.new()
+ return addon
+ end,
+ }
+ elseif lib == "AceConfig-3.0" then
+ ns.LibStubReturn[lib] = {
+ RegisterOptionsTable = spy.new(),
+ }
+ elseif lib == "AceConfigDialog-3.0" then
+ ns.LibStubReturn[lib] = {
+ AddToBlizOptions = spy.new(),
+ Close = spy.new(),
+ Open = spy.new(),
+ }
+ elseif lib == "AceConfigRegistry-3.0" then
+ ns.LibStubReturn[lib] = {
+ NotifyChange = spy.new(),
+ }
+ elseif lib == "AceDB-3.0" then
+ ns.LibStubReturn[lib] = {
+ New = function()
+ return { global = {} }
+ end,
+ }
+ elseif lib == "AceGUI-3.0" then
+ ns.LibStubReturn[lib] = {
+ Create = function()
+ return {
+ AddChild = spy.new(),
+ DisableButton = spy.new(),
+ DoLayout = spy.new(),
+ EnableResize = spy.new(),
+ IsShown = function()
+ return true
+ end,
+ SetLayout = spy.new(),
+ SetTitle = spy.new(),
+ SetStatusText = spy.new(),
+ SetCallback = spy.new(),
+ SetText = spy.new(),
+ SetValue = spy.new(),
+ SetColor = spy.new(),
+ SetDisabled = spy.new(),
+ SetFullWidth = spy.new(),
+ SetFullHeight = spy.new(),
+ SetItemValue = spy.new(),
+ SetRelativeWidth = spy.new(),
+ SetRelativeHeight = spy.new(),
+ SetList = spy.new(),
+ SetMultiselect = spy.new(),
+ SetNumLines = spy.new(),
+ SetPoint = spy.new(),
+ SetWidth = spy.new(),
+ SetHeight = spy.new(),
+ SetLabel = spy.new(),
+ SetImage = spy.new(),
+ SetImageSize = spy.new(),
+ SetImageCoords = spy.new(),
+ Show = spy.new(),
+ Hide = spy.new(),
+ }
+ end,
+ }
+ elseif lib == "AceLocale-3.0" then
+ ns.LibStubReturn[lib] = {
+ GetLocale = function()
+ return ns.L
+ end,
+ }
+ elseif lib == "LibSharedMedia-3.0" then
+ ns.LibStubReturn[lib] = {
+ Register = spy.new(),
+ MediaType = {
+ FONT = "font",
+ },
+ }
+ elseif lib == "Masque" then
+ ns.LibStubReturn[lib] = {
+ Group = function()
+ return {
+ ReSkin = spy.new(),
+ }
+ end,
+ }
+ else
+ error("Unmocked library: " .. lib)
+ end
+ return ns.LibStubReturn[lib]
end
- -- Spy or stub common methods if needed
- spy.on(ns.LootDisplay, "ShowLoot")
-
return ns
end
+function common_stubs.stub_WoWGlobals(spy)
+ common_stubs.stub_Unit_Funcs()
+ common_stubs.stub_Money_Funcs()
+
+ _G.Enum = {
+ ItemArmorSubclass = {
+ Plate = 4,
+ },
+ ItemClass = { Armor = 4, Miscellaneous = 15 },
+ ItemMiscellaneousSubclass = { Mount = 5 },
+ ItemQuality = { Legendary = 5 },
+ }
+
+ _G.C_CVar = {
+ SetCVar = function() end,
+ }
+
+ _G.EditModeManagerFrame = {
+ IsInMode = function() end,
+ }
+ _G.EventRegistry = {
+ RegisterCallback = function() end,
+ }
+
+ _G.date = function()
+ return "2023-01-01 12:00:00"
+ end
+ _G.debugprofilestop = function()
+ return 0
+ end
+ _G.format = string.format
+ _G.handledError = function(err)
+ print("\n")
+ print(err)
+ print("The above error was thrown during a test and caught by xpcall")
+ print("This is usually indicative of an issue, or an improperly mocked test")
+ print("\n")
+ return false
+ end
+ _G.unpack = table.unpack
+
+ _G.RunNextFrame = function(func)
+ func()
+ end
+
+ _G.IsInRaid = function()
+ return false
+ end
+
+ _G.IsInInstance = function()
+ return false, ""
+ end
+
+ _G.MEMBERS_PER_RAID_GROUP = 5
+ _G.GetNumGroupMembers = function()
+ return 1
+ end
+
+ _G.GetPlayerGuid = function()
+ return "player"
+ end
+
+ _G.GetLocale = function()
+ return "enUS"
+ end
+end
+
function common_stubs.stub_C_CurrencyInfo()
_G.C_CurrencyInfo = {
GetCurrencyInfo = function(currencyType)
@@ -102,6 +353,20 @@ function common_stubs.stub_C_CurrencyInfo()
}
end
+function common_stubs.stub_C_Item()
+ _G.C_Item = {
+ GetItemCount = function(itemID)
+ return 1
+ end,
+ GetItemInfo = function(itemLink)
+ return 18803, "Finkle's Lava Dredger", 2, 60, 1, "INV_AXE_33"
+ end,
+ GetItemIDForItemInfo = function(itemLink)
+ return 18803
+ end,
+ }
+end
+
function common_stubs.stub_C_Reputation()
_G.ACCOUNT_WIDE_FONT_COLOR = { r = 0, g = 0, b = 1 }
_G.FACTION_GREEN_COLOR = { r = 0, g = 1, b = 0 }
@@ -146,7 +411,8 @@ function common_stubs.stub_C_Reputation()
factionID = 1,
reaction = 1,
currentStanding = 20,
- currentReactionThreshold = 3000,
+ currentReactionThreshold = 0,
+ nextReactionThreshold = 3000,
}
end
if id == 2640 then
@@ -164,6 +430,21 @@ function common_stubs.stub_C_Reputation()
return false
end,
}
+ _G.C_GossipInfo = {
+ GetFriendshipReputationRanks = function()
+ return {
+ currentLevel = 3,
+ maxLevel = 60,
+ }
+ end,
+ GetFriendshipReputation = function()
+ return {
+ standing = 63,
+ reactionThreshold = 60,
+ nextThreshold = 100,
+ }
+ end,
+ }
end
function common_stubs.stub_C_DelvesUI()
@@ -178,9 +459,15 @@ function common_stubs.stub_C_DelvesUI()
end
function common_stubs.stub_Unit_Funcs()
+ _G.UnitGUID = function(unit)
+ return "player"
+ end
_G.UnitLevel = function()
return 2
end
+ _G.UnitName = function()
+ return "Player"
+ end
_G.UnitXP = function()
return 10
end
diff --git a/spec/config/Features_spec.lua b/spec/config/Features_spec.lua
index 4d75d74..d207bfa 100644
--- a/spec/config/Features_spec.lua
+++ b/spec/config/Features_spec.lua
@@ -31,7 +31,7 @@ describe("Features module", function()
},
}
-- Load the list module before each test
- assert(loadfile("config/Features.lua"))("TestAddon", ns)
+ assert(loadfile("config/Features/Features.lua"))("TestAddon", ns)
end)
it("TODO", function()
diff --git a/spec/utils/AddonMethods_spec.lua b/spec/utils/AddonMethods_spec.lua
new file mode 100644
index 0000000..ba9e4b7
--- /dev/null
+++ b/spec/utils/AddonMethods_spec.lua
@@ -0,0 +1,138 @@
+local common_stubs = require("spec/common_stubs")
+
+local function contains_string(state, arguments)
+ local expected = arguments[1]
+ return function(value)
+ return type(value) == "string" and string.find(value, expected, 1, true) ~= nil
+ end
+end
+
+assert:register("matcher", "contains_string", contains_string)
+
+describe("AddonMethods", function()
+ local _ = match._
+ local ns, errorHandlerSpy
+ before_each(function()
+ errorHandlerSpy = spy.new()
+ _G.geterrorhandler = function()
+ return errorHandlerSpy
+ end
+ -- Define the global G_RLF
+ ns = ns or common_stubs.setup_G_RLF(spy)
+
+ -- Load the module before each test
+ assert(loadfile("utils/AddonMethods.lua"))("TestAddon", ns)
+ end)
+
+ describe("fn", function()
+ it("calls the function with xpcall and errorhandler", function()
+ local funcSpy = spy.new()
+ local func = function(...)
+ funcSpy(...)
+ end
+ ns:fn(func, 1, 2, 3)
+ assert.spy(funcSpy).was.called_with(1, 2, 3)
+ end)
+
+ it("calls the errorhandler when the function throws an error", function()
+ local func = function()
+ error("test error")
+ end
+ pcall(function()
+ ns:fn(func)
+ end)
+ assert.spy(errorHandlerSpy).was.called()
+ assert.spy(errorHandlerSpy).was_not_called_with(match.contains_string("Trace"))
+ end)
+
+ it("calls the errorhandler with stack trace if the calling module function throws an error", function()
+ local func = function()
+ error("test error")
+ end
+ local module = { moduleName = "TestModule" }
+ pcall(function()
+ ns.fn(module, func)
+ end)
+ assert.spy(errorHandlerSpy).was_called_with(match.contains_string("Trace"))
+ end)
+ end)
+
+ describe("SendMessage", function()
+ it("sends a message to the addon channel", function()
+ ns.RLF.SendMessage = spy.new()
+ ns:SendMessage("TEST_TOPIC", "test message", 1)
+ assert.spy(ns.RLF.SendMessage).was.called_with(_, "TEST_TOPIC", "test message", 1)
+ end)
+ end)
+
+ describe("logging", function()
+ it("logs a debug message", function()
+ ns.SendMessage = spy.new()
+ ns:LogDebug("test debug message")
+ assert.spy(ns.SendMessage).was.called_with(_, "RLF_LOG", { "DEBUG", "test debug message" })
+ end)
+
+ it("logs an info message", function()
+ ns.SendMessage = spy.new()
+ ns:LogInfo("test info message")
+ assert.spy(ns.SendMessage).was.called_with(_, "RLF_LOG", { "INFO", "test info message" })
+ end)
+
+ it("logs a warning message", function()
+ ns.SendMessage = spy.new()
+ ns:LogWarn("test warning message")
+ assert.spy(ns.SendMessage).was.called_with(_, "RLF_LOG", { "WARN", "test warning message" })
+ end)
+
+ it("logs an error message", function()
+ ns.SendMessage = spy.new()
+ ns:LogError("test error message")
+ assert.spy(ns.SendMessage).was.called_with(_, "RLF_LOG", { "ERROR", "test error message" })
+ end)
+ end)
+
+ describe("RGBAToHexFormat", function()
+ it("converts RGBA01 to WoW's hex color format", function()
+ local result = ns:RGBAToHexFormat(0.1, 0.2, 0.3, 0.4)
+ assert.are.equal(result, "|c6619334C")
+ end)
+ end)
+
+ describe("Print", function()
+ it("prints a message using RLF's Print method", function()
+ ns.RLF.Print = spy.new()
+ ns:Print("test message")
+ assert.spy(ns.RLF.Print).was.called_with(_, "test message")
+ end)
+ end)
+
+ describe("CreatePatternSegmentsForStringNumber", function()
+ it("creates pattern segments for a string with a number", function()
+ local segments = ns:CreatePatternSegmentsForStringNumber("Hello %s, you have %d messages")
+ assert.are.same(segments, { "Hello ", ", you have ", " messages" })
+ end)
+ end)
+
+ describe("ExtractDynamicsFromPattern", function()
+ it("extracts dynamic parts from a pattern", function()
+ local segments = { "Hello ", ", you have ", " messages" }
+ local str, num = ns:ExtractDynamicsFromPattern("Hello John, you have 5 messages", segments)
+ assert.are.equal(str, "John")
+ assert.are.equal(num, 5)
+ end)
+
+ it("extracts dynamic parts from a pattern that ends with a number", function()
+ local segments = { "Hello ", ", you got ", "" }
+ local str, num = ns:ExtractDynamicsFromPattern("Hello John, you got 5", segments)
+ assert.are.equal(str, "John")
+ assert.are.equal(num, 5)
+ end)
+
+ it("returns nil if the pattern does not match", function()
+ local segments = { "Hello ", ", you have ", " messages" }
+ local str, num = ns:ExtractDynamicsFromPattern("Goodbye John, you have 5 messages", segments)
+ assert.is_nil(str)
+ assert.is_nil(num)
+ end)
+ end)
+end)
diff --git a/spec/utils/AlphaHelpers_spec.lua b/spec/utils/AlphaHelpers_spec.lua
new file mode 100644
index 0000000..6b38430
--- /dev/null
+++ b/spec/utils/AlphaHelpers_spec.lua
@@ -0,0 +1,44 @@
+local common_stubs = require("spec.common_stubs")
+
+describe("AlphaHelpers", function()
+ local ns
+
+ before_each(function()
+ ns = common_stubs.setup_G_RLF(spy)
+ assert(loadfile("utils/AlphaHelpers.lua"))("TestAddon", ns)
+ end)
+
+ describe("dump", function()
+ local function parse_dumped_table(dump_string)
+ local func, err = load("return " .. dump_string)
+ if not func then
+ error("Failed to parse dumped table: " .. err)
+ end
+ return func()
+ end
+
+ it("dumps a table to a string", function()
+ local t = { key = "value", nested = { 1, 2, 3 } }
+ local result = dump(t)
+
+ local parsed = parse_dumped_table(result)
+
+ assert.are.same(parsed, t)
+ end)
+
+ it("dumps a non-table value to a string", function()
+ local result = dump(123)
+ assert.are.equal(result, "123")
+ end)
+ end)
+
+ describe("ProfileFunction", function()
+ it("profiles a function and prints if it takes too long", function()
+ local func = function() end
+ local profiledFunc = ns:ProfileFunction(func, "testFunc")
+ spy.on(ns, "Print")
+ profiledFunc()
+ assert.spy(ns.Print).was_not_called()
+ end)
+ end)
+end)
diff --git a/spec/DoubleLinkedList_spec.lua b/spec/utils/DoubleLinkedList_spec.lua
similarity index 84%
rename from spec/DoubleLinkedList_spec.lua
rename to spec/utils/DoubleLinkedList_spec.lua
index fe33c3b..a6b4463 100644
--- a/spec/DoubleLinkedList_spec.lua
+++ b/spec/utils/DoubleLinkedList_spec.lua
@@ -11,7 +11,7 @@ describe("List module", function()
-- Define the global G_RLF
ns = ns or {}
-- Load the list module before each test
- assert(loadfile("DoubleLinkedList.lua"))("TestAddon", ns)
+ assert(loadfile("utils/DoubleLinkedList.lua"))("TestAddon", ns)
list = ns.list
end)
@@ -76,6 +76,26 @@ describe("List module", function()
assert.are.equal(l.last._prev.value, "first")
end)
+ it("can remove the only node from the list", function()
+ local l = list(create_node("first"))
+ l:remove(l.last) -- remove "first"
+
+ assert.are.equal(l.length, 0)
+ assert.is_nil(l.first)
+ assert.is_nil(l.last)
+ end)
+
+ it("can remove the last node from the list", function()
+ local l = list(create_node("first"), create_node("second"))
+ l:remove(l.last) -- remove "second"
+
+ assert.are.equal(l.length, 1)
+ assert.are.equal(l.first.value, "first")
+ assert.is_nil(l.first._next)
+ assert.are.equal(l.last, l.first)
+ assert.is_not_nil(l.last)
+ end)
+
it("should iterate over the list", function()
local l = list(create_node("first"), create_node("second"), create_node("third"))
local result = {}
diff --git a/spec/utils/Enums_spec.lua b/spec/utils/Enums_spec.lua
new file mode 100644
index 0000000..0d2f06c
--- /dev/null
+++ b/spec/utils/Enums_spec.lua
@@ -0,0 +1,29 @@
+describe("Enums", function()
+ local G_RLF
+
+ before_each(function()
+ G_RLF = {}
+ _G.G_RLF = G_RLF
+ assert(loadfile("utils/Enums.lua"))("TestAddon", G_RLF)
+ end)
+
+ it("defines DisableBossBanner enum", function()
+ assert.is_not_nil(G_RLF.DisableBossBanner)
+ end)
+
+ it("defines LogEventSource enum", function()
+ assert.is_not_nil(G_RLF.LogEventSource)
+ end)
+
+ it("defines LogLevel enum", function()
+ assert.is_not_nil(G_RLF.LogLevel)
+ end)
+
+ it("defines FeatureModule enum", function()
+ assert.is_not_nil(G_RLF.FeatureModule)
+ end)
+
+ it("defines WrapCharEnum enum", function()
+ assert.is_not_nil(G_RLF.WrapCharEnum)
+ end)
+end)
diff --git a/spec/utils/ItemInfo_spec.lua b/spec/utils/ItemInfo_spec.lua
new file mode 100644
index 0000000..17f2c45
--- /dev/null
+++ b/spec/utils/ItemInfo_spec.lua
@@ -0,0 +1,321 @@
+local common_stubs = require("spec.common_stubs")
+
+describe("ItemInfo", function()
+ local ns, ItemInfo
+
+ before_each(function()
+ ns = common_stubs.setup_G_RLF(spy)
+ common_stubs.stub_C_Item()
+
+ assert(loadfile("utils/ItemInfo.lua"))("TestAddon", ns)
+ ItemInfo = ns.ItemInfo
+ end)
+
+ it("creates a new ItemInfo instance", function()
+ local item = ItemInfo:new(
+ 18803,
+ "Test Item",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Weapon",
+ "Sword",
+ 1,
+ "INVTYPE_WEAPON",
+ "texture",
+ 100,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.are.equal(item.itemId, 18803)
+ assert.are.equal(item.itemName, "Test Item")
+ end)
+
+ it("returns nil if itemName is not provided", function()
+ local item = ItemInfo:new(
+ 18803,
+ nil,
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Weapon",
+ "Sword",
+ 1,
+ "INVTYPE_WEAPON",
+ "texture",
+ 100,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_nil(item)
+ end)
+
+ it("retrieves the item ID if not provided", function()
+ local item = ItemInfo:new(
+ nil,
+ "Test Item",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Weapon",
+ "Sword",
+ 1,
+ "INVTYPE_WEAPON",
+ "texture",
+ 100,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.are.equal(item.itemId, 18803)
+ end)
+
+ it("checks if an item is a mount", function()
+ local item = ItemInfo:new(
+ 18803,
+ "Test Mount",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Miscellaneous",
+ "Mount",
+ 1,
+ "INVTYPE_WEAPON",
+ "texture",
+ 100,
+ 15,
+ 5,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_true(item:IsMount())
+ end)
+
+ it("checks if an item is not a mount", function()
+ local item = ItemInfo:new(
+ 18803,
+ "Test Item",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Weapon",
+ "Sword",
+ 1,
+ "INVTYPE_WEAPON",
+ "texture",
+ 100,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_false(item:IsMount())
+ end)
+
+ it("checks if an item is legendary", function()
+ local item = ItemInfo:new(
+ 18803,
+ "Test Legendary",
+ "itemLink",
+ 5,
+ 10,
+ 1,
+ "Weapon",
+ "Sword",
+ 1,
+ "INVTYPE_WEAPON",
+ "texture",
+ 100,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_true(item:IsLegendary())
+ end)
+
+ it("checks if an item is not legendary", function()
+ local item = ItemInfo:new(
+ 18803,
+ "Test Item",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Weapon",
+ "Sword",
+ 1,
+ "INVTYPE_WEAPON",
+ "texture",
+ 100,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_false(item:IsLegendary())
+ end)
+
+ describe("IsEligibleEquipment", function()
+ it("checks if an item is eligible equipment", function()
+ _G.UnitClass = function()
+ return nil, "Warrior"
+ end
+ ns.armorClassMapping = { Warrior = 4 }
+ ns.equipSlotMap = { INVTYPE_CHEST = 5 }
+
+ local item = ItemInfo:new(
+ 18803,
+ "Test Armor",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Armor",
+ "Plate",
+ 1,
+ "INVTYPE_CHEST",
+ "texture",
+ 100,
+ Enum.ItemClass.Armor,
+ Enum.ItemArmorSubclass.Plate,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_true(item:IsEligibleEquipment())
+ end)
+
+ it("checks if an item is not eligible equipment due to classID", function()
+ local item = ItemInfo:new(
+ 18803,
+ "Test Weapon",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Weapon",
+ "Sword",
+ 1,
+ "INVTYPE_WEAPON",
+ "texture",
+ 100,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_false(item:IsEligibleEquipment())
+ end)
+
+ it("checks if an item is not eligible equipment due to missing itemEquipLoc", function()
+ local item = ItemInfo:new(
+ 18803,
+ "Test Armor",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Armor",
+ "Plate",
+ 1,
+ nil,
+ "texture",
+ 100,
+ 4,
+ 4,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_false(item:IsEligibleEquipment())
+ end)
+
+ it("checks if an item is not eligible equipment due to mismatched armor class", function()
+ _G.UnitClass = function()
+ return nil, "Mage"
+ end
+ ns.armorClassMapping = { Mage = 1 }
+
+ local item = ItemInfo:new(
+ 18803,
+ "Test Armor",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Armor",
+ "Plate",
+ 1,
+ "INVTYPE_CHEST",
+ "texture",
+ 100,
+ 4,
+ 4,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_false(item:IsEligibleEquipment())
+ end)
+
+ it("checks if an item is not eligible equipment due to missing equip slot", function()
+ _G.UnitClass = function()
+ return nil, "Warrior"
+ end
+ ns.armorClassMapping = { Warrior = 4 }
+ ns.equipSlotMap = {}
+
+ local item = ItemInfo:new(
+ 18803,
+ "Test Armor",
+ "itemLink",
+ 2,
+ 10,
+ 1,
+ "Armor",
+ "Plate",
+ 1,
+ "INVTYPE_CHEST",
+ "texture",
+ 100,
+ 4,
+ 4,
+ 1,
+ 1,
+ 1,
+ false
+ )
+ assert.is_false(item:IsEligibleEquipment())
+ end)
+ end)
+end)
diff --git a/spec/utils/Logger_spec.lua b/spec/utils/Logger_spec.lua
new file mode 100644
index 0000000..3e626d3
--- /dev/null
+++ b/spec/utils/Logger_spec.lua
@@ -0,0 +1,92 @@
+local common_stubs = require("spec/common_stubs")
+
+describe("Logger module", function()
+ local ns, Logger
+
+ before_each(function()
+ -- Define the global G_RLF
+ ns = ns or common_stubs.setup_G_RLF(spy)
+ ns.db.global.logger = {
+ sessionsLogged = 0,
+ logs = {},
+ }
+
+ -- Load the module before each test
+ Logger = assert(loadfile("utils/Logger.lua"))("TestAddon", ns)
+ end)
+
+ describe("addon initialization", function()
+ it("initializes the logger", function()
+ spy.on(Logger, "RegisterEvent")
+ spy.on(Logger, "RegisterBucketMessage")
+ spy.on(Logger, "InitializeFrame")
+ Logger:OnInitialize()
+ assert.spy(Logger.RegisterEvent).was.called_with(Logger, "PLAYER_ENTERING_WORLD")
+ assert.spy(Logger.RegisterBucketMessage).was.called_with(Logger, "RLF_LOG", 0.5, "ProcessLogs")
+ assert.spy(Logger.InitializeFrame).was.called()
+ end)
+
+ it("increments sessionsLogged on PLAYER_ENTERING_WORLD event", function()
+ Logger:PLAYER_ENTERING_WORLD(nil, true, false)
+ assert.are.equal(ns.db.global.logger.sessionsLogged, 1)
+ end)
+ end)
+
+ describe("logging functionality", function()
+ before_each(function()
+ Logger:OnInitialize()
+ Logger:PLAYER_ENTERING_WORLD(nil, true, false)
+ end)
+
+ it("adds a log entry", function()
+ local logEntry = {
+ timestamp = "2023-01-01 12:00:00",
+ level = "INFO",
+ source = "TestAddon",
+ type = "General",
+ id = "",
+ content = "",
+ amount = "",
+ new = true,
+ message = "Test log entry",
+ }
+ Logger:ProcessLogs({
+ [{
+ logEntry.level,
+ logEntry.message,
+ logEntry.source,
+ logEntry.type,
+ logEntry.id,
+ logEntry.content,
+ logEntry.amount,
+ logEntry.new,
+ }] = 1,
+ })
+ assert.are.equal(#ns.db.global.logger.logs[1], 1)
+ assert.are.same(ns.db.global.logger.logs[1][1], logEntry)
+ end)
+
+ it("formats log entries correctly", function()
+ local logEntry = {
+ timestamp = "2023-01-01 12:00:00",
+ level = "INFO",
+ source = "TestAddon",
+ type = "General",
+ id = "",
+ content = "",
+ amount = "",
+ new = true,
+ message = "Test log entry",
+ }
+ local formattedEntry = Logger:FormatLogEntry(logEntry)
+ assert.are.equal(formattedEntry, "[|cFF80808012:00:00|r]|cFFADD8E6{I}|r(TestAddon): Test log entry\n")
+ end)
+
+ it("hides the frame if it is already shown", function()
+ Logger:Show()
+ spy.on(Logger, "Hide")
+ Logger:Show()
+ assert.spy(Logger.Hide).was.called(1)
+ end)
+ end)
+end)
diff --git a/spec/utils/Maps_spec.lua b/spec/utils/Maps_spec.lua
new file mode 100644
index 0000000..ba38b08
--- /dev/null
+++ b/spec/utils/Maps_spec.lua
@@ -0,0 +1,25 @@
+describe("Maps", function()
+ local G_RLF
+
+ before_each(function()
+ G_RLF = {}
+ _G.G_RLF = G_RLF
+ _G.Enum = {
+ ItemArmorSubclass = {
+ Plate = 4,
+ Mail = 3,
+ Leather = 2,
+ Cloth = 1,
+ },
+ }
+ assert(loadfile("utils/Maps.lua"))("TestAddon", G_RLF)
+ end)
+
+ it("defines armorClassMapping", function()
+ assert.is_not_nil(G_RLF.armorClassMapping)
+ end)
+
+ it("defines equipSlotMap", function()
+ assert.is_not_nil(G_RLF.equipSlotMap)
+ end)
+end)
diff --git a/spec/utils/Queue_spec.lua b/spec/utils/Queue_spec.lua
new file mode 100644
index 0000000..b0de4b5
--- /dev/null
+++ b/spec/utils/Queue_spec.lua
@@ -0,0 +1,97 @@
+describe("Queue module", function()
+ local Queue
+
+ -- Helper function to create an item with a value
+ local function create_item(value)
+ return { value = value }
+ end
+
+ local ns
+ before_each(function()
+ -- Define the global G_RLF
+ ns = ns or {}
+ -- Load the Queue module before each test
+ assert(loadfile("utils/Queue.lua"))("TestAddon", ns)
+
+ Queue = ns.Queue
+ end)
+
+ it("should initialize an empty queue", function()
+ local q = Queue:new()
+ assert.are.equal(q:size(), 0)
+ assert.is_true(q:isEmpty())
+ end)
+
+ it("should enqueue items to the queue", function()
+ local q = Queue:new()
+ local item1 = create_item("first")
+ local item2 = create_item("second")
+ q:enqueue(item1)
+ q:enqueue(item2)
+
+ assert.are.equal(q:size(), 2)
+ assert.are.equal(q:peek().value, "first")
+ end)
+
+ it("should dequeue items from the queue", function()
+ local q = Queue:new()
+ local item1 = create_item("first")
+ local item2 = create_item("second")
+ q:enqueue(item1)
+ q:enqueue(item2)
+
+ local dequeued = q:dequeue()
+ assert.are.equal(dequeued.value, "first")
+ assert.are.equal(q:size(), 1)
+ assert.are.equal(q:peek().value, "second")
+ end)
+
+ it("should handle dequeue on an empty queue gracefully", function()
+ local q = Queue:new()
+ local result = q:dequeue()
+ assert.is_nil(result)
+ assert.are.equal(q:size(), 0)
+ end)
+
+ it("should not allow the same item to be enqueued twice", function()
+ local q = Queue:new()
+ local item = create_item("first")
+ q:enqueue(item)
+ q:enqueue(item) -- Attempt to enqueue the same item again
+
+ assert.are.equal(q:size(), 1)
+ end)
+
+ it("should check if the queue is empty", function()
+ local q = Queue:new()
+ assert.is_true(q:isEmpty())
+
+ local item = create_item("first")
+ q:enqueue(item)
+ assert.is_false(q:isEmpty())
+ end)
+
+ it("should peek at the front item without removing it", function()
+ local q = Queue:new()
+ local item1 = create_item("first")
+ local item2 = create_item("second")
+ q:enqueue(item1)
+ q:enqueue(item2)
+
+ local peeked = q:peek()
+ assert.are.equal(peeked.value, "first")
+ assert.are.equal(q:size(), 2)
+ end)
+
+ it("should get the size of the queue", function()
+ local q = Queue:new()
+ assert.are.equal(q:size(), 0)
+
+ local item1 = create_item("first")
+ local item2 = create_item("second")
+ q:enqueue(item1)
+ q:enqueue(item2)
+
+ assert.are.equal(q:size(), 2)
+ end)
+end)
diff --git a/utils/AddonMethods.lua b/utils/AddonMethods.lua
new file mode 100644
index 0000000..6f0dfd8
--- /dev/null
+++ b/utils/AddonMethods.lua
@@ -0,0 +1,109 @@
+local addonName, G_RLF = ...
+
+function G_RLF:fn(func, ...)
+ local s = self
+ local function errorhandler(err)
+ local suffix = "\n\n==== Addon Info " .. addonName .. " " .. G_RLF.addonVersion .. " ====\n\n"
+ local status, trace = pcall(function()
+ local logger = G_RLF.RLF:GetModule("Logger")
+ if s.moduleName then
+ return logger:Trace(s.moduleName)
+ end
+ return nil
+ end)
+ if status and trace then
+ suffix = suffix .. "Log traces related to " .. s.moduleName .. "\n"
+ suffix = suffix .. "-------------------------------------------------\n"
+ suffix = suffix .. trace
+ suffix = suffix .. "-------------------------------------------------\n\n"
+ end
+ suffix = suffix .. G_RLF.L["Issues"] .. "\n\n"
+
+ return geterrorhandler()(err .. suffix)
+ end
+ -- Borrowed from AceAddon-3.0
+ if type(func) == "function" then
+ return xpcall(func, errorhandler, ...)
+ else
+ error("fn: func is not a function")
+ end
+end
+
+local acr = LibStub("AceConfigRegistry-3.0")
+function G_RLF:NotifyChange(...)
+ acr:NotifyChange(...)
+end
+
+function G_RLF:Print(...)
+ G_RLF.RLF:Print(...)
+end
+
+function G_RLF:SendMessage(...)
+ local args = { ... }
+ RunNextFrame(function()
+ G_RLF.RLF:SendMessage(unpack(args))
+ end)
+end
+
+function G_RLF:RGBAToHexFormat(r, g, b, a)
+ local red = string.format("%02X", math.floor(r * 255))
+ local green = string.format("%02X", math.floor(g * 255))
+ local blue = string.format("%02X", math.floor(b * 255))
+ local alpha = string.format("%02X", math.floor((a or 1) * 255)) -- Default alpha to 1 if not provided
+
+ -- Return in WoW format with |c prefix
+ return "|c" .. alpha .. red .. green .. blue
+end
+
+local function log(...)
+ G_RLF:SendMessage("RLF_LOG", { ... })
+end
+
+function G_RLF:LogDebug(...)
+ log(G_RLF.LogLevel.debug, ...)
+end
+
+function G_RLF:LogInfo(...)
+ log(G_RLF.LogLevel.info, ...)
+end
+
+function G_RLF:LogWarn(...)
+ log(G_RLF.LogLevel.warn, ...)
+end
+
+function G_RLF:LogError(...)
+ log(G_RLF.LogLevel.error, ...)
+end
+
+function G_RLF:CreatePatternSegmentsForStringNumber(localeString)
+ local preStart, preEnd = string.find(localeString, "%%s")
+ local prePattern = string.sub(localeString, 1, preStart - 1)
+ local midStart, midEnd = string.find(localeString, "%%d", preEnd + 1)
+ local midPattern = string.sub(localeString, preEnd + 1, midStart - 1)
+ local postPattern = string.sub(localeString, midEnd + 1)
+ return { prePattern, midPattern, postPattern }
+end
+
+function G_RLF:ExtractDynamicsFromPattern(localeString, segments)
+ local prePattern, midPattern, postPattern = unpack(segments)
+ local preMatchStart, preMatchEnd = string.find(localeString, prePattern, 1, true)
+ if preMatchStart then
+ local msgLoop = localeString:sub(preMatchEnd + 1)
+ local midMatchStart, midMatchEnd = string.find(msgLoop, midPattern, 1, true)
+ if midMatchStart then
+ local postMatchStart, postMatchEnd = string.find(msgLoop, postPattern, midMatchEnd, true)
+ if postMatchStart then
+ local str = msgLoop:sub(1, midMatchStart - 1)
+ local num
+ if midMatchEnd == postMatchStart then
+ num = msgLoop:sub(midMatchEnd + 1)
+ else
+ num = msgLoop:sub(midMatchEnd + 1, postMatchStart - 1)
+ end
+ return str, tonumber(num)
+ end
+ end
+ end
+
+ return nil, nil
+end
diff --git a/utils/AlphaHelpers.lua b/utils/AlphaHelpers.lua
new file mode 100644
index 0000000..93e132f
--- /dev/null
+++ b/utils/AlphaHelpers.lua
@@ -0,0 +1,43 @@
+local addonName, G_RLF = ...
+
+function dump(o, depth)
+ depth = depth or 0
+ local indent = string.rep(" ", depth)
+
+ if type(o) == "table" then
+ local s = "{\n"
+ -- Collect and sort keys
+ local keys = {}
+ for k in pairs(o) do
+ table.insert(keys, k)
+ end
+ table.sort(keys, function(a, b)
+ return tostring(a) < tostring(b)
+ end)
+
+ -- Iterate over sorted keys
+ for _, k in ipairs(keys) do
+ local key = type(k) == "number" and k or '"' .. k .. '"'
+ s = s .. indent .. " [" .. key .. "] = " .. dump(o[k], depth + 1) .. ",\n"
+ end
+ return s .. indent .. "}"
+ elseif type(o) == "string" then
+ return '"' .. o .. '"' -- Wrap strings in quotes
+ else
+ return tostring(o)
+ end
+end
+
+function G_RLF:ProfileFunction(func, funcName)
+ return function(...)
+ local startTime = debugprofilestop()
+ local result = { func(...) }
+ local endTime = debugprofilestop()
+ local duration = endTime - startTime
+ if duration > 0.3 then
+ G_RLF:Print(string.format("%s took %.2f ms", funcName, endTime - startTime))
+ end
+
+ return unpack(result)
+ end
+end
diff --git a/DoubleLinkedList.lua b/utils/DoubleLinkedList.lua
similarity index 100%
rename from DoubleLinkedList.lua
rename to utils/DoubleLinkedList.lua
diff --git a/utils/Enums.lua b/utils/Enums.lua
new file mode 100644
index 0000000..419d752
--- /dev/null
+++ b/utils/Enums.lua
@@ -0,0 +1,45 @@
+local addonName, G_RLF = ...
+
+G_RLF.DisableBossBanner = {
+ ENABLED = 0,
+ FULLY_DISABLE = 1,
+ DISABLE_LOOT = 2,
+ DISABLE_MY_LOOT = 3,
+ DISABLE_GROUP_LOOT = 4,
+}
+
+G_RLF.LogEventSource = {
+ ADDON = addonName,
+ WOWEVENT = "WOWEVENT",
+}
+
+G_RLF.LogLevel = {
+ debug = "DEBUG",
+ info = "INFO",
+ warn = "WARN",
+ error = "ERROR",
+}
+
+G_RLF.FeatureModule = {
+ ItemLoot = "ItemLoot",
+ Currency = "Currency",
+ Money = "Money",
+ Reputation = "Reputation",
+ Experience = "Experience",
+ Profession = "Professions",
+}
+
+G_RLF.PricesEnum = {
+ None = "none",
+ Vendor = "vendor",
+ AH = "ah",
+}
+
+G_RLF.WrapCharEnum = {
+ DEFAULT = 0,
+ SPACE = 1,
+ PARENTHESIS = 2,
+ BRACKET = 3,
+ BRACE = 4,
+ ANGLE = 5,
+}
diff --git a/utils/ItemInfo.lua b/utils/ItemInfo.lua
new file mode 100644
index 0000000..7a378dc
--- /dev/null
+++ b/utils/ItemInfo.lua
@@ -0,0 +1,72 @@
+local addonName, G_RLF = ...
+
+local ItemInfo = {}
+ItemInfo.__index = ItemInfo
+function ItemInfo:new(...)
+ local self = {}
+ setmetatable(self, ItemInfo)
+ self.itemId, self.itemName, self.itemLink, self.itemQuality, self.itemLevel, self.itemMinLevel, self.itemType, self.itemSubType, self.itemStackCount, self.itemEquipLoc, self.itemTexture, self.sellPrice, self.classID, self.subclassID, self.bindType, self.expansionID, self.setID, self.isCraftingReagent =
+ ...
+ if not self.itemName then
+ return nil
+ end
+ if not self.itemId then
+ self.itemId = C_Item.GetItemIDForItemInfo(self.itemLink)
+ end
+ return self
+end
+
+function ItemInfo:IsMount()
+ -- Highlight Mounts
+ if self.classID == Enum.ItemClass.Miscellaneous and self.subclassID == Enum.ItemMiscellaneousSubclass.Mount then
+ return true
+ end
+
+ return false
+end
+
+function ItemInfo:IsLegendary()
+ -- Highlight Legendary Items
+ if self.itemQuality == Enum.ItemQuality.Legendary then
+ return true
+ end
+
+ return false
+end
+
+local function GetHighestArmorClass()
+ if G_RLF.cachedArmorClass then
+ return G_RLF.cachedArmorClass
+ end
+ local _, playerClass = UnitClass("player")
+ G_RLF.cachedArmorClass = G_RLF.armorClassMapping[playerClass]
+ return G_RLF.cachedArmorClass
+end
+
+function ItemInfo:IsEligibleEquipment()
+ if self.classID ~= Enum.ItemClass.Armor then
+ return false
+ end
+
+ if not self.itemEquipLoc then
+ return false
+ end
+
+ local armorClass = GetHighestArmorClass()
+ if not armorClass then
+ return false
+ end
+
+ if self.subclassID ~= armorClass and self.subclassID ~= Enum.ItemArmorSubclass.Generic then
+ return false
+ end
+
+ local slot = G_RLF.equipSlotMap[self.itemEquipLoc]
+ if not slot then
+ return false
+ end
+
+ return true
+end
+
+G_RLF.ItemInfo = ItemInfo
diff --git a/Logger.lua b/utils/Logger.lua
similarity index 65%
rename from Logger.lua
rename to utils/Logger.lua
index 85f0323..d5197b1 100644
--- a/Logger.lua
+++ b/utils/Logger.lua
@@ -1,6 +1,6 @@
local addonName, G_RLF = ...
-local Logger = G_RLF.RLF:NewModule("Logger", "AceEvent-3.0")
+local Logger = G_RLF.RLF:NewModule("Logger", "AceBucket-3.0", "AceEvent-3.0")
local gui = LibStub("AceGUI-3.0")
local loggerName = addonName .. "Logger"
@@ -10,11 +10,15 @@ local defaults = {
}
local updateContent
-local getLogger
-local WOWEVENT = "WOWEVENT"
+local function getLogger()
+ if G_RLF.db.global.logger ~= nil and G_RLF.db.global.logger.sessionsLogged > 0 then
+ return G_RLF.db.global.logger.logs[G_RLF.db.global.logger.sessionsLogged]
+ end
+end
+local WOWEVENT = G_RLF.LogEventSource.WOWEVENT
local eventSource = {
- [addonName] = true,
+ [G_RLF.LogEventSource.ADDON] = true,
[WOWEVENT] = false,
}
local function OnEventSourceChange(_, _, k, v)
@@ -22,10 +26,10 @@ local function OnEventSourceChange(_, _, k, v)
updateContent()
end
-local debug = "DEBUG"
-local info = "INFO"
-local warn = "WARN"
-local error = "ERROR"
+local debug = G_RLF.LogLevel.debug
+local info = G_RLF.LogLevel.info
+local warn = G_RLF.LogLevel.warn
+local error = G_RLF.LogLevel.error
local eventLevel = {
[debug] = false,
[info] = true,
@@ -37,17 +41,19 @@ local function OnEventLevelChange(_, _, k, v)
updateContent()
end
-local ItemLoot = "ItemLoot"
-local Currency = "Currency"
-local Money = "Money"
-local Reputation = "Reputation"
-local Experience = "Experience"
+local ItemLoot = G_RLF.FeatureModule.ItemLoot
+local Currency = G_RLF.FeatureModule.Currency
+local Money = G_RLF.FeatureModule.Money
+local Reputation = G_RLF.FeatureModule.Reputation
+local Experience = G_RLF.FeatureModule.Experience
+local Profession = G_RLF.FeatureModule.Profession
local eventType = {
[ItemLoot] = true,
[Currency] = true,
[Money] = true,
[Reputation] = true,
[Experience] = true,
+ [Profession] = true,
}
local function OnEventTypeChange(_, _, k, v)
eventType[k] = v
@@ -63,93 +69,109 @@ local function OnClearLog()
end
local frame, contentBox
-local function initializeFrame()
+function Logger:InitializeFrame()
if not frame then
frame = gui:Create("Frame")
- frame:SetTitle("Loot Log")
- frame:EnableResize(false)
- frame:SetCallback("OnClose", function(widget)
- gui:Release(widget)
- frame = nil
+ frame:Hide()
+ RunNextFrame(function()
+ frame:SetTitle("Loot Log")
+ frame:EnableResize(false)
+ frame:SetCallback("OnClose", function(widget)
+ gui:Release(widget)
+ frame = nil
+ end)
+ frame:SetLayout("Flow")
end)
- frame:SetLayout("Flow")
local filterBar = gui:Create("SimpleGroup")
- filterBar:SetFullWidth(true)
- filterBar:SetLayout("Flow")
frame:AddChild(filterBar)
+ RunNextFrame(function()
+ filterBar:SetFullWidth(true)
+ filterBar:SetLayout("Flow")
+ end)
contentBox = gui:Create("MultiLineEditBox")
- contentBox:SetLabel("Logs")
- contentBox:DisableButton(true)
- contentBox:SetFullWidth(true)
- contentBox:SetNumLines(23)
frame:AddChild(contentBox)
+ RunNextFrame(function()
+ contentBox:SetLabel("Logs")
+ contentBox:DisableButton(true)
+ contentBox:SetFullWidth(true)
+ contentBox:SetNumLines(23)
+ end)
local logSources = gui:Create("Dropdown")
- logSources:SetLabel("Log Sources")
- logSources:SetMultiselect(true)
- logSources:SetList({
- [addonName] = addonName,
- [WOWEVENT] = WOWEVENT,
- }, {
- addonName,
- WOWEVENT,
- })
- logSources:SetCallback("OnValueChanged", OnEventSourceChange)
- for k, v in pairs(eventSource) do
- logSources:SetItemValue(k, v)
- end
filterBar:AddChild(logSources)
+ RunNextFrame(function()
+ logSources:SetLabel("Log Sources")
+ logSources:SetMultiselect(true)
+ logSources:SetList({
+ [addonName] = addonName,
+ [WOWEVENT] = WOWEVENT,
+ }, {
+ addonName,
+ WOWEVENT,
+ })
+ logSources:SetCallback("OnValueChanged", OnEventSourceChange)
+ for k, v in pairs(eventSource) do
+ logSources:SetItemValue(k, v)
+ end
+ end)
local logLevels = gui:Create("Dropdown")
- logLevels:SetLabel("Log Levels")
- logLevels:SetMultiselect(true)
- logLevels:SetList({
- [debug] = debug,
- [info] = info,
- [warn] = warn,
- [error] = error,
- })
- logLevels:SetCallback("OnValueChanged", OnEventLevelChange)
- for k, v in pairs(eventLevel) do
- logLevels:SetItemValue(k, v)
- end
filterBar:AddChild(logLevels)
+ RunNextFrame(function()
+ logLevels:SetLabel("Log Levels")
+ logLevels:SetMultiselect(true)
+ logLevels:SetList({
+ [debug] = debug,
+ [info] = info,
+ [warn] = warn,
+ [error] = error,
+ })
+ logLevels:SetCallback("OnValueChanged", OnEventLevelChange)
+ for k, v in pairs(eventLevel) do
+ logLevels:SetItemValue(k, v)
+ end
+ end)
local logTypes = gui:Create("Dropdown")
- logTypes:SetLabel("Log Types")
- logTypes:SetMultiselect(true)
- logTypes:SetList({
- [ItemLoot] = ItemLoot,
- [Currency] = Currency,
- [Money] = Money,
- [Reputation] = Reputation,
- [Experience] = Experience,
- })
- logTypes:SetCallback("OnValueChanged", OnEventTypeChange)
- for k, v in pairs(eventType) do
- logTypes:SetItemValue(k, v)
- end
filterBar:AddChild(logTypes)
+ RunNextFrame(function()
+ logTypes:SetLabel("Log Types")
+ logTypes:SetMultiselect(true)
+ logTypes:SetList({
+ [ItemLoot] = ItemLoot,
+ [Currency] = Currency,
+ [Money] = Money,
+ [Reputation] = Reputation,
+ [Experience] = Experience,
+ [Profession] = Profession,
+ })
+ logTypes:SetCallback("OnValueChanged", OnEventTypeChange)
+ for k, v in pairs(eventType) do
+ logTypes:SetItemValue(k, v)
+ end
+ end)
local clearButton = gui:Create("Button")
- clearButton:SetText("Clear Current Log")
- clearButton:SetCallback("OnClick", OnClearLog)
filterBar:AddChild(clearButton)
+ RunNextFrame(function()
+ clearButton:SetText("Clear Current Log")
+ clearButton:SetCallback("OnClick", OnClearLog)
+ end)
- frame:DoLayout()
- end
-end
-
-getLogger = function()
- if G_RLF.db.global.logger ~= nil and G_RLF.db.global.logger.sessionsLogged > 0 then
- return G_RLF.db.global.logger.logs[G_RLF.db.global.logger.sessionsLogged]
+ RunNextFrame(function()
+ frame:DoLayout()
+ end)
end
end
function Logger:OnInitialize()
self:RegisterEvent("PLAYER_ENTERING_WORLD")
+ self:RegisterBucketMessage("RLF_LOG", 0.5, "ProcessLogs")
+ RunNextFrame(function()
+ self:InitializeFrame()
+ end)
end
function Logger:PLAYER_ENTERING_WORLD(_, isLogin, isReload)
@@ -161,7 +183,7 @@ function Logger:PLAYER_ENTERING_WORLD(_, isLogin, isReload)
tremove(G_RLF.db.global.logger.logs, 1)
G_RLF.db.global.logger.sessionsLogged = G_RLF.db.global.logger.sessionsLogged - 1
end
- self:Debug("Logger is ready", addonName)
+ G_RLF:LogDebug("Logger is ready", addonName)
end
end
@@ -186,6 +208,7 @@ local function getType(logEntry)
[Money] = "|cFFC0C0C0[GOLD]|r", -- Silver/Gray for money
[Reputation] = "|cFF1E90FF[REPU]|r", -- Blue for reputation
[Experience] = "|cFF9932CC[EXPR]|r", -- Purple for experience
+ [Profession] = "|cFF8B4513[PROF]|r", -- Brown for profession
}
-- Return an empty string for "General" and the corresponding value for others
@@ -195,7 +218,7 @@ end
local function getSource(logEntry)
local source = logEntry.source
local sourceStrings = {
- [addonName] = "(RLF)",
+ [addonName] = "(" .. addonName .. ")",
[WOWEVENT] = "(WOW)",
}
return sourceStrings[source] or ""
@@ -237,7 +260,7 @@ local function getId(logEntry)
return format(" [%s]", logEntry.id)
end
-local function formatLogEntry(logEntry)
+function Logger:FormatLogEntry(logEntry)
return format(
"[%s]%s%s%s: %s%s%s%s\n",
getTimestamp(logEntry),
@@ -255,7 +278,7 @@ updateContent = function()
local text = ""
local function addText(logEntry)
- text = formatLogEntry(logEntry) .. text
+ text = Logger:FormatLogEntry(logEntry) .. text
end
for i, logEntry in ipairs(getLogger()) do
@@ -298,20 +321,10 @@ local function addLogEntry(level, message, source, type, id, content, amount, is
end
end
-function Logger:Debug(message, source, type, id, content, amount, isNew)
- addLogEntry(debug, message, source, type, id, content, amount, isNew)
-end
-
-function Logger:Info(message, source, type, id, content, amount, isNew)
- addLogEntry(info, message, source, type, id, content, amount, isNew)
-end
-
-function Logger:Warn(message, source, type, id, content, amount, isNew)
- addLogEntry(warn, message, source, type, id, content, amount, isNew)
-end
-
-function Logger:Error(message, source, type, id, content, amount, isNew)
- addLogEntry(error, message, source, type, id, content, amount, isNew)
+function Logger:ProcessLogs(logs)
+ for log, _ in pairs(logs) do
+ addLogEntry(unpack(log))
+ end
end
function Logger:Trace(type, traceSize)
@@ -322,7 +335,7 @@ function Logger:Trace(type, traceSize)
for i = #logs, 1, -1 do
if logs[i].type == type then
count = count + 1
- trace = trace .. formatLogEntry(logs[i])
+ trace = trace .. self:FormatLogEntry(logs[i])
end
if count >= traceSize then
break
@@ -333,10 +346,9 @@ function Logger:Trace(type, traceSize)
end
function Logger:Show()
- if frame then
+ if frame:IsShown() then
self:Hide()
else
- initializeFrame()
updateContent()
frame:Show()
end
@@ -345,3 +357,5 @@ end
function Logger:Hide()
frame:Hide()
end
+
+return Logger
diff --git a/utils/Maps.lua b/utils/Maps.lua
new file mode 100644
index 0000000..e2d849a
--- /dev/null
+++ b/utils/Maps.lua
@@ -0,0 +1,41 @@
+local addonName, G_RLF = ...
+
+G_RLF.armorClassMapping = {
+ WARRIOR = Enum.ItemArmorSubclass.Plate,
+ PALADIN = Enum.ItemArmorSubclass.Plate,
+ DEATHKNIGHT = Enum.ItemArmorSubclass.Plate,
+ HUNTER = Enum.ItemArmorSubclass.Mail,
+ SHAMAN = Enum.ItemArmorSubclass.Mail,
+ EVOKER = Enum.ItemArmorSubclass.Mail,
+ ROGUE = Enum.ItemArmorSubclass.Leather,
+ DRUID = Enum.ItemArmorSubclass.Leather,
+ DEMONHUNTER = Enum.ItemArmorSubclass.Leather,
+ MONK = Enum.ItemArmorSubclass.Leather,
+ PRIEST = Enum.ItemArmorSubclass.Cloth,
+ MAGE = Enum.ItemArmorSubclass.Cloth,
+ WARLOCK = Enum.ItemArmorSubclass.Cloth,
+}
+
+G_RLF.equipSlotMap = {
+ INVTYPE_HEAD = 1,
+ INVTYPE_NECK = 2,
+ INVTYPE_SHOULDER = 3,
+ INVTYPE_BODY = 4,
+ INVTYPE_CHEST = 5,
+ INVTYPE_WAIST = 6,
+ INVTYPE_LEGS = 7,
+ INVTYPE_FEET = 8,
+ INVTYPE_WRIST = 9,
+ INVTYPE_HAND = 10,
+ INVTYPE_FINGER = { 11, 12 }, -- Rings
+ INVTYPE_TRINKET = { 13, 14 }, -- Trinkets
+ INVTYPE_CLOAK = 15,
+ INVTYPE_WEAPON = { 16, 17 }, -- One-handed weapons
+ INVTYPE_SHIELD = 17, -- Off-hand
+ INVTYPE_2HWEAPON = 16, -- Two-handed weapons
+ INVTYPE_WEAPONMAINHAND = 16,
+ INVTYPE_WEAPONOFFHAND = 17,
+ INVTYPE_HOLDABLE = 17, -- Off-hand items
+ INVTYPE_RANGED = 18, -- Bows, guns, wands
+ INVTYPE_TABARD = 19,
+}
diff --git a/Queue.lua b/utils/Queue.lua
similarity index 100%
rename from Queue.lua
rename to utils/Queue.lua
diff --git a/utils/utils.xml b/utils/utils.xml
new file mode 100644
index 0000000..c097642
--- /dev/null
+++ b/utils/utils.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+