diff --git a/Broker.lua b/Broker.lua
index 5e648c9..d7843cc 100644
--- a/Broker.lua
+++ b/Broker.lua
@@ -1,174 +1,174 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
----@class KillTrackBroker
-local KTB = {
- Text = {
- Short = "KPM: %.2f",
- Long = "Kills Per Minute: %.2f"
- }
-}
-
-KT.Broker = KTB
-local KTT = KT.Tools
-
-local UPDATE = 1
-local t = 0
-
-local ldb = LibStub:GetLibrary("LibDataBroker-1.1")
-local icon = LibStub:GetLibrary("LibDBIcon-1.0")
-
-local frame = CreateFrame("Frame")
-
-local tooltipVisible = false
-
-local data = {
- type = "data source",
- label = KT.Name,
- icon = "Interface\\AddOns\\KillTrack\\icon.tga",
- tocname = KT.Name
-}
-
-local clickFunctions = {
- none = {
- LeftButton = function() KT.MobList:Toggle() end,
- MiddleButton = function() KTB:ToggleTextMode() end,
- RightButton = function() KT:ResetSession() end
- },
- ctrl = {
- LeftButton = function() KT:Announce("GROUP") end, -- Announce to group/say
- MiddleButton = function() KT.Immediate:Toggle() end, -- Show the immediate frame
- RightButton = function() KT:Announce("GUILD") end -- Announce to guild
- },
- shift = {
- LeftButton = function() KT.Options:Open() end
- }
-}
-
-local obj = ldb:NewDataObject("Broker_KillTrack", data) --[[@as LibDataBroker.DataDisplay]]
-
----@param self GameTooltip
-function obj.OnTooltipShow(self)
- self:AddLine(("%s |cff00FF00(%s)|r"):format(KT.Name, KT.Version), 1, 1, 1)
- self:AddLine(" ")
- local _, kpm, kph, length = KT:GetSessionStats()
- self:AddDoubleLine("Session Length", ("|cffFFFFFF%s|r"):format(KTT:FormatSeconds(length)))
- self:AddDoubleLine("Kills Per Minute", ("|cffFFFFFF%.2f|r"):format(kpm))
- self:AddDoubleLine("Kills Per Hour", ("|cffFFFFFF%.2f|r"):format(kph))
- self:AddLine(" ")
- self:AddDoubleLine("Kills this session", ("|cffFFFFFF%d|r"):format(KT.Session.Count), 1, 1, 0)
- local added = 0
- for _, v in pairs(KT:GetSortedSessionKills()) do
- self:AddDoubleLine(v.Name, ("|cffFFFFFF%d|r"):format(v.Kills))
- added = added + 1
- end
- if added <= 0 then
- self:AddLine("No kills this session", 1, 0, 0)
- end
- self:AddLine(" ")
- self:AddDoubleLine("Kills in total", ("|cffFFFFFF%d|r"):format(KT:GetTotalKills()), 1, 1, 0)
- added = 0
- for _, v in pairs(KT:GetSortedMobTable()) do
- self:AddDoubleLine(v.Name, ("|cffFFFFFF%d (%d)|r"):format(v.cKills, v.gKills))
- added = added + 1
- if added >= 3 then break end
- end
- if added <= 0 then
- self:AddLine("No kills recorded yet", 1, 0, 0)
- end
- self:AddLine(" ")
- self:AddDoubleLine("Left Click", "Open mob database", 0, 1, 0, 0, 1, 0)
- self:AddDoubleLine("Middle Click", "Toggle short/long text", 0, 1, 0, 0, 1, 0)
- self:AddDoubleLine("Right Click", "Reset session statistics", 0, 1, 0, 0, 1, 0)
- self:AddDoubleLine("Ctrl + Left Click", "Announce to group/say", 0, 1, 0, 0, 1, 0)
- self:AddDoubleLine("Ctrl + Middle Click", "Toggle immediate frame", 0, 1, 0, 0, 1, 0)
- self:AddDoubleLine("Ctrl + Right Click", "Announce to guild", 0, 1, 0, 0, 1, 0)
- self:AddDoubleLine("Shift + Left Click", "Open options panel", 0, 1, 0, 0, 1, 0)
-end
-
-function obj:OnClick(button)
- local mod = ((IsControlKeyDown() and "ctrl") or (IsShiftKeyDown() and "shift")) or "none"
- if not clickFunctions[mod] then return end
- local func = clickFunctions[mod][button]
- if func then func() end
-end
-
-function obj:OnEnter()
- KT:DebugMsg("Broker OnEnter")
- GameTooltip:SetOwner(self --[[@as Frame]], "ANCHOR_NONE")
- GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT")
- KTB:UpdateTooltip()
- tooltipVisible = true
-end
-
-function obj:OnLeave()
- KT:DebugMsg("Broker OnLeave")
- tooltipVisible = false
- GameTooltip:Hide()
-end
-
-function KTB:UpdateText()
- local text = KT.Global.BROKER.SHORT_TEXT and self.Text.Short or self.Text.Long
- obj.text = text:format(select(2, KT:GetSessionStats()))
-end
-
-function KTB:UpdateTooltip()
- KT:DebugMsg("Broker UpdateTooltip")
- GameTooltip:ClearLines()
- obj.OnTooltipShow(GameTooltip)
- GameTooltip:Show()
-end
-
----@param _ Frame
----@param elapsed number
-function KTB:OnUpdate(_, elapsed)
- t = t + elapsed
- if t >= UPDATE then
- self:UpdateText()
- if tooltipVisible then self:UpdateTooltip() end
- t = 0
- end
-end
-
-function KTB:ToggleTextMode()
- KT.Global.BROKER.SHORT_TEXT = not KT.Global.BROKER.SHORT_TEXT
- self:UpdateText()
-end
-
-function KTB:OnLoad()
- if type(KT.Global.BROKER.MINIMAP.hide) ~= "boolean" then
- KT.Global.BROKER.MINIMAP.hide = true
- end
- icon:Register(KT.Name, obj, KT.Global.BROKER.MINIMAP)
- frame:SetScript("OnUpdate", function(f, elapsed) KTB:OnUpdate(f, elapsed) end)
- self:UpdateText()
-end
-
----@param enabled boolean
-function KTB:SetMinimap(enabled)
- KT.Global.BROKER.MINIMAP.hide = not enabled
- if enabled then
- icon:Show(KT.Name)
- else
- icon:Hide(KT.Name)
- end
-end
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+---@class KillTrackBroker
+local KTB = {
+ Text = {
+ Short = "KPM: %.2f",
+ Long = "Kills Per Minute: %.2f"
+ }
+}
+
+KT.Broker = KTB
+local KTT = KT.Tools
+
+local UPDATE = 1
+local t = 0
+
+local ldb = LibStub:GetLibrary("LibDataBroker-1.1")
+local icon = LibStub:GetLibrary("LibDBIcon-1.0")
+
+local frame = CreateFrame("Frame")
+
+local tooltipVisible = false
+
+local data = {
+ type = "data source",
+ label = KT.Name,
+ icon = "Interface\\AddOns\\KillTrack\\icon.tga",
+ tocname = KT.Name
+}
+
+local clickFunctions = {
+ none = {
+ LeftButton = function() KT.MobList:Toggle() end,
+ MiddleButton = function() KTB:ToggleTextMode() end,
+ RightButton = function() KT:ResetSession() end
+ },
+ ctrl = {
+ LeftButton = function() KT:Announce("GROUP") end, -- Announce to group/say
+ MiddleButton = function() KT.Immediate:Toggle() end, -- Show the immediate frame
+ RightButton = function() KT:Announce("GUILD") end -- Announce to guild
+ },
+ shift = {
+ LeftButton = function() KT.Options:Open() end
+ }
+}
+
+local obj = ldb:NewDataObject("Broker_KillTrack", data) --[[@as LibDataBroker.DataDisplay]]
+
+---@param self GameTooltip
+function obj.OnTooltipShow(self)
+ self:AddLine(("%s |cff00FF00(%s)|r"):format(KT.Name, KT.Version), 1, 1, 1)
+ self:AddLine(" ")
+ local _, kpm, kph, length = KT:GetSessionStats()
+ self:AddDoubleLine("Session Length", ("|cffFFFFFF%s|r"):format(KTT:FormatSeconds(length)))
+ self:AddDoubleLine("Kills Per Minute", ("|cffFFFFFF%.2f|r"):format(kpm))
+ self:AddDoubleLine("Kills Per Hour", ("|cffFFFFFF%.2f|r"):format(kph))
+ self:AddLine(" ")
+ self:AddDoubleLine("Kills this session", ("|cffFFFFFF%d|r"):format(KT.Session.Count), 1, 1, 0)
+ local added = 0
+ for _, v in pairs(KT:GetSortedSessionKills()) do
+ self:AddDoubleLine(v.Name, ("|cffFFFFFF%d|r"):format(v.Kills))
+ added = added + 1
+ end
+ if added <= 0 then
+ self:AddLine("No kills this session", 1, 0, 0)
+ end
+ self:AddLine(" ")
+ self:AddDoubleLine("Kills in total", ("|cffFFFFFF%d|r"):format(KT:GetTotalKills()), 1, 1, 0)
+ added = 0
+ for _, v in pairs(KT:GetSortedMobTable()) do
+ self:AddDoubleLine(v.Name, ("|cffFFFFFF%d (%d)|r"):format(v.cKills, v.gKills))
+ added = added + 1
+ if added >= 3 then break end
+ end
+ if added <= 0 then
+ self:AddLine("No kills recorded yet", 1, 0, 0)
+ end
+ self:AddLine(" ")
+ self:AddDoubleLine("Left Click", "Open mob database", 0, 1, 0, 0, 1, 0)
+ self:AddDoubleLine("Middle Click", "Toggle short/long text", 0, 1, 0, 0, 1, 0)
+ self:AddDoubleLine("Right Click", "Reset session statistics", 0, 1, 0, 0, 1, 0)
+ self:AddDoubleLine("Ctrl + Left Click", "Announce to group/say", 0, 1, 0, 0, 1, 0)
+ self:AddDoubleLine("Ctrl + Middle Click", "Toggle immediate frame", 0, 1, 0, 0, 1, 0)
+ self:AddDoubleLine("Ctrl + Right Click", "Announce to guild", 0, 1, 0, 0, 1, 0)
+ self:AddDoubleLine("Shift + Left Click", "Open options panel", 0, 1, 0, 0, 1, 0)
+end
+
+function obj:OnClick(button)
+ local mod = ((IsControlKeyDown() and "ctrl") or (IsShiftKeyDown() and "shift")) or "none"
+ if not clickFunctions[mod] then return end
+ local func = clickFunctions[mod][button]
+ if func then func() end
+end
+
+function obj:OnEnter()
+ KT:DebugMsg("Broker OnEnter")
+ GameTooltip:SetOwner(self --[[@as Frame]], "ANCHOR_NONE")
+ GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT")
+ KTB:UpdateTooltip()
+ tooltipVisible = true
+end
+
+function obj:OnLeave()
+ KT:DebugMsg("Broker OnLeave")
+ tooltipVisible = false
+ GameTooltip:Hide()
+end
+
+function KTB:UpdateText()
+ local text = KT.Global.BROKER.SHORT_TEXT and self.Text.Short or self.Text.Long
+ obj.text = text:format(select(2, KT:GetSessionStats()))
+end
+
+function KTB:UpdateTooltip()
+ KT:DebugMsg("Broker UpdateTooltip")
+ GameTooltip:ClearLines()
+ obj.OnTooltipShow(GameTooltip)
+ GameTooltip:Show()
+end
+
+---@param _ Frame
+---@param elapsed number
+function KTB:OnUpdate(_, elapsed)
+ t = t + elapsed
+ if t >= UPDATE then
+ self:UpdateText()
+ if tooltipVisible then self:UpdateTooltip() end
+ t = 0
+ end
+end
+
+function KTB:ToggleTextMode()
+ KT.Global.BROKER.SHORT_TEXT = not KT.Global.BROKER.SHORT_TEXT
+ self:UpdateText()
+end
+
+function KTB:OnLoad()
+ if type(KT.Global.BROKER.MINIMAP.hide) ~= "boolean" then
+ KT.Global.BROKER.MINIMAP.hide = true
+ end
+ icon:Register(KT.Name, obj, KT.Global.BROKER.MINIMAP)
+ frame:SetScript("OnUpdate", function(f, elapsed) KTB:OnUpdate(f, elapsed) end)
+ self:UpdateText()
+end
+
+---@param enabled boolean
+function KTB:SetMinimap(enabled)
+ KT.Global.BROKER.MINIMAP.hide = not enabled
+ if enabled then
+ icon:Show(KT.Name)
+ else
+ icon:Hide(KT.Name)
+ end
+end
diff --git a/Command.lua b/Command.lua
index 640d765..151dc44 100644
--- a/Command.lua
+++ b/Command.lua
@@ -1,296 +1,296 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
----@class KillTrackCommand
-local C = {
- Slash = {
- "killtrack",
- "kt"
- },
- ---@type { [string]: fun(args: string[]) }
- Commands = {}
-}
-
-KT.Command = C
-local KTT = KT.Tools
-
--- Argument #1 (command) can be either string or a table.
----@param command string|string[]
----@param func fun(args: string[])
-function C:Register(command, func)
- if type(command) == "string" then
- command = {command}
- end
- for _,v in pairs(command) do
- if not self:HasCommand(v) then
- if v ~= "__DEFAULT__" then v = v:lower() end
- self.Commands[v] = func
- end
- end
-end
-
----@param command string
----@return boolean
-function C:HasCommand(command)
- for k,_ in pairs(self.Commands) do
- if k == command then return true end
- end
- return false
-end
-
----@param command string
----@return fun(args: string[])
-function C:GetCommand(command)
- local cmd = self.Commands[command]
- if cmd then return cmd else return self.Commands["__DEFAULT__"] end
-end
-
----@param command string
----@param args string[]
-function C:HandleCommand(command, args)
- local cmd = self:GetCommand(command)
- if cmd then
- cmd(args)
- else
- KT:Msg(("%q is not a valid command."):format(command))
- end
-end
-
-C:Register("__DEFAULT__", function()
- KT:Msg("/kt loadmessage - Toggles showing a message when AddOn loads.")
- KT:Msg("/kt target - Display number of kills on target mob.")
- KT:Msg("/kt lookup - Display number of kills on , can also be NPC ID.")
- KT:Msg("/kt print - Toggle printing kill updates to chat.")
- KT:Msg("/kt list - Display a list of all mobs entries.")
- KT:Msg("/kt set - Set kill counts for a mob")
- KT:Msg("/kt delete - Delete entry with NPC id .")
- KT:Msg("/kt purge [threshold] - Open dialog to purge entries, specifiying a threshold here is optional.")
- KT:Msg("/kt reset - Clear the mob database.")
- KT:Msg("/kt time - Track kills within specified time.")
- KT:Msg("/kt immediate - Track kills made from this point on.")
- KT:Msg("/kt threshold - Set threshold for kill record notices to show.")
- KT:Msg("/kt countmode - Toggle between counting group killing blows or your killing blows only.")
- KT:Msg("/kt minimap - Toggles the minimap icon")
- KT:Msg("/kt tooltip - Toggles showing mob data in tooltip")
- KT:Msg("/kt - Displays this help message.")
-end)
-
-C:Register({"loadmessage", "lm"}, function()
- KT:ToggleLoadMessage()
-end)
-
-C:Register({"target", "t", "tar"}, function()
- if not UnitExists("target") or UnitIsPlayer("target") then return end
- local id = KTT.GUIDToID(UnitGUID("target"))
- KT:PrintKills(id)
-end)
-
-C:Register({"print", "p"}, function()
- KT.Global.PRINTKILLS = not KT.Global.PRINTKILLS
- if KT.Global.PRINTKILLS then
- KT:Msg("Announcing kill updates.")
- else
- KT:Msg("No longer announcing kill updates.")
- end
-end)
-
-C:Register({"printnew", "pn"}, function()
- KT.Global.PRINTNEW = not KT.Global.PRINTNEW
- if KT.Global.PRINTNEW then
- KT:Msg("Announcing new mob entries")
- else
- KT:Msg("No longer announcing new mob entries")
- end
-end)
-
-C:Register({"set", "edit"}, function(args)
- local id = tonumber(args[1]) --[[@as integer]]
- local name = args[2]
- local global = tonumber(args[3]) --[[@as integer]]
- local char = tonumber(args[4]) --[[@as integer]]
-
- local err
-
- if not id then
- KT:Msg("Missing or invalid argument: id")
- err = true
- end
-
- if not name then
- KT:Msg("Missing or invalid argument: name")
- err = true
- end
-
- if not global then
- KT:Msg("Missing or invalid argument: global")
- err = true
- end
-
- if not char then
- KT:Msg("Missing or invalid argument: char")
- err = true
- end
-
- if err then
- KT:Msg("Usage: /kt set ")
- return
- end
-
- KT:SetKills(id, name, global, char)
-end)
-
-C:Register({"delete", "del", "remove", "rem"}, function(args)
- if #args <= 0 then
- KT:Msg("Missing argument: id")
- return
- end
- local id = tonumber(args[1])
- if not id then
- KT:Msg("Id must be a number")
- return
- end
- if not KT.Global.MOBS[id] then
- KT:Msg(("Id %d does not exist in the database."):format(id))
- return
- end
- local name = KT.Global.MOBS[id].Name
- KT:ShowDelete(id, name)
-end)
-
-C:Register({"purge"}, function(args)
- ---@type integer?
- local threshold
- if #args >= 1 then threshold = tonumber(args[1]) --[[@as integer]] end
- KT:ShowPurge(threshold)
-end)
-
-C:Register({"reset", "r"}, function() KT:ShowReset() end)
-
-C:Register({"lookup", "lo", "check"}, function(args)
- if #args <= 0 then
- KT:Msg("Missing argument: name")
- return
- end
- local name = table.concat(args, " ")
- KT:PrintKills(name)
-end)
-
-C:Register({"list", "moblist", "mobs"}, function() KT.MobList:Show() end)
-
-C:Register({"time", "timer"}, function(args)
- if #args <= 0 then
- KT:Msg("Usage: time [minutes] [hours]")
- KT:Msg("Usage: time [s][m][h]")
- return
- end
-
- local s, m, h
-
- if #args == 1 then
- if not tonumber(args[1]) then
- args[1] = args[1]:lower()
- s = args[1]:match("(%d+)s")
- m = args[1]:match("(%d+)m")
- h = args[1]:match("(%d+)h")
- if not s and not m and not h then
- KT:Msg("Invalid number format.")
- return
- end
- else
- s = tonumber(args[1])
- end
- else
- s = tonumber(args[1])
- m = tonumber(args[2])
- h = tonumber(args[3])
- end
- KT.TimerFrame:Start(s, m, h)
-end)
-
-C:Register({"immediate", "imm", "i"}, function(args)
- if #args < 1 then
- KT.Immediate:Show()
- elseif args[1]:match("^t") then
- local threshold = tonumber(args[2]) --[[@as integer]]
- if #args < 2 then
- KT:Msg("Usage: immediate threshold ")
- KT:Msg("E.g: /kt immediate threshold 50")
- KT:Msg(" Notice will be shown on every 50th kill when immediate frame is active")
- else
- KT:SetImmediateThreshold(threshold)
- end
- elseif args[1]:match("^f") then
- if #args < 2 then
- local current = KT.Global.IMMEDIATE.FILTER
- KT:Msg("The current immediate filter is set to: " .. (current and current or ""))
- KT:Msg("Use /kt immediate filter to set a new filter")
- KT:Msg("Use /kt immediate clearfilter to clear the filter")
- else
- KT:SetImmediateFilter(tostring(args[2]))
- end
- elseif args[1]:match("^c") then
- KT:ClearImmediateFilter()
- end
-end)
-
-C:Register({"threshold"}, function(args)
- if #args <= 0 then
- KT:Msg("Usage: threshold ")
- KT:Msg("E.g: /kt threshold 100")
- KT:Msg(" Notice will be shown on every 100th kill.")
- return
- end
- local t = tonumber(args[1])
- if t then
- KT:SetThreshold(t)
- else
- KT:Msg("Argument must be a number.")
- end
-end)
-
-C:Register({"countmode", "cm", "count", "counttype", "changecount", "switchcount"}, function() KT:ToggleCountMode() end)
-C:Register({"debug", "toggledebug", "d", "td"}, function() KT:ToggleDebug() end)
-C:Register({"exp", "xp", "experience", "shoexp", "showxp"}, function() KT:ToggleExp() end)
-C:Register({"options", "opt", "config", "conf"}, function() KT.Options:Open() end)
-C:Register({"minimap", "mp"}, function() KT.Broker:SetMinimap(KT.Global.BROKER.MINIMAP.hide) end)
-
-C:Register({"tooltip", "tt"}, function()
- KT.Global.TOOLTIP = not KT.Global.TOOLTIP
- KT:Msg("Tooltip data " .. (KT.Global.TOOLTIP and "enabled" or "disabled"))
-end)
-
-for i,v in ipairs(C.Slash) do
- _G["SLASH_" .. KT.Name:upper() .. i] = "/" .. v
-end
-
-SlashCmdList[KT.Name:upper()] = function(msg)
- msg = KTT:Trim(msg)
- local args = KTT:Split(msg)
- local cmd = args[1]
- local t = {}
- if #args > 1 then
- for i = 2, #args do
- t[#t + 1] = args[i]
- end
- end
- C:HandleCommand(cmd, t)
-end
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+---@class KillTrackCommand
+local C = {
+ Slash = {
+ "killtrack",
+ "kt"
+ },
+ ---@type { [string]: fun(args: string[]) }
+ Commands = {}
+}
+
+KT.Command = C
+local KTT = KT.Tools
+
+-- Argument #1 (command) can be either string or a table.
+---@param command string|string[]
+---@param func fun(args: string[])
+function C:Register(command, func)
+ if type(command) == "string" then
+ command = {command}
+ end
+ for _,v in pairs(command) do
+ if not self:HasCommand(v) then
+ if v ~= "__DEFAULT__" then v = v:lower() end
+ self.Commands[v] = func
+ end
+ end
+end
+
+---@param command string
+---@return boolean
+function C:HasCommand(command)
+ for k,_ in pairs(self.Commands) do
+ if k == command then return true end
+ end
+ return false
+end
+
+---@param command string
+---@return fun(args: string[])
+function C:GetCommand(command)
+ local cmd = self.Commands[command]
+ if cmd then return cmd else return self.Commands["__DEFAULT__"] end
+end
+
+---@param command string
+---@param args string[]
+function C:HandleCommand(command, args)
+ local cmd = self:GetCommand(command)
+ if cmd then
+ cmd(args)
+ else
+ KT:Msg(("%q is not a valid command."):format(command))
+ end
+end
+
+C:Register("__DEFAULT__", function()
+ KT:Msg("/kt loadmessage - Toggles showing a message when AddOn loads.")
+ KT:Msg("/kt target - Display number of kills on target mob.")
+ KT:Msg("/kt lookup - Display number of kills on , can also be NPC ID.")
+ KT:Msg("/kt print - Toggle printing kill updates to chat.")
+ KT:Msg("/kt list - Display a list of all mobs entries.")
+ KT:Msg("/kt set - Set kill counts for a mob")
+ KT:Msg("/kt delete - Delete entry with NPC id .")
+ KT:Msg("/kt purge [threshold] - Open dialog to purge entries, specifiying a threshold here is optional.")
+ KT:Msg("/kt reset - Clear the mob database.")
+ KT:Msg("/kt time - Track kills within specified time.")
+ KT:Msg("/kt immediate - Track kills made from this point on.")
+ KT:Msg("/kt threshold - Set threshold for kill record notices to show.")
+ KT:Msg("/kt countmode - Toggle between counting group killing blows or your killing blows only.")
+ KT:Msg("/kt minimap - Toggles the minimap icon")
+ KT:Msg("/kt tooltip - Toggles showing mob data in tooltip")
+ KT:Msg("/kt - Displays this help message.")
+end)
+
+C:Register({"loadmessage", "lm"}, function()
+ KT:ToggleLoadMessage()
+end)
+
+C:Register({"target", "t", "tar"}, function()
+ if not UnitExists("target") or UnitIsPlayer("target") then return end
+ local id = KTT.GUIDToID(UnitGUID("target"))
+ KT:PrintKills(id)
+end)
+
+C:Register({"print", "p"}, function()
+ KT.Global.PRINTKILLS = not KT.Global.PRINTKILLS
+ if KT.Global.PRINTKILLS then
+ KT:Msg("Announcing kill updates.")
+ else
+ KT:Msg("No longer announcing kill updates.")
+ end
+end)
+
+C:Register({"printnew", "pn"}, function()
+ KT.Global.PRINTNEW = not KT.Global.PRINTNEW
+ if KT.Global.PRINTNEW then
+ KT:Msg("Announcing new mob entries")
+ else
+ KT:Msg("No longer announcing new mob entries")
+ end
+end)
+
+C:Register({"set", "edit"}, function(args)
+ local id = tonumber(args[1]) --[[@as integer]]
+ local name = args[2]
+ local global = tonumber(args[3]) --[[@as integer]]
+ local char = tonumber(args[4]) --[[@as integer]]
+
+ local err
+
+ if not id then
+ KT:Msg("Missing or invalid argument: id")
+ err = true
+ end
+
+ if not name then
+ KT:Msg("Missing or invalid argument: name")
+ err = true
+ end
+
+ if not global then
+ KT:Msg("Missing or invalid argument: global")
+ err = true
+ end
+
+ if not char then
+ KT:Msg("Missing or invalid argument: char")
+ err = true
+ end
+
+ if err then
+ KT:Msg("Usage: /kt set ")
+ return
+ end
+
+ KT:SetKills(id, name, global, char)
+end)
+
+C:Register({"delete", "del", "remove", "rem"}, function(args)
+ if #args <= 0 then
+ KT:Msg("Missing argument: id")
+ return
+ end
+ local id = tonumber(args[1])
+ if not id then
+ KT:Msg("Id must be a number")
+ return
+ end
+ if not KT.Global.MOBS[id] then
+ KT:Msg(("Id %d does not exist in the database."):format(id))
+ return
+ end
+ local name = KT.Global.MOBS[id].Name
+ KT:ShowDelete(id, name)
+end)
+
+C:Register({"purge"}, function(args)
+ ---@type integer?
+ local threshold
+ if #args >= 1 then threshold = tonumber(args[1]) --[[@as integer]] end
+ KT:ShowPurge(threshold)
+end)
+
+C:Register({"reset", "r"}, function() KT:ShowReset() end)
+
+C:Register({"lookup", "lo", "check"}, function(args)
+ if #args <= 0 then
+ KT:Msg("Missing argument: name")
+ return
+ end
+ local name = table.concat(args, " ")
+ KT:PrintKills(name)
+end)
+
+C:Register({"list", "moblist", "mobs"}, function() KT.MobList:Show() end)
+
+C:Register({"time", "timer"}, function(args)
+ if #args <= 0 then
+ KT:Msg("Usage: time [minutes] [hours]")
+ KT:Msg("Usage: time [s][m][h]")
+ return
+ end
+
+ local s, m, h
+
+ if #args == 1 then
+ if not tonumber(args[1]) then
+ args[1] = args[1]:lower()
+ s = args[1]:match("(%d+)s")
+ m = args[1]:match("(%d+)m")
+ h = args[1]:match("(%d+)h")
+ if not s and not m and not h then
+ KT:Msg("Invalid number format.")
+ return
+ end
+ else
+ s = tonumber(args[1])
+ end
+ else
+ s = tonumber(args[1])
+ m = tonumber(args[2])
+ h = tonumber(args[3])
+ end
+ KT.TimerFrame:Start(s, m, h)
+end)
+
+C:Register({"immediate", "imm", "i"}, function(args)
+ if #args < 1 then
+ KT.Immediate:Show()
+ elseif args[1]:match("^t") then
+ local threshold = tonumber(args[2]) --[[@as integer]]
+ if #args < 2 then
+ KT:Msg("Usage: immediate threshold ")
+ KT:Msg("E.g: /kt immediate threshold 50")
+ KT:Msg(" Notice will be shown on every 50th kill when immediate frame is active")
+ else
+ KT:SetImmediateThreshold(threshold)
+ end
+ elseif args[1]:match("^f") then
+ if #args < 2 then
+ local current = KT.Global.IMMEDIATE.FILTER
+ KT:Msg("The current immediate filter is set to: " .. (current and current or ""))
+ KT:Msg("Use /kt immediate filter to set a new filter")
+ KT:Msg("Use /kt immediate clearfilter to clear the filter")
+ else
+ KT:SetImmediateFilter(tostring(args[2]))
+ end
+ elseif args[1]:match("^c") then
+ KT:ClearImmediateFilter()
+ end
+end)
+
+C:Register({"threshold"}, function(args)
+ if #args <= 0 then
+ KT:Msg("Usage: threshold ")
+ KT:Msg("E.g: /kt threshold 100")
+ KT:Msg(" Notice will be shown on every 100th kill.")
+ return
+ end
+ local t = tonumber(args[1])
+ if t then
+ KT:SetThreshold(t)
+ else
+ KT:Msg("Argument must be a number.")
+ end
+end)
+
+C:Register({"countmode", "cm", "count", "counttype", "changecount", "switchcount"}, function() KT:ToggleCountMode() end)
+C:Register({"debug", "toggledebug", "d", "td"}, function() KT:ToggleDebug() end)
+C:Register({"exp", "xp", "experience", "shoexp", "showxp"}, function() KT:ToggleExp() end)
+C:Register({"options", "opt", "config", "conf"}, function() KT.Options:Open() end)
+C:Register({"minimap", "mp"}, function() KT.Broker:SetMinimap(KT.Global.BROKER.MINIMAP.hide) end)
+
+C:Register({"tooltip", "tt"}, function()
+ KT.Global.TOOLTIP = not KT.Global.TOOLTIP
+ KT:Msg("Tooltip data " .. (KT.Global.TOOLTIP and "enabled" or "disabled"))
+end)
+
+for i,v in ipairs(C.Slash) do
+ _G["SLASH_" .. KT.Name:upper() .. i] = "/" .. v
+end
+
+SlashCmdList[KT.Name:upper()] = function(msg)
+ msg = KTT:Trim(msg)
+ local args = KTT:Split(msg)
+ local cmd = args[1]
+ local t = {}
+ if #args > 1 then
+ for i = 2, #args do
+ t[#t + 1] = args[i]
+ end
+ end
+ C:HandleCommand(cmd, t)
+end
diff --git a/Dialogs.lua b/Dialogs.lua
index 427e388..fd107a0 100644
--- a/Dialogs.lua
+++ b/Dialogs.lua
@@ -1,107 +1,107 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
-StaticPopupDialogs.KILLTRACK_FINISH = {
- text = "%s entries removed.",
- button1 = "Okay",
- timeout = 10,
- enterClicksFirstButton = true,
- whileDead = true,
- hideOnEscape = true
-}
-
-StaticPopupDialogs.KILLTRACK_DELETE = {
- text = "Delete %s with ID %s?",
- button1 = "Delete all",
- button2 = "CANCEL",
- button3 = "Character Only",
- OnAccept = function() KT:Delete(KT.Temp.DeleteId) KT.MobList:UpdateMobs() KT.MobList:UpdateEntries() end,
- OnAlt = function() KT:Delete(KT.Temp.DeleteId, true) KT.MobList:UpdateMobs() KT.MobList:UpdateEntries() end,
- showAlert = true,
- timeout = 10,
- whileDead = true,
- hideOnEscape = true
-}
-
-StaticPopupDialogs.KILLTRACK_PURGE = {
- text = "Remove all mob entries with their kill count below this threshold:",
- button1 = "Purge",
- button2 = "Cancel",
- hasEditBox = true,
- OnAccept = function(self)
- KT:Purge(tonumber(self.editBox:GetText()) --[[@as integer]]) KT.MobList:UpdateMobs() KT.MobList:UpdateEntries()
- end,
- OnCancel = function() KT.Temp.Threshold = nil end,
- OnShow = function(self)
- if tonumber(KT.Temp.Threshold) then
- self.editBox:SetText(tostring(KT.Temp.Threshold))
- else
- self.button1:Disable()
- end
- end,
- EditBoxOnTextChanged = function(self)
- if tonumber(self:GetText()) then
- self:GetParent().button1:Enable()
- else
- self:GetParent().button1:Disable()
- end
- end,
- showAlert = true,
- enterClicksFirstButton = true,
- timeout = 0,
- whileDead = true,
- hideOnEscape = true
-}
-
-StaticPopupDialogs.KILLTRACK_RESET = {
- text = "Remove all mob entries from the database? THIS CANNOT BE REVERSED.",
- button1 = "Yes",
- button2 = "No",
- OnAccept = function() KT:Reset() KT.MobList:UpdateMobs() KT.MobList:UpdateEntries() end,
- showAlert = true,
- enterClicksFirstButton = true,
- timeout = 0,
- whileDead = true,
- hideOnEscape = true
-}
-
----@param id integer|string
----@param name string
-function KT:ShowDelete(id, name)
- id = tonumber(id) --[[@as integer]]
- name = tostring(name)
- if not id then error("'id' must be a number.") end
- self.Temp.DeleteId = id
- StaticPopup_Show("KILLTRACK_DELETE", name, id)
-end
-
----@param threshold integer?
-function KT:ShowPurge(threshold)
- if tonumber(threshold) then
- self.Temp.Threshold = tonumber(threshold)
- end
- StaticPopup_Show("KILLTRACK_PURGE")
-end
-
-function KT:ShowReset()
- StaticPopup_Show("KILLTRACK_RESET")
-end
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+StaticPopupDialogs.KILLTRACK_FINISH = {
+ text = "%s entries removed.",
+ button1 = "Okay",
+ timeout = 10,
+ enterClicksFirstButton = true,
+ whileDead = true,
+ hideOnEscape = true
+}
+
+StaticPopupDialogs.KILLTRACK_DELETE = {
+ text = "Delete %s with ID %s?",
+ button1 = "Delete all",
+ button2 = "CANCEL",
+ button3 = "Character Only",
+ OnAccept = function() KT:Delete(KT.Temp.DeleteId) KT.MobList:UpdateMobs() KT.MobList:UpdateEntries() end,
+ OnAlt = function() KT:Delete(KT.Temp.DeleteId, true) KT.MobList:UpdateMobs() KT.MobList:UpdateEntries() end,
+ showAlert = true,
+ timeout = 10,
+ whileDead = true,
+ hideOnEscape = true
+}
+
+StaticPopupDialogs.KILLTRACK_PURGE = {
+ text = "Remove all mob entries with their kill count below this threshold:",
+ button1 = "Purge",
+ button2 = "Cancel",
+ hasEditBox = true,
+ OnAccept = function(self)
+ KT:Purge(tonumber(self.editBox:GetText()) --[[@as integer]]) KT.MobList:UpdateMobs() KT.MobList:UpdateEntries()
+ end,
+ OnCancel = function() KT.Temp.Threshold = nil end,
+ OnShow = function(self)
+ if tonumber(KT.Temp.Threshold) then
+ self.editBox:SetText(tostring(KT.Temp.Threshold))
+ else
+ self.button1:Disable()
+ end
+ end,
+ EditBoxOnTextChanged = function(self)
+ if tonumber(self:GetText()) then
+ self:GetParent().button1:Enable()
+ else
+ self:GetParent().button1:Disable()
+ end
+ end,
+ showAlert = true,
+ enterClicksFirstButton = true,
+ timeout = 0,
+ whileDead = true,
+ hideOnEscape = true
+}
+
+StaticPopupDialogs.KILLTRACK_RESET = {
+ text = "Remove all mob entries from the database? THIS CANNOT BE REVERSED.",
+ button1 = "Yes",
+ button2 = "No",
+ OnAccept = function() KT:Reset() KT.MobList:UpdateMobs() KT.MobList:UpdateEntries() end,
+ showAlert = true,
+ enterClicksFirstButton = true,
+ timeout = 0,
+ whileDead = true,
+ hideOnEscape = true
+}
+
+---@param id integer|string
+---@param name string
+function KT:ShowDelete(id, name)
+ id = tonumber(id) --[[@as integer]]
+ name = tostring(name)
+ if not id then error("'id' must be a number.") end
+ self.Temp.DeleteId = id
+ StaticPopup_Show("KILLTRACK_DELETE", name, id)
+end
+
+---@param threshold integer?
+function KT:ShowPurge(threshold)
+ if tonumber(threshold) then
+ self.Temp.Threshold = tonumber(threshold)
+ end
+ StaticPopup_Show("KILLTRACK_PURGE")
+end
+
+function KT:ShowReset()
+ StaticPopup_Show("KILLTRACK_RESET")
+end
diff --git a/ExpTracker.lua b/ExpTracker.lua
index 12925c7..a2bbd34 100644
--- a/ExpTracker.lua
+++ b/ExpTracker.lua
@@ -1,73 +1,73 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
----@class KillTrackExpTracker
-local ET = {
- ---@type string[]
- Strings = {
- ---@diagnostic disable: undefined-field
- _G.COMBATLOG_XPGAIN_EXHAUSTION1, -- %s dies, you gain %d experience. (%s exp %s bonus)
- _G.COMBATLOG_XPGAIN_EXHAUSTION1_GROUP, -- %s dies, you gain %d experience. (%s exp %s bonus, +%d group bonus)
- _G.COMBATLOG_XPGAIN_EXHAUSTION1_RAID, -- %s dies, you gain %d experience. (%s exp %s bonus, -%d raid penalty)
- _G.COMBATLOG_XPGAIN_EXHAUSTION2, -- %s dies, you gain %d experience. (%s exp %s bonus)
- _G.COMBATLOG_XPGAIN_EXHAUSTION2_GROUP, -- %s dies, you gain %d experience. (%s exp %s bonus, +%d group bonus)
- _G.COMBATLOG_XPGAIN_EXHAUSTION2_RAID, -- %s dies, you gain %d experience. (%s exp %s bonus, -%d raid penalty)
- _G.COMBATLOG_XPGAIN_EXHAUSTION4, -- %s dies, you gain %d experience. (%s exp %s penalty)
- _G.COMBATLOG_XPGAIN_EXHAUSTION4_GROUP, -- %s dies, you gain %d experience. (%s exp %s penalty, +%d group bonus)
- _G.COMBATLOG_XPGAIN_EXHAUSTION4_RAID, -- %s dies, you gain %d experience. (%s exp %s penalty, -%d raid penalty)
- _G.COMBATLOG_XPGAIN_EXHAUSTION5, -- %s dies, you gain %d experience. (%s exp %s penalty)
- _G.COMBATLOG_XPGAIN_EXHAUSTION5_GROUP, -- %s dies, you gain %d experience. (%s exp %s penalty, +%d group bonus)
- _G.COMBATLOG_XPGAIN_EXHAUSTION5_RAID, -- %s dies, you gain %d experience. (%s exp %s penalty, -%d raid penalty)
- _G.COMBATLOG_XPGAIN_FIRSTPERSON, -- %s dies, you gain %d experience.
- _G.COMBATLOG_XPGAIN_FIRSTPERSON_GROUP, -- %s dies, you gain %d experience. (+%d group bonus)
- _G.COMBATLOG_XPGAIN_FIRSTPERSON_RAID -- %s dies, you gain %d experience. (-%d raid penalty)
- ---@diagnostic enable: undefined-field
- }
-}
-
-KT.ExpTracker = ET
-
-local initialized = false
-
-local function Initialize()
- for i = 1, #ET.Strings do
- ET.Strings[i] = ET.Strings[i]:gsub("([%(%)])", "%%%1"):gsub("%%%d?$?s", "(.-)"):gsub("%%%d?$?d", "(%%d+)")
- end
- initialized = true
-end
-
----@param message string
-function ET:CheckMessage(message)
- if not initialized then Initialize() end
- local name, exp
- for i = 1, #self.Strings do
- local str = self.Strings[i]
- name, exp = message:match(str)
- if name and exp then break end
- end
-
- exp = tonumber(exp)
-
- if type(name) == "string" and name ~= "" and type(exp) == "number" then
- KT:SetExp(name, exp)
- end
-end
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+---@class KillTrackExpTracker
+local ET = {
+ ---@type string[]
+ Strings = {
+ ---@diagnostic disable: undefined-field
+ _G.COMBATLOG_XPGAIN_EXHAUSTION1, -- %s dies, you gain %d experience. (%s exp %s bonus)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION1_GROUP, -- %s dies, you gain %d experience. (%s exp %s bonus, +%d group bonus)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION1_RAID, -- %s dies, you gain %d experience. (%s exp %s bonus, -%d raid penalty)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION2, -- %s dies, you gain %d experience. (%s exp %s bonus)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION2_GROUP, -- %s dies, you gain %d experience. (%s exp %s bonus, +%d group bonus)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION2_RAID, -- %s dies, you gain %d experience. (%s exp %s bonus, -%d raid penalty)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION4, -- %s dies, you gain %d experience. (%s exp %s penalty)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION4_GROUP, -- %s dies, you gain %d experience. (%s exp %s penalty, +%d group bonus)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION4_RAID, -- %s dies, you gain %d experience. (%s exp %s penalty, -%d raid penalty)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION5, -- %s dies, you gain %d experience. (%s exp %s penalty)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION5_GROUP, -- %s dies, you gain %d experience. (%s exp %s penalty, +%d group bonus)
+ _G.COMBATLOG_XPGAIN_EXHAUSTION5_RAID, -- %s dies, you gain %d experience. (%s exp %s penalty, -%d raid penalty)
+ _G.COMBATLOG_XPGAIN_FIRSTPERSON, -- %s dies, you gain %d experience.
+ _G.COMBATLOG_XPGAIN_FIRSTPERSON_GROUP, -- %s dies, you gain %d experience. (+%d group bonus)
+ _G.COMBATLOG_XPGAIN_FIRSTPERSON_RAID -- %s dies, you gain %d experience. (-%d raid penalty)
+ ---@diagnostic enable: undefined-field
+ }
+}
+
+KT.ExpTracker = ET
+
+local initialized = false
+
+local function Initialize()
+ for i = 1, #ET.Strings do
+ ET.Strings[i] = ET.Strings[i]:gsub("([%(%)])", "%%%1"):gsub("%%%d?$?s", "(.-)"):gsub("%%%d?$?d", "(%%d+)")
+ end
+ initialized = true
+end
+
+---@param message string
+function ET:CheckMessage(message)
+ if not initialized then Initialize() end
+ local name, exp
+ for i = 1, #self.Strings do
+ local str = self.Strings[i]
+ name, exp = message:match(str)
+ if name and exp then break end
+ end
+
+ exp = tonumber(exp)
+
+ if type(name) == "string" and name ~= "" and type(exp) == "number" then
+ KT:SetExp(name, exp)
+ end
+end
diff --git a/ImmediateFrame.lua b/ImmediateFrame.lua
index dde3dd7..1d570e6 100644
--- a/ImmediateFrame.lua
+++ b/ImmediateFrame.lua
@@ -1,126 +1,126 @@
---[[
- * Copyright (c) 2013 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
----@class KillTrackImmediateFrame
-local I = {
- Active = false,
- Kills = 0
-}
-
-KT.Immediate = I
-
-local frame
-
-local function SetupFrame()
- if frame then return end
- local G = KT.Global.IMMEDIATE
- frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
- frame:Hide()
- frame:EnableMouse(true)
- frame:SetMovable(true)
- if G.POSITION.POINT then
- frame:SetPoint(G.POSITION.POINT, UIParent, G.POSITION.RELATIVE, G.POSITION.X, G.POSITION.Y)
- else
- frame:SetPoint("CENTER")
- end
- frame:SetWidth(240)
- frame:SetHeight(30)
-
- local bd = {
- bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
- edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
- tile = true,
- edgeSize = 16,
- tileSize = 32,
- insets = {
- left = 2.5,
- right = 2.5,
- top = 2.5,
- bottom = 2.5
- }
- }
-
- frame:SetBackdrop(bd)
-
- frame:SetScript("OnMouseDown", function(f) f:StartMoving() end)
- frame:SetScript("OnMouseUp", function(f)
- f:StopMovingOrSizing()
- local point, _, relative, x, y = f:GetPoint()
- G.POSITION.POINT = point
- G.POSITION.RELATIVE = relative
- G.POSITION.X = x
- G.POSITION.Y = y
- end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.killLabel = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.killLabel:SetFont("Fonts\\FRIZQT__.TTF", 16, nil)
- frame.killLabel:SetWidth(100)
- --frame.killLabel:SetHeight(24)
- frame.killLabel:SetPoint("LEFT", frame, "LEFT", 2, 0)
- frame.killLabel:SetText("Kills so far:")
-
- ---@diagnostic disable-next-line: inject-field
- frame.killCount = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.killCount:SetFont("Fonts\\FRIZQT__.TTF", 16, nil)
- frame.killCount:SetWidth(100)
- --frame.killCount:SetHeight(24)
- frame.killCount:SetPoint("RIGHT", frame, "RIGHT", -68, 0)
- frame.killCount:SetJustifyH("RIGHT")
- frame.killCount:SetText("0")
-
- ---@diagnostic disable-next-line: inject-field
- frame.closeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
- frame.closeButton:SetWidth(60)
- frame.closeButton:SetHeight(24)
- frame.closeButton:SetPoint("RIGHT", frame, "RIGHT", -3, 0)
- frame.closeButton:SetText("Close")
- frame.closeButton:SetScript("OnClick", function() I:Hide() end)
-end
-
-function I:Show()
- if not frame then SetupFrame() end
- self.Kills = 0
- frame.killCount:SetText(tostring(self.Kills))
- frame:Show()
- self.Active = true
-end
-
-function I:Hide()
- frame:Hide()
- self.Kills = 0
- frame.killCount:SetText(tostring(self.Kills))
- self.Active = false
-end
-
-function I:Toggle()
- if frame and frame:IsShown() then
- self:Hide()
- else
- self:Show()
- end
-end
-
-function I:AddKill()
- self.Kills = self.Kills + 1
- frame.killCount:SetText(tostring(self.Kills))
-end
+--[[
+ * Copyright (c) 2013 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+---@class KillTrackImmediateFrame
+local I = {
+ Active = false,
+ Kills = 0
+}
+
+KT.Immediate = I
+
+local frame
+
+local function SetupFrame()
+ if frame then return end
+ local G = KT.Global.IMMEDIATE
+ frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
+ frame:Hide()
+ frame:EnableMouse(true)
+ frame:SetMovable(true)
+ if G.POSITION.POINT then
+ frame:SetPoint(G.POSITION.POINT, UIParent, G.POSITION.RELATIVE, G.POSITION.X, G.POSITION.Y)
+ else
+ frame:SetPoint("CENTER")
+ end
+ frame:SetWidth(240)
+ frame:SetHeight(30)
+
+ local bd = {
+ bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
+ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
+ tile = true,
+ edgeSize = 16,
+ tileSize = 32,
+ insets = {
+ left = 2.5,
+ right = 2.5,
+ top = 2.5,
+ bottom = 2.5
+ }
+ }
+
+ frame:SetBackdrop(bd)
+
+ frame:SetScript("OnMouseDown", function(f) f:StartMoving() end)
+ frame:SetScript("OnMouseUp", function(f)
+ f:StopMovingOrSizing()
+ local point, _, relative, x, y = f:GetPoint()
+ G.POSITION.POINT = point
+ G.POSITION.RELATIVE = relative
+ G.POSITION.X = x
+ G.POSITION.Y = y
+ end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.killLabel = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.killLabel:SetFont("Fonts\\FRIZQT__.TTF", 16, nil)
+ frame.killLabel:SetWidth(100)
+ --frame.killLabel:SetHeight(24)
+ frame.killLabel:SetPoint("LEFT", frame, "LEFT", 2, 0)
+ frame.killLabel:SetText("Kills so far:")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.killCount = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.killCount:SetFont("Fonts\\FRIZQT__.TTF", 16, nil)
+ frame.killCount:SetWidth(100)
+ --frame.killCount:SetHeight(24)
+ frame.killCount:SetPoint("RIGHT", frame, "RIGHT", -68, 0)
+ frame.killCount:SetJustifyH("RIGHT")
+ frame.killCount:SetText("0")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.closeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
+ frame.closeButton:SetWidth(60)
+ frame.closeButton:SetHeight(24)
+ frame.closeButton:SetPoint("RIGHT", frame, "RIGHT", -3, 0)
+ frame.closeButton:SetText("Close")
+ frame.closeButton:SetScript("OnClick", function() I:Hide() end)
+end
+
+function I:Show()
+ if not frame then SetupFrame() end
+ self.Kills = 0
+ frame.killCount:SetText(tostring(self.Kills))
+ frame:Show()
+ self.Active = true
+end
+
+function I:Hide()
+ frame:Hide()
+ self.Kills = 0
+ frame.killCount:SetText(tostring(self.Kills))
+ self.Active = false
+end
+
+function I:Toggle()
+ if frame and frame:IsShown() then
+ self:Hide()
+ else
+ self:Show()
+ end
+end
+
+function I:AddKill()
+ self.Kills = self.Kills + 1
+ frame.killCount:SetText(tostring(self.Kills))
+end
diff --git a/KillTrack.lua b/KillTrack.lua
index 786314b..6f22dc0 100644
--- a/KillTrack.lua
+++ b/KillTrack.lua
@@ -1,889 +1,889 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@type string
-local NAME = ...
-
----@class KillTrack
----@field PlayerName string
----@field PlayerGUID string?
-local KT = select(2, ...)
-
----@class KillTrackMobData
----@field Kills integer
----@field Name string
----@field LastKillAt integer?
----@field AchievCount integer
----@field Exp integer?
-
----@class KillTrackCharMobData
----@field Kills integer
----@field Name string
----@field LastKillAt integer?
-
-_G[NAME] = KT
-
--- Upvalue some functions used in CLEU
-local IsGUIDInGroup = IsGUIDInGroup
-local UnitGUID = UnitGUID
-local UnitIsTapDenied = UnitIsTapDenied
-local UnitTokenFromGUID = UnitTokenFromGUID
-local CombatLogGetCurrentEventInfo = CombatLogGetCurrentEventInfo
-local GetServerTime = GetServerTime
-
-local NO_NAME = ""
-
-KT.Name = NAME
-KT.Version = C_AddOns.GetAddOnMetadata(NAME, "Version")
-KT.Events = {}
-
----@class KillTrackImmediatePosition
----@field POINT string?
----@field RELATIVE string?
----@field X number?
----@field Y number?
-
----@class KillTrackGlobal
----@field LOAD_MESSAGE boolean
----@field PRINTKILLS boolean
----@field PRINTNEW boolean
----@field ACHIEV_THRESHOLD integer
----@field COUNT_GROUP boolean
----@field SHOW_EXP boolean
----@field MOBS { [integer]: KillTrackMobData }
----@field IMMEDIATE { POSITION: KillTrackImmediatePosition, THRESHOLD: integer, FILTER: string? }
----@field BROKER { SHORT_TEXT: boolean, MINIMAP: { hide: boolean } }
----@field DISABLE_DUNGEONS boolean
----@field DISABLE_RAIDS boolean
----@field TOOLTIP boolean
----@field DATETIME_FORMAT string
-KT.Global = {}
-
----@class KillTrackCharGlobal
----@field MOBS { [integer]: KillTrackCharMobData }
-KT.CharGlobal = {}
-
----@class KillTrackTemp
----@field Threshold integer?
----@field DeleteId integer?
-KT.Temp = {}
-
----@enum KillTrackMobSortMode
-KT.Sort = {
- Desc = 0,
- Asc = 1,
- CharDesc = 2,
- CharAsc = 3,
- AlphaD = 4,
- AlphaA = 5,
- IdDesc = 6,
- IdAsc = 7
-}
-KT.Session = {
- Count = 0,
- ---@type { [string]: integer? }
- Kills = {}
-}
-KT.Messages = {
- Announce = "[KillTrack] Session Length: %s. Session Kills: %d. Kills Per Minute: %.2f."
-}
-
-KT.Defaults = {
- DateTimeFormat = "%Y-%m-%d %H:%M:%S"
-}
-
----@type KillTrackExpTracker
-local ET
-
-local KTT = KT.Tools
-
--- Upvalue as it's used in CLEU
-local GUIDToID = KTT.GUIDToID
-
----@type { [string]: string? }
-local FirstDamage = {} -- Tracks first damage to a mob registered by CLEU
-
----@type { [string]: string? }
-local LastDamage = {} -- Tracks whoever did the most recent damage to a mob
-
----@type { [string]: boolean? }
-local HasPlayerDamage = {} -- Tracks whether the player (or pet) has dealt damage to a mob
-
----@type { [string]: boolean? }
-local HasGroupDamage = {} -- Tracks whether a group member has dealt damage to a mob
-
----@type { [string]: boolean? }
-local DamageValid = {} -- Determines if mob is tapped by player/group
-
-local combat_log_damage_events = {}
-do
- local prefixes = { "SWING", "RANGE", "SPELL", "SPELL_PERIODIC", "SPELL_BUILDING" }
- local suffixes = { "DAMAGE", "DRAIN", "LEECH", "INSTAKILL" }
- for _, prefix in pairs(prefixes) do
- for _, suffix in pairs(suffixes) do
- combat_log_damage_events[prefix .. "_" .. suffix] = true
- end
- end
-end
-
-if KT.Version == "@" .. "project-version" .. "@" then
- KT.Version = "Development"
- KT.Debug = true
-end
-
-if not UnitTokenFromGUID then
- local units = {
- "player",
- "vehicle",
- "pet",
- "party1", "party2", "party3", "party4",
- "partypet1", "partypet2", "partypet3", "partypet4"
- }
- -- Multiple loops to get the same ordering as mainline API
- for i = 1, 40 do
- units[#units + 1] = "raid" .. i
- end
- for i = 1, 40 do
- units[#units + 1] = "raidpet" .. i
- end
- for i = 1, 40 do
- units[#units + 1] = "nameplate" .. i
- end
- for i = 1, 5 do
- units[#units + 1] = "arena" .. i
- end
- for i = 1, 5 do
- units[#units + 1] = "arenapet" .. i
- end
- for i = 1, 8 do
- units[#units + 1] = "boss" .. i
- end
- units[#units + 1] = "target"
- units[#units + 1] = "focus"
- units[#units + 1] = "npc"
- units[#units + 1] = "mouseover"
- units[#units + 1] = "softenemy"
- units[#units + 1] = "softfriend"
- units[#units + 1] = "softinteract"
-
- UnitTokenFromGUID = function(guid)
- for _, unit in ipairs(units) do
- if UnitGUID(unit) == guid then
- return unit
- end
- end
- return nil
- end
-end
-
----@param _ Frame
----@param event string
----@param ... any
-function KT:OnEvent(_, event, ...)
- if self.Events[event] then
- self.Events[event](self, ...)
- end
-end
-
----@param self KillTrack
----@param name string
-function KT.Events.ADDON_LOADED(self, name)
- if name ~= NAME then return end
- ET = KT.ExpTracker
- if type(_G["KILLTRACK"]) ~= "table" then
- _G["KILLTRACK"] = {}
- end
- self.Global = _G["KILLTRACK"]
- if type(self.Global.LOAD_MESSAGE) ~= "boolean" then
- self.Global.LOAD_MESSAGE = false
- end
- if type(self.Global.PRINTKILLS) ~= "boolean" then
- self.Global.PRINTKILLS = false
- end
- if type(self.Global.PRINTNEW) ~= "boolean" then
- self.Global.PRINTNEW = false
- end
- if type(self.Global.ACHIEV_THRESHOLD) ~= "number" then
- self.Global.ACHIEV_THRESHOLD = 1000
- end
- if type(self.Global.COUNT_GROUP) ~= "boolean" then
- self.Global.COUNT_GROUP = false
- end
- if type(self.Global.SHOW_EXP) ~= "boolean" then
- self.Global.SHOW_EXP = false
- end
- if type(self.Global.MOBS) ~= "table" then
- self.Global.MOBS = {}
- end
- if type(self.Global.IMMEDIATE) ~= "table" then
- self.Global.IMMEDIATE = {}
- end
- if type(self.Global.IMMEDIATE.POSITION) ~= "table" then
- self.Global.IMMEDIATE.POSITION = {}
- end
- if type(self.Global.IMMEDIATE.THRESHOLD) ~= "number" then
- self.Global.IMMEDIATE.THRESHOLD = 0
- end
- if type(self.Global.BROKER) ~= "table" then
- self.Global.BROKER = {}
- end
- if type(self.Global.BROKER.SHORT_TEXT) ~= "boolean" then
- self.Global.BROKER.SHORT_TEXT = false
- end
- if type(self.Global.BROKER.MINIMAP) ~= "table" then
- self.Global.BROKER.MINIMAP = {}
- end
- if type(self.Global.DISABLE_DUNGEONS) ~= "boolean" then
- self.Global.DISABLE_DUNGEONS = false
- end
- if type(self.Global.DISABLE_RAIDS) ~= "boolean" then
- self.Global.DISABLE_RAIDS = false
- end
- if type(self.Global.TOOLTIP) ~= "boolean" then
- self.Global.TOOLTIP = true
- end
- if type(self.Global.DATETIME_FORMAT) ~= "string" then
- self.Global.DATETIME_FORMAT = self.Defaults.DateTimeFormat
- end
- if type(_G["KILLTRACK_CHAR"]) ~= "table" then
- _G["KILLTRACK_CHAR"] = {}
- end
- self.CharGlobal = _G["KILLTRACK_CHAR"]
- if type(self.CharGlobal.MOBS) ~= "table" then
- self.CharGlobal.MOBS = {}
- end
- self.PlayerName = UnitName("player")
- self.PlayerGUID = UnitGUID("player")
-
- if self.Global.LOAD_MESSAGE then
- self:Msg("AddOn Loaded!")
- end
-
- self.Session.Start = time()
- self.Broker:OnLoad()
-end
-
----@param self KillTrack
-function KT.Events.COMBAT_LOG_EVENT_UNFILTERED(self)
- local _, event, _, s_guid, _, _, _, d_guid, d_name, _, _ = CombatLogGetCurrentEventInfo()
- if combat_log_damage_events[event] then
- if FirstDamage[d_guid] == nil then
- -- s_guid is (probably) the player who first damaged this mob and probably has the tag
- FirstDamage[d_guid] = s_guid
- end
-
- LastDamage[d_guid] = s_guid
-
- if s_guid == self.PlayerGUID or s_guid == UnitGUID("pet") then
- HasPlayerDamage[d_guid] = true
- elseif self:IsInGroup(s_guid) then
- HasGroupDamage[d_guid] = true
- end
-
- if not DamageValid[d_guid] then
- -- if DamageValid returns true for a GUID, we can tell with 100% certainty that it's valid
- -- But this relies on one of the valid unit names currently being the damaged mob
-
- local d_unit = UnitTokenFromGUID(d_guid)
-
- if not d_unit then return end
-
- DamageValid[d_guid] = not UnitIsTapDenied(d_unit)
- end
-
- return
- end
-
- if event ~= "UNIT_DIED" then return end
-
- -- Perform solo/group checks
- local d_id = GUIDToID(d_guid)
- local firstDamage = FirstDamage[d_guid]
- local lastDamage = LastDamage[d_guid]
- local damageValid = DamageValid[d_guid]
- local hasPlayerDamage = HasPlayerDamage[d_guid]
- local hasGroupDamage = HasGroupDamage[d_guid]
- FirstDamage[d_guid] = nil
- LastDamage[d_guid] = nil
- DamageValid[d_guid] = nil
- HasPlayerDamage[d_guid] = nil
- HasGroupDamage[d_guid] = nil
-
- -- If we can't identify the mob there's no point continuing
- if d_id == nil or d_id == 0 then return end
-
- local petGUID = UnitGUID("pet")
- local firstByPlayer = firstDamage == self.PlayerGUID or firstDamage == petGUID
- local firstByGroup = self:IsInGroup(firstDamage)
- local lastByPlayer = lastDamage == self.PlayerGUID or lastDamage == petGUID
-
- -- The checks when DamageValid is non-false are not 100% failsafe
- -- Scenario: You deal the killing blow to an already tapped mob <- Would count as kill with current code
-
- -- If damageValid is false, it means tap is denied on the mob and there is no way the kill would count
- if damageValid == false then return end
-
- -- If neither the player not group was involved in the battle, we don't count the kill
- if not hasPlayerDamage and not hasGroupDamage then return end
-
- if damageValid == nil and not firstByPlayer and not firstByGroup then
- -- If we couldn't get a proper tapped status and the first recorded damage was not by player or group,
- -- we can't be sure the kill was valid, so ignore it
- return
- end
-
- if not lastByPlayer and not self.Global.COUNT_GROUP then
- return -- Player or player's pet did not deal the killing blow and addon only tracks player kills
- end
-
- self:AddKill(d_id, d_name)
- if self.Timer:IsRunning() then
- self.Timer:SetData("Kills", self.Timer:GetData("Kills", true) + 1)
- end
-end
-
----@param _ KillTrack
----@param message string
-function KT.Events.CHAT_MSG_COMBAT_XP_GAIN(_, message)
- ET:CheckMessage(message)
-end
-
----@param self KillTrack
----@param size integer
-function KT.Events.ENCOUNTER_START(self, _, _, _, size)
- if (self.Global.DISABLE_DUNGEONS and size == 5) or (self.Global.DISABLE_RAIDS and size > 5) then
- self.Frame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
- self.Frame:UnregisterEvent("UPDATE_MOUSEOVER_UNIT")
- self.Frame:UnregisterEvent("CHAT_MSG_COMBAT_XP_GAIN")
- end
-end
-
----@param self KillTrack
----@param size integer
-function KT.Events.ENCOUNTER_END(self, _, _, _, size)
- if (self.Global.DISABLE_DUNGEONS and size == 5) or (self.Global.DISABLE_RAIDS and size > 5) then
- self.Frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
- self.Frame:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
- self.Frame:RegisterEvent("CHAT_MSG_COMBAT_XP_GAIN")
- end
-end
-
----@param self GameTooltip
-local function tooltip_enhancer(self)
- if not KT.Global.TOOLTIP then return end
- local _, unit = self:GetUnit()
- if not unit then return end
- if UnitIsPlayer(unit) then return end
- local id = KTT.GUIDToID(UnitGUID(unit))
- if not id then return end
- if UnitCanAttack("player", unit) then
- local mob, charMob = KT:InitMob(id, UnitName(unit))
- local gKills, cKills = mob.Kills, charMob.Kills --self:GetKills(id)
- local exp = mob.Exp
- self:AddLine(("Killed %d (%d) times."):format(cKills, gKills), 1, 1, 1)
- if KT.Global.SHOW_EXP and exp then
- local toLevel = exp > 0 and math.ceil((UnitXPMax("player") - UnitXP("player")) / exp) or "N/A"
- self:AddLine(("EXP: %d (%s kills to level)"):format(exp, toLevel), 1, 1, 1)
- end
- end
- if KT.Debug then
- self:AddLine(("ID = %d"):format(id))
- end
- self:Show()
-end
-
-if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then
- TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Unit, tooltip_enhancer)
-else
- GameTooltip:HookScript("OnTooltipSetUnit", tooltip_enhancer)
-end
-
-function KT:ToggleLoadMessage()
- self.Global.LOAD_MESSAGE = not self.Global.LOAD_MESSAGE
- if self.Global.LOAD_MESSAGE then
- self:Msg("Now showing message on AddOn load")
- else
- self:Msg("No longer showing message on AddOn load")
- end
-end
-
-function KT:ToggleExp()
- self.Global.SHOW_EXP = not self.Global.SHOW_EXP
- if self.Global.SHOW_EXP then
- self:Msg("Now showing EXP on tooltips!")
- else
- self:Msg("No longer showing EXP on tooltips.")
- end
-end
-
-function KT:ToggleDebug()
- self.Debug = not self.Debug
- if self.Debug then
- self:Msg("Debug enabled!")
- else
- self:Msg("Debug disabled!")
- end
-end
-
----@param guid string?
----@return boolean
-function KT:IsInGroup(guid)
- if not guid or guid == "" then return false end
- if guid == self.PlayerName or guid == self.PlayerGUID then return true end
- return IsGUIDInGroup(guid)
-end
-
----@param threshold integer
-function KT:SetThreshold(threshold)
- if type(threshold) ~= "number" then
- error("KillTrack.SetThreshold: Argument #1 (threshold) must be of type 'number'")
- end
- self.Global.ACHIEV_THRESHOLD = threshold
- if threshold > 0 then
- self:ResetAchievCount()
- self:Msg(("New kill notice (achievement) threshold set to %d."):format(threshold))
- else
- self:Msg("Kill notices have been disabled (set threshold to a value greater than 0 to re-enable).")
- end
-end
-
----@param threshold integer
-function KT:SetImmediateThreshold(threshold)
- if type(threshold) ~= "number" then
- error("KillTrack.SetImmediateThreshold: Argument #1 (threshold) must be of type 'number'")
- end
- self.Global.IMMEDIATE.THRESHOLD = threshold
- if threshold > 0 then
- self:Msg(("New immediate threshold set to %d."):format(threshold))
- else
- self:Msg("Immediate threshold disabled.")
- end
-end
-
----@param filter string
-function KT:SetImmediateFilter(filter)
- if type(filter) ~= "string" then
- error("KillTrack.SetImmediateFilter: Argument #1 (filter) must be of type 'string'")
- end
- self.Global.IMMEDIATE.FILTER = filter
- self:Msg("New immediate filter set to: " .. filter)
-end
-
-function KT:ClearImmediateFilter()
- self.Global.IMMEDIATE.FILTER = nil
- KT:Msg("Immediate filter cleared!")
-end
-
-function KT:ToggleCountMode()
- self.Global.COUNT_GROUP = not self.Global.COUNT_GROUP
- if self.Global.COUNT_GROUP then
- self:Msg("Now counting kills for every player in the group (party/raid)!")
- else
- self:Msg("Now counting your own killing blows ONLY.")
- end
-end
-
----@param id integer
----@param name string?
----@return KillTrackMobData
----@return KillTrackCharMobData
-function KT:InitMob(id, name)
- name = name or NO_NAME
-
- if type(self.Global.MOBS[id]) ~= "table" then
- self.Global.MOBS[id] = { Name = name, Kills = 0, AchievCount = 0 }
- if self.Global.PRINTNEW then
- self:Msg(("Created new entry for %q"):format(name))
- end
- elseif self.Global.MOBS[id].Name ~= name then
- self.Global.MOBS[id].Name = name
- end
-
- if type(self.CharGlobal.MOBS[id]) ~= "table" then
- self.CharGlobal.MOBS[id] = { Name = name, Kills = 0 }
- if self.Global.PRINTNEW then
- self:Msg(("Created new entry for %q on this character."):format(name))
- end
- elseif self.CharGlobal.MOBS[id].Name ~= name then
- self.CharGlobal.MOBS[id].Name = name
- end
-
- return self.Global.MOBS[id], self.CharGlobal.MOBS[id]
-end
-
----@param id integer
----@param name string?
-function KT:AddKill(id, name)
- name = name or NO_NAME
- local current_time = GetServerTime()
- self:InitMob(id, name)
- local globalMob = self.Global.MOBS[id]
- local charMob = self.CharGlobal.MOBS[id]
- globalMob.Kills = self.Global.MOBS[id].Kills + 1
- globalMob.LastKillAt = current_time
- charMob.Kills = self.CharGlobal.MOBS[id].Kills + 1
- charMob.LastKillAt = current_time
- if self.Global.PRINTKILLS then
- local kills = self.Global.MOBS[id].Kills
- local cKills = self.CharGlobal.MOBS[id].Kills
- self:Msg(("Updated %q, new kill count: %d. Kill count on this character: %d"):format(name, kills, cKills))
- end
- self:AddSessionKill(name)
- if self.Immediate.Active then
- local filter = self.Global.IMMEDIATE.FILTER
- local filterPass = not filter or name:match(filter)
- if filterPass then
- self.Immediate:AddKill()
- if self.Global.IMMEDIATE.THRESHOLD > 0 and self.Immediate.Kills % self.Global.IMMEDIATE.THRESHOLD == 0 then
- PlaySound(SOUNDKIT.RAID_WARNING)
- PlaySound(SOUNDKIT.PVP_THROUGH_QUEUE)
- RaidNotice_AddMessage(
- RaidWarningFrame,
- ("%d KILLS!"):format(self.Immediate.Kills),
- ChatTypeInfo.SYSTEM)
- end
- end
- end
- if self.Global.ACHIEV_THRESHOLD <= 0 then return end
- if type(self.Global.MOBS[id].AchievCount) ~= "number" then
- self.Global.MOBS[id].AchievCount = floor(self.Global.MOBS[id].Kills / self.Global.ACHIEV_THRESHOLD)
- if self.Global.MOBS[id].AchievCount >= 1 then
- self:KillAlert(self.Global.MOBS[id])
- end
- else
- local achievCount = self.Global.MOBS[id].AchievCount
- self.Global.MOBS[id].AchievCount = floor(self.Global.MOBS[id].Kills / self.Global.ACHIEV_THRESHOLD)
- if self.Global.MOBS[id].AchievCount > achievCount then
- self:KillAlert(self.Global.MOBS[id])
- end
- end
-end
-
----@param id integer
----@param name string?
----@param globalCount integer
----@param charCount integer
-function KT:SetKills(id, name, globalCount, charCount)
- if type(id) ~= "number" then
- error("'id' argument must be a number")
- end
-
- if type(globalCount) ~= "number" then
- error("'globalCount' argument must be a number")
- end
-
- if type(charCount) ~= "number" then
- error("'charCount' argument must be a number")
- end
-
- name = name or NO_NAME
-
- self:InitMob(id, name)
- self.Global.MOBS[id].Kills = globalCount
- self.CharGlobal.MOBS[id].Kills = charCount
-
- self:Msg(("Updated %q to %d global and %d character kills"):format(name, globalCount, charCount))
-end
-
----@param name string
-function KT:AddSessionKill(name)
- if self.Session.Kills[name] then
- self.Session.Kills[name] = self.Session.Kills[name] + 1
- else
- self.Session.Kills[name] = 1
- end
- self.Session.Count = self.Session.Count + 1
-end
-
----@param name string
----@param exp integer|string
-function KT:SetExp(name, exp)
- for _, mob in pairs(self.Global.MOBS) do
- if mob.Name == name then mob.Exp = tonumber(exp) end
- end
-end
-
----@param max integer|string?
----@return { Name: string, Kills: integer }[]
-function KT:GetSortedSessionKills(max)
- max = tonumber(max) or 3
- local t = {}
- for k,v in pairs(self.Session.Kills) do
- t[#t + 1] = {Name = k, Kills = v}
- end
- table.sort(t, function(a, b) return a.Kills > b.Kills end)
- -- Trim table to only contain 3 entries
- local trimmed = {}
- local c = 0
- for i,v in ipairs(t) do
- trimmed[i] = v
- c = c + 1
- if c >= max then break end
- end
- return trimmed
-end
-
-function KT:ResetSession()
- wipe(self.Session.Kills)
- self.Session.Count = 0
- self.Session.Start = time()
-end
-
----@param id integer
----@return integer globalKills
----@return integer charKills
-function KT:GetKills(id)
- local gKills, cKills = 0, 0
- local mob = self.Global.MOBS[id]
- if type(mob) == "table" then
- gKills = mob.Kills
- local cMob = self.CharGlobal.MOBS[id]
- if type(cMob) == "table" then
- cKills = cMob.Kills
- end
- end
- return gKills, cKills
-end
-
----@return integer
-function KT:GetTotalKills()
- local count = 0
- for _, mob in pairs(self.Global.MOBS) do
- count = count + mob.Kills
- end
- return count
-end
-
----@return integer killsPerSecond
----@return integer killsPerMinute
----@return integer killsPerHour
----@return integer killsThisSession
-function KT:GetSessionStats()
- if not self.Session.Start then return 0, 0, 0, 0 end
- local now = time()
- local session = now - self.Session.Start
- local kps = session == 0 and 0 or self.Session.Count / session
- local kpm = kps * 60
- local kph = kpm * 60
- return kps, kpm, kph, session
-end
-
----@param identifier string|integer?
-function KT:PrintKills(identifier)
- local found = false
- local name = NO_NAME
- local gKills = 0
- local cKills = 0
- local lastKillAt = nil
- if type(identifier) ~= "string" and type(identifier) ~= "number" then identifier = NO_NAME end
- for k,v in pairs(self.Global.MOBS) do
- if type(v) == "table" and (tostring(k) == tostring(identifier) or v.Name == identifier) then
- name = v.Name
- gKills = v.Kills
- if type(v.LastKillAt) == "number" then
- lastKillAt = KTT:FormatDateTime(v.LastKillAt)
- end
- if self.CharGlobal.MOBS[k] then
- cKills = self.CharGlobal.MOBS[k].Kills
- end
- found = true
- end
- end
- if found then
- self:Msg(("You have killed %q %d times in total, %d times on this character"):format(name, gKills, cKills))
- if lastKillAt then
- self:Msg(("Last killed at %s"):format(lastKillAt))
- end
- else
- if UnitExists("target") and not UnitIsPlayer("target") then
- identifier = UnitName("target")
- end
- self:Msg(("Unable to find %q in mob database."):format(tostring(identifier)))
- end
-end
-
----@param target string
-function KT:Announce(target)
- if target == "GROUP" then
- target = ((IsInRaid() and "RAID") or (IsInGroup() and "PARTY")) or "SAY"
- end
- local _, kpm, _, length = self:GetSessionStats()
- local msg = self.Messages.Announce:format(KTT:FormatSeconds(length), self.Session.Count, kpm)
- SendChatMessage(msg, target)
-end
-
----@param msg string
-function KT:Msg(msg)
- DEFAULT_CHAT_FRAME:AddMessage("\124cff00FF00[KillTrack]\124r " .. msg)
-end
-
----@param msg string
-function KT:DebugMsg(msg)
- if not self.Debug then return end
- self:Msg("[DEBUG] " .. msg)
-end
-
----@param mob KillTrackMobData
-function KT:KillAlert(mob)
- local data = {
- Text = ("%d kills on %s!"):format(mob.Kills, mob.Name),
- Title = "Kill Record!",
- bTitle = "Congratulations!",
- Icon = "Interface\\Icons\\ABILITY_Deathwing_Bloodcorruption_Death",
- FrameStyle = "GuildAchievement"
- }
- if C_AddOns.IsAddOnLoaded("Glamour") then
- if not _G["GlamourShowAlert"] then
- self:Msg("ERROR: GlamourShowAlert == nil! Notify AddOn developer.")
- return
- end
- _G.GlamourShowAlert(500, data)
- else
- RaidNotice_AddMessage(RaidBossEmoteFrame, data.Text, ChatTypeInfo.SYSTEM)
- RaidNotice_AddMessage(RaidBossEmoteFrame, data.Text, ChatTypeInfo.SYSTEM)
- end
- self:Msg(data.Text)
-end
-
----@param id integer|string
----@return KillTrackMobData|false
----@return KillTrackCharMobData|nil
-function KT:GetMob(id)
- for k,v in pairs(self.Global.MOBS) do
- if type(v) == "table" and (tostring(k) == tostring(id) or v.Name == id) then
- return v, self.CharGlobal.MOBS[k]
- end
- end
- return false, nil
-end
-
----@param mode KillTrackMobSortMode|integer?
----@param filter string?
----@param caseSensitive boolean|nil
----@return { Id: integer, Name: string, gKills: integer, cKills: integer }[]
-function KT:GetSortedMobTable(mode, filter, caseSensitive)
- if not tonumber(mode) then mode = self.Sort.Desc end
- if mode < 0 or mode > 7 then mode = self.Sort.Desc end
- if filter and filter == "" then filter = nil end
- local t = {}
- for k,v in pairs(self.Global.MOBS) do
- assert(type(v) == "table", "Unexpected mob entry type in db: " .. type(v) .. ". Expected table")
- local matches = nil
- if filter then
- local name = caseSensitive and v.Name or v.Name:lower()
- filter = caseSensitive and filter or filter:lower()
- local status, result = pcall(string.match, name, filter)
- matches = status and result
- end
- if matches or not filter then
- local cKills = 0
- if self.CharGlobal.MOBS[k] and type(self.CharGlobal.MOBS[k]) == "table" then
- cKills = self.CharGlobal.MOBS[k].Kills
- end
- local entry = {Id = k, Name = v.Name, gKills = v.Kills, cKills = cKills}
- table.insert(t, entry)
- end
- end
- local function compare(a, b)
- if mode == self.Sort.Asc then
- return a.gKills < b.gKills
- elseif mode == self.Sort.CharDesc then
- return a.cKills > b.cKills
- elseif mode == self.Sort.CharAsc then
- return a.cKills < b.cKills
- elseif mode == self.Sort.AlphaD then
- return a.Name > b.Name
- elseif mode == self.Sort.AlphaA then
- return a.Name < b.Name
- elseif mode == self.Sort.IdDesc then
- return a.Id > b.Id
- elseif mode == self.Sort.IdAsc then
- return a.Id < b.Id
- else
- return a.gKills > b.gKills -- Descending
- end
- end
- table.sort(t, compare)
- return t
-end
-
----@param id integer|string
----@param charOnly boolean?
-function KT:Delete(id, charOnly)
- id = tonumber(id) --[[@as integer]]
- if not id then error(("Expected 'id' param to be number, got %s."):format(type(id))) end
- local found = false
- local name
- if self.Global.MOBS[id] then
- name = self.Global.MOBS[id].Name
- if not charOnly then self.Global.MOBS[id] = nil end
- if self.CharGlobal.MOBS[id] then
- self.CharGlobal.MOBS[id] = nil
- end
- found = true
- end
- if found then
- self:Msg(("Deleted %q (%d) from database."):format(name, id))
- StaticPopup_Show("KILLTRACK_FINISH", 1)
- else
- self:Msg(("ID: %d was not found in the database."):format(id))
- end
-end
-
----@param threshold integer
-function KT:Purge(threshold)
- local count = 0
- for k,v in pairs(self.Global.MOBS) do
- if type(v) == "table" and v.Kills < threshold then
- self.Global.MOBS[k] = nil
- count = count + 1
- end
- end
- for k,v in pairs(self.CharGlobal.MOBS) do
- if type(v) == "table" and v.Kills < threshold then
- self.CharGlobal.MOBS[k] = nil
- count = count + 1
- end
- end
- self:Msg(("Purged %d entries with a kill count below %d"):format(count, threshold))
- self.Temp.Threshold = nil
- StaticPopup_Show("KILLTRACK_FINISH", tostring(count))
-end
-
-function KT:Reset()
- local count = #self.Global.MOBS + #self.CharGlobal.MOBS
- wipe(self.Global.MOBS)
- wipe(self.CharGlobal.MOBS)
- self:Msg(("%d mob entries have been removed!"):format(count))
- StaticPopup_Show("KILLTRACK_FINISH", tostring(count))
-end
-
-function KT:ResetAchievCount()
- for _,v in pairs(self.Global.MOBS) do
- v.AchievCount = floor(v.Kills / self.Global.ACHIEV_THRESHOLD)
- end
-end
-
-KT.Frame = CreateFrame("Frame")
-
-for k,_ in pairs(KT.Events) do
- KT.Frame:RegisterEvent(k)
-end
-
-KT.Frame:SetScript("OnEvent", function(_, event, ...) KT:OnEvent(_, event, ...) end)
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@type string
+local NAME = ...
+
+---@class KillTrack
+---@field PlayerName string
+---@field PlayerGUID string?
+local KT = select(2, ...)
+
+---@class KillTrackMobData
+---@field Kills integer
+---@field Name string
+---@field LastKillAt integer?
+---@field AchievCount integer
+---@field Exp integer?
+
+---@class KillTrackCharMobData
+---@field Kills integer
+---@field Name string
+---@field LastKillAt integer?
+
+_G[NAME] = KT
+
+-- Upvalue some functions used in CLEU
+local IsGUIDInGroup = IsGUIDInGroup
+local UnitGUID = UnitGUID
+local UnitIsTapDenied = UnitIsTapDenied
+local UnitTokenFromGUID = UnitTokenFromGUID
+local CombatLogGetCurrentEventInfo = CombatLogGetCurrentEventInfo
+local GetServerTime = GetServerTime
+
+local NO_NAME = ""
+
+KT.Name = NAME
+KT.Version = C_AddOns.GetAddOnMetadata(NAME, "Version")
+KT.Events = {}
+
+---@class KillTrackImmediatePosition
+---@field POINT string?
+---@field RELATIVE string?
+---@field X number?
+---@field Y number?
+
+---@class KillTrackGlobal
+---@field LOAD_MESSAGE boolean
+---@field PRINTKILLS boolean
+---@field PRINTNEW boolean
+---@field ACHIEV_THRESHOLD integer
+---@field COUNT_GROUP boolean
+---@field SHOW_EXP boolean
+---@field MOBS { [integer]: KillTrackMobData }
+---@field IMMEDIATE { POSITION: KillTrackImmediatePosition, THRESHOLD: integer, FILTER: string? }
+---@field BROKER { SHORT_TEXT: boolean, MINIMAP: { hide: boolean } }
+---@field DISABLE_DUNGEONS boolean
+---@field DISABLE_RAIDS boolean
+---@field TOOLTIP boolean
+---@field DATETIME_FORMAT string
+KT.Global = {}
+
+---@class KillTrackCharGlobal
+---@field MOBS { [integer]: KillTrackCharMobData }
+KT.CharGlobal = {}
+
+---@class KillTrackTemp
+---@field Threshold integer?
+---@field DeleteId integer?
+KT.Temp = {}
+
+---@enum KillTrackMobSortMode
+KT.Sort = {
+ Desc = 0,
+ Asc = 1,
+ CharDesc = 2,
+ CharAsc = 3,
+ AlphaD = 4,
+ AlphaA = 5,
+ IdDesc = 6,
+ IdAsc = 7
+}
+KT.Session = {
+ Count = 0,
+ ---@type { [string]: integer? }
+ Kills = {}
+}
+KT.Messages = {
+ Announce = "[KillTrack] Session Length: %s. Session Kills: %d. Kills Per Minute: %.2f."
+}
+
+KT.Defaults = {
+ DateTimeFormat = "%Y-%m-%d %H:%M:%S"
+}
+
+---@type KillTrackExpTracker
+local ET
+
+local KTT = KT.Tools
+
+-- Upvalue as it's used in CLEU
+local GUIDToID = KTT.GUIDToID
+
+---@type { [string]: string? }
+local FirstDamage = {} -- Tracks first damage to a mob registered by CLEU
+
+---@type { [string]: string? }
+local LastDamage = {} -- Tracks whoever did the most recent damage to a mob
+
+---@type { [string]: boolean? }
+local HasPlayerDamage = {} -- Tracks whether the player (or pet) has dealt damage to a mob
+
+---@type { [string]: boolean? }
+local HasGroupDamage = {} -- Tracks whether a group member has dealt damage to a mob
+
+---@type { [string]: boolean? }
+local DamageValid = {} -- Determines if mob is tapped by player/group
+
+local combat_log_damage_events = {}
+do
+ local prefixes = { "SWING", "RANGE", "SPELL", "SPELL_PERIODIC", "SPELL_BUILDING" }
+ local suffixes = { "DAMAGE", "DRAIN", "LEECH", "INSTAKILL" }
+ for _, prefix in pairs(prefixes) do
+ for _, suffix in pairs(suffixes) do
+ combat_log_damage_events[prefix .. "_" .. suffix] = true
+ end
+ end
+end
+
+if KT.Version == "@" .. "project-version" .. "@" then
+ KT.Version = "Development"
+ KT.Debug = true
+end
+
+if not UnitTokenFromGUID then
+ local units = {
+ "player",
+ "vehicle",
+ "pet",
+ "party1", "party2", "party3", "party4",
+ "partypet1", "partypet2", "partypet3", "partypet4"
+ }
+ -- Multiple loops to get the same ordering as mainline API
+ for i = 1, 40 do
+ units[#units + 1] = "raid" .. i
+ end
+ for i = 1, 40 do
+ units[#units + 1] = "raidpet" .. i
+ end
+ for i = 1, 40 do
+ units[#units + 1] = "nameplate" .. i
+ end
+ for i = 1, 5 do
+ units[#units + 1] = "arena" .. i
+ end
+ for i = 1, 5 do
+ units[#units + 1] = "arenapet" .. i
+ end
+ for i = 1, 8 do
+ units[#units + 1] = "boss" .. i
+ end
+ units[#units + 1] = "target"
+ units[#units + 1] = "focus"
+ units[#units + 1] = "npc"
+ units[#units + 1] = "mouseover"
+ units[#units + 1] = "softenemy"
+ units[#units + 1] = "softfriend"
+ units[#units + 1] = "softinteract"
+
+ UnitTokenFromGUID = function(guid)
+ for _, unit in ipairs(units) do
+ if UnitGUID(unit) == guid then
+ return unit
+ end
+ end
+ return nil
+ end
+end
+
+---@param _ Frame
+---@param event string
+---@param ... any
+function KT:OnEvent(_, event, ...)
+ if self.Events[event] then
+ self.Events[event](self, ...)
+ end
+end
+
+---@param self KillTrack
+---@param name string
+function KT.Events.ADDON_LOADED(self, name)
+ if name ~= NAME then return end
+ ET = KT.ExpTracker
+ if type(_G["KILLTRACK"]) ~= "table" then
+ _G["KILLTRACK"] = {}
+ end
+ self.Global = _G["KILLTRACK"]
+ if type(self.Global.LOAD_MESSAGE) ~= "boolean" then
+ self.Global.LOAD_MESSAGE = false
+ end
+ if type(self.Global.PRINTKILLS) ~= "boolean" then
+ self.Global.PRINTKILLS = false
+ end
+ if type(self.Global.PRINTNEW) ~= "boolean" then
+ self.Global.PRINTNEW = false
+ end
+ if type(self.Global.ACHIEV_THRESHOLD) ~= "number" then
+ self.Global.ACHIEV_THRESHOLD = 1000
+ end
+ if type(self.Global.COUNT_GROUP) ~= "boolean" then
+ self.Global.COUNT_GROUP = false
+ end
+ if type(self.Global.SHOW_EXP) ~= "boolean" then
+ self.Global.SHOW_EXP = false
+ end
+ if type(self.Global.MOBS) ~= "table" then
+ self.Global.MOBS = {}
+ end
+ if type(self.Global.IMMEDIATE) ~= "table" then
+ self.Global.IMMEDIATE = {}
+ end
+ if type(self.Global.IMMEDIATE.POSITION) ~= "table" then
+ self.Global.IMMEDIATE.POSITION = {}
+ end
+ if type(self.Global.IMMEDIATE.THRESHOLD) ~= "number" then
+ self.Global.IMMEDIATE.THRESHOLD = 0
+ end
+ if type(self.Global.BROKER) ~= "table" then
+ self.Global.BROKER = {}
+ end
+ if type(self.Global.BROKER.SHORT_TEXT) ~= "boolean" then
+ self.Global.BROKER.SHORT_TEXT = false
+ end
+ if type(self.Global.BROKER.MINIMAP) ~= "table" then
+ self.Global.BROKER.MINIMAP = {}
+ end
+ if type(self.Global.DISABLE_DUNGEONS) ~= "boolean" then
+ self.Global.DISABLE_DUNGEONS = false
+ end
+ if type(self.Global.DISABLE_RAIDS) ~= "boolean" then
+ self.Global.DISABLE_RAIDS = false
+ end
+ if type(self.Global.TOOLTIP) ~= "boolean" then
+ self.Global.TOOLTIP = true
+ end
+ if type(self.Global.DATETIME_FORMAT) ~= "string" then
+ self.Global.DATETIME_FORMAT = self.Defaults.DateTimeFormat
+ end
+ if type(_G["KILLTRACK_CHAR"]) ~= "table" then
+ _G["KILLTRACK_CHAR"] = {}
+ end
+ self.CharGlobal = _G["KILLTRACK_CHAR"]
+ if type(self.CharGlobal.MOBS) ~= "table" then
+ self.CharGlobal.MOBS = {}
+ end
+ self.PlayerName = UnitName("player")
+ self.PlayerGUID = UnitGUID("player")
+
+ if self.Global.LOAD_MESSAGE then
+ self:Msg("AddOn Loaded!")
+ end
+
+ self.Session.Start = time()
+ self.Broker:OnLoad()
+end
+
+---@param self KillTrack
+function KT.Events.COMBAT_LOG_EVENT_UNFILTERED(self)
+ local _, event, _, s_guid, _, _, _, d_guid, d_name, _, _ = CombatLogGetCurrentEventInfo()
+ if combat_log_damage_events[event] then
+ if FirstDamage[d_guid] == nil then
+ -- s_guid is (probably) the player who first damaged this mob and probably has the tag
+ FirstDamage[d_guid] = s_guid
+ end
+
+ LastDamage[d_guid] = s_guid
+
+ if s_guid == self.PlayerGUID or s_guid == UnitGUID("pet") then
+ HasPlayerDamage[d_guid] = true
+ elseif self:IsInGroup(s_guid) then
+ HasGroupDamage[d_guid] = true
+ end
+
+ if not DamageValid[d_guid] then
+ -- if DamageValid returns true for a GUID, we can tell with 100% certainty that it's valid
+ -- But this relies on one of the valid unit names currently being the damaged mob
+
+ local d_unit = UnitTokenFromGUID(d_guid)
+
+ if not d_unit then return end
+
+ DamageValid[d_guid] = not UnitIsTapDenied(d_unit)
+ end
+
+ return
+ end
+
+ if event ~= "UNIT_DIED" then return end
+
+ -- Perform solo/group checks
+ local d_id = GUIDToID(d_guid)
+ local firstDamage = FirstDamage[d_guid]
+ local lastDamage = LastDamage[d_guid]
+ local damageValid = DamageValid[d_guid]
+ local hasPlayerDamage = HasPlayerDamage[d_guid]
+ local hasGroupDamage = HasGroupDamage[d_guid]
+ FirstDamage[d_guid] = nil
+ LastDamage[d_guid] = nil
+ DamageValid[d_guid] = nil
+ HasPlayerDamage[d_guid] = nil
+ HasGroupDamage[d_guid] = nil
+
+ -- If we can't identify the mob there's no point continuing
+ if d_id == nil or d_id == 0 then return end
+
+ local petGUID = UnitGUID("pet")
+ local firstByPlayer = firstDamage == self.PlayerGUID or firstDamage == petGUID
+ local firstByGroup = self:IsInGroup(firstDamage)
+ local lastByPlayer = lastDamage == self.PlayerGUID or lastDamage == petGUID
+
+ -- The checks when DamageValid is non-false are not 100% failsafe
+ -- Scenario: You deal the killing blow to an already tapped mob <- Would count as kill with current code
+
+ -- If damageValid is false, it means tap is denied on the mob and there is no way the kill would count
+ if damageValid == false then return end
+
+ -- If neither the player not group was involved in the battle, we don't count the kill
+ if not hasPlayerDamage and not hasGroupDamage then return end
+
+ if damageValid == nil and not firstByPlayer and not firstByGroup then
+ -- If we couldn't get a proper tapped status and the first recorded damage was not by player or group,
+ -- we can't be sure the kill was valid, so ignore it
+ return
+ end
+
+ if not lastByPlayer and not self.Global.COUNT_GROUP then
+ return -- Player or player's pet did not deal the killing blow and addon only tracks player kills
+ end
+
+ self:AddKill(d_id, d_name)
+ if self.Timer:IsRunning() then
+ self.Timer:SetData("Kills", self.Timer:GetData("Kills", true) + 1)
+ end
+end
+
+---@param _ KillTrack
+---@param message string
+function KT.Events.CHAT_MSG_COMBAT_XP_GAIN(_, message)
+ ET:CheckMessage(message)
+end
+
+---@param self KillTrack
+---@param size integer
+function KT.Events.ENCOUNTER_START(self, _, _, _, size)
+ if (self.Global.DISABLE_DUNGEONS and size == 5) or (self.Global.DISABLE_RAIDS and size > 5) then
+ self.Frame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
+ self.Frame:UnregisterEvent("UPDATE_MOUSEOVER_UNIT")
+ self.Frame:UnregisterEvent("CHAT_MSG_COMBAT_XP_GAIN")
+ end
+end
+
+---@param self KillTrack
+---@param size integer
+function KT.Events.ENCOUNTER_END(self, _, _, _, size)
+ if (self.Global.DISABLE_DUNGEONS and size == 5) or (self.Global.DISABLE_RAIDS and size > 5) then
+ self.Frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
+ self.Frame:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
+ self.Frame:RegisterEvent("CHAT_MSG_COMBAT_XP_GAIN")
+ end
+end
+
+---@param self GameTooltip
+local function tooltip_enhancer(self)
+ if not KT.Global.TOOLTIP then return end
+ local _, unit = self:GetUnit()
+ if not unit then return end
+ if UnitIsPlayer(unit) then return end
+ local id = KTT.GUIDToID(UnitGUID(unit))
+ if not id then return end
+ if UnitCanAttack("player", unit) then
+ local mob, charMob = KT:InitMob(id, UnitName(unit))
+ local gKills, cKills = mob.Kills, charMob.Kills --self:GetKills(id)
+ local exp = mob.Exp
+ self:AddLine(("Killed %d (%d) times."):format(cKills, gKills), 1, 1, 1)
+ if KT.Global.SHOW_EXP and exp then
+ local toLevel = exp > 0 and math.ceil((UnitXPMax("player") - UnitXP("player")) / exp) or "N/A"
+ self:AddLine(("EXP: %d (%s kills to level)"):format(exp, toLevel), 1, 1, 1)
+ end
+ end
+ if KT.Debug then
+ self:AddLine(("ID = %d"):format(id))
+ end
+ self:Show()
+end
+
+if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then
+ TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Unit, tooltip_enhancer)
+else
+ GameTooltip:HookScript("OnTooltipSetUnit", tooltip_enhancer)
+end
+
+function KT:ToggleLoadMessage()
+ self.Global.LOAD_MESSAGE = not self.Global.LOAD_MESSAGE
+ if self.Global.LOAD_MESSAGE then
+ self:Msg("Now showing message on AddOn load")
+ else
+ self:Msg("No longer showing message on AddOn load")
+ end
+end
+
+function KT:ToggleExp()
+ self.Global.SHOW_EXP = not self.Global.SHOW_EXP
+ if self.Global.SHOW_EXP then
+ self:Msg("Now showing EXP on tooltips!")
+ else
+ self:Msg("No longer showing EXP on tooltips.")
+ end
+end
+
+function KT:ToggleDebug()
+ self.Debug = not self.Debug
+ if self.Debug then
+ self:Msg("Debug enabled!")
+ else
+ self:Msg("Debug disabled!")
+ end
+end
+
+---@param guid string?
+---@return boolean
+function KT:IsInGroup(guid)
+ if not guid or guid == "" then return false end
+ if guid == self.PlayerName or guid == self.PlayerGUID then return true end
+ return IsGUIDInGroup(guid)
+end
+
+---@param threshold integer
+function KT:SetThreshold(threshold)
+ if type(threshold) ~= "number" then
+ error("KillTrack.SetThreshold: Argument #1 (threshold) must be of type 'number'")
+ end
+ self.Global.ACHIEV_THRESHOLD = threshold
+ if threshold > 0 then
+ self:ResetAchievCount()
+ self:Msg(("New kill notice (achievement) threshold set to %d."):format(threshold))
+ else
+ self:Msg("Kill notices have been disabled (set threshold to a value greater than 0 to re-enable).")
+ end
+end
+
+---@param threshold integer
+function KT:SetImmediateThreshold(threshold)
+ if type(threshold) ~= "number" then
+ error("KillTrack.SetImmediateThreshold: Argument #1 (threshold) must be of type 'number'")
+ end
+ self.Global.IMMEDIATE.THRESHOLD = threshold
+ if threshold > 0 then
+ self:Msg(("New immediate threshold set to %d."):format(threshold))
+ else
+ self:Msg("Immediate threshold disabled.")
+ end
+end
+
+---@param filter string
+function KT:SetImmediateFilter(filter)
+ if type(filter) ~= "string" then
+ error("KillTrack.SetImmediateFilter: Argument #1 (filter) must be of type 'string'")
+ end
+ self.Global.IMMEDIATE.FILTER = filter
+ self:Msg("New immediate filter set to: " .. filter)
+end
+
+function KT:ClearImmediateFilter()
+ self.Global.IMMEDIATE.FILTER = nil
+ KT:Msg("Immediate filter cleared!")
+end
+
+function KT:ToggleCountMode()
+ self.Global.COUNT_GROUP = not self.Global.COUNT_GROUP
+ if self.Global.COUNT_GROUP then
+ self:Msg("Now counting kills for every player in the group (party/raid)!")
+ else
+ self:Msg("Now counting your own killing blows ONLY.")
+ end
+end
+
+---@param id integer
+---@param name string?
+---@return KillTrackMobData
+---@return KillTrackCharMobData
+function KT:InitMob(id, name)
+ name = name or NO_NAME
+
+ if type(self.Global.MOBS[id]) ~= "table" then
+ self.Global.MOBS[id] = { Name = name, Kills = 0, AchievCount = 0 }
+ if self.Global.PRINTNEW then
+ self:Msg(("Created new entry for %q"):format(name))
+ end
+ elseif self.Global.MOBS[id].Name ~= name then
+ self.Global.MOBS[id].Name = name
+ end
+
+ if type(self.CharGlobal.MOBS[id]) ~= "table" then
+ self.CharGlobal.MOBS[id] = { Name = name, Kills = 0 }
+ if self.Global.PRINTNEW then
+ self:Msg(("Created new entry for %q on this character."):format(name))
+ end
+ elseif self.CharGlobal.MOBS[id].Name ~= name then
+ self.CharGlobal.MOBS[id].Name = name
+ end
+
+ return self.Global.MOBS[id], self.CharGlobal.MOBS[id]
+end
+
+---@param id integer
+---@param name string?
+function KT:AddKill(id, name)
+ name = name or NO_NAME
+ local current_time = GetServerTime()
+ self:InitMob(id, name)
+ local globalMob = self.Global.MOBS[id]
+ local charMob = self.CharGlobal.MOBS[id]
+ globalMob.Kills = self.Global.MOBS[id].Kills + 1
+ globalMob.LastKillAt = current_time
+ charMob.Kills = self.CharGlobal.MOBS[id].Kills + 1
+ charMob.LastKillAt = current_time
+ if self.Global.PRINTKILLS then
+ local kills = self.Global.MOBS[id].Kills
+ local cKills = self.CharGlobal.MOBS[id].Kills
+ self:Msg(("Updated %q, new kill count: %d. Kill count on this character: %d"):format(name, kills, cKills))
+ end
+ self:AddSessionKill(name)
+ if self.Immediate.Active then
+ local filter = self.Global.IMMEDIATE.FILTER
+ local filterPass = not filter or name:match(filter)
+ if filterPass then
+ self.Immediate:AddKill()
+ if self.Global.IMMEDIATE.THRESHOLD > 0 and self.Immediate.Kills % self.Global.IMMEDIATE.THRESHOLD == 0 then
+ PlaySound(SOUNDKIT.RAID_WARNING)
+ PlaySound(SOUNDKIT.PVP_THROUGH_QUEUE)
+ RaidNotice_AddMessage(
+ RaidWarningFrame,
+ ("%d KILLS!"):format(self.Immediate.Kills),
+ ChatTypeInfo.SYSTEM)
+ end
+ end
+ end
+ if self.Global.ACHIEV_THRESHOLD <= 0 then return end
+ if type(self.Global.MOBS[id].AchievCount) ~= "number" then
+ self.Global.MOBS[id].AchievCount = floor(self.Global.MOBS[id].Kills / self.Global.ACHIEV_THRESHOLD)
+ if self.Global.MOBS[id].AchievCount >= 1 then
+ self:KillAlert(self.Global.MOBS[id])
+ end
+ else
+ local achievCount = self.Global.MOBS[id].AchievCount
+ self.Global.MOBS[id].AchievCount = floor(self.Global.MOBS[id].Kills / self.Global.ACHIEV_THRESHOLD)
+ if self.Global.MOBS[id].AchievCount > achievCount then
+ self:KillAlert(self.Global.MOBS[id])
+ end
+ end
+end
+
+---@param id integer
+---@param name string?
+---@param globalCount integer
+---@param charCount integer
+function KT:SetKills(id, name, globalCount, charCount)
+ if type(id) ~= "number" then
+ error("'id' argument must be a number")
+ end
+
+ if type(globalCount) ~= "number" then
+ error("'globalCount' argument must be a number")
+ end
+
+ if type(charCount) ~= "number" then
+ error("'charCount' argument must be a number")
+ end
+
+ name = name or NO_NAME
+
+ self:InitMob(id, name)
+ self.Global.MOBS[id].Kills = globalCount
+ self.CharGlobal.MOBS[id].Kills = charCount
+
+ self:Msg(("Updated %q to %d global and %d character kills"):format(name, globalCount, charCount))
+end
+
+---@param name string
+function KT:AddSessionKill(name)
+ if self.Session.Kills[name] then
+ self.Session.Kills[name] = self.Session.Kills[name] + 1
+ else
+ self.Session.Kills[name] = 1
+ end
+ self.Session.Count = self.Session.Count + 1
+end
+
+---@param name string
+---@param exp integer|string
+function KT:SetExp(name, exp)
+ for _, mob in pairs(self.Global.MOBS) do
+ if mob.Name == name then mob.Exp = tonumber(exp) end
+ end
+end
+
+---@param max integer|string?
+---@return { Name: string, Kills: integer }[]
+function KT:GetSortedSessionKills(max)
+ max = tonumber(max) or 3
+ local t = {}
+ for k,v in pairs(self.Session.Kills) do
+ t[#t + 1] = {Name = k, Kills = v}
+ end
+ table.sort(t, function(a, b) return a.Kills > b.Kills end)
+ -- Trim table to only contain 3 entries
+ local trimmed = {}
+ local c = 0
+ for i,v in ipairs(t) do
+ trimmed[i] = v
+ c = c + 1
+ if c >= max then break end
+ end
+ return trimmed
+end
+
+function KT:ResetSession()
+ wipe(self.Session.Kills)
+ self.Session.Count = 0
+ self.Session.Start = time()
+end
+
+---@param id integer
+---@return integer globalKills
+---@return integer charKills
+function KT:GetKills(id)
+ local gKills, cKills = 0, 0
+ local mob = self.Global.MOBS[id]
+ if type(mob) == "table" then
+ gKills = mob.Kills
+ local cMob = self.CharGlobal.MOBS[id]
+ if type(cMob) == "table" then
+ cKills = cMob.Kills
+ end
+ end
+ return gKills, cKills
+end
+
+---@return integer
+function KT:GetTotalKills()
+ local count = 0
+ for _, mob in pairs(self.Global.MOBS) do
+ count = count + mob.Kills
+ end
+ return count
+end
+
+---@return integer killsPerSecond
+---@return integer killsPerMinute
+---@return integer killsPerHour
+---@return integer killsThisSession
+function KT:GetSessionStats()
+ if not self.Session.Start then return 0, 0, 0, 0 end
+ local now = time()
+ local session = now - self.Session.Start
+ local kps = session == 0 and 0 or self.Session.Count / session
+ local kpm = kps * 60
+ local kph = kpm * 60
+ return kps, kpm, kph, session
+end
+
+---@param identifier string|integer?
+function KT:PrintKills(identifier)
+ local found = false
+ local name = NO_NAME
+ local gKills = 0
+ local cKills = 0
+ local lastKillAt = nil
+ if type(identifier) ~= "string" and type(identifier) ~= "number" then identifier = NO_NAME end
+ for k,v in pairs(self.Global.MOBS) do
+ if type(v) == "table" and (tostring(k) == tostring(identifier) or v.Name == identifier) then
+ name = v.Name
+ gKills = v.Kills
+ if type(v.LastKillAt) == "number" then
+ lastKillAt = KTT:FormatDateTime(v.LastKillAt)
+ end
+ if self.CharGlobal.MOBS[k] then
+ cKills = self.CharGlobal.MOBS[k].Kills
+ end
+ found = true
+ end
+ end
+ if found then
+ self:Msg(("You have killed %q %d times in total, %d times on this character"):format(name, gKills, cKills))
+ if lastKillAt then
+ self:Msg(("Last killed at %s"):format(lastKillAt))
+ end
+ else
+ if UnitExists("target") and not UnitIsPlayer("target") then
+ identifier = UnitName("target")
+ end
+ self:Msg(("Unable to find %q in mob database."):format(tostring(identifier)))
+ end
+end
+
+---@param target string
+function KT:Announce(target)
+ if target == "GROUP" then
+ target = ((IsInRaid() and "RAID") or (IsInGroup() and "PARTY")) or "SAY"
+ end
+ local _, kpm, _, length = self:GetSessionStats()
+ local msg = self.Messages.Announce:format(KTT:FormatSeconds(length), self.Session.Count, kpm)
+ SendChatMessage(msg, target)
+end
+
+---@param msg string
+function KT:Msg(msg)
+ DEFAULT_CHAT_FRAME:AddMessage("\124cff00FF00[KillTrack]\124r " .. msg)
+end
+
+---@param msg string
+function KT:DebugMsg(msg)
+ if not self.Debug then return end
+ self:Msg("[DEBUG] " .. msg)
+end
+
+---@param mob KillTrackMobData
+function KT:KillAlert(mob)
+ local data = {
+ Text = ("%d kills on %s!"):format(mob.Kills, mob.Name),
+ Title = "Kill Record!",
+ bTitle = "Congratulations!",
+ Icon = "Interface\\Icons\\ABILITY_Deathwing_Bloodcorruption_Death",
+ FrameStyle = "GuildAchievement"
+ }
+ if C_AddOns.IsAddOnLoaded("Glamour") then
+ if not _G["GlamourShowAlert"] then
+ self:Msg("ERROR: GlamourShowAlert == nil! Notify AddOn developer.")
+ return
+ end
+ _G.GlamourShowAlert(500, data)
+ else
+ RaidNotice_AddMessage(RaidBossEmoteFrame, data.Text, ChatTypeInfo.SYSTEM)
+ RaidNotice_AddMessage(RaidBossEmoteFrame, data.Text, ChatTypeInfo.SYSTEM)
+ end
+ self:Msg(data.Text)
+end
+
+---@param id integer|string
+---@return KillTrackMobData|false
+---@return KillTrackCharMobData|nil
+function KT:GetMob(id)
+ for k,v in pairs(self.Global.MOBS) do
+ if type(v) == "table" and (tostring(k) == tostring(id) or v.Name == id) then
+ return v, self.CharGlobal.MOBS[k]
+ end
+ end
+ return false, nil
+end
+
+---@param mode KillTrackMobSortMode|integer?
+---@param filter string?
+---@param caseSensitive boolean|nil
+---@return { Id: integer, Name: string, gKills: integer, cKills: integer }[]
+function KT:GetSortedMobTable(mode, filter, caseSensitive)
+ if not tonumber(mode) then mode = self.Sort.Desc end
+ if mode < 0 or mode > 7 then mode = self.Sort.Desc end
+ if filter and filter == "" then filter = nil end
+ local t = {}
+ for k,v in pairs(self.Global.MOBS) do
+ assert(type(v) == "table", "Unexpected mob entry type in db: " .. type(v) .. ". Expected table")
+ local matches = nil
+ if filter then
+ local name = caseSensitive and v.Name or v.Name:lower()
+ filter = caseSensitive and filter or filter:lower()
+ local status, result = pcall(string.match, name, filter)
+ matches = status and result
+ end
+ if matches or not filter then
+ local cKills = 0
+ if self.CharGlobal.MOBS[k] and type(self.CharGlobal.MOBS[k]) == "table" then
+ cKills = self.CharGlobal.MOBS[k].Kills
+ end
+ local entry = {Id = k, Name = v.Name, gKills = v.Kills, cKills = cKills}
+ table.insert(t, entry)
+ end
+ end
+ local function compare(a, b)
+ if mode == self.Sort.Asc then
+ return a.gKills < b.gKills
+ elseif mode == self.Sort.CharDesc then
+ return a.cKills > b.cKills
+ elseif mode == self.Sort.CharAsc then
+ return a.cKills < b.cKills
+ elseif mode == self.Sort.AlphaD then
+ return a.Name > b.Name
+ elseif mode == self.Sort.AlphaA then
+ return a.Name < b.Name
+ elseif mode == self.Sort.IdDesc then
+ return a.Id > b.Id
+ elseif mode == self.Sort.IdAsc then
+ return a.Id < b.Id
+ else
+ return a.gKills > b.gKills -- Descending
+ end
+ end
+ table.sort(t, compare)
+ return t
+end
+
+---@param id integer|string
+---@param charOnly boolean?
+function KT:Delete(id, charOnly)
+ id = tonumber(id) --[[@as integer]]
+ if not id then error(("Expected 'id' param to be number, got %s."):format(type(id))) end
+ local found = false
+ local name
+ if self.Global.MOBS[id] then
+ name = self.Global.MOBS[id].Name
+ if not charOnly then self.Global.MOBS[id] = nil end
+ if self.CharGlobal.MOBS[id] then
+ self.CharGlobal.MOBS[id] = nil
+ end
+ found = true
+ end
+ if found then
+ self:Msg(("Deleted %q (%d) from database."):format(name, id))
+ StaticPopup_Show("KILLTRACK_FINISH", 1)
+ else
+ self:Msg(("ID: %d was not found in the database."):format(id))
+ end
+end
+
+---@param threshold integer
+function KT:Purge(threshold)
+ local count = 0
+ for k,v in pairs(self.Global.MOBS) do
+ if type(v) == "table" and v.Kills < threshold then
+ self.Global.MOBS[k] = nil
+ count = count + 1
+ end
+ end
+ for k,v in pairs(self.CharGlobal.MOBS) do
+ if type(v) == "table" and v.Kills < threshold then
+ self.CharGlobal.MOBS[k] = nil
+ count = count + 1
+ end
+ end
+ self:Msg(("Purged %d entries with a kill count below %d"):format(count, threshold))
+ self.Temp.Threshold = nil
+ StaticPopup_Show("KILLTRACK_FINISH", tostring(count))
+end
+
+function KT:Reset()
+ local count = #self.Global.MOBS + #self.CharGlobal.MOBS
+ wipe(self.Global.MOBS)
+ wipe(self.CharGlobal.MOBS)
+ self:Msg(("%d mob entries have been removed!"):format(count))
+ StaticPopup_Show("KILLTRACK_FINISH", tostring(count))
+end
+
+function KT:ResetAchievCount()
+ for _,v in pairs(self.Global.MOBS) do
+ v.AchievCount = floor(v.Kills / self.Global.ACHIEV_THRESHOLD)
+ end
+end
+
+KT.Frame = CreateFrame("Frame")
+
+for k,_ in pairs(KT.Events) do
+ KT.Frame:RegisterEvent(k)
+end
+
+KT.Frame:SetScript("OnEvent", function(_, event, ...) KT:OnEvent(_, event, ...) end)
diff --git a/MobList.lua b/MobList.lua
index b805a97..29ae418 100644
--- a/MobList.lua
+++ b/MobList.lua
@@ -1,476 +1,476 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
--- Beware of some possibly messy code in this file
-
----@class KillTrack
-local KT = select(2, ...)
-
----@class KillTrackMobList
-local ML = {}
-
-KT.MobList = ML
-local KTT = KT.Tools
-
-local Sort = KT.Sort.Desc
----@type { Id: integer, Name: string, gKills: integer, cKills: integer }[]
-local Mobs = nil
-local LastFilter = nil
-local LastOffset = 0
-
--- Frame Constants
-local FRAME_WIDTH = 600
-local FRAME_HEIGHT = 534
-local HEADER_HEIGHT = 24
-local HEADER_LEFT = 3
-local HEADER_TOP = -80
-local ROW_HEIGHT = 15
-local ROW_COUNT = 27
-local ROW_TEXT_PADDING = 5
-local ID_WIDTH = 100
-local NAME_WIDTH = 300
-local CHAR_WIDTH = 100
-local GLOBAL_WIDTH = 100
-local SCROLL_WIDTH = 27 -- Scrollbar width
-local STATUS_TEXT = "Showing entries %d through %d out of %d total (%d hidden)"
-
----@type table|BackdropTemplate|Frame
-local frame = nil
-local created = false
-
--- Frame helper functions
-
----@param parent table|Frame
----@return Button
-local function CreateHeader(parent)
- local h = CreateFrame("Button", nil, parent)
- h:SetHeight(HEADER_HEIGHT)
- h:SetNormalFontObject("GameFontHighlightSmall")
-
- local bgl = h:CreateTexture(nil, "BACKGROUND")
- bgl:SetTexture("Interface\\FriendsFrame\\WhoFrame-ColumnTabs")
- bgl:SetWidth(5)
- bgl:SetHeight(HEADER_HEIGHT)
- bgl:SetPoint("TOPLEFT")
- bgl:SetTexCoord(0, 0.07815, 0, 0.75)
-
- local bgr = h:CreateTexture(nil, "BACKGROUND")
- bgr:SetTexture("Interface\\FriendsFrame\\WhoFrame-ColumnTabs")
- bgr:SetWidth(5)
- bgr:SetHeight(HEADER_HEIGHT)
- bgr:SetPoint("TOPRIGHT")
- bgr:SetTexCoord(0.90625, 0.96875, 0, 0.75)
-
- local bgm = h:CreateTexture(nil, "BACKGROUND")
- bgm:SetTexture("Interface\\FriendsFrame\\WhoFrame-ColumnTabs")
- bgm:SetHeight(HEADER_HEIGHT)
- bgm:SetPoint("LEFT", bgl, "RIGHT")
- bgm:SetPoint("RIGHT", bgr, "LEFT")
- bgm:SetTexCoord(0.07815, 0.90625, 0, 0.75)
-
- local hl = h:CreateTexture()
- h:SetHighlightTexture("Interface\\PaperDollInfoFrame\\UI-Character-Tab-Highlight", "ADD")
- hl:SetPoint("TOPLEFT", bgl, "TOPLEFT", -2, 5)
- hl:SetPoint("BOTTOMRIGHT", bgr, "BOTTOMRIGHT", 2, -7)
-
- return h
-end
-
----@param container table|Frame
----@param previous table|Frame
----@return Button
-local function CreateRow(container, previous)
- local row = CreateFrame("Button", nil, container)
- row:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight", "ADD")
- row:SetHeight(ROW_HEIGHT)
- row:SetPoint("LEFT")
- row:SetPoint("RIGHT")
- row:SetPoint("TOPLEFT", previous, "BOTTOMLEFT", 0, 0)
-
- ---@diagnostic disable-next-line: inject-field
- row.idField = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
- row.idField:SetHeight(ROW_HEIGHT)
- row.idField:SetWidth(ID_WIDTH - ROW_TEXT_PADDING * 2)
- row.idField:SetPoint("LEFT", row, "LEFT", ROW_TEXT_PADDING, 0)
- row.idField:SetJustifyH("RIGHT")
-
- ---@diagnostic disable-next-line: inject-field
- row.nameField = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
- row.nameField:SetHeight(ROW_HEIGHT)
- row.nameField:SetWidth(NAME_WIDTH - SCROLL_WIDTH - ROW_TEXT_PADDING * 3)
- row.nameField:SetPoint("LEFT", row.idField, "RIGHT", 2 * ROW_TEXT_PADDING, 0)
- row.nameField:SetJustifyH("LEFT")
-
- ---@diagnostic disable-next-line: inject-field
- row.charKillField = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
- row.charKillField:SetHeight(ROW_HEIGHT)
- row.charKillField:SetWidth(CHAR_WIDTH - ROW_TEXT_PADDING * 2)
- row.charKillField:SetPoint("LEFT", row.nameField, "RIGHT", 2 * ROW_TEXT_PADDING, 0)
- row.charKillField:SetJustifyH("RIGHT")
-
- ---@diagnostic disable-next-line: inject-field
- row.globalKillField = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
- row.globalKillField:SetHeight(ROW_HEIGHT)
- row.globalKillField:SetWidth(GLOBAL_WIDTH - ROW_TEXT_PADDING * 2)
- row.globalKillField:SetPoint("LEFT", row.charKillField, "RIGHT", 2 * ROW_TEXT_PADDING, 0)
- row.globalKillField:SetJustifyH("RIGHT")
-
- row:SetScript("OnClick", function(s)
- local id = tonumber(s.idField:GetText())
- if not id then return end
- local name = s.nameField:GetText()
- KT:ShowDelete(id, name)
- end)
-
- row:SetScript("OnEnter", function(s)
- local id = tonumber(s.idField:GetText())
- if not id then return end
- local globalData = KT.Global.MOBS[id]
- if not globalData then return end
- local killTimestamp = globalData.LastKillAt
- if not killTimestamp then return end
- local lastKillAt = KTT:FormatDateTime(killTimestamp)
- local tpString = ("Last killed at %s"):format(lastKillAt)
- GameTooltip:SetOwner(s, "ANCHOR_NONE")
- GameTooltip:SetPoint("TOPLEFT", s, "BOTTOMLEFT")
- GameTooltip:ClearLines()
- GameTooltip:AddLine(tpString)
- GameTooltip:Show()
- end)
-
- row:SetScript("OnLeave", function()
- GameTooltip:Hide()
- end)
-
- return row
-end
-
-function ML:Show()
- if not created then
- ML:Create()
- end
- if frame:IsShown() then return end
- frame:Show()
-end
-
-function ML:Hide()
- if not frame or not frame:IsShown() then return end
- frame:Hide()
-end
-
-function ML:Toggle()
- if frame and frame:IsShown() then
- ML:Hide()
- else
- ML:Show()
- end
-end
-
-function ML:Create()
- if frame then return end
- frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
- frame:Hide()
- frame:SetToplevel(true)
- frame:EnableMouse(true)
- frame:SetMovable(true)
- frame:SetPoint("CENTER")
- frame:SetWidth(FRAME_WIDTH)
- frame:SetHeight(FRAME_HEIGHT)
-
- local bd = {
- bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
- edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
- tile = true,
- edgeSize = 16,
- tileSize = 32,
- insets = {
- left = 2.5,
- right = 2.5,
- top = 2.5,
- bottom = 2.5
- }
- }
-
- frame:SetBackdrop(bd)
-
- ---@diagnostic disable-next-line: inject-field
- frame.titleLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
- frame.titleLabel:SetWidth(250)
- frame.titleLabel:SetHeight(16)
- frame.titleLabel:SetPoint("TOP", frame, "TOP", 0, -5)
- frame.titleLabel:SetText("KillTrack Mob Database (" .. KT.Version .. ")")
-
- frame:SetScript("OnMouseDown", function(s) s:StartMoving() end)
- frame:SetScript("OnMouseUp", function(s) s:StopMovingOrSizing() end)
- frame:SetScript("OnShow", function() ML:UpdateMobs(Sort, LastFilter) ML:UpdateEntries(LastOffset) end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
- frame.closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
- frame.closeButton:SetScript("OnClick", function() ML:Hide() end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.purgeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
- frame.purgeButton:SetHeight(24)
- frame.purgeButton:SetWidth(100)
- frame.purgeButton:SetPoint("TOPLEFT", frame, "TOPLEFT", 10, -5)
- frame.purgeButton:SetText("Purge Data")
- frame.purgeButton:SetScript("OnClick", function()
- KT:ShowPurge()
- end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.resetButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
- frame.resetButton:SetHeight(24)
- frame.resetButton:SetWidth(100)
- frame.resetButton:SetPoint("TOPLEFT", frame.purgeButton, "BOTTOMLEFT", 0, -3)
- frame.resetButton:SetText("Reset All")
- frame.resetButton:SetScript("OnClick", function()
- KT:ShowReset()
- end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.helpLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
- frame.helpLabel:SetWidth(380)
- frame.helpLabel:SetHeight(16)
- frame.helpLabel:SetPoint("TOP", frame, "TOP", 0, -28)
- frame.helpLabel:SetWordWrap(true)
- frame.helpLabel:SetMaxLines(2)
- frame.helpLabel:SetText(
- "Click on an individual entry to delete it from the database. Use the search to filter database by name.")
-
- ---@diagnostic disable-next-line: inject-field
- frame.searchBox = CreateFrame("EditBox", "KillTrackMobListSearchBox", frame, "SearchBoxTemplate")
- frame.searchBox:SetWidth(200)
- frame.searchBox:SetHeight(16)
- frame.searchBox:SetPoint("TOPLEFT", frame.resetButton, "BOTTOMLEFT", 8, -3)
- frame.searchBox:HookScript("OnTextChanged", function(s)
- local text = s:GetText()
- if (not _G[s:GetName() .. "ClearButton"]:IsShown()) then
- text = nil
- LastFilter = nil
- end
- if not text or text == "" then
- ML:UpdateMobs(Sort)
- else
- ML:UpdateMobs(Sort, text)
- end
- ML:UpdateEntries(LastOffset)
- end)
- frame.searchBox:SetScript("OnEnterPressed", function(s) s:ClearFocus() end)
- ---@diagnostic disable-next-line: inject-field
- frame.searchBox.clearButton = _G[frame.searchBox:GetName() .. "ClearButton"]
- local sBoxOldFunc = frame.searchBox.clearButton:GetScript("OnHide")
- frame.searchBox.clearButton:SetScript("OnHide", function(s)
- if sBoxOldFunc then sBoxOldFunc(s) end
- if not frame:IsShown() then return end
- ML:UpdateMobs(Sort, nil)
- ML:UpdateEntries(LastOffset)
- end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.searchTipLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
- frame.searchTipLabel:SetWidth(200)
- frame.searchTipLabel:SetHeight(16)
- frame.searchTipLabel:SetPoint("LEFT", frame.searchBox, "RIGHT", -22, 0)
- frame.searchTipLabel:SetText("(Supports Lua patterns)")
-
- ---@diagnostic disable-next-line: inject-field
- frame.idHeader = CreateHeader(frame)
- frame.idHeader:SetPoint("TOPLEFT", frame, "TOPLEFT", HEADER_LEFT, HEADER_TOP)
- frame.idHeader:SetWidth(ID_WIDTH)
- frame.idHeader:SetText("NPC ID")
- frame.idHeader:SetScript("OnClick", function()
- local sort = KT.Sort.IdAsc
- if Sort == sort then
- sort = KT.Sort.IdDesc
- end
- ML:UpdateMobs(sort, LastFilter)
- ML:UpdateEntries(LastOffset)
- end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.nameHeader = CreateHeader(frame)
- frame.nameHeader:SetPoint("TOPLEFT", frame.idHeader, "TOPRIGHT", -2, 0)
- frame.nameHeader:SetWidth(NAME_WIDTH - SCROLL_WIDTH)
- frame.nameHeader:SetText("Name")
- frame.nameHeader:SetScript("OnClick", function()
- local sort = KT.Sort.AlphaA
- if Sort == sort then
- sort = KT.Sort.AlphaD
- end
- ML:UpdateMobs(sort, LastFilter)
- ML:UpdateEntries(LastOffset)
- end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.charKillHeader = CreateHeader(frame)
- frame.charKillHeader:SetPoint("TOPLEFT", frame.nameHeader, "TOPRIGHT", -2, 0)
- frame.charKillHeader:SetWidth(CHAR_WIDTH)
- frame.charKillHeader:SetText("Character Kills")
- frame.charKillHeader:SetScript("OnClick", function()
- local sort = KT.Sort.CharDesc
- if Sort == sort then
- sort = KT.Sort.CharAsc
- end
- ML:UpdateMobs(sort, LastFilter)
- ML:UpdateEntries(LastOffset)
- end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.globalKillHeader = CreateHeader(frame)
- frame.globalKillHeader:SetPoint("TOPLEFT", frame.charKillHeader, "TOPRIGHT", -2, 0)
- frame.globalKillHeader:SetWidth(GLOBAL_WIDTH + HEADER_LEFT)
- frame.globalKillHeader:SetText("Global Kills")
- frame.globalKillHeader:SetScript("OnClick", function()
- local sort = KT.Sort.Desc
- if Sort == sort then
- sort = KT.Sort.Asc
- end
- ML:UpdateMobs(sort, LastFilter)
- ML:UpdateEntries(LastOffset)
- end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.rows = CreateFrame("Frame", nil, frame)
- frame.rows:SetPoint("LEFT")
- frame.rows:SetPoint("RIGHT", frame, "RIGHT", -SCROLL_WIDTH, 0)
- frame.rows:SetPoint("TOP", frame.idHeader, "BOTTOM", 0, 0)
- frame.rows:SetPoint("BOTTOM", frame, "BOTTOM", 0, 0)
- frame.rows:SetPoint("TOPLEFT", frame.idHeader, "BOTTOMLEFT", 0, 30)
-
- local previous = frame.idHeader
- for i = 1, ROW_COUNT do
- local key = "row" .. i
- frame.rows[key] = CreateRow(frame.rows, previous)
- frame.rows[key].idField:SetText("")
- frame.rows[key].nameField:SetText("")
- frame.rows[key].charKillField:SetText("")
- frame.rows[key].globalKillField:SetText("")
- previous = frame.rows[key]
- end
-
- ---@diagnostic disable-next-line: inject-field
- frame.rows.scroller = CreateFrame(
- "ScrollFrame",
- "KillTrackMobListScrollFrame",
- frame.rows,
- "FauxScrollFrameTemplateLight")
- frame.rows.scroller.name = frame.rows.scroller:GetName()
- frame.rows.scroller:SetWidth(frame.rows:GetWidth())
- frame.rows.scroller:SetPoint("TOPRIGHT", frame.rows, "TOPRIGHT", -1, -2)
- frame.rows.scroller:SetPoint("BOTTOMRIGHT", 0, 4)
- frame.rows.scroller:SetScript(
- "OnVerticalScroll",
- function(s, val)
- FauxScrollFrame_OnVerticalScroll(
- s, val, ROW_HEIGHT,
- function()
- local offset = FauxScrollFrame_GetOffset(frame.rows.scroller)
- ML:UpdateEntries(offset)
- end
- )
- end
- )
-
- self:UpdateMobs(Sort)
-
- ---@diagnostic disable-next-line: inject-field
- frame.statusLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
- frame.statusLabel:SetWidth(420)
- frame.statusLabel:SetHeight(16)
- frame.statusLabel:SetPoint("BOTTOM", frame, "BOTTOM", 0, 8)
- frame.statusLabel:SetText(STATUS_TEXT:format(1, ROW_COUNT, #Mobs))
-
- self:UpdateEntries(LastOffset)
-
- created = true
-end
-
----@param sort KillTrackMobSortMode?
----@param filter string?
-function ML:UpdateMobs(sort, filter)
- sort = (sort or Sort) or KT.Sort.Desc
- Sort = sort
- LastFilter = filter
- Mobs = KT:GetSortedMobTable(Sort, filter and filter:lower() or nil)
- FauxScrollFrame_Update(frame.rows.scroller, #Mobs, ROW_COUNT, ROW_HEIGHT)
-end
-
----@param offset integer?
-function ML:UpdateEntries(offset)
- if (#Mobs <= 0) then
- for i = 1, ROW_COUNT do
- local row = frame.rows["row" .. i]
- row.idField:SetText("")
- if i == 1 then
- row.nameField:SetText("No entries in database or none matched search!")
- else
- row.nameField:SetText("")
- end
- row.charKillField:SetText("")
- row.globalKillField:SetText("")
- row:Disable()
- end
-
- frame.statusLabel:SetText(STATUS_TEXT:format(0, 0, 0))
-
- return
- elseif #Mobs < ROW_COUNT then
- for i = 1, ROW_COUNT do
- local row = frame.rows["row" .. i]
- row.idField:SetText("")
- row.nameField:SetText("")
- row.charKillField:SetText("")
- row.globalKillField:SetText("")
- row:Disable()
- end
- end
- offset = (tonumber(offset) or LastOffset) or 0
- LastOffset = offset
- local limit = ROW_COUNT
- if limit > #Mobs then
- limit = #Mobs
- end
- for i = 1, limit do
- local row = frame.rows["row" .. i]
- local mob = Mobs[i + offset]
- row.idField:SetText(mob.Id)
- row.nameField:SetText(mob.Name)
- row.charKillField:SetText(mob.cKills)
- row.globalKillField:SetText(mob.gKills)
- row:Enable()
- end
-
- local mobCount = KTT:TableLength(KT.Global.MOBS)
- local hidden = mobCount - #Mobs
- frame.statusLabel:SetText(STATUS_TEXT:format(1 + offset, math.min(#Mobs, offset + ROW_COUNT), #Mobs, hidden))
-
- if offset == 0 then
- _G[frame.rows.scroller.name .. "ScrollBarScrollUpButton"]:Disable()
- else
- _G[frame.rows.scroller.name .. "ScrollBarScrollUpButton"]:Enable()
- end
-
- if offset + ROW_COUNT == #Mobs then
- _G[frame.rows.scroller.name .. "ScrollBarScrollDownButton"]:Disable()
- else
- _G[frame.rows.scroller.name .. "ScrollBarScrollDownButton"]:Enable()
- end
-end
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+-- Beware of some possibly messy code in this file
+
+---@class KillTrack
+local KT = select(2, ...)
+
+---@class KillTrackMobList
+local ML = {}
+
+KT.MobList = ML
+local KTT = KT.Tools
+
+local Sort = KT.Sort.Desc
+---@type { Id: integer, Name: string, gKills: integer, cKills: integer }[]
+local Mobs = nil
+local LastFilter = nil
+local LastOffset = 0
+
+-- Frame Constants
+local FRAME_WIDTH = 600
+local FRAME_HEIGHT = 534
+local HEADER_HEIGHT = 24
+local HEADER_LEFT = 3
+local HEADER_TOP = -80
+local ROW_HEIGHT = 15
+local ROW_COUNT = 27
+local ROW_TEXT_PADDING = 5
+local ID_WIDTH = 100
+local NAME_WIDTH = 300
+local CHAR_WIDTH = 100
+local GLOBAL_WIDTH = 100
+local SCROLL_WIDTH = 27 -- Scrollbar width
+local STATUS_TEXT = "Showing entries %d through %d out of %d total (%d hidden)"
+
+---@type table|BackdropTemplate|Frame
+local frame = nil
+local created = false
+
+-- Frame helper functions
+
+---@param parent table|Frame
+---@return Button
+local function CreateHeader(parent)
+ local h = CreateFrame("Button", nil, parent)
+ h:SetHeight(HEADER_HEIGHT)
+ h:SetNormalFontObject("GameFontHighlightSmall")
+
+ local bgl = h:CreateTexture(nil, "BACKGROUND")
+ bgl:SetTexture("Interface\\FriendsFrame\\WhoFrame-ColumnTabs")
+ bgl:SetWidth(5)
+ bgl:SetHeight(HEADER_HEIGHT)
+ bgl:SetPoint("TOPLEFT")
+ bgl:SetTexCoord(0, 0.07815, 0, 0.75)
+
+ local bgr = h:CreateTexture(nil, "BACKGROUND")
+ bgr:SetTexture("Interface\\FriendsFrame\\WhoFrame-ColumnTabs")
+ bgr:SetWidth(5)
+ bgr:SetHeight(HEADER_HEIGHT)
+ bgr:SetPoint("TOPRIGHT")
+ bgr:SetTexCoord(0.90625, 0.96875, 0, 0.75)
+
+ local bgm = h:CreateTexture(nil, "BACKGROUND")
+ bgm:SetTexture("Interface\\FriendsFrame\\WhoFrame-ColumnTabs")
+ bgm:SetHeight(HEADER_HEIGHT)
+ bgm:SetPoint("LEFT", bgl, "RIGHT")
+ bgm:SetPoint("RIGHT", bgr, "LEFT")
+ bgm:SetTexCoord(0.07815, 0.90625, 0, 0.75)
+
+ local hl = h:CreateTexture()
+ h:SetHighlightTexture("Interface\\PaperDollInfoFrame\\UI-Character-Tab-Highlight", "ADD")
+ hl:SetPoint("TOPLEFT", bgl, "TOPLEFT", -2, 5)
+ hl:SetPoint("BOTTOMRIGHT", bgr, "BOTTOMRIGHT", 2, -7)
+
+ return h
+end
+
+---@param container table|Frame
+---@param previous table|Frame
+---@return Button
+local function CreateRow(container, previous)
+ local row = CreateFrame("Button", nil, container)
+ row:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight", "ADD")
+ row:SetHeight(ROW_HEIGHT)
+ row:SetPoint("LEFT")
+ row:SetPoint("RIGHT")
+ row:SetPoint("TOPLEFT", previous, "BOTTOMLEFT", 0, 0)
+
+ ---@diagnostic disable-next-line: inject-field
+ row.idField = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
+ row.idField:SetHeight(ROW_HEIGHT)
+ row.idField:SetWidth(ID_WIDTH - ROW_TEXT_PADDING * 2)
+ row.idField:SetPoint("LEFT", row, "LEFT", ROW_TEXT_PADDING, 0)
+ row.idField:SetJustifyH("RIGHT")
+
+ ---@diagnostic disable-next-line: inject-field
+ row.nameField = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
+ row.nameField:SetHeight(ROW_HEIGHT)
+ row.nameField:SetWidth(NAME_WIDTH - SCROLL_WIDTH - ROW_TEXT_PADDING * 3)
+ row.nameField:SetPoint("LEFT", row.idField, "RIGHT", 2 * ROW_TEXT_PADDING, 0)
+ row.nameField:SetJustifyH("LEFT")
+
+ ---@diagnostic disable-next-line: inject-field
+ row.charKillField = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
+ row.charKillField:SetHeight(ROW_HEIGHT)
+ row.charKillField:SetWidth(CHAR_WIDTH - ROW_TEXT_PADDING * 2)
+ row.charKillField:SetPoint("LEFT", row.nameField, "RIGHT", 2 * ROW_TEXT_PADDING, 0)
+ row.charKillField:SetJustifyH("RIGHT")
+
+ ---@diagnostic disable-next-line: inject-field
+ row.globalKillField = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
+ row.globalKillField:SetHeight(ROW_HEIGHT)
+ row.globalKillField:SetWidth(GLOBAL_WIDTH - ROW_TEXT_PADDING * 2)
+ row.globalKillField:SetPoint("LEFT", row.charKillField, "RIGHT", 2 * ROW_TEXT_PADDING, 0)
+ row.globalKillField:SetJustifyH("RIGHT")
+
+ row:SetScript("OnClick", function(s)
+ local id = tonumber(s.idField:GetText())
+ if not id then return end
+ local name = s.nameField:GetText()
+ KT:ShowDelete(id, name)
+ end)
+
+ row:SetScript("OnEnter", function(s)
+ local id = tonumber(s.idField:GetText())
+ if not id then return end
+ local globalData = KT.Global.MOBS[id]
+ if not globalData then return end
+ local killTimestamp = globalData.LastKillAt
+ if not killTimestamp then return end
+ local lastKillAt = KTT:FormatDateTime(killTimestamp)
+ local tpString = ("Last killed at %s"):format(lastKillAt)
+ GameTooltip:SetOwner(s, "ANCHOR_NONE")
+ GameTooltip:SetPoint("TOPLEFT", s, "BOTTOMLEFT")
+ GameTooltip:ClearLines()
+ GameTooltip:AddLine(tpString)
+ GameTooltip:Show()
+ end)
+
+ row:SetScript("OnLeave", function()
+ GameTooltip:Hide()
+ end)
+
+ return row
+end
+
+function ML:Show()
+ if not created then
+ ML:Create()
+ end
+ if frame:IsShown() then return end
+ frame:Show()
+end
+
+function ML:Hide()
+ if not frame or not frame:IsShown() then return end
+ frame:Hide()
+end
+
+function ML:Toggle()
+ if frame and frame:IsShown() then
+ ML:Hide()
+ else
+ ML:Show()
+ end
+end
+
+function ML:Create()
+ if frame then return end
+ frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
+ frame:Hide()
+ frame:SetToplevel(true)
+ frame:EnableMouse(true)
+ frame:SetMovable(true)
+ frame:SetPoint("CENTER")
+ frame:SetWidth(FRAME_WIDTH)
+ frame:SetHeight(FRAME_HEIGHT)
+
+ local bd = {
+ bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
+ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
+ tile = true,
+ edgeSize = 16,
+ tileSize = 32,
+ insets = {
+ left = 2.5,
+ right = 2.5,
+ top = 2.5,
+ bottom = 2.5
+ }
+ }
+
+ frame:SetBackdrop(bd)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.titleLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+ frame.titleLabel:SetWidth(250)
+ frame.titleLabel:SetHeight(16)
+ frame.titleLabel:SetPoint("TOP", frame, "TOP", 0, -5)
+ frame.titleLabel:SetText("KillTrack Mob Database (" .. KT.Version .. ")")
+
+ frame:SetScript("OnMouseDown", function(s) s:StartMoving() end)
+ frame:SetScript("OnMouseUp", function(s) s:StopMovingOrSizing() end)
+ frame:SetScript("OnShow", function() ML:UpdateMobs(Sort, LastFilter) ML:UpdateEntries(LastOffset) end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
+ frame.closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
+ frame.closeButton:SetScript("OnClick", function() ML:Hide() end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.purgeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
+ frame.purgeButton:SetHeight(24)
+ frame.purgeButton:SetWidth(100)
+ frame.purgeButton:SetPoint("TOPLEFT", frame, "TOPLEFT", 10, -5)
+ frame.purgeButton:SetText("Purge Data")
+ frame.purgeButton:SetScript("OnClick", function()
+ KT:ShowPurge()
+ end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.resetButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
+ frame.resetButton:SetHeight(24)
+ frame.resetButton:SetWidth(100)
+ frame.resetButton:SetPoint("TOPLEFT", frame.purgeButton, "BOTTOMLEFT", 0, -3)
+ frame.resetButton:SetText("Reset All")
+ frame.resetButton:SetScript("OnClick", function()
+ KT:ShowReset()
+ end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.helpLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+ frame.helpLabel:SetWidth(380)
+ frame.helpLabel:SetHeight(16)
+ frame.helpLabel:SetPoint("TOP", frame, "TOP", 0, -28)
+ frame.helpLabel:SetWordWrap(true)
+ frame.helpLabel:SetMaxLines(2)
+ frame.helpLabel:SetText(
+ "Click on an individual entry to delete it from the database. Use the search to filter database by name.")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.searchBox = CreateFrame("EditBox", "KillTrackMobListSearchBox", frame, "SearchBoxTemplate")
+ frame.searchBox:SetWidth(200)
+ frame.searchBox:SetHeight(16)
+ frame.searchBox:SetPoint("TOPLEFT", frame.resetButton, "BOTTOMLEFT", 8, -3)
+ frame.searchBox:HookScript("OnTextChanged", function(s)
+ local text = s:GetText()
+ if (not _G[s:GetName() .. "ClearButton"]:IsShown()) then
+ text = nil
+ LastFilter = nil
+ end
+ if not text or text == "" then
+ ML:UpdateMobs(Sort)
+ else
+ ML:UpdateMobs(Sort, text)
+ end
+ ML:UpdateEntries(LastOffset)
+ end)
+ frame.searchBox:SetScript("OnEnterPressed", function(s) s:ClearFocus() end)
+ ---@diagnostic disable-next-line: inject-field
+ frame.searchBox.clearButton = _G[frame.searchBox:GetName() .. "ClearButton"]
+ local sBoxOldFunc = frame.searchBox.clearButton:GetScript("OnHide")
+ frame.searchBox.clearButton:SetScript("OnHide", function(s)
+ if sBoxOldFunc then sBoxOldFunc(s) end
+ if not frame:IsShown() then return end
+ ML:UpdateMobs(Sort, nil)
+ ML:UpdateEntries(LastOffset)
+ end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.searchTipLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+ frame.searchTipLabel:SetWidth(200)
+ frame.searchTipLabel:SetHeight(16)
+ frame.searchTipLabel:SetPoint("LEFT", frame.searchBox, "RIGHT", -22, 0)
+ frame.searchTipLabel:SetText("(Supports Lua patterns)")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.idHeader = CreateHeader(frame)
+ frame.idHeader:SetPoint("TOPLEFT", frame, "TOPLEFT", HEADER_LEFT, HEADER_TOP)
+ frame.idHeader:SetWidth(ID_WIDTH)
+ frame.idHeader:SetText("NPC ID")
+ frame.idHeader:SetScript("OnClick", function()
+ local sort = KT.Sort.IdAsc
+ if Sort == sort then
+ sort = KT.Sort.IdDesc
+ end
+ ML:UpdateMobs(sort, LastFilter)
+ ML:UpdateEntries(LastOffset)
+ end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.nameHeader = CreateHeader(frame)
+ frame.nameHeader:SetPoint("TOPLEFT", frame.idHeader, "TOPRIGHT", -2, 0)
+ frame.nameHeader:SetWidth(NAME_WIDTH - SCROLL_WIDTH)
+ frame.nameHeader:SetText("Name")
+ frame.nameHeader:SetScript("OnClick", function()
+ local sort = KT.Sort.AlphaA
+ if Sort == sort then
+ sort = KT.Sort.AlphaD
+ end
+ ML:UpdateMobs(sort, LastFilter)
+ ML:UpdateEntries(LastOffset)
+ end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.charKillHeader = CreateHeader(frame)
+ frame.charKillHeader:SetPoint("TOPLEFT", frame.nameHeader, "TOPRIGHT", -2, 0)
+ frame.charKillHeader:SetWidth(CHAR_WIDTH)
+ frame.charKillHeader:SetText("Character Kills")
+ frame.charKillHeader:SetScript("OnClick", function()
+ local sort = KT.Sort.CharDesc
+ if Sort == sort then
+ sort = KT.Sort.CharAsc
+ end
+ ML:UpdateMobs(sort, LastFilter)
+ ML:UpdateEntries(LastOffset)
+ end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.globalKillHeader = CreateHeader(frame)
+ frame.globalKillHeader:SetPoint("TOPLEFT", frame.charKillHeader, "TOPRIGHT", -2, 0)
+ frame.globalKillHeader:SetWidth(GLOBAL_WIDTH + HEADER_LEFT)
+ frame.globalKillHeader:SetText("Global Kills")
+ frame.globalKillHeader:SetScript("OnClick", function()
+ local sort = KT.Sort.Desc
+ if Sort == sort then
+ sort = KT.Sort.Asc
+ end
+ ML:UpdateMobs(sort, LastFilter)
+ ML:UpdateEntries(LastOffset)
+ end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.rows = CreateFrame("Frame", nil, frame)
+ frame.rows:SetPoint("LEFT")
+ frame.rows:SetPoint("RIGHT", frame, "RIGHT", -SCROLL_WIDTH, 0)
+ frame.rows:SetPoint("TOP", frame.idHeader, "BOTTOM", 0, 0)
+ frame.rows:SetPoint("BOTTOM", frame, "BOTTOM", 0, 0)
+ frame.rows:SetPoint("TOPLEFT", frame.idHeader, "BOTTOMLEFT", 0, 30)
+
+ local previous = frame.idHeader
+ for i = 1, ROW_COUNT do
+ local key = "row" .. i
+ frame.rows[key] = CreateRow(frame.rows, previous)
+ frame.rows[key].idField:SetText("")
+ frame.rows[key].nameField:SetText("")
+ frame.rows[key].charKillField:SetText("")
+ frame.rows[key].globalKillField:SetText("")
+ previous = frame.rows[key]
+ end
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.rows.scroller = CreateFrame(
+ "ScrollFrame",
+ "KillTrackMobListScrollFrame",
+ frame.rows,
+ "FauxScrollFrameTemplateLight")
+ frame.rows.scroller.name = frame.rows.scroller:GetName()
+ frame.rows.scroller:SetWidth(frame.rows:GetWidth())
+ frame.rows.scroller:SetPoint("TOPRIGHT", frame.rows, "TOPRIGHT", -1, -2)
+ frame.rows.scroller:SetPoint("BOTTOMRIGHT", 0, 4)
+ frame.rows.scroller:SetScript(
+ "OnVerticalScroll",
+ function(s, val)
+ FauxScrollFrame_OnVerticalScroll(
+ s, val, ROW_HEIGHT,
+ function()
+ local offset = FauxScrollFrame_GetOffset(frame.rows.scroller)
+ ML:UpdateEntries(offset)
+ end
+ )
+ end
+ )
+
+ self:UpdateMobs(Sort)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.statusLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+ frame.statusLabel:SetWidth(420)
+ frame.statusLabel:SetHeight(16)
+ frame.statusLabel:SetPoint("BOTTOM", frame, "BOTTOM", 0, 8)
+ frame.statusLabel:SetText(STATUS_TEXT:format(1, ROW_COUNT, #Mobs))
+
+ self:UpdateEntries(LastOffset)
+
+ created = true
+end
+
+---@param sort KillTrackMobSortMode?
+---@param filter string?
+function ML:UpdateMobs(sort, filter)
+ sort = (sort or Sort) or KT.Sort.Desc
+ Sort = sort
+ LastFilter = filter
+ Mobs = KT:GetSortedMobTable(Sort, filter and filter:lower() or nil)
+ FauxScrollFrame_Update(frame.rows.scroller, #Mobs, ROW_COUNT, ROW_HEIGHT)
+end
+
+---@param offset integer?
+function ML:UpdateEntries(offset)
+ if (#Mobs <= 0) then
+ for i = 1, ROW_COUNT do
+ local row = frame.rows["row" .. i]
+ row.idField:SetText("")
+ if i == 1 then
+ row.nameField:SetText("No entries in database or none matched search!")
+ else
+ row.nameField:SetText("")
+ end
+ row.charKillField:SetText("")
+ row.globalKillField:SetText("")
+ row:Disable()
+ end
+
+ frame.statusLabel:SetText(STATUS_TEXT:format(0, 0, 0))
+
+ return
+ elseif #Mobs < ROW_COUNT then
+ for i = 1, ROW_COUNT do
+ local row = frame.rows["row" .. i]
+ row.idField:SetText("")
+ row.nameField:SetText("")
+ row.charKillField:SetText("")
+ row.globalKillField:SetText("")
+ row:Disable()
+ end
+ end
+ offset = (tonumber(offset) or LastOffset) or 0
+ LastOffset = offset
+ local limit = ROW_COUNT
+ if limit > #Mobs then
+ limit = #Mobs
+ end
+ for i = 1, limit do
+ local row = frame.rows["row" .. i]
+ local mob = Mobs[i + offset]
+ row.idField:SetText(mob.Id)
+ row.nameField:SetText(mob.Name)
+ row.charKillField:SetText(mob.cKills)
+ row.globalKillField:SetText(mob.gKills)
+ row:Enable()
+ end
+
+ local mobCount = KTT:TableLength(KT.Global.MOBS)
+ local hidden = mobCount - #Mobs
+ frame.statusLabel:SetText(STATUS_TEXT:format(1 + offset, math.min(#Mobs, offset + ROW_COUNT), #Mobs, hidden))
+
+ if offset == 0 then
+ _G[frame.rows.scroller.name .. "ScrollBarScrollUpButton"]:Disable()
+ else
+ _G[frame.rows.scroller.name .. "ScrollBarScrollUpButton"]:Enable()
+ end
+
+ if offset + ROW_COUNT == #Mobs then
+ _G[frame.rows.scroller.name .. "ScrollBarScrollDownButton"]:Disable()
+ else
+ _G[frame.rows.scroller.name .. "ScrollBarScrollDownButton"]:Enable()
+ end
+end
diff --git a/Options.lua b/Options.lua
index ee67e18..aca9bf9 100644
--- a/Options.lua
+++ b/Options.lua
@@ -1,276 +1,276 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
-local KTT = KT.Tools
-
----@class KillTrackOptions
-local Opt = {
- Panel = CreateFrame("Frame")
-}
-
-KT.Options = Opt
-
-local panel = Opt.Panel
-
----@diagnostic disable-next-line: inject-field
-panel.name = "KillTrack"
-panel:Hide()
-
-local category = Settings.RegisterCanvasLayoutCategory(panel, panel.name)
-Opt.Category = category
-
--- Dirty hack to give a name to option checkboxes
-local checkCounter = 0
-
----@param label string
----@param description string
----@param onclick function
----@return table|CheckButton
-local function checkbox(label, description, onclick)
- local check = CreateFrame(
- "CheckButton",
- "KillTrackOptCheck" .. checkCounter,
- panel,
- "InterfaceOptionsCheckButtonTemplate")
- check:SetScript("OnClick", function(self)
- local checked = self:GetChecked()
- PlaySound(checked and SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON or SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF)
- onclick(self, checked and true or false)
- end)
- ---@diagnostic disable-next-line: inject-field
- check.label = _G[check:GetName() .. "Text"]
- check.label:SetText(label)
- ---@diagnostic disable-next-line: inject-field
- check.tooltipText = label
- ---@diagnostic disable-next-line: inject-field
- check.tooltipRequirement = description
- checkCounter = checkCounter + 1
- return check
-end
-
----@param text string
----@param tooltip string
----@param onclick function
----@return table|Button
-local function button(text, tooltip, onclick)
- local btn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
- btn:SetText(text)
- ---@diagnostic disable-next-line: inject-field
- btn.tooltipText = tooltip
- btn:SetScript("OnClick", function(self) onclick(self) end)
- btn:SetHeight(24)
- return btn
-end
-
-local function HideBlizzOptions()
- HideUIPanel(InterfaceOptionsFrame)
- HideUIPanel(GameMenuFrame)
-end
-
-function Opt:Open()
- Settings.OpenToCategory(self.Category.ID)
-end
-
----@param self Frame
-function Opt.Show(self)
- local title = self:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
- title:SetPoint("TOPLEFT", 16, -16)
- title:SetText("KillTrack")
-
- local printKills = checkbox("Print kill updates to chat",
- "With this enabled, every kill you make is going to be announced locally in the chatbox",
- function(_, checked) KT.Global.PRINTKILLS = checked end)
- printKills:SetPoint("TOPLEFT", title, "BOTTOMLEFT", -2, -16)
-
- local tooltipControl = checkbox("Show mob data in tooltip",
- "With this enabled, KillTrack will print data about mobs in the tooltip",
- function(_, checked) KT.Global.TOOLTIP = checked end)
- tooltipControl:SetPoint("LEFT", printKills, "RIGHT", 180, 0)
-
- local printNew = checkbox("Print new mob entries to chat",
- "With this enabled, new mobs added to the database will be announced locally in the chat",
- function(_, checked) KT.Global.PRINTNEW = checked end)
- printNew:SetPoint("TOPLEFT", printKills, "BOTTOMLEFT", 0, -8)
-
- local countGroup = checkbox("Count group kills",
- "With this disabled, only killing blows made by yourself will count",
- function(_, checked) KT.Global.COUNT_GROUP = checked end)
- countGroup:SetPoint("TOPLEFT", printNew, "BOTTOMLEFT", 0, -8)
-
- local thresholdDesc = self:CreateFontString(nil, "ARTWORK", "GameFontNormal")
- thresholdDesc:SetPoint("TOPLEFT", countGroup, "BOTTOMLEFT", 0, -8)
- thresholdDesc:SetTextColor(1, 1, 1)
- thresholdDesc:SetText("Threshold for displaying kill achievements (press enter to apply)")
-
- local threshold = CreateFrame("EditBox", "KillTrackOptThreshold", panel, "InputBoxTemplate")
- threshold:SetHeight(22)
- threshold:SetWidth(150)
- threshold:SetPoint("LEFT", thresholdDesc, "RIGHT", 8, 0)
- threshold:SetAutoFocus(false)
- threshold:EnableMouse(true)
- threshold:SetScript("OnEditFocusGained", function(box)
- box:SetTextColor(0, 1, 0)
- box:HighlightText()
- end)
- local function setThreshold(box, enter)
- box:SetTextColor(1, 1, 1)
- local value = tonumber(box:GetNumber())
- if value and value > 0 then
- KT.Global.ACHIEV_THRESHOLD = value
- if not enter then
- KT:Msg("Updated threshold value!")
- end
- box:ClearFocus()
- box:SetText(KT.Global.ACHIEV_THRESHOLD)
- else
- box:SetText(KT.Global.ACHIEV_THRESHOLD)
- box:HighlightText()
- end
- end
- threshold:SetScript("OnEditFocusLost", function(box) setThreshold(box) end)
- threshold:SetScript("OnEnterPressed", function(box) setThreshold(box, true) end)
-
- local showTarget = button("Target", "Show information about the currently selected target",
- function()
- if not UnitExists("target") or UnitIsPlayer("target") then return end
- local id = KTT.GUIDToID(UnitGUID("target"))
- KT:PrintKills(id)
- end)
- showTarget:SetWidth(150)
- showTarget:SetPoint("TOPLEFT", thresholdDesc, "BOTTOMLEFT", 0, -8)
-
- local list = button("List", "Open the mob database",
- function()
- HideBlizzOptions()
- KT.MobList:Show()
- end)
- list:SetWidth(150)
- list:SetPoint("TOPLEFT", showTarget, "TOPRIGHT", 8, 0)
-
- local purge = button("Purge", "Purge mob entries with a kill count below a specified number",
- function() KT:ShowPurge() end)
- purge:SetWidth(150)
- purge:SetPoint("TOPLEFT", showTarget, "BOTTOMLEFT", 0, -8)
-
- local reset = button("Reset", "Clear the database of ALL mob entries",
- function() KT:ShowReset() end)
- reset:SetWidth(150)
- reset:SetPoint("TOPLEFT", purge, "TOPRIGHT", 8, 0)
-
- local minimap = checkbox("Show minimap icon", "Adds the KillTrack broker to your minimap",
- function(_, checked) KT.Broker:SetMinimap(checked) end)
- minimap:SetPoint("TOPLEFT", purge, "BOTTOMLEFT", 0, -8)
-
- local disableDungeons = checkbox("Disable in dungeons (save CPU)",
- "When this is checked, mob kills in dungeons won't be counted.",
- function(_, checked) KT.Global.DISABLE_DUNGEONS = checked end)
- disableDungeons:SetPoint("TOPLEFT", minimap, "BOTTOMLEFT", 0, -8)
-
- local disableRaids = checkbox("Disable in raids (save CPU)",
- "When this is checked, mob kills in raids won't be counted.",
- function(_, checked) KT.Global.DISABLE_RAIDS = checked end)
- disableRaids:SetPoint("TOPLEFT", disableDungeons, "BOTTOMLEFT", 0, -8)
-
- local datetimeFormatDesc = self:CreateFontString(nil, "ARTWORK", "GameFontNormal")
- datetimeFormatDesc:SetPoint("TOPLEFT", disableRaids, "BOTTOMLEFT", 0, -8)
- datetimeFormatDesc:SetTextColor(1, 1, 1)
- datetimeFormatDesc:SetText("Datetime format template (press enter to apply)")
-
- local datetimeFormat = CreateFrame("EditBox", "KillTrackOptDateTimeFormat", panel, "InputBoxTemplate")
- datetimeFormat:SetHeight(22)
- datetimeFormat:SetWidth(200)
- datetimeFormat:SetPoint("LEFT", datetimeFormatDesc, "RIGHT", 8, 0)
- datetimeFormat:SetAutoFocus(false)
- datetimeFormat:EnableMouse(true)
- local datetimeFormatPreview = self:CreateFontString(nil, "ARTWORK", "GameFontNormal")
- datetimeFormatPreview:SetPoint("TOPLEFT", datetimeFormat, "BOTTOMLEFT", 0, -2)
- datetimeFormatPreview:SetTextColor(1, 1, 1)
- datetimeFormatPreview:SetText("Preview:")
- local datetimeFormatPreviewValue = self:CreateFontString(nil, "ARTWORK", "GameFontNormal")
- datetimeFormatPreviewValue:SetPoint("LEFT", datetimeFormatPreview, "RIGHT", 8, 0)
- datetimeFormatPreviewValue:SetTextColor(1, 1, 1)
- datetimeFormatPreviewValue:SetText(KTT:FormatDateTime() --[[@as string]])
-
- datetimeFormat:SetScript("OnEditFocusGained", function(box)
- box:SetTextColor(0, 1, 0)
- box:HighlightText()
- end)
- local function setDateTimeFormat(box, enter)
- box:SetTextColor(1, 1, 1)
- local value = box:GetText()
- if type(value) ~= "string" then
- box:SetText(KT.Global.DATETIME_FORMAT)
- box:HighlightText()
- return
- end
- local valid, errMsg = pcall(KTT.FormatDateTime, KTT, nil, value)
- if not valid then
- KT:Msg("Invalid format string: " .. (errMsg or "unknown error"))
- box:HighlightText()
- return
- end
- KT.Global.DATETIME_FORMAT = value
- if not enter then
- KT:Msg("Updated datetime format!")
- end
- box:ClearFocus()
- box:SetText(KT.Global.DATETIME_FORMAT)
- end
- datetimeFormat:SetScript("OnEditFocusLost", function(box) setDateTimeFormat(box) end)
- datetimeFormat:SetScript("OnEnterPressed", function(box) setDateTimeFormat(box, true) end)
- datetimeFormat:SetScript("OnTextChanged", function(box)
- local value = box:GetText()
- if type(value) ~= "string" then return end
- local valid, result = pcall(KTT.FormatDateTime, KTT, nil, value)
- if valid then
- datetimeFormatPreviewValue:SetText(result --[[@as string]])
- else
- datetimeFormatPreviewValue:SetText("invalid format")
- end
- end)
- local datetimeFormatReset = button("Reset", "Reset the datetime format to the default", function()
- KT.Global.DATETIME_FORMAT = KT.Defaults.DateTimeFormat
- datetimeFormat:SetText(KT.Global.DATETIME_FORMAT)
- end)
- datetimeFormatReset:SetWidth(80)
- datetimeFormatReset:SetPoint("LEFT", datetimeFormat, "RIGHT", 5, 0)
-
- local function init()
- printKills:SetChecked(KT.Global.PRINTKILLS)
- tooltipControl:SetChecked(KT.Global.TOOLTIP)
- printNew:SetChecked(KT.Global.PRINTNEW)
- countGroup:SetChecked(KT.Global.COUNT_GROUP)
- threshold:SetText(tostring(KT.Global.ACHIEV_THRESHOLD))
- minimap:SetChecked(not KT.Global.BROKER.MINIMAP.hide)
- disableDungeons:SetChecked(KT.Global.DISABLE_DUNGEONS)
- disableRaids:SetChecked(KT.Global.DISABLE_RAIDS)
- datetimeFormat:SetText(KT.Global.DATETIME_FORMAT)
- end
-
- init()
-
- self:SetScript("OnShow", init)
-end
-
-panel:SetScript("OnShow", function(self) Opt.Show(self) end)
-
-Settings.RegisterAddOnCategory(category)
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+local KTT = KT.Tools
+
+---@class KillTrackOptions
+local Opt = {
+ Panel = CreateFrame("Frame")
+}
+
+KT.Options = Opt
+
+local panel = Opt.Panel
+
+---@diagnostic disable-next-line: inject-field
+panel.name = "KillTrack"
+panel:Hide()
+
+local category = Settings.RegisterCanvasLayoutCategory(panel, panel.name)
+Opt.Category = category
+
+-- Dirty hack to give a name to option checkboxes
+local checkCounter = 0
+
+---@param label string
+---@param description string
+---@param onclick function
+---@return table|CheckButton
+local function checkbox(label, description, onclick)
+ local check = CreateFrame(
+ "CheckButton",
+ "KillTrackOptCheck" .. checkCounter,
+ panel,
+ "InterfaceOptionsCheckButtonTemplate")
+ check:SetScript("OnClick", function(self)
+ local checked = self:GetChecked()
+ PlaySound(checked and SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON or SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF)
+ onclick(self, checked and true or false)
+ end)
+ ---@diagnostic disable-next-line: inject-field
+ check.label = _G[check:GetName() .. "Text"]
+ check.label:SetText(label)
+ ---@diagnostic disable-next-line: inject-field
+ check.tooltipText = label
+ ---@diagnostic disable-next-line: inject-field
+ check.tooltipRequirement = description
+ checkCounter = checkCounter + 1
+ return check
+end
+
+---@param text string
+---@param tooltip string
+---@param onclick function
+---@return table|Button
+local function button(text, tooltip, onclick)
+ local btn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
+ btn:SetText(text)
+ ---@diagnostic disable-next-line: inject-field
+ btn.tooltipText = tooltip
+ btn:SetScript("OnClick", function(self) onclick(self) end)
+ btn:SetHeight(24)
+ return btn
+end
+
+local function HideBlizzOptions()
+ HideUIPanel(InterfaceOptionsFrame)
+ HideUIPanel(GameMenuFrame)
+end
+
+function Opt:Open()
+ Settings.OpenToCategory(self.Category.ID)
+end
+
+---@param self Frame
+function Opt.Show(self)
+ local title = self:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
+ title:SetPoint("TOPLEFT", 16, -16)
+ title:SetText("KillTrack")
+
+ local printKills = checkbox("Print kill updates to chat",
+ "With this enabled, every kill you make is going to be announced locally in the chatbox",
+ function(_, checked) KT.Global.PRINTKILLS = checked end)
+ printKills:SetPoint("TOPLEFT", title, "BOTTOMLEFT", -2, -16)
+
+ local tooltipControl = checkbox("Show mob data in tooltip",
+ "With this enabled, KillTrack will print data about mobs in the tooltip",
+ function(_, checked) KT.Global.TOOLTIP = checked end)
+ tooltipControl:SetPoint("LEFT", printKills, "RIGHT", 180, 0)
+
+ local printNew = checkbox("Print new mob entries to chat",
+ "With this enabled, new mobs added to the database will be announced locally in the chat",
+ function(_, checked) KT.Global.PRINTNEW = checked end)
+ printNew:SetPoint("TOPLEFT", printKills, "BOTTOMLEFT", 0, -8)
+
+ local countGroup = checkbox("Count group kills",
+ "With this disabled, only killing blows made by yourself will count",
+ function(_, checked) KT.Global.COUNT_GROUP = checked end)
+ countGroup:SetPoint("TOPLEFT", printNew, "BOTTOMLEFT", 0, -8)
+
+ local thresholdDesc = self:CreateFontString(nil, "ARTWORK", "GameFontNormal")
+ thresholdDesc:SetPoint("TOPLEFT", countGroup, "BOTTOMLEFT", 0, -8)
+ thresholdDesc:SetTextColor(1, 1, 1)
+ thresholdDesc:SetText("Threshold for displaying kill achievements (press enter to apply)")
+
+ local threshold = CreateFrame("EditBox", "KillTrackOptThreshold", panel, "InputBoxTemplate")
+ threshold:SetHeight(22)
+ threshold:SetWidth(150)
+ threshold:SetPoint("LEFT", thresholdDesc, "RIGHT", 8, 0)
+ threshold:SetAutoFocus(false)
+ threshold:EnableMouse(true)
+ threshold:SetScript("OnEditFocusGained", function(box)
+ box:SetTextColor(0, 1, 0)
+ box:HighlightText()
+ end)
+ local function setThreshold(box, enter)
+ box:SetTextColor(1, 1, 1)
+ local value = tonumber(box:GetNumber())
+ if value and value > 0 then
+ KT.Global.ACHIEV_THRESHOLD = value
+ if not enter then
+ KT:Msg("Updated threshold value!")
+ end
+ box:ClearFocus()
+ box:SetText(KT.Global.ACHIEV_THRESHOLD)
+ else
+ box:SetText(KT.Global.ACHIEV_THRESHOLD)
+ box:HighlightText()
+ end
+ end
+ threshold:SetScript("OnEditFocusLost", function(box) setThreshold(box) end)
+ threshold:SetScript("OnEnterPressed", function(box) setThreshold(box, true) end)
+
+ local showTarget = button("Target", "Show information about the currently selected target",
+ function()
+ if not UnitExists("target") or UnitIsPlayer("target") then return end
+ local id = KTT.GUIDToID(UnitGUID("target"))
+ KT:PrintKills(id)
+ end)
+ showTarget:SetWidth(150)
+ showTarget:SetPoint("TOPLEFT", thresholdDesc, "BOTTOMLEFT", 0, -8)
+
+ local list = button("List", "Open the mob database",
+ function()
+ HideBlizzOptions()
+ KT.MobList:Show()
+ end)
+ list:SetWidth(150)
+ list:SetPoint("TOPLEFT", showTarget, "TOPRIGHT", 8, 0)
+
+ local purge = button("Purge", "Purge mob entries with a kill count below a specified number",
+ function() KT:ShowPurge() end)
+ purge:SetWidth(150)
+ purge:SetPoint("TOPLEFT", showTarget, "BOTTOMLEFT", 0, -8)
+
+ local reset = button("Reset", "Clear the database of ALL mob entries",
+ function() KT:ShowReset() end)
+ reset:SetWidth(150)
+ reset:SetPoint("TOPLEFT", purge, "TOPRIGHT", 8, 0)
+
+ local minimap = checkbox("Show minimap icon", "Adds the KillTrack broker to your minimap",
+ function(_, checked) KT.Broker:SetMinimap(checked) end)
+ minimap:SetPoint("TOPLEFT", purge, "BOTTOMLEFT", 0, -8)
+
+ local disableDungeons = checkbox("Disable in dungeons (save CPU)",
+ "When this is checked, mob kills in dungeons won't be counted.",
+ function(_, checked) KT.Global.DISABLE_DUNGEONS = checked end)
+ disableDungeons:SetPoint("TOPLEFT", minimap, "BOTTOMLEFT", 0, -8)
+
+ local disableRaids = checkbox("Disable in raids (save CPU)",
+ "When this is checked, mob kills in raids won't be counted.",
+ function(_, checked) KT.Global.DISABLE_RAIDS = checked end)
+ disableRaids:SetPoint("TOPLEFT", disableDungeons, "BOTTOMLEFT", 0, -8)
+
+ local datetimeFormatDesc = self:CreateFontString(nil, "ARTWORK", "GameFontNormal")
+ datetimeFormatDesc:SetPoint("TOPLEFT", disableRaids, "BOTTOMLEFT", 0, -8)
+ datetimeFormatDesc:SetTextColor(1, 1, 1)
+ datetimeFormatDesc:SetText("Datetime format template (press enter to apply)")
+
+ local datetimeFormat = CreateFrame("EditBox", "KillTrackOptDateTimeFormat", panel, "InputBoxTemplate")
+ datetimeFormat:SetHeight(22)
+ datetimeFormat:SetWidth(200)
+ datetimeFormat:SetPoint("LEFT", datetimeFormatDesc, "RIGHT", 8, 0)
+ datetimeFormat:SetAutoFocus(false)
+ datetimeFormat:EnableMouse(true)
+ local datetimeFormatPreview = self:CreateFontString(nil, "ARTWORK", "GameFontNormal")
+ datetimeFormatPreview:SetPoint("TOPLEFT", datetimeFormat, "BOTTOMLEFT", 0, -2)
+ datetimeFormatPreview:SetTextColor(1, 1, 1)
+ datetimeFormatPreview:SetText("Preview:")
+ local datetimeFormatPreviewValue = self:CreateFontString(nil, "ARTWORK", "GameFontNormal")
+ datetimeFormatPreviewValue:SetPoint("LEFT", datetimeFormatPreview, "RIGHT", 8, 0)
+ datetimeFormatPreviewValue:SetTextColor(1, 1, 1)
+ datetimeFormatPreviewValue:SetText(KTT:FormatDateTime() --[[@as string]])
+
+ datetimeFormat:SetScript("OnEditFocusGained", function(box)
+ box:SetTextColor(0, 1, 0)
+ box:HighlightText()
+ end)
+ local function setDateTimeFormat(box, enter)
+ box:SetTextColor(1, 1, 1)
+ local value = box:GetText()
+ if type(value) ~= "string" then
+ box:SetText(KT.Global.DATETIME_FORMAT)
+ box:HighlightText()
+ return
+ end
+ local valid, errMsg = pcall(KTT.FormatDateTime, KTT, nil, value)
+ if not valid then
+ KT:Msg("Invalid format string: " .. (errMsg or "unknown error"))
+ box:HighlightText()
+ return
+ end
+ KT.Global.DATETIME_FORMAT = value
+ if not enter then
+ KT:Msg("Updated datetime format!")
+ end
+ box:ClearFocus()
+ box:SetText(KT.Global.DATETIME_FORMAT)
+ end
+ datetimeFormat:SetScript("OnEditFocusLost", function(box) setDateTimeFormat(box) end)
+ datetimeFormat:SetScript("OnEnterPressed", function(box) setDateTimeFormat(box, true) end)
+ datetimeFormat:SetScript("OnTextChanged", function(box)
+ local value = box:GetText()
+ if type(value) ~= "string" then return end
+ local valid, result = pcall(KTT.FormatDateTime, KTT, nil, value)
+ if valid then
+ datetimeFormatPreviewValue:SetText(result --[[@as string]])
+ else
+ datetimeFormatPreviewValue:SetText("invalid format")
+ end
+ end)
+ local datetimeFormatReset = button("Reset", "Reset the datetime format to the default", function()
+ KT.Global.DATETIME_FORMAT = KT.Defaults.DateTimeFormat
+ datetimeFormat:SetText(KT.Global.DATETIME_FORMAT)
+ end)
+ datetimeFormatReset:SetWidth(80)
+ datetimeFormatReset:SetPoint("LEFT", datetimeFormat, "RIGHT", 5, 0)
+
+ local function init()
+ printKills:SetChecked(KT.Global.PRINTKILLS)
+ tooltipControl:SetChecked(KT.Global.TOOLTIP)
+ printNew:SetChecked(KT.Global.PRINTNEW)
+ countGroup:SetChecked(KT.Global.COUNT_GROUP)
+ threshold:SetText(tostring(KT.Global.ACHIEV_THRESHOLD))
+ minimap:SetChecked(not KT.Global.BROKER.MINIMAP.hide)
+ disableDungeons:SetChecked(KT.Global.DISABLE_DUNGEONS)
+ disableRaids:SetChecked(KT.Global.DISABLE_RAIDS)
+ datetimeFormat:SetText(KT.Global.DATETIME_FORMAT)
+ end
+
+ init()
+
+ self:SetScript("OnShow", init)
+end
+
+panel:SetScript("OnShow", function(self) Opt.Show(self) end)
+
+Settings.RegisterAddOnCategory(category)
diff --git a/Timer.lua b/Timer.lua
index 2d773b9..cb32f97 100644
--- a/Timer.lua
+++ b/Timer.lua
@@ -1,166 +1,166 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
----@class KillTrackTimer
-local T = {
- Time = {
- Start = 0,
- Stop = 0
- },
- Running = false,
- ---@enum KillTrackTimerState
- State = {
- START = 0,
- UPDATE = 1,
- STOP = 2
- }
-}
-
-KT.Timer = T
-
-local KTT = KT.Tools
-
----@class KillTrackTimerData
----@field Last integer
----@field Current integer
----@field Start integer
----@field Stop integer
----@field Total integer
----@field Left integer
----@field LeftFormat string
----@field Progress number
----@field __DATA__ { [any]: any }
-local TimerData = {}
-
----@alias KillTrackTimerCallback fun(data: KillTrackTimerData, state: KillTrackTimerState)
-
-T.Frame = CreateFrame("Frame")
-
-local function TimeCheck(_, _)
- if not T.Running then T.Frame:SetScript("OnUpdate", nil) return end
- local now = time()
- TimerData.Last = now
- TimerData.Current = now - T.Time.Start
- TimerData.Start = T.Time.Start
- TimerData.Stop = T.Time.Stop
- TimerData.Total = TimerData.Stop - TimerData.Start
- TimerData.Left = TimerData.Total - TimerData.Current
- TimerData.LeftFormat = KTT:FormatSeconds(TimerData.Left)
- TimerData.Progress = TimerData.Current / TimerData.Total
- T:RunCallback(T:GetAllData(), T.State.UPDATE)
- if now >= T.Time.Stop then T:Stop() end
-end
-
----@return KillTrackTimerData
-function T:GetAllData()
- return KTT:TableCopy(TimerData)
-end
-
----@param key any
----@param failsafe boolean
----@return any
-function T:GetData(key, failsafe)
- local r
- if failsafe then r = 0 end
- if not TimerData.__DATA__ then if failsafe then return 0 else return nil end end
- return TimerData.__DATA__[key] or r
-end
-
----@param key any
----@param value any
-function T:SetData(key, value)
- if type(TimerData.__DATA__) ~= "table" then TimerData.__DATA__ = {} end
- TimerData.__DATA__[key] = value
-end
-
----@return boolean
-function T:IsRunning()
- return self.Running
-end
-
----@param seconds integer?
----@param minutes integer?
----@param hours integer?
----@param callback KillTrackTimerCallback?
----@param data { [any]: any }?
----@return boolean
-function T:Start(seconds, minutes, hours, callback, data)
- if self.Running then return false end
- self.Running = true
- self:Reset()
- seconds = tonumber(seconds) or 0
- minutes = tonumber(minutes) or 0
- hours = tonumber(hours) or 0
- seconds = seconds + minutes * 60 + hours * 60 ^ 2
- if seconds <= 0 then
- self.Running = false
- KT:Msg("Time must be greater than zero.")
- return false
- end
- if type(callback) == "function" then
- self:SetCallback(callback)
- end
- if type(data) == "table" then
- for k,v in pairs(data) do
- self:SetData(k, v)
- end
- end
- self.Time.Start = time()
- self.Time.Stop = self.Time.Start + seconds
- self:RunCallback(self:GetAllData(), self.State.START)
- self.Frame:SetScript("OnUpdate", TimeCheck)
- return true
-end
-
-function T:Stop()
- if not self.Running then return end
- self.Frame:SetScript("OnUpdate", nil)
- self:RunCallback(self:GetAllData(), self.State.STOP)
- self.Running = false
- self.Time.Diff = self.Time.Stop - self.Time.Start
- return self.Time.Diff
-end
-
-function T:Reset()
- wipe(TimerData)
- self.Time.Start = 0
- self.Time.Stop = 0
-end
-
----@return KillTrackTimerCallback
-function T:GetCallback()
- return self.Callback
-end
-
----@param func KillTrackTimerCallback
-function T:SetCallback(func)
- if type(func) ~= "function" then error("Argument 'func' must be of type 'function'.") end
- self.Callback = func
-end
-
----@param data KillTrackTimerData
----@param state KillTrackTimerState
-function T:RunCallback(data, state)
- if type(data) ~= "table" then error("Argument 'data' must be of type 'table'.") end
- local callback = self:GetCallback()
- if callback then callback(data, state) end
-end
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+---@class KillTrackTimer
+local T = {
+ Time = {
+ Start = 0,
+ Stop = 0
+ },
+ Running = false,
+ ---@enum KillTrackTimerState
+ State = {
+ START = 0,
+ UPDATE = 1,
+ STOP = 2
+ }
+}
+
+KT.Timer = T
+
+local KTT = KT.Tools
+
+---@class KillTrackTimerData
+---@field Last integer
+---@field Current integer
+---@field Start integer
+---@field Stop integer
+---@field Total integer
+---@field Left integer
+---@field LeftFormat string
+---@field Progress number
+---@field __DATA__ { [any]: any }
+local TimerData = {}
+
+---@alias KillTrackTimerCallback fun(data: KillTrackTimerData, state: KillTrackTimerState)
+
+T.Frame = CreateFrame("Frame")
+
+local function TimeCheck(_, _)
+ if not T.Running then T.Frame:SetScript("OnUpdate", nil) return end
+ local now = time()
+ TimerData.Last = now
+ TimerData.Current = now - T.Time.Start
+ TimerData.Start = T.Time.Start
+ TimerData.Stop = T.Time.Stop
+ TimerData.Total = TimerData.Stop - TimerData.Start
+ TimerData.Left = TimerData.Total - TimerData.Current
+ TimerData.LeftFormat = KTT:FormatSeconds(TimerData.Left)
+ TimerData.Progress = TimerData.Current / TimerData.Total
+ T:RunCallback(T:GetAllData(), T.State.UPDATE)
+ if now >= T.Time.Stop then T:Stop() end
+end
+
+---@return KillTrackTimerData
+function T:GetAllData()
+ return KTT:TableCopy(TimerData)
+end
+
+---@param key any
+---@param failsafe boolean
+---@return any
+function T:GetData(key, failsafe)
+ local r
+ if failsafe then r = 0 end
+ if not TimerData.__DATA__ then if failsafe then return 0 else return nil end end
+ return TimerData.__DATA__[key] or r
+end
+
+---@param key any
+---@param value any
+function T:SetData(key, value)
+ if type(TimerData.__DATA__) ~= "table" then TimerData.__DATA__ = {} end
+ TimerData.__DATA__[key] = value
+end
+
+---@return boolean
+function T:IsRunning()
+ return self.Running
+end
+
+---@param seconds integer?
+---@param minutes integer?
+---@param hours integer?
+---@param callback KillTrackTimerCallback?
+---@param data { [any]: any }?
+---@return boolean
+function T:Start(seconds, minutes, hours, callback, data)
+ if self.Running then return false end
+ self.Running = true
+ self:Reset()
+ seconds = tonumber(seconds) or 0
+ minutes = tonumber(minutes) or 0
+ hours = tonumber(hours) or 0
+ seconds = seconds + minutes * 60 + hours * 60 ^ 2
+ if seconds <= 0 then
+ self.Running = false
+ KT:Msg("Time must be greater than zero.")
+ return false
+ end
+ if type(callback) == "function" then
+ self:SetCallback(callback)
+ end
+ if type(data) == "table" then
+ for k,v in pairs(data) do
+ self:SetData(k, v)
+ end
+ end
+ self.Time.Start = time()
+ self.Time.Stop = self.Time.Start + seconds
+ self:RunCallback(self:GetAllData(), self.State.START)
+ self.Frame:SetScript("OnUpdate", TimeCheck)
+ return true
+end
+
+function T:Stop()
+ if not self.Running then return end
+ self.Frame:SetScript("OnUpdate", nil)
+ self:RunCallback(self:GetAllData(), self.State.STOP)
+ self.Running = false
+ self.Time.Diff = self.Time.Stop - self.Time.Start
+ return self.Time.Diff
+end
+
+function T:Reset()
+ wipe(TimerData)
+ self.Time.Start = 0
+ self.Time.Stop = 0
+end
+
+---@return KillTrackTimerCallback
+function T:GetCallback()
+ return self.Callback
+end
+
+---@param func KillTrackTimerCallback
+function T:SetCallback(func)
+ if type(func) ~= "function" then error("Argument 'func' must be of type 'function'.") end
+ self.Callback = func
+end
+
+---@param data KillTrackTimerData
+---@param state KillTrackTimerState
+function T:RunCallback(data, state)
+ if type(data) ~= "table" then error("Argument 'data' must be of type 'table'.") end
+ local callback = self:GetCallback()
+ if callback then callback(data, state) end
+end
diff --git a/TimerFrame.lua b/TimerFrame.lua
index db7194d..9a0a6bf 100644
--- a/TimerFrame.lua
+++ b/TimerFrame.lua
@@ -1,216 +1,216 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
----@class KillTrackTimerFrame
-local TF = {
- Running = false
-}
-
-KT.TimerFrame = TF
-
-local T = KT.Timer
-
-local function Enabled(object, enabled)
- if not object.Enable or not object.Disable then return end
- if enabled then
- object:Enable()
- else
- object:Disable()
- end
-end
-
-local frame
-
-local function SetupFrame()
- if frame then return end
- frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
- frame:Hide()
- frame:EnableMouse(true)
- frame:SetMovable(true)
- frame:SetWidth(200)
- frame:SetHeight(93)
-
- frame:SetPoint("CENTER")
-
- local bd = {
- bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
- edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
- tile = true,
- edgeSize = 16,
- tileSize = 32,
- insets = {
- left = 2.5,
- right = 2.5,
- top = 2.5,
- bottom = 2.5
- }
- }
-
- frame:SetBackdrop(bd)
-
- frame:SetScript("OnMouseDown", function(f) f:StartMoving() end)
- frame:SetScript("OnMouseUp", function(f) f:StopMovingOrSizing() end)
-
- ---@diagnostic disable-next-line: inject-field
- frame.currentLabel = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.currentLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
- frame.currentLabel:SetPoint("TOPLEFT", frame, "TOPLEFT", 6, -6)
- frame.currentLabel:SetText("Number of kills:")
-
- ---@diagnostic disable-next-line: inject-field
- frame.currentCount = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.currentCount:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
- frame.currentCount:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -6, -6)
- frame.currentCount:SetText("0")
-
- ---@diagnostic disable-next-line: inject-field
- frame.timeLabel = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.timeLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
- frame.timeLabel:SetPoint("TOPLEFT", frame.currentLabel, "BOTTOMLEFT", 0, -2)
- frame.timeLabel:SetText("Time left:")
-
- ---@diagnostic disable-next-line: inject-field
- frame.timeCount = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.timeCount:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
- frame.timeCount:SetPoint("TOPRIGHT", frame.currentCount, "BOTTOMRIGHT", 0, -2)
- frame.timeCount:SetText("00:00:00")
-
- ---@diagnostic disable-next-line: inject-field
- frame.killsPerMinuteLabel = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.killsPerMinuteLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
- frame.killsPerMinuteLabel:SetPoint("TOPLEFT", frame.timeLabel, "BOTTOMLEFT", 0, -2)
- frame.killsPerMinuteLabel:SetText("Kills Per Minute:")
-
- ---@diagnostic disable-next-line: inject-field
- frame.killsPerMinuteCount = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.killsPerMinuteCount:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
- frame.killsPerMinuteCount:SetPoint("TOPRIGHT", frame.timeCount, "BOTTOMRIGHT", 0, -2)
- frame.killsPerMinuteCount:SetText("0")
-
- ---@diagnostic disable-next-line: inject-field
- frame.killsPerSecondLabel = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.killsPerSecondLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
- frame.killsPerSecondLabel:SetPoint("TOPLEFT", frame.killsPerMinuteLabel, "BOTTOMLEFT", 0, -2)
- frame.killsPerSecondLabel:SetText("Kills Per Second:")
-
- ---@diagnostic disable-next-line: inject-field
- frame.killsPerSecondCount = frame:CreateFontString(nil, "OVERLAY", nil)
- frame.killsPerSecondCount:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
- frame.killsPerSecondCount:SetPoint("TOPRIGHT", frame.killsPerMinuteCount, "BOTTOMRIGHT", 0, -2)
- frame.killsPerSecondCount:SetText("Kills Per Minute:")
-
- ---@diagnostic disable-next-line: inject-field
- frame.cancelButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
- frame.cancelButton:SetSize(60, 16)
- frame.cancelButton:SetPoint("BOTTOM", frame, "BOTTOM", -40, 7)
- frame.cancelButton:SetScript("OnLoad", function(self) self:Disable() end)
- frame.cancelButton:SetScript("OnClick", function() TF:Cancel() end)
- frame.cancelButton:SetText("Stop")
-
- ---@diagnostic disable-next-line: inject-field
- frame.closeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
- frame.closeButton:SetSize(60, 16)
- frame.closeButton:SetPoint("BOTTOM", frame, "BOTTOM", 40, 7)
- frame.closeButton:SetScript("OnLoad", function(self) self:Disable() end)
- frame.closeButton:SetScript("OnClick", function() TF:Close() end)
- frame.closeButton:SetText("Close")
-
- ---@diagnostic disable-next-line: inject-field
- frame.progressBar = CreateFrame("StatusBar", nil, frame)
- frame.progressBar:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
- frame.progressBar:SetStatusBarColor(0, 1, 0)
- frame.progressBar:SetMinMaxValues(0, 1)
- frame.progressBar:SetValue(0)
- frame.progressBar:SetPoint("TOPLEFT", frame.killsPerSecondLabel, "BOTTOMLEFT", -1, -2)
- frame.progressBar:SetPoint("RIGHT", frame.killsPerSecondCount, "RIGHT", 0, 0)
- frame.progressBar:SetPoint("BOTTOM", frame.cancelButton, "TOP", 0, 2)
-
- ---@diagnostic disable-next-line: inject-field
- frame.progressLabel = frame.progressBar:CreateFontString(nil, "OVERLAY", nil)
- frame.progressLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE")
- frame.progressLabel:SetAllPoints(frame.progressBar)
- frame.progressLabel:SetText("0%")
-end
-
-function TF:InitializeControls()
- frame.currentCount:SetText("0")
- frame.timeCount:SetText("00:00:00")
- frame.progressLabel:SetText("0%")
- frame.progressBar:SetValue(0)
- self:UpdateControls()
-end
-
-function TF:UpdateControls()
- Enabled(frame.cancelButton, self.Running)
- Enabled(frame.closeButton, not self.Running)
-end
-
-function TF:UpdateData(data, state)
- if state == T.State.START then
- self:InitializeControls()
- else
- local kills = T:GetData("Kills", true)
- local kpm, kps
- if data.Current <= 0 then
- kpm, kps = 0, 0
- else
- kpm = kills / (data.Current / 60)
- kps = kills / data.Current
- end
- frame.currentCount:SetText(kills)
- frame.timeCount:SetText(data.LeftFormat)
- frame.progressLabel:SetText(floor(data.Progress*100) .. "%")
- frame.progressBar:SetMinMaxValues(0, data.Total)
- frame.progressBar:SetValue(data.Current)
- frame.killsPerMinuteCount:SetText(("%.2f"):format(kpm))
- frame.killsPerSecondCount:SetText(("%.2f"):format(kps))
- if state == T.State.STOP then self:Stop() end
- end
- self:UpdateControls()
-end
-
-function TF:Start(s, m, h)
- if self.Running then return end
- self.Running = true
- SetupFrame()
- self:InitializeControls()
- frame:Show()
- if not T:Start(s, m, h, function(d, u) TF:UpdateData(d, u) end, nil) then
- self:Stop()
- frame:Hide()
- end
-end
-
-function TF:Stop()
- if not self.Running then return end
- self.Running = false
-end
-
-function TF:Cancel()
- T:Stop()
-end
-
-function TF:Close()
- if not frame then return end
- self:InitializeControls()
- frame:Hide()
-end
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+---@class KillTrackTimerFrame
+local TF = {
+ Running = false
+}
+
+KT.TimerFrame = TF
+
+local T = KT.Timer
+
+local function Enabled(object, enabled)
+ if not object.Enable or not object.Disable then return end
+ if enabled then
+ object:Enable()
+ else
+ object:Disable()
+ end
+end
+
+local frame
+
+local function SetupFrame()
+ if frame then return end
+ frame = CreateFrame("Frame", nil, UIParent, BackdropTemplateMixin and "BackdropTemplate")
+ frame:Hide()
+ frame:EnableMouse(true)
+ frame:SetMovable(true)
+ frame:SetWidth(200)
+ frame:SetHeight(93)
+
+ frame:SetPoint("CENTER")
+
+ local bd = {
+ bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
+ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
+ tile = true,
+ edgeSize = 16,
+ tileSize = 32,
+ insets = {
+ left = 2.5,
+ right = 2.5,
+ top = 2.5,
+ bottom = 2.5
+ }
+ }
+
+ frame:SetBackdrop(bd)
+
+ frame:SetScript("OnMouseDown", function(f) f:StartMoving() end)
+ frame:SetScript("OnMouseUp", function(f) f:StopMovingOrSizing() end)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.currentLabel = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.currentLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
+ frame.currentLabel:SetPoint("TOPLEFT", frame, "TOPLEFT", 6, -6)
+ frame.currentLabel:SetText("Number of kills:")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.currentCount = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.currentCount:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
+ frame.currentCount:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -6, -6)
+ frame.currentCount:SetText("0")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.timeLabel = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.timeLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
+ frame.timeLabel:SetPoint("TOPLEFT", frame.currentLabel, "BOTTOMLEFT", 0, -2)
+ frame.timeLabel:SetText("Time left:")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.timeCount = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.timeCount:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
+ frame.timeCount:SetPoint("TOPRIGHT", frame.currentCount, "BOTTOMRIGHT", 0, -2)
+ frame.timeCount:SetText("00:00:00")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.killsPerMinuteLabel = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.killsPerMinuteLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
+ frame.killsPerMinuteLabel:SetPoint("TOPLEFT", frame.timeLabel, "BOTTOMLEFT", 0, -2)
+ frame.killsPerMinuteLabel:SetText("Kills Per Minute:")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.killsPerMinuteCount = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.killsPerMinuteCount:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
+ frame.killsPerMinuteCount:SetPoint("TOPRIGHT", frame.timeCount, "BOTTOMRIGHT", 0, -2)
+ frame.killsPerMinuteCount:SetText("0")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.killsPerSecondLabel = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.killsPerSecondLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
+ frame.killsPerSecondLabel:SetPoint("TOPLEFT", frame.killsPerMinuteLabel, "BOTTOMLEFT", 0, -2)
+ frame.killsPerSecondLabel:SetText("Kills Per Second:")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.killsPerSecondCount = frame:CreateFontString(nil, "OVERLAY", nil)
+ frame.killsPerSecondCount:SetFont("Fonts\\FRIZQT__.TTF", 10, nil)
+ frame.killsPerSecondCount:SetPoint("TOPRIGHT", frame.killsPerMinuteCount, "BOTTOMRIGHT", 0, -2)
+ frame.killsPerSecondCount:SetText("Kills Per Minute:")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.cancelButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
+ frame.cancelButton:SetSize(60, 16)
+ frame.cancelButton:SetPoint("BOTTOM", frame, "BOTTOM", -40, 7)
+ frame.cancelButton:SetScript("OnLoad", function(self) self:Disable() end)
+ frame.cancelButton:SetScript("OnClick", function() TF:Cancel() end)
+ frame.cancelButton:SetText("Stop")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.closeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
+ frame.closeButton:SetSize(60, 16)
+ frame.closeButton:SetPoint("BOTTOM", frame, "BOTTOM", 40, 7)
+ frame.closeButton:SetScript("OnLoad", function(self) self:Disable() end)
+ frame.closeButton:SetScript("OnClick", function() TF:Close() end)
+ frame.closeButton:SetText("Close")
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.progressBar = CreateFrame("StatusBar", nil, frame)
+ frame.progressBar:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
+ frame.progressBar:SetStatusBarColor(0, 1, 0)
+ frame.progressBar:SetMinMaxValues(0, 1)
+ frame.progressBar:SetValue(0)
+ frame.progressBar:SetPoint("TOPLEFT", frame.killsPerSecondLabel, "BOTTOMLEFT", -1, -2)
+ frame.progressBar:SetPoint("RIGHT", frame.killsPerSecondCount, "RIGHT", 0, 0)
+ frame.progressBar:SetPoint("BOTTOM", frame.cancelButton, "TOP", 0, 2)
+
+ ---@diagnostic disable-next-line: inject-field
+ frame.progressLabel = frame.progressBar:CreateFontString(nil, "OVERLAY", nil)
+ frame.progressLabel:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE")
+ frame.progressLabel:SetAllPoints(frame.progressBar)
+ frame.progressLabel:SetText("0%")
+end
+
+function TF:InitializeControls()
+ frame.currentCount:SetText("0")
+ frame.timeCount:SetText("00:00:00")
+ frame.progressLabel:SetText("0%")
+ frame.progressBar:SetValue(0)
+ self:UpdateControls()
+end
+
+function TF:UpdateControls()
+ Enabled(frame.cancelButton, self.Running)
+ Enabled(frame.closeButton, not self.Running)
+end
+
+function TF:UpdateData(data, state)
+ if state == T.State.START then
+ self:InitializeControls()
+ else
+ local kills = T:GetData("Kills", true)
+ local kpm, kps
+ if data.Current <= 0 then
+ kpm, kps = 0, 0
+ else
+ kpm = kills / (data.Current / 60)
+ kps = kills / data.Current
+ end
+ frame.currentCount:SetText(kills)
+ frame.timeCount:SetText(data.LeftFormat)
+ frame.progressLabel:SetText(floor(data.Progress*100) .. "%")
+ frame.progressBar:SetMinMaxValues(0, data.Total)
+ frame.progressBar:SetValue(data.Current)
+ frame.killsPerMinuteCount:SetText(("%.2f"):format(kpm))
+ frame.killsPerSecondCount:SetText(("%.2f"):format(kps))
+ if state == T.State.STOP then self:Stop() end
+ end
+ self:UpdateControls()
+end
+
+function TF:Start(s, m, h)
+ if self.Running then return end
+ self.Running = true
+ SetupFrame()
+ self:InitializeControls()
+ frame:Show()
+ if not T:Start(s, m, h, function(d, u) TF:UpdateData(d, u) end, nil) then
+ self:Stop()
+ frame:Hide()
+ end
+end
+
+function TF:Stop()
+ if not self.Running then return end
+ self.Running = false
+end
+
+function TF:Cancel()
+ T:Stop()
+end
+
+function TF:Close()
+ if not frame then return end
+ self:InitializeControls()
+ frame:Hide()
+end
diff --git a/Tools.lua b/Tools.lua
index 7d022aa..d88f553 100644
--- a/Tools.lua
+++ b/Tools.lua
@@ -1,146 +1,146 @@
---[[
- * Copyright (c) 2011-2020 by Adam Hellberg.
- *
- * This file is part of KillTrack.
- *
- * KillTrack is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * KillTrack is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with KillTrack. If not, see .
---]]
-
----@class KillTrack
-local KT = select(2, ...)
-
----@class KillTrackTools
-local KTT = {}
-
-KT.Tools = KTT
-
-------------------
--- NUMBER TOOLS --
-------------------
-
----@param seconds number
----@return string
-function KTT:FormatSeconds(seconds)
- local hours = floor(seconds / 3600)
- local minutes = floor(seconds / 60) - hours * 60
- seconds = seconds - minutes * 60 - hours * 3600
- return ("%02d:%02d:%02d"):format(hours, minutes, seconds)
-end
-
-------------------
--- STRING TOOLS --
-------------------
-
----@param s string
----@return string
-function KTT:Trim(s)
- return (s:gsub("^%s*(.-)%s*$", "%1"))
-end
-
----@param s string
----@return string[]
-function KTT:Split(s)
- local r = {}
- local tokpat = "%S+"
- local spat = [=[^(['"])]=]
- local epat = [=[(['"])$]=]
- local escpat = [=[(\*)['"]$]=]
- local buf, quoted
- for token in string.gmatch(s, tokpat) do
- local squoted = token:match(spat)
- local equoted = token:match(epat)
- local escaped = token:match(escpat)
- if squoted and not quoted and not equoted then
- buf, quoted = token, squoted
- elseif buf and equoted == quoted and #escaped % 2 == 0 then
- token, buf, quoted = buf .. " " .. token, nil, nil
- elseif buf then
- buf = buf .. " " .. token
- end
- if not buf then
- r[#r + 1] = token:gsub(spat, ""):gsub(epat, ""):gsub([[\(.)]], "%1")
- end
- end
- if buf then
- r[#r + 1] = buf
- end
- return r
-end
-
------------------
--- TABLE TOOLS --
------------------
-
----@param tbl table
----@param val any
----@return boolean
-function KTT:InTable(tbl, val)
- for _,v in pairs(tbl) do
- if v == val then return true end
- end
- return false
-end
-
----@param tbl table
----@param cache table?
----@return table
-function KTT:TableCopy(tbl, cache)
- if type(tbl) ~= "table" then return tbl end
- cache = cache or {}
- if cache[tbl] then return cache[tbl] end
- local copy = {}
- cache[tbl] = copy
- for k, v in pairs(tbl) do
- copy[self:TableCopy(k, cache)] = self:TableCopy(v, cache)
- end
- return copy
-end
-
----@param table table
----@return integer
-function KTT:TableLength(table)
- local count = 0
- for _, _ in pairs(table) do
- count = count + 1
- end
- return count
-end
-
---------------------
--- DATETIME TOOLS --
---------------------
-
----@param timestamp number?
----@param format string?
----@return string|osdate
-function KTT:FormatDateTime(timestamp, format)
- timestamp = timestamp or GetServerTime()
- format = format or KT.Global.DATETIME_FORMAT or KT.Defaults.DateTimeFormat
- return date(format, timestamp)
-end
-
------------------
--- OTHER TOOLS --
------------------
-
-local ssplit = strsplit
-
----@param guid string?
----@return integer?
-function KTT.GUIDToID(guid)
- if not guid then return nil end
- -- local id = guid:match("^%w+%-0%-%d+%-%d+%-%d+%-(%d+)%-[A-Z%d]+$")
- local _, _, _, _, _, id = ssplit("-", guid)
- return tonumber(id)
-end
+--[[
+ * Copyright (c) 2011-2020 by Adam Hellberg.
+ *
+ * This file is part of KillTrack.
+ *
+ * KillTrack is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * KillTrack is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with KillTrack. If not, see .
+--]]
+
+---@class KillTrack
+local KT = select(2, ...)
+
+---@class KillTrackTools
+local KTT = {}
+
+KT.Tools = KTT
+
+------------------
+-- NUMBER TOOLS --
+------------------
+
+---@param seconds number
+---@return string
+function KTT:FormatSeconds(seconds)
+ local hours = floor(seconds / 3600)
+ local minutes = floor(seconds / 60) - hours * 60
+ seconds = seconds - minutes * 60 - hours * 3600
+ return ("%02d:%02d:%02d"):format(hours, minutes, seconds)
+end
+
+------------------
+-- STRING TOOLS --
+------------------
+
+---@param s string
+---@return string
+function KTT:Trim(s)
+ return (s:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+---@param s string
+---@return string[]
+function KTT:Split(s)
+ local r = {}
+ local tokpat = "%S+"
+ local spat = [=[^(['"])]=]
+ local epat = [=[(['"])$]=]
+ local escpat = [=[(\*)['"]$]=]
+ local buf, quoted
+ for token in string.gmatch(s, tokpat) do
+ local squoted = token:match(spat)
+ local equoted = token:match(epat)
+ local escaped = token:match(escpat)
+ if squoted and not quoted and not equoted then
+ buf, quoted = token, squoted
+ elseif buf and equoted == quoted and #escaped % 2 == 0 then
+ token, buf, quoted = buf .. " " .. token, nil, nil
+ elseif buf then
+ buf = buf .. " " .. token
+ end
+ if not buf then
+ r[#r + 1] = token:gsub(spat, ""):gsub(epat, ""):gsub([[\(.)]], "%1")
+ end
+ end
+ if buf then
+ r[#r + 1] = buf
+ end
+ return r
+end
+
+-----------------
+-- TABLE TOOLS --
+-----------------
+
+---@param tbl table
+---@param val any
+---@return boolean
+function KTT:InTable(tbl, val)
+ for _,v in pairs(tbl) do
+ if v == val then return true end
+ end
+ return false
+end
+
+---@param tbl table
+---@param cache table?
+---@return table
+function KTT:TableCopy(tbl, cache)
+ if type(tbl) ~= "table" then return tbl end
+ cache = cache or {}
+ if cache[tbl] then return cache[tbl] end
+ local copy = {}
+ cache[tbl] = copy
+ for k, v in pairs(tbl) do
+ copy[self:TableCopy(k, cache)] = self:TableCopy(v, cache)
+ end
+ return copy
+end
+
+---@param table table
+---@return integer
+function KTT:TableLength(table)
+ local count = 0
+ for _, _ in pairs(table) do
+ count = count + 1
+ end
+ return count
+end
+
+--------------------
+-- DATETIME TOOLS --
+--------------------
+
+---@param timestamp number?
+---@param format string?
+---@return string|osdate
+function KTT:FormatDateTime(timestamp, format)
+ timestamp = timestamp or GetServerTime()
+ format = format or KT.Global.DATETIME_FORMAT or KT.Defaults.DateTimeFormat
+ return date(format, timestamp)
+end
+
+-----------------
+-- OTHER TOOLS --
+-----------------
+
+local ssplit = strsplit
+
+---@param guid string?
+---@return integer?
+function KTT.GUIDToID(guid)
+ if not guid then return nil end
+ -- local id = guid:match("^%w+%-0%-%d+%-%d+%-%d+%-(%d+)%-[A-Z%d]+$")
+ local _, _, _, _, _, id = ssplit("-", guid)
+ return tonumber(id)
+end