Compare commits
12 Commits
5f1c24495c
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f97b7556cd | ||
|
|
cf78405148 | ||
| feaa62309c | |||
|
|
02e062d66b | ||
|
|
f1d2a761e4 | ||
|
|
258cadeba5 | ||
|
|
8c37da2d38 | ||
|
|
6151b434b1 | ||
|
|
50ff7c93b4 | ||
|
|
7ab82e7655 | ||
|
|
e5d39d88ea | ||
|
|
c3326dde88 |
@@ -19,38 +19,41 @@ jobs:
|
|||||||
- name: Variablen setzen
|
- name: Variablen setzen
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
echo "REPO=${{ gitea.repository }}" >> $GITHUB_ENV
|
echo "REPO=${{ gitea.repository }}" >> "$GITHUB_ENV"
|
||||||
echo "TAG=${{ gitea.ref_name }}" >> $GITHUB_ENV
|
echo "TAG=${{ gitea.ref_name }}" >> "$GITHUB_ENV"
|
||||||
echo "SERVER_URL=${{ gitea.server_url }}" >> $GITHUB_ENV
|
echo "SERVER_URL=https://git.local.unique-studios.de" >> "$GITHUB_ENV"
|
||||||
echo "API_BASE=${{ gitea.server_url }}/api/v1" >> $GITHUB_ENV
|
echo "API_BASE=https://git.local.unique-studios.de/api/v1" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Repo klonen
|
- name: Repo klonen
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
rm -rf /tmp/repo /tmp/build /tmp/release.json /tmp/assets.json
|
rm -rf /tmp/repo /tmp/build /tmp/release.json /tmp/assets.json
|
||||||
|
|
||||||
CLONE_URL="${SERVER_URL#https://}"
|
git clone "https://oauth2:${{ secrets.PAT_TOKEN }}@git.local.unique-studios.de/${REPO}.git" /tmp/repo
|
||||||
git clone "https://oauth2:${{ secrets.PAT_TOKEN }}@${CLONE_URL}/${REPO}.git" /tmp/repo
|
|
||||||
|
|
||||||
- name: ZIP mit Addon-Ordner bauen
|
- name: ZIP mit Addon-Ordner bauen
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
mkdir -p /tmp/build/HailMaryGuildTools
|
|
||||||
|
REPO_NAME="${REPO##*/}"
|
||||||
|
|
||||||
|
mkdir -p "/tmp/build/${REPO_NAME}"
|
||||||
|
|
||||||
rsync -a \
|
rsync -a \
|
||||||
--exclude='.git' \
|
--exclude='.git' \
|
||||||
--exclude='.gitea' \
|
--exclude='.gitea' \
|
||||||
/tmp/repo/ /tmp/build/HailMaryGuildTools/
|
/tmp/repo/ "/tmp/build/${REPO_NAME}/"
|
||||||
|
|
||||||
cd /tmp/build
|
cd /tmp/build
|
||||||
zip -r "/tmp/HailMaryGuildTools-${TAG}.zip" HailMaryGuildTools
|
zip -r "/tmp/${REPO_NAME}-${TAG}.zip" "${REPO_NAME}"
|
||||||
ls -lh "/tmp/HailMaryGuildTools-${TAG}.zip"
|
ls -lh "/tmp/${REPO_NAME}-${TAG}.zip"
|
||||||
|
|
||||||
- name: Release anlegen oder laden
|
- name: Release anlegen oder laden
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
API="${API_BASE}/repos/${REPO}"
|
API="${API_BASE}/repos/${REPO}"
|
||||||
|
REPO_NAME="${REPO##*/}"
|
||||||
|
|
||||||
echo "Server: ${SERVER_URL}"
|
echo "Server: ${SERVER_URL}"
|
||||||
echo "Repo: ${REPO}"
|
echo "Repo: ${REPO}"
|
||||||
@@ -72,7 +75,7 @@ jobs:
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{
|
-d "{
|
||||||
\"tag_name\": \"${TAG}\",
|
\"tag_name\": \"${TAG}\",
|
||||||
\"name\": \"${REPO##*/} ${TAG}\",
|
\"name\": \"${REPO_NAME} ${TAG}\",
|
||||||
\"draft\": false,
|
\"draft\": false,
|
||||||
\"prerelease\": false
|
\"prerelease\": false
|
||||||
}" \
|
}" \
|
||||||
@@ -91,7 +94,8 @@ jobs:
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
RELEASE_ID=$(jq -r '.id' /tmp/release.json)
|
RELEASE_ID=$(jq -r '.id' /tmp/release.json)
|
||||||
FILE_NAME="HailMaryGuildTools-${TAG}.zip"
|
REPO_NAME="${REPO##*/}"
|
||||||
|
FILE_NAME="${REPO_NAME}-${TAG}.zip"
|
||||||
ASSET_API="${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets"
|
ASSET_API="${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets"
|
||||||
|
|
||||||
curl --fail -s \
|
curl --fail -s \
|
||||||
@@ -117,7 +121,8 @@ jobs:
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
RELEASE_ID=$(jq -r '.id' /tmp/release.json)
|
RELEASE_ID=$(jq -r '.id' /tmp/release.json)
|
||||||
FILE="/tmp/HailMaryGuildTools-${TAG}.zip"
|
REPO_NAME="${REPO##*/}"
|
||||||
|
FILE="/tmp/${REPO_NAME}-${TAG}.zip"
|
||||||
FILE_NAME="$(basename "$FILE")"
|
FILE_NAME="$(basename "$FILE")"
|
||||||
UPLOAD_URL="${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${FILE_NAME}"
|
UPLOAD_URL="${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${FILE_NAME}"
|
||||||
|
|
||||||
|
|||||||
@@ -1,486 +0,0 @@
|
|||||||
local ADDON_NAME = "HailMaryGuildTools"
|
|
||||||
local HMGT = _G[ADDON_NAME]
|
|
||||||
if not HMGT then return end
|
|
||||||
|
|
||||||
local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
|
||||||
local AceGUI = LibStub("AceGUI-3.0", true)
|
|
||||||
|
|
||||||
local function GetOrderedDebugLevels()
|
|
||||||
return { "error", "info", "verbose" }
|
|
||||||
end
|
|
||||||
|
|
||||||
local function GetOrderedDebugScopes()
|
|
||||||
local values = HMGT:GetDebugScopeOptions() or {}
|
|
||||||
local names = { "ALL" }
|
|
||||||
for scope in pairs(values) do
|
|
||||||
if scope ~= "ALL" then
|
|
||||||
names[#names + 1] = scope
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.sort(names, function(a, b)
|
|
||||||
if a == "ALL" then return true end
|
|
||||||
if b == "ALL" then return false end
|
|
||||||
return tostring(values[a] or a) < tostring(values[b] or b)
|
|
||||||
end)
|
|
||||||
return names, values
|
|
||||||
end
|
|
||||||
|
|
||||||
local function SetFilterButtonText(buttonWidget, prefix, valueLabel)
|
|
||||||
if not buttonWidget then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
buttonWidget:SetText(string.format("%s: %s", tostring(prefix or ""), tostring(valueLabel or "")))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function AdvanceDebugLevel(step)
|
|
||||||
local levels = GetOrderedDebugLevels()
|
|
||||||
local current = HMGT:GetConfiguredDebugLevel()
|
|
||||||
local nextIndex = 1
|
|
||||||
for index, value in ipairs(levels) do
|
|
||||||
if value == current then
|
|
||||||
nextIndex = index + (step or 1)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if nextIndex < 1 then
|
|
||||||
nextIndex = #levels
|
|
||||||
elseif nextIndex > #levels then
|
|
||||||
nextIndex = 1
|
|
||||||
end
|
|
||||||
HMGT.db.profile.debugLevel = levels[nextIndex]
|
|
||||||
HMGT:RefreshDebugWindow()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function AdvanceDebugScope(step)
|
|
||||||
local scopes, labels = GetOrderedDebugScopes()
|
|
||||||
local current = (HMGT.db and HMGT.db.profile and HMGT.db.profile.debugScope) or "ALL"
|
|
||||||
local nextIndex = 1
|
|
||||||
for index, value in ipairs(scopes) do
|
|
||||||
if value == current then
|
|
||||||
nextIndex = index + (step or 1)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if nextIndex < 1 then
|
|
||||||
nextIndex = #scopes
|
|
||||||
elseif nextIndex > #scopes then
|
|
||||||
nextIndex = 1
|
|
||||||
end
|
|
||||||
HMGT.db.profile.debugScope = scopes[nextIndex]
|
|
||||||
HMGT:RefreshDebugWindow()
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:SetDebugWindowMinimized(minimized)
|
|
||||||
local frame = self.debugWindow
|
|
||||||
if not frame then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
minimized = minimized and true or false
|
|
||||||
self.debugWindowStatus = self.debugWindowStatus or {
|
|
||||||
width = 860,
|
|
||||||
height = 340,
|
|
||||||
}
|
|
||||||
self.debugWindowStatus.minimized = minimized
|
|
||||||
|
|
||||||
local collapsedHeight = 64
|
|
||||||
if minimized then
|
|
||||||
self.debugWindowStatus.restoreHeight = self.debugWindowStatus.height or frame:GetHeight() or 340
|
|
||||||
end
|
|
||||||
|
|
||||||
local targetHeight = minimized
|
|
||||||
and collapsedHeight
|
|
||||||
or (self.debugWindowStatus.restoreHeight or self.debugWindowStatus.height or 340)
|
|
||||||
|
|
||||||
if frame.aceWidget then
|
|
||||||
frame.aceWidget:EnableResize(not minimized)
|
|
||||||
frame.aceWidget:SetHeight(targetHeight)
|
|
||||||
else
|
|
||||||
frame:SetHeight(targetHeight)
|
|
||||||
end
|
|
||||||
|
|
||||||
if frame.minimizeButton then
|
|
||||||
frame.minimizeButton:SetText(minimized and "+" or "-")
|
|
||||||
end
|
|
||||||
if frame.clearButton then
|
|
||||||
local buttonFrame = frame.clearButton.frame or frame.clearButton
|
|
||||||
buttonFrame:SetShown(not minimized)
|
|
||||||
end
|
|
||||||
if frame.selectButton then
|
|
||||||
local buttonFrame = frame.selectButton.frame or frame.selectButton
|
|
||||||
buttonFrame:SetShown(not minimized)
|
|
||||||
end
|
|
||||||
if frame.levelFilter then
|
|
||||||
local filterFrame = frame.levelFilter.frame or frame.levelFilter
|
|
||||||
filterFrame:SetShown(not minimized)
|
|
||||||
end
|
|
||||||
if frame.scopeFilter then
|
|
||||||
local filterFrame = frame.scopeFilter.frame or frame.scopeFilter
|
|
||||||
filterFrame:SetShown(not minimized)
|
|
||||||
end
|
|
||||||
if frame.logWidget then
|
|
||||||
frame.logWidget.frame:SetShown(not minimized)
|
|
||||||
end
|
|
||||||
if frame.scrollBG then
|
|
||||||
frame.scrollBG:SetShown(not minimized)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not minimized then
|
|
||||||
self:RefreshDebugWindow()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:ToggleDebugWindowMinimized()
|
|
||||||
self:SetDebugWindowMinimized(not (self.debugWindowStatus and self.debugWindowStatus.minimized))
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:EnsureDebugWindow()
|
|
||||||
if self.debugWindow then
|
|
||||||
return self.debugWindow
|
|
||||||
end
|
|
||||||
|
|
||||||
local frameWidget
|
|
||||||
if AceGUI then
|
|
||||||
frameWidget = AceGUI:Create("Frame")
|
|
||||||
self.debugWindowStatus = self.debugWindowStatus or {
|
|
||||||
width = 860,
|
|
||||||
height = 340,
|
|
||||||
}
|
|
||||||
frameWidget:SetTitle(L["DEBUG_WINDOW_TITLE"] or "HMGT Debug Console")
|
|
||||||
frameWidget:SetStatusText(L["DEBUG_WINDOW_HINT"] or "Mouse wheel scrolls, Ctrl+A selects all, Ctrl+C copies selected text")
|
|
||||||
frameWidget:SetStatusTable(self.debugWindowStatus)
|
|
||||||
frameWidget:SetWidth(self.debugWindowStatus.width or 860)
|
|
||||||
frameWidget:SetHeight(self.debugWindowStatus.height or 340)
|
|
||||||
frameWidget:EnableResize(true)
|
|
||||||
frameWidget.frame:SetClampedToScreen(true)
|
|
||||||
frameWidget.frame:SetToplevel(true)
|
|
||||||
frameWidget.frame:SetFrameStrata("FULLSCREEN_DIALOG")
|
|
||||||
frameWidget:Hide()
|
|
||||||
end
|
|
||||||
|
|
||||||
local frame = frameWidget and frameWidget.frame or CreateFrame("Frame", "HMGT_DebugWindow", UIParent, "BackdropTemplate")
|
|
||||||
if not frameWidget then
|
|
||||||
frame:SetSize(860, 340)
|
|
||||||
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
|
||||||
frame:SetFrameStrata("DIALOG")
|
|
||||||
frame:SetClampedToScreen(true)
|
|
||||||
frame:SetMovable(true)
|
|
||||||
frame:EnableMouse(true)
|
|
||||||
frame:RegisterForDrag("LeftButton")
|
|
||||||
frame:SetScript("OnDragStart", function(selfFrame) selfFrame:StartMoving() end)
|
|
||||||
frame:SetScript("OnDragStop", function(selfFrame) selfFrame:StopMovingOrSizing() end)
|
|
||||||
frame:SetBackdrop({
|
|
||||||
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
|
|
||||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
||||||
edgeSize = 12,
|
|
||||||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
|
||||||
})
|
|
||||||
frame:SetBackdropColor(0.05, 0.05, 0.06, 0.95)
|
|
||||||
frame:SetBackdropBorderColor(0.35, 0.55, 0.85, 1)
|
|
||||||
frame:Hide()
|
|
||||||
|
|
||||||
local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
|
||||||
title:SetPoint("TOPLEFT", frame, "TOPLEFT", 14, -12)
|
|
||||||
title:SetText(L["DEBUG_WINDOW_TITLE"] or "HMGT Debug Console")
|
|
||||||
frame.title = title
|
|
||||||
|
|
||||||
local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
|
|
||||||
closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -5, -5)
|
|
||||||
frame.closeButton = closeButton
|
|
||||||
|
|
||||||
local minimizeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
|
|
||||||
minimizeButton:SetSize(22, 20)
|
|
||||||
minimizeButton:SetPoint("TOPRIGHT", closeButton, "TOPLEFT", -2, 0)
|
|
||||||
minimizeButton:SetText((self.debugWindowStatus and self.debugWindowStatus.minimized) and "+" or "-")
|
|
||||||
minimizeButton:SetScript("OnClick", function()
|
|
||||||
HMGT:ToggleDebugWindowMinimized()
|
|
||||||
end)
|
|
||||||
frame.minimizeButton = minimizeButton
|
|
||||||
end
|
|
||||||
|
|
||||||
frame.aceWidget = frameWidget
|
|
||||||
|
|
||||||
if frameWidget and AceGUI then
|
|
||||||
local content = frameWidget.content
|
|
||||||
|
|
||||||
local minimizeButton = AceGUI:Create("Button")
|
|
||||||
minimizeButton:SetText((self.debugWindowStatus and self.debugWindowStatus.minimized) and "+" or "-")
|
|
||||||
minimizeButton:SetWidth(24)
|
|
||||||
minimizeButton:SetCallback("OnClick", function()
|
|
||||||
HMGT:ToggleDebugWindowMinimized()
|
|
||||||
end)
|
|
||||||
minimizeButton.frame:SetParent(frame)
|
|
||||||
minimizeButton.frame:ClearAllPoints()
|
|
||||||
minimizeButton.frame:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -34, -4)
|
|
||||||
minimizeButton.frame:SetHeight(20)
|
|
||||||
minimizeButton.frame:Show()
|
|
||||||
frame.minimizeButton = minimizeButton
|
|
||||||
|
|
||||||
local clearButton = AceGUI:Create("Button")
|
|
||||||
clearButton:SetText(L["OPT_DEBUG_CLEAR"] or "Clear log")
|
|
||||||
clearButton:SetWidth(120)
|
|
||||||
clearButton:SetCallback("OnClick", function()
|
|
||||||
HMGT:ClearDebugLog()
|
|
||||||
end)
|
|
||||||
clearButton.frame:SetParent(content)
|
|
||||||
clearButton.frame:ClearAllPoints()
|
|
||||||
clearButton.frame:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, -2)
|
|
||||||
clearButton.frame:Show()
|
|
||||||
frame.clearButton = clearButton
|
|
||||||
|
|
||||||
local selectButton = AceGUI:Create("Button")
|
|
||||||
selectButton:SetText(L["OPT_DEBUG_SELECT_ALL"] or "Select all")
|
|
||||||
selectButton:SetWidth(120)
|
|
||||||
selectButton:SetCallback("OnClick", function()
|
|
||||||
if frame.editBox then
|
|
||||||
frame.editBox:SetFocus()
|
|
||||||
frame.editBox:HighlightText(0)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
selectButton.frame:SetParent(content)
|
|
||||||
selectButton.frame:ClearAllPoints()
|
|
||||||
selectButton.frame:SetPoint("TOPRIGHT", clearButton.frame, "TOPLEFT", -6, 0)
|
|
||||||
selectButton.frame:Show()
|
|
||||||
frame.selectButton = selectButton
|
|
||||||
|
|
||||||
local levelFilter = AceGUI:Create("Button")
|
|
||||||
levelFilter:SetWidth(150)
|
|
||||||
levelFilter:SetCallback("OnClick", function()
|
|
||||||
AdvanceDebugLevel(1)
|
|
||||||
end)
|
|
||||||
levelFilter.frame:SetParent(content)
|
|
||||||
levelFilter.frame:ClearAllPoints()
|
|
||||||
levelFilter.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, 0)
|
|
||||||
levelFilter.frame:Show()
|
|
||||||
frame.levelFilter = levelFilter
|
|
||||||
|
|
||||||
local scopeFilter = AceGUI:Create("Button")
|
|
||||||
scopeFilter:SetWidth(180)
|
|
||||||
scopeFilter:SetCallback("OnClick", function()
|
|
||||||
AdvanceDebugScope(1)
|
|
||||||
end)
|
|
||||||
scopeFilter.frame:SetParent(content)
|
|
||||||
scopeFilter.frame:ClearAllPoints()
|
|
||||||
scopeFilter.frame:SetPoint("TOPLEFT", levelFilter.frame, "TOPRIGHT", 8, 0)
|
|
||||||
scopeFilter.frame:Show()
|
|
||||||
frame.scopeFilter = scopeFilter
|
|
||||||
|
|
||||||
local logWidget = AceGUI:Create("MultiLineEditBox")
|
|
||||||
logWidget:SetLabel("")
|
|
||||||
logWidget:DisableButton(true)
|
|
||||||
logWidget:SetNumLines(18)
|
|
||||||
logWidget:SetText("")
|
|
||||||
logWidget.frame:SetParent(content)
|
|
||||||
logWidget.frame:ClearAllPoints()
|
|
||||||
logWidget.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -54)
|
|
||||||
logWidget.frame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", 0, 0)
|
|
||||||
logWidget.frame:Show()
|
|
||||||
logWidget:SetCallback("OnTextChanged", function()
|
|
||||||
HMGT:RefreshDebugWindow()
|
|
||||||
end)
|
|
||||||
logWidget.editBox:SetScript("OnKeyDown", function(selfBox, key)
|
|
||||||
if IsControlKeyDown() and (key == "A" or key == "a") then
|
|
||||||
selfBox:HighlightText(0)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
frame.logWidget = logWidget
|
|
||||||
frame.editBox = logWidget.editBox
|
|
||||||
frame.scrollFrame = logWidget.scrollFrame
|
|
||||||
|
|
||||||
self.debugWindow = frame
|
|
||||||
self:SetDebugWindowMinimized(self.debugWindowStatus and self.debugWindowStatus.minimized)
|
|
||||||
return frame
|
|
||||||
end
|
|
||||||
|
|
||||||
local clearButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
|
|
||||||
clearButton:SetSize(90, 22)
|
|
||||||
clearButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -30, -6)
|
|
||||||
clearButton:SetText(L["OPT_DEBUG_CLEAR"] or "Clear log")
|
|
||||||
clearButton:SetScript("OnClick", function()
|
|
||||||
HMGT:ClearDebugLog()
|
|
||||||
end)
|
|
||||||
frame.clearButton = clearButton
|
|
||||||
|
|
||||||
local selectButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
|
|
||||||
selectButton:SetSize(90, 22)
|
|
||||||
selectButton:SetPoint("TOPRIGHT", clearButton, "TOPLEFT", -6, 0)
|
|
||||||
selectButton:SetText(L["OPT_DEBUG_SELECT_ALL"] or "Select all")
|
|
||||||
selectButton:SetScript("OnClick", function()
|
|
||||||
if frame.editBox then
|
|
||||||
frame.editBox:SetFocus()
|
|
||||||
frame.editBox:HighlightText(0)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
frame.selectButton = selectButton
|
|
||||||
|
|
||||||
local scopeFilter = CreateFrame("Frame", nil, frame)
|
|
||||||
scopeFilter:SetSize(170, 22)
|
|
||||||
scopeFilter:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -8)
|
|
||||||
frame.scopeFilter = scopeFilter
|
|
||||||
|
|
||||||
local scrollBG = CreateFrame("Frame", nil, frame, "BackdropTemplate")
|
|
||||||
scrollBG:SetBackdrop({
|
|
||||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
||||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
||||||
edgeSize = 16,
|
|
||||||
insets = { left = 4, right = 3, top = 4, bottom = 3 },
|
|
||||||
})
|
|
||||||
scrollBG:SetBackdropColor(0, 0, 0, 0.95)
|
|
||||||
scrollBG:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
|
|
||||||
scrollBG:SetPoint("TOPLEFT", frame, "TOPLEFT", 14, -36)
|
|
||||||
scrollBG:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -30, 14)
|
|
||||||
frame.scrollBG = scrollBG
|
|
||||||
|
|
||||||
local scrollFrame = CreateFrame("ScrollFrame", nil, scrollBG, "UIPanelScrollFrameTemplate")
|
|
||||||
scrollFrame:SetPoint("TOPLEFT", scrollBG, "TOPLEFT", 6, -6)
|
|
||||||
scrollFrame:SetPoint("BOTTOMRIGHT", scrollBG, "BOTTOMRIGHT", -27, 4)
|
|
||||||
scrollFrame:EnableMouseWheel(true)
|
|
||||||
scrollFrame:SetScript("OnMouseWheel", function(selfMsg, delta)
|
|
||||||
if delta > 0 then
|
|
||||||
selfMsg:SetVerticalScroll(math.max(0, selfMsg:GetVerticalScroll() - 42))
|
|
||||||
else
|
|
||||||
selfMsg:SetVerticalScroll(selfMsg:GetVerticalScroll() + 42)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
frame.scrollFrame = scrollFrame
|
|
||||||
|
|
||||||
local editBox = CreateFrame("EditBox", nil, scrollFrame)
|
|
||||||
editBox:SetMultiLine(true)
|
|
||||||
editBox:SetAutoFocus(false)
|
|
||||||
editBox:SetFontObject(ChatFontNormal)
|
|
||||||
editBox:SetWidth(780)
|
|
||||||
editBox:SetTextInsets(6, 6, 6, 6)
|
|
||||||
editBox:EnableMouse(true)
|
|
||||||
editBox:SetScript("OnEscapePressed", function(selfBox)
|
|
||||||
selfBox:ClearFocus()
|
|
||||||
end)
|
|
||||||
editBox:SetScript("OnKeyDown", function(selfBox, key)
|
|
||||||
if IsControlKeyDown() and (key == "A" or key == "a") then
|
|
||||||
selfBox:HighlightText(0)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
editBox:SetScript("OnTextChanged", function(selfBox, userInput)
|
|
||||||
if userInput then
|
|
||||||
HMGT:RefreshDebugWindow()
|
|
||||||
else
|
|
||||||
selfBox:SetCursorPosition(selfBox:GetNumLetters())
|
|
||||||
selfBox:SetHeight(math.max(scrollFrame:GetHeight(), HMGT:GetDebugWindowTextHeight(frame, selfBox:GetText()) + 16))
|
|
||||||
scrollFrame:UpdateScrollChildRect()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
editBox:SetScript("OnMouseUp", function(selfBox)
|
|
||||||
selfBox:SetFocus()
|
|
||||||
end)
|
|
||||||
scrollFrame:SetScrollChild(editBox)
|
|
||||||
frame.editBox = editBox
|
|
||||||
|
|
||||||
local measureText = frame:CreateFontString(nil, "ARTWORK", "ChatFontNormal")
|
|
||||||
measureText:SetJustifyH("LEFT")
|
|
||||||
measureText:SetJustifyV("TOP")
|
|
||||||
if measureText.SetSpacing then
|
|
||||||
measureText:SetSpacing(2)
|
|
||||||
end
|
|
||||||
measureText:SetWidth(768)
|
|
||||||
frame.measureText = measureText
|
|
||||||
|
|
||||||
self.debugWindow = frame
|
|
||||||
self:SetDebugWindowMinimized(self.debugWindowStatus and self.debugWindowStatus.minimized)
|
|
||||||
return frame
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:GetDebugWindowTextHeight(frame, text)
|
|
||||||
if not frame or not frame.measureText then
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local width = 768
|
|
||||||
if frame.editBox then
|
|
||||||
width = math.max(1, (frame.editBox:GetWidth() or width) - 12)
|
|
||||||
end
|
|
||||||
frame.measureText:SetWidth(width)
|
|
||||||
frame.measureText:SetText(text or "")
|
|
||||||
return frame.measureText:GetStringHeight()
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:RefreshDebugWindow()
|
|
||||||
local frame = self:EnsureDebugWindow()
|
|
||||||
if not frame then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local filtered = self:GetFilteredDebugBuffer() or self.debugBuffer or {}
|
|
||||||
local text = table.concat(filtered, "\n")
|
|
||||||
if frame.logWidget and frame.editBox then
|
|
||||||
if frame.levelFilter then
|
|
||||||
local levelOptions = self:GetDebugLevelOptions()
|
|
||||||
SetFilterButtonText(frame.levelFilter, L["OPT_DEBUG_LEVEL"] or "Level", levelOptions[self:GetConfiguredDebugLevel()])
|
|
||||||
end
|
|
||||||
if frame.scopeFilter then
|
|
||||||
local scopeOptions = self:GetDebugScopeOptions()
|
|
||||||
local currentScope = (self.db and self.db.profile and self.db.profile.debugScope) or "ALL"
|
|
||||||
SetFilterButtonText(frame.scopeFilter, L["OPT_DEBUG_SCOPE"] or "Module", scopeOptions[currentScope] or currentScope)
|
|
||||||
end
|
|
||||||
frame.logWidget:SetText(text)
|
|
||||||
frame.editBox:SetCursorPosition(frame.editBox:GetNumLetters())
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if not frame.editBox then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
frame.editBox:SetText(text)
|
|
||||||
frame.editBox:SetCursorPosition(#text)
|
|
||||||
frame.editBox:SetHeight(math.max(frame.scrollFrame:GetHeight(), self:GetDebugWindowTextHeight(frame, text) + 16))
|
|
||||||
frame.scrollFrame:SetVerticalScroll(math.max(0, frame.editBox:GetHeight() - frame.scrollFrame:GetHeight()))
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:UpdateDebugWindowVisibility()
|
|
||||||
if self.db and self.db.profile then
|
|
||||||
self.db.profile.debug = false
|
|
||||||
end
|
|
||||||
local frame = self.debugWindow
|
|
||||||
if not frame then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local widget = frame.aceWidget
|
|
||||||
if widget then
|
|
||||||
widget:Hide()
|
|
||||||
else
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:ClearDebugLog()
|
|
||||||
wipe(self.debugBuffer)
|
|
||||||
if self.debugWindow and self.debugWindow.logWidget then
|
|
||||||
self.debugWindow.logWidget:SetText("")
|
|
||||||
self.debugWindow.editBox:SetCursorPosition(0)
|
|
||||||
self.debugWindow.scrollFrame:SetVerticalScroll(0)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if self.debugWindow and self.debugWindow.editBox then
|
|
||||||
self.debugWindow.editBox:SetText("")
|
|
||||||
self.debugWindow.scrollFrame:SetVerticalScroll(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:ToggleDebugWindowShortcut()
|
|
||||||
if self.db and self.db.profile then
|
|
||||||
self.db.profile.debug = false
|
|
||||||
end
|
|
||||||
local frame = self.debugWindow
|
|
||||||
if not frame then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local widget = frame.aceWidget
|
|
||||||
if widget then
|
|
||||||
widget:Hide()
|
|
||||||
else
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function HMGT:DumpDebugLog(maxLines)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
@@ -5,7 +5,7 @@ if not HMGT then return end
|
|||||||
local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
||||||
|
|
||||||
HMGT.devToolsBuffer = HMGT.devToolsBuffer or {}
|
HMGT.devToolsBuffer = HMGT.devToolsBuffer or {}
|
||||||
HMGT.devToolsBufferMax = HMGT.devToolsBufferMax or 300
|
HMGT.devToolsBufferMax = HMGT.devToolsBufferMax or 500
|
||||||
|
|
||||||
local DEVTOOLS_SCOPE_ALL = "ALL"
|
local DEVTOOLS_SCOPE_ALL = "ALL"
|
||||||
local DEVTOOLS_SCOPE_LABELS = {
|
local DEVTOOLS_SCOPE_LABELS = {
|
||||||
@@ -20,7 +20,8 @@ local DEVTOOLS_SCOPE_LABELS = {
|
|||||||
|
|
||||||
local DEVTOOLS_LEVELS = {
|
local DEVTOOLS_LEVELS = {
|
||||||
error = 1,
|
error = 1,
|
||||||
trace = 2,
|
info = 2,
|
||||||
|
verbose = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function TrimText(value)
|
local function TrimText(value)
|
||||||
@@ -76,8 +77,10 @@ function HMGT:GetDevToolsSettings()
|
|||||||
profile.devTools = type(profile.devTools) == "table" and profile.devTools or {}
|
profile.devTools = type(profile.devTools) == "table" and profile.devTools or {}
|
||||||
local settings = profile.devTools
|
local settings = profile.devTools
|
||||||
settings.enabled = settings.enabled == true
|
settings.enabled = settings.enabled == true
|
||||||
if settings.level ~= "error" and settings.level ~= "trace" then
|
if settings.level == "trace" then
|
||||||
settings.level = "error"
|
settings.level = "verbose"
|
||||||
|
elseif settings.level ~= "error" and settings.level ~= "info" and settings.level ~= "verbose" then
|
||||||
|
settings.level = "info"
|
||||||
end
|
end
|
||||||
if type(settings.scope) ~= "string" or settings.scope == "" then
|
if type(settings.scope) ~= "string" or settings.scope == "" then
|
||||||
settings.scope = DEVTOOLS_SCOPE_ALL
|
settings.scope = DEVTOOLS_SCOPE_ALL
|
||||||
@@ -94,24 +97,25 @@ function HMGT:IsDevToolsEnabled()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function HMGT:GetDevToolsLevelOptions()
|
function HMGT:GetDevToolsLevelOptions()
|
||||||
return {
|
return self:GetDebugLevelOptions()
|
||||||
error = L["OPT_DEVTOOLS_LEVEL_ERROR"] or "Errors",
|
|
||||||
trace = L["OPT_DEVTOOLS_LEVEL_TRACE"] or "Trace",
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function HMGT:GetConfiguredDevToolsLevel()
|
function HMGT:GetConfiguredDevToolsLevel()
|
||||||
return self:GetDevToolsSettings().level or "error"
|
return self:GetConfiguredDebugLevel()
|
||||||
end
|
end
|
||||||
|
|
||||||
function HMGT:ShouldIncludeDevToolsLevel(level)
|
function HMGT:ShouldIncludeDevToolsLevel(level)
|
||||||
local configured = self:GetConfiguredDevToolsLevel()
|
local configured = self:GetConfiguredDevToolsLevel()
|
||||||
return (DEVTOOLS_LEVELS[tostring(level or "error")] or DEVTOOLS_LEVELS.error)
|
local normalizedLevel = tostring(level or "info")
|
||||||
<= (DEVTOOLS_LEVELS[configured] or DEVTOOLS_LEVELS.error)
|
if normalizedLevel == "trace" then
|
||||||
|
normalizedLevel = "verbose"
|
||||||
|
end
|
||||||
|
return (DEVTOOLS_LEVELS[normalizedLevel] or DEVTOOLS_LEVELS.info)
|
||||||
|
<= (DEVTOOLS_LEVELS[configured] or DEVTOOLS_LEVELS.info)
|
||||||
end
|
end
|
||||||
|
|
||||||
function HMGT:GetDevToolsScopeOptions()
|
function HMGT:GetDevToolsScopeOptions()
|
||||||
local values = {
|
local values = self:GetDebugScopeOptions() or {
|
||||||
[DEVTOOLS_SCOPE_ALL] = L["OPT_DEVTOOLS_SCOPE_ALL"] or "All scopes",
|
[DEVTOOLS_SCOPE_ALL] = L["OPT_DEVTOOLS_SCOPE_ALL"] or "All scopes",
|
||||||
}
|
}
|
||||||
for scope, label in pairs(DEVTOOLS_SCOPE_LABELS) do
|
for scope, label in pairs(DEVTOOLS_SCOPE_LABELS) do
|
||||||
@@ -128,8 +132,11 @@ end
|
|||||||
|
|
||||||
function HMGT:FormatDevToolsEntry(entry)
|
function HMGT:FormatDevToolsEntry(entry)
|
||||||
local stamp = tostring(entry and entry.stamp or date("%H:%M:%S"))
|
local stamp = tostring(entry and entry.stamp or date("%H:%M:%S"))
|
||||||
local level = string.upper(tostring(entry and entry.level or "error"))
|
local level = string.upper(tostring(entry and entry.level or "info"))
|
||||||
local scope = tostring(entry and entry.scope or "System")
|
local scope = tostring(entry and entry.scope or "System")
|
||||||
|
if entry and entry.kind == "debug" then
|
||||||
|
return string.format("%s [%s][%s] %s", stamp, level, scope, tostring(entry.message or ""))
|
||||||
|
end
|
||||||
local eventName = tostring(entry and entry.event or "")
|
local eventName = tostring(entry and entry.event or "")
|
||||||
local payload = TrimText(entry and entry.payload or "")
|
local payload = TrimText(entry and entry.payload or "")
|
||||||
if payload ~= "" then
|
if payload ~= "" then
|
||||||
@@ -164,8 +171,10 @@ function HMGT:RecordDevEvent(level, scope, eventName, payload)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local normalizedLevel = tostring(level or "error")
|
local normalizedLevel = tostring(level or "error")
|
||||||
if normalizedLevel ~= "error" and normalizedLevel ~= "trace" then
|
if normalizedLevel == "trace" then
|
||||||
normalizedLevel = "trace"
|
normalizedLevel = "verbose"
|
||||||
|
elseif normalizedLevel ~= "error" and normalizedLevel ~= "info" and normalizedLevel ~= "verbose" then
|
||||||
|
normalizedLevel = "verbose"
|
||||||
end
|
end
|
||||||
if not self:ShouldIncludeDevToolsLevel(normalizedLevel) then
|
if not self:ShouldIncludeDevToolsLevel(normalizedLevel) then
|
||||||
return
|
return
|
||||||
@@ -182,6 +191,7 @@ function HMGT:RecordDevEvent(level, scope, eventName, payload)
|
|||||||
scope = normalizedScope,
|
scope = normalizedScope,
|
||||||
event = TrimText(eventName or "event"),
|
event = TrimText(eventName or "event"),
|
||||||
payload = EncodePayloadValue(payload, 0),
|
payload = EncodePayloadValue(payload, 0),
|
||||||
|
kind = "event",
|
||||||
}
|
}
|
||||||
|
|
||||||
table.insert(self.devToolsBuffer, entry)
|
table.insert(self.devToolsBuffer, entry)
|
||||||
@@ -194,6 +204,40 @@ function HMGT:RecordDevEvent(level, scope, eventName, payload)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function HMGT:RecordDebugEntry(level, scope, message)
|
||||||
|
if not self:IsDevToolsEnabled() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local normalizedLevel = tostring(level or "info")
|
||||||
|
if normalizedLevel == "trace" then
|
||||||
|
normalizedLevel = "verbose"
|
||||||
|
elseif normalizedLevel ~= "error" and normalizedLevel ~= "info" and normalizedLevel ~= "verbose" then
|
||||||
|
normalizedLevel = "info"
|
||||||
|
end
|
||||||
|
|
||||||
|
local normalizedScope = TrimText(scope or "General")
|
||||||
|
if normalizedScope == "" then
|
||||||
|
normalizedScope = "General"
|
||||||
|
end
|
||||||
|
|
||||||
|
self.devToolsBuffer = self.devToolsBuffer or {}
|
||||||
|
self.devToolsBuffer[#self.devToolsBuffer + 1] = {
|
||||||
|
stamp = date("%H:%M:%S"),
|
||||||
|
level = normalizedLevel,
|
||||||
|
scope = normalizedScope,
|
||||||
|
message = TrimText(message or ""),
|
||||||
|
kind = "debug",
|
||||||
|
}
|
||||||
|
while #self.devToolsBuffer > (tonumber(self.devToolsBufferMax) or 500) do
|
||||||
|
table.remove(self.devToolsBuffer, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.devToolsWindow and self.devToolsWindow:IsShown() and self.RefreshDevToolsWindow then
|
||||||
|
self:RefreshDevToolsWindow()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function HMGT:DevError(scope, eventName, payload)
|
function HMGT:DevError(scope, eventName, payload)
|
||||||
self:RecordDevEvent("error", scope, eventName, payload)
|
self:RecordDevEvent("error", scope, eventName, payload)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ local AceGUI = LibStub("AceGUI-3.0", true)
|
|||||||
if not AceGUI then return end
|
if not AceGUI then return end
|
||||||
|
|
||||||
local function GetOrderedLevels()
|
local function GetOrderedLevels()
|
||||||
return { "error", "trace" }
|
return { "error", "info", "verbose" }
|
||||||
end
|
end
|
||||||
|
|
||||||
local function GetOrderedScopes()
|
local function GetOrderedScopes()
|
||||||
@@ -78,8 +78,8 @@ function HMGT:EnsureDevToolsWindow()
|
|||||||
|
|
||||||
local settings = self:GetDevToolsSettings()
|
local settings = self:GetDevToolsSettings()
|
||||||
local window = self:CreateAceWindow("devTools", {
|
local window = self:CreateAceWindow("devTools", {
|
||||||
title = L["DEVTOOLS_WINDOW_TITLE"] or "HMGT Developer Tools",
|
title = L["DEVTOOLS_WINDOW_TITLE"] or "HMGT Debug Console",
|
||||||
statusText = L["DEVTOOLS_WINDOW_HINT"] or "Structured developer events for the current session",
|
statusText = L["DEVTOOLS_WINDOW_HINT"] or "Debug and developer events for the current session",
|
||||||
statusTable = settings.window,
|
statusTable = settings.window,
|
||||||
width = settings.window.width or 920,
|
width = settings.window.width or 920,
|
||||||
height = settings.window.height or 420,
|
height = settings.window.height or 420,
|
||||||
@@ -93,7 +93,7 @@ function HMGT:EnsureDevToolsWindow()
|
|||||||
local content = window:GetContent()
|
local content = window:GetContent()
|
||||||
|
|
||||||
local clearButton = AceGUI:Create("Button")
|
local clearButton = AceGUI:Create("Button")
|
||||||
clearButton:SetText(L["OPT_DEVTOOLS_CLEAR"] or "Clear developer log")
|
clearButton:SetText(L["OPT_DEVTOOLS_CLEAR"] or L["OPT_DEBUG_CLEAR"] or "Clear log")
|
||||||
clearButton:SetWidth(140)
|
clearButton:SetWidth(140)
|
||||||
clearButton:SetCallback("OnClick", function()
|
clearButton:SetCallback("OnClick", function()
|
||||||
HMGT:ClearDevToolsLog()
|
HMGT:ClearDevToolsLog()
|
||||||
@@ -176,11 +176,11 @@ function HMGT:RefreshDevToolsWindow()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local levelOptions = self:GetDevToolsLevelOptions()
|
local levelOptions = self:GetDevToolsLevelOptions()
|
||||||
SetFilterButtonText(window.levelFilter, L["OPT_DEVTOOLS_LEVEL"] or "Capture level", levelOptions[self:GetConfiguredDevToolsLevel()])
|
SetFilterButtonText(window.levelFilter, L["OPT_DEBUG_LEVEL"] or L["OPT_DEVTOOLS_LEVEL"] or "Level", levelOptions[self:GetConfiguredDevToolsLevel()])
|
||||||
|
|
||||||
local scopeValues = self:GetDevToolsScopeOptions()
|
local scopeValues = self:GetDevToolsScopeOptions()
|
||||||
local currentScope = self:GetDevToolsSettings().scope or "ALL"
|
local currentScope = self:GetDevToolsSettings().scope or "ALL"
|
||||||
SetFilterButtonText(window.scopeFilter, L["OPT_DEVTOOLS_SCOPE"] or "Scope", scopeValues[currentScope] or currentScope)
|
SetFilterButtonText(window.scopeFilter, L["OPT_DEBUG_SCOPE"] or L["OPT_DEVTOOLS_SCOPE"] or "Module", scopeValues[currentScope] or currentScope)
|
||||||
|
|
||||||
local text = table.concat(self:GetFilteredDevToolsLines(), "\n")
|
local text = table.concat(self:GetFilteredDevToolsLines(), "\n")
|
||||||
window.logWidget:SetText(text)
|
window.logWidget:SetText(text)
|
||||||
|
|||||||
@@ -3,6 +3,218 @@ local HMGT = _G[ADDON_NAME]
|
|||||||
if not HMGT then return end
|
if not HMGT then return end
|
||||||
|
|
||||||
local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
||||||
|
local AceGUI = LibStub("AceGUI-3.0", true)
|
||||||
|
local CLASS_ICON_TCOORDS = CLASS_ICON_TCOORDS or {}
|
||||||
|
|
||||||
|
local function NormalizeName(name)
|
||||||
|
if HMGT.NormalizePlayerName then
|
||||||
|
return HMGT:NormalizePlayerName(name)
|
||||||
|
end
|
||||||
|
return tostring(name or "")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetRosterRows()
|
||||||
|
local rows = {}
|
||||||
|
local seen = {}
|
||||||
|
|
||||||
|
local function addUnit(unitId)
|
||||||
|
if not unitId or not UnitExists(unitId) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local name = NormalizeName(UnitName(unitId))
|
||||||
|
if not name or name == "" or seen[name] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
seen[name] = true
|
||||||
|
rows[#rows + 1] = {
|
||||||
|
name = name,
|
||||||
|
class = select(2, UnitClass(unitId)),
|
||||||
|
isLeader = UnitIsGroupLeader and UnitIsGroupLeader(unitId) or false,
|
||||||
|
isAssistant = UnitIsGroupAssistant and UnitIsGroupAssistant(unitId) or false,
|
||||||
|
connected = UnitIsConnected and UnitIsConnected(unitId) ~= false or true,
|
||||||
|
isPlayer = UnitIsUnit and UnitIsUnit(unitId, "player") or unitId == "player",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if IsInRaid() then
|
||||||
|
for i = 1, GetNumGroupMembers() do
|
||||||
|
addUnit("raid" .. i)
|
||||||
|
end
|
||||||
|
elseif IsInGroup() then
|
||||||
|
addUnit("player")
|
||||||
|
for i = 1, GetNumSubgroupMembers() do
|
||||||
|
addUnit("party" .. i)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
addUnit("player")
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(rows, function(a, b)
|
||||||
|
if a.isLeader ~= b.isLeader then
|
||||||
|
return a.isLeader
|
||||||
|
end
|
||||||
|
if a.isPlayer ~= b.isPlayer then
|
||||||
|
return a.isPlayer
|
||||||
|
end
|
||||||
|
return tostring(a.name or "") < tostring(b.name or "")
|
||||||
|
end)
|
||||||
|
|
||||||
|
return rows
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetPlayerVersionText(name)
|
||||||
|
local normalized = NormalizeName(name)
|
||||||
|
if normalized == NormalizeName(UnitName("player")) then
|
||||||
|
return tostring(HMGT.ADDON_VERSION or "dev"), tonumber(HMGT.PROTOCOL_VERSION) or 0, true
|
||||||
|
end
|
||||||
|
|
||||||
|
local addonStatus = HMGT.GetPlayerAddonStatus and HMGT:GetPlayerAddonStatus(normalized) or nil
|
||||||
|
if addonStatus and addonStatus.mode == "hmgt" and addonStatus.version and addonStatus.version ~= "" then
|
||||||
|
return tostring(addonStatus.version), tonumber(addonStatus.protocol) or 0, true
|
||||||
|
end
|
||||||
|
if addonStatus and addonStatus.mode == "bridge" then
|
||||||
|
return L["VERSION_WINDOW_BRIDGE_MODE"] or "Bridge Mode", 0, true
|
||||||
|
end
|
||||||
|
return nil, tonumber(addonStatus and addonStatus.protocol) or 0, false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ApplyClassIcon(texture, classTag)
|
||||||
|
if not texture then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local coords = classTag and CLASS_ICON_TCOORDS[classTag]
|
||||||
|
if coords then
|
||||||
|
texture:SetTexture("Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES")
|
||||||
|
texture:SetTexCoord(coords[1], coords[2], coords[3], coords[4])
|
||||||
|
texture:Show()
|
||||||
|
else
|
||||||
|
texture:SetTexture(nil)
|
||||||
|
texture:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function AcquireVersionRow(window, index)
|
||||||
|
window.versionRows = window.versionRows or {}
|
||||||
|
local row = window.versionRows[index]
|
||||||
|
if row then
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
local parent = window.scrollChild
|
||||||
|
row = CreateFrame("Frame", nil, parent)
|
||||||
|
row:SetHeight(22)
|
||||||
|
|
||||||
|
row.background = row:CreateTexture(nil, "BACKGROUND")
|
||||||
|
row.background:SetAllPoints(row)
|
||||||
|
row.background:SetColorTexture(1, 1, 1, 0.03)
|
||||||
|
|
||||||
|
row.classIcon = row:CreateTexture(nil, "ARTWORK")
|
||||||
|
row.classIcon:SetSize(16, 16)
|
||||||
|
row.classIcon:SetPoint("LEFT", row, "LEFT", 4, 0)
|
||||||
|
|
||||||
|
row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||||
|
row.nameText:SetPoint("LEFT", row.classIcon, "RIGHT", 6, 0)
|
||||||
|
row.nameText:SetJustifyH("LEFT")
|
||||||
|
|
||||||
|
row.versionText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||||
|
row.versionText:SetPoint("LEFT", row, "LEFT", 250, 0)
|
||||||
|
row.versionText:SetWidth(150)
|
||||||
|
row.versionText:SetJustifyH("LEFT")
|
||||||
|
|
||||||
|
row.protocolText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||||
|
row.protocolText:SetPoint("LEFT", row, "LEFT", 410, 0)
|
||||||
|
row.protocolText:SetWidth(100)
|
||||||
|
row.protocolText:SetJustifyH("LEFT")
|
||||||
|
|
||||||
|
window.versionRows[index] = row
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:RefreshVersionNoticeWindow()
|
||||||
|
local window = self.versionNoticeWindow
|
||||||
|
if not window then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local roster = GetRosterRows()
|
||||||
|
local localName = NormalizeName(UnitName("player"))
|
||||||
|
|
||||||
|
for index, info in ipairs(roster) do
|
||||||
|
local row = AcquireVersionRow(window, index)
|
||||||
|
row:ClearAllPoints()
|
||||||
|
if index == 1 then
|
||||||
|
row:SetPoint("TOPLEFT", window.scrollChild, "TOPLEFT", 0, 0)
|
||||||
|
row:SetPoint("TOPRIGHT", window.scrollChild, "TOPRIGHT", 0, 0)
|
||||||
|
else
|
||||||
|
row:SetPoint("TOPLEFT", window.versionRows[index - 1], "BOTTOMLEFT", 0, -2)
|
||||||
|
row:SetPoint("TOPRIGHT", window.versionRows[index - 1], "BOTTOMRIGHT", 0, -2)
|
||||||
|
end
|
||||||
|
|
||||||
|
ApplyClassIcon(row.classIcon, info.class)
|
||||||
|
|
||||||
|
local nameLabel = tostring(info.name or UNKNOWN)
|
||||||
|
if info.isLeader then
|
||||||
|
nameLabel = string.format("%s %s", nameLabel, L["VERSION_WINDOW_LEADER_TAG"] or "(Leader)")
|
||||||
|
elseif info.isAssistant then
|
||||||
|
nameLabel = string.format("%s %s", nameLabel, L["VERSION_WINDOW_ASSISTANT_TAG"] or "(Assist)")
|
||||||
|
end
|
||||||
|
if info.isPlayer or info.name == localName then
|
||||||
|
nameLabel = string.format("%s %s", nameLabel, L["VERSION_WINDOW_SELF_TAG"] or "(You)")
|
||||||
|
end
|
||||||
|
|
||||||
|
row.nameText:SetText(nameLabel)
|
||||||
|
row.nameText:SetTextColor(1, 0.82, 0.1, 1)
|
||||||
|
|
||||||
|
local versionText, protocol, hasAddon = GetPlayerVersionText(info.name)
|
||||||
|
if hasAddon then
|
||||||
|
row.versionText:SetText(versionText or "?")
|
||||||
|
if versionText == (L["VERSION_WINDOW_BRIDGE_MODE"] or "Bridge Mode") then
|
||||||
|
row.versionText:SetTextColor(0.55, 0.82, 1, 1)
|
||||||
|
else
|
||||||
|
row.versionText:SetTextColor(0.9, 0.9, 0.9, 1)
|
||||||
|
end
|
||||||
|
row.protocolText:SetText(protocol > 0 and tostring(protocol) or "-")
|
||||||
|
row.protocolText:SetTextColor(0.75, 0.75, 0.75, 1)
|
||||||
|
else
|
||||||
|
row.versionText:SetText(L["VERSION_WINDOW_MISSING_ADDON"] or "Addon not installed")
|
||||||
|
row.versionText:SetTextColor(1, 0.25, 0.25, 1)
|
||||||
|
row.protocolText:SetText("-")
|
||||||
|
row.protocolText:SetTextColor(1, 0.25, 0.25, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
row:Show()
|
||||||
|
end
|
||||||
|
|
||||||
|
if window.versionRows then
|
||||||
|
for index = #roster + 1, #window.versionRows do
|
||||||
|
window.versionRows[index]:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local contentHeight = math.max(1, (#roster * 24))
|
||||||
|
window.scrollChild:SetHeight(contentHeight)
|
||||||
|
|
||||||
|
local known = 0
|
||||||
|
for _, info in ipairs(roster) do
|
||||||
|
local _, _, hasAddon = GetPlayerVersionText(info.name)
|
||||||
|
if hasAddon then
|
||||||
|
known = known + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
window.messageText:SetText(L["VERSION_WINDOW_MESSAGE"] or "Hail Mary Guild Tools versions in your current group")
|
||||||
|
window.detailText:SetText(string.format(
|
||||||
|
L["VERSION_WINDOW_CURRENT"] or "Current version: %s | Protocol: %s",
|
||||||
|
tostring(HMGT.ADDON_VERSION or "dev"),
|
||||||
|
tostring(HMGT.PROTOCOL_VERSION or "?")
|
||||||
|
))
|
||||||
|
window:SetStatusText(string.format(
|
||||||
|
L["VERSION_WINDOW_STATUS"] or "Detected HMGT on %d/%d players",
|
||||||
|
tonumber(known) or 0,
|
||||||
|
tonumber(#roster) or 0
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
function HMGT:EnsureVersionNoticeWindow()
|
function HMGT:EnsureVersionNoticeWindow()
|
||||||
if self.versionNoticeWindow then
|
if self.versionNoticeWindow then
|
||||||
@@ -10,21 +222,21 @@ function HMGT:EnsureVersionNoticeWindow()
|
|||||||
end
|
end
|
||||||
|
|
||||||
self.versionNoticeWindowStatus = self.versionNoticeWindowStatus or {
|
self.versionNoticeWindowStatus = self.versionNoticeWindowStatus or {
|
||||||
width = 560,
|
width = 640,
|
||||||
height = 240,
|
height = 420,
|
||||||
}
|
}
|
||||||
|
|
||||||
local window = self:CreateAceWindow("versionNotice", {
|
local window = self:CreateAceWindow("versionNotice", {
|
||||||
title = L["VERSION_WINDOW_TITLE"] or "HMGT Version Check",
|
title = L["VERSION_WINDOW_TITLE"] or "HMGT Version Check",
|
||||||
statusText = "",
|
statusText = "",
|
||||||
statusTable = self.versionNoticeWindowStatus,
|
statusTable = self.versionNoticeWindowStatus,
|
||||||
width = self.versionNoticeWindowStatus.width or 560,
|
width = self.versionNoticeWindowStatus.width or 640,
|
||||||
height = self.versionNoticeWindowStatus.height or 240,
|
height = self.versionNoticeWindowStatus.height or 420,
|
||||||
backgroundTexture = "Interface\\AddOns\\HailMaryGuildTools\\Media\\HailMaryLogo.png",
|
backgroundTexture = "Interface\\AddOns\\HailMaryGuildTools\\Media\\HailMaryLogo.png",
|
||||||
backgroundWidth = 220,
|
backgroundWidth = 220,
|
||||||
backgroundHeight = 120,
|
backgroundHeight = 120,
|
||||||
backgroundOffsetY = -8,
|
backgroundOffsetY = -8,
|
||||||
backgroundAlpha = 0.12,
|
backgroundAlpha = 0.08,
|
||||||
strata = "FULLSCREEN_DIALOG",
|
strata = "FULLSCREEN_DIALOG",
|
||||||
})
|
})
|
||||||
if not window then
|
if not window then
|
||||||
@@ -32,26 +244,64 @@ function HMGT:EnsureVersionNoticeWindow()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local content = window:GetContent()
|
local content = window:GetContent()
|
||||||
|
|
||||||
local messageText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
|
local messageText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
|
||||||
messageText:SetPoint("TOPLEFT", content, "TOPLEFT", 28, -28)
|
messageText:SetPoint("TOPLEFT", content, "TOPLEFT", 24, -22)
|
||||||
messageText:SetPoint("TOPRIGHT", content, "TOPRIGHT", -28, -28)
|
messageText:SetPoint("TOPRIGHT", content, "TOPRIGHT", -24, -22)
|
||||||
messageText:SetJustifyH("CENTER")
|
messageText:SetJustifyH("LEFT")
|
||||||
messageText:SetJustifyV("MIDDLE")
|
|
||||||
messageText:SetTextColor(1, 0.82, 0.1, 1)
|
messageText:SetTextColor(1, 0.82, 0.1, 1)
|
||||||
messageText:SetText(L["VERSION_WINDOW_MESSAGE"] or "A new version of Hail Mary Guild Tools is available.")
|
|
||||||
window.messageText = messageText
|
window.messageText = messageText
|
||||||
|
|
||||||
local detailText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
local detailText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||||
detailText:SetPoint("TOPLEFT", messageText, "BOTTOMLEFT", 0, -18)
|
detailText:SetPoint("TOPLEFT", messageText, "BOTTOMLEFT", 0, -8)
|
||||||
detailText:SetPoint("TOPRIGHT", messageText, "BOTTOMRIGHT", 0, -18)
|
detailText:SetPoint("TOPRIGHT", messageText, "BOTTOMRIGHT", 0, -8)
|
||||||
detailText:SetJustifyH("CENTER")
|
detailText:SetJustifyH("LEFT")
|
||||||
detailText:SetJustifyV("TOP")
|
|
||||||
if detailText.SetSpacing then
|
|
||||||
detailText:SetSpacing(2)
|
|
||||||
end
|
|
||||||
detailText:SetTextColor(0.9, 0.9, 0.9, 1)
|
detailText:SetTextColor(0.9, 0.9, 0.9, 1)
|
||||||
window.detailText = detailText
|
window.detailText = detailText
|
||||||
|
|
||||||
|
local refreshButton = AceGUI and AceGUI:Create("Button") or nil
|
||||||
|
if refreshButton then
|
||||||
|
refreshButton:SetText(L["VERSION_WINDOW_REFRESH"] or "Refresh")
|
||||||
|
refreshButton:SetWidth(120)
|
||||||
|
refreshButton:SetCallback("OnClick", function()
|
||||||
|
HMGT:RequestSync("VersionWindow")
|
||||||
|
HMGT:RefreshVersionNoticeWindow()
|
||||||
|
end)
|
||||||
|
refreshButton.frame:SetParent(window.frame)
|
||||||
|
refreshButton.frame:ClearAllPoints()
|
||||||
|
refreshButton.frame:SetPoint("TOPRIGHT", content, "TOPRIGHT", -24, -68)
|
||||||
|
refreshButton.frame:Show()
|
||||||
|
window.refreshButton = refreshButton
|
||||||
|
end
|
||||||
|
|
||||||
|
local header = CreateFrame("Frame", nil, content)
|
||||||
|
header:SetPoint("TOPLEFT", detailText, "BOTTOMLEFT", 0, -14)
|
||||||
|
header:SetPoint("TOPRIGHT", content, "TOPRIGHT", -24, -96)
|
||||||
|
header:SetHeight(18)
|
||||||
|
window.header = header
|
||||||
|
|
||||||
|
local nameHeader = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
nameHeader:SetPoint("LEFT", header, "LEFT", 24, 0)
|
||||||
|
nameHeader:SetText(L["VERSION_WINDOW_COLUMN_PLAYER"] or "Player")
|
||||||
|
|
||||||
|
local versionHeader = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
versionHeader:SetPoint("LEFT", header, "LEFT", 250, 0)
|
||||||
|
versionHeader:SetText(L["VERSION_WINDOW_COLUMN_VERSION"] or "Version")
|
||||||
|
|
||||||
|
local protocolHeader = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
protocolHeader:SetPoint("LEFT", header, "LEFT", 410, 0)
|
||||||
|
protocolHeader:SetText(L["VERSION_WINDOW_COLUMN_PROTOCOL"] or "Protocol")
|
||||||
|
|
||||||
|
local scrollFrame = CreateFrame("ScrollFrame", nil, content, "UIPanelScrollFrameTemplate")
|
||||||
|
scrollFrame:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -6)
|
||||||
|
scrollFrame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -28, 24)
|
||||||
|
window.scrollFrame = scrollFrame
|
||||||
|
|
||||||
|
local scrollChild = CreateFrame("Frame", nil, scrollFrame)
|
||||||
|
scrollChild:SetSize(1, 1)
|
||||||
|
scrollFrame:SetScrollChild(scrollChild)
|
||||||
|
window.scrollChild = scrollChild
|
||||||
|
|
||||||
self.versionNoticeWindow = window
|
self.versionNoticeWindow = window
|
||||||
return window
|
return window
|
||||||
end
|
end
|
||||||
@@ -67,36 +317,16 @@ function HMGT:ShowVersionMismatchPopup(playerName, detail, sourceTag, opts)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local info = self.latestVersionMismatch or {}
|
|
||||||
local window = self:EnsureVersionNoticeWindow()
|
local window = self:EnsureVersionNoticeWindow()
|
||||||
if not window then
|
if not window then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local hasMismatch = info.playerName or info.detail
|
|
||||||
|
|
||||||
window:SetTitle(L["VERSION_WINDOW_TITLE"] or "HMGT Version Check")
|
self:RefreshVersionNoticeWindow()
|
||||||
|
self:DevTrace("Version", "window_show", {
|
||||||
if hasMismatch then
|
player = playerName,
|
||||||
window.messageText:SetText(L["VERSION_WINDOW_MESSAGE"] or "A new version of Hail Mary Guild Tools is available.")
|
source = sourceTag,
|
||||||
window.detailText:SetText(string.format(
|
detail = detail,
|
||||||
L["VERSION_WINDOW_DETAIL"] or "Detected via %s from %s.\n%s",
|
|
||||||
tostring(info.sourceTag or "?"),
|
|
||||||
tostring(info.playerName or UNKNOWN),
|
|
||||||
tostring(info.detail or "")
|
|
||||||
))
|
|
||||||
else
|
|
||||||
window.messageText:SetText(L["VERSION_WINDOW_NO_MISMATCH"] or "No newer HMGT version has been detected in your current group.")
|
|
||||||
window.detailText:SetText(string.format(
|
|
||||||
L["VERSION_WINDOW_CURRENT"] or "Current version: %s | Protocol: %s",
|
|
||||||
tostring(HMGT.ADDON_VERSION or "dev"),
|
|
||||||
tostring(HMGT.PROTOCOL_VERSION or "?")
|
|
||||||
))
|
|
||||||
end
|
|
||||||
|
|
||||||
self:DevTrace("Version", hasMismatch and "window_show_mismatch" or "window_show_current", {
|
|
||||||
player = info.playerName,
|
|
||||||
source = info.sourceTag,
|
|
||||||
detail = info.detail,
|
|
||||||
})
|
})
|
||||||
window:Show()
|
window:Show()
|
||||||
window:Raise()
|
window:Raise()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
## Interface: 120000,120001
|
## Interface: 120000,120001,120005
|
||||||
## IconTexture: Interface\Addons\HailMaryGuildTools\Media\HailMaryIcon.png
|
## IconTexture: Interface\Addons\HailMaryGuildTools\Media\HailMaryIcon.png
|
||||||
## Author: Torsten Brendgen
|
## Author: Torsten Brendgen
|
||||||
## Title: Hail Mary Guild Tools
|
## Title: Hail Mary Guild Tools
|
||||||
@@ -31,10 +31,20 @@ HailMaryGuildToolsOptions.lua
|
|||||||
# ────── Tracker ──────────────────────────────────────────────────────
|
# ────── Tracker ──────────────────────────────────────────────────────
|
||||||
Modules\Tracker\Frame.lua
|
Modules\Tracker\Frame.lua
|
||||||
Modules\Tracker\SpellDatabase.lua
|
Modules\Tracker\SpellDatabase.lua
|
||||||
Modules\Tracker\SingleFrameTrackerBase.lua
|
Modules\Tracker\TrackerCore.lua
|
||||||
|
Modules\Tracker\TrackerState.lua
|
||||||
|
Modules\Tracker\TrackerPlayerState.lua
|
||||||
|
Modules\Tracker\TrackerBridge.lua
|
||||||
|
Modules\Tracker\TrackerDataProvider.lua
|
||||||
|
Modules\Tracker\TrackerSync.lua
|
||||||
|
Modules\Tracker\TrackerAvailability.lua
|
||||||
|
Modules\Tracker\TrackerDetection.lua
|
||||||
|
Modules\Tracker\InterruptTracker\InterruptTracker.lua
|
||||||
|
Modules\Tracker\RaidCooldownTracker\RaidCooldownTracker.lua
|
||||||
|
Modules\Tracker\GroupCooldownTracker\GroupCooldownTracker.lua
|
||||||
|
|
||||||
Modules\Tracker\InterruptTracker\InterruptSpellDatabase.lua
|
Modules\Tracker\InterruptTracker\InterruptSpellDatabase.lua
|
||||||
Modules\Tracker\RaidcooldownTracker\RaidCooldownSpellDatabase.lua
|
Modules\Tracker\RaidCooldownTracker\RaidCooldownSpellDatabase.lua
|
||||||
Modules\Tracker\GroupCooldownTracker\GroupCooldownSpellDatabase.lua
|
Modules\Tracker\GroupCooldownTracker\GroupCooldownSpellDatabase.lua
|
||||||
Modules\Tracker\TrackerManager.lua
|
Modules\Tracker\TrackerManager.lua
|
||||||
Modules\Tracker\NormalTrackerFrames.lua
|
Modules\Tracker\NormalTrackerFrames.lua
|
||||||
@@ -55,5 +65,9 @@ Modules\MapOverlay\MapOverlay.xml
|
|||||||
Modules\RaidTimeline\RaidTimelineBossAbilityData.lua
|
Modules\RaidTimeline\RaidTimelineBossAbilityData.lua
|
||||||
Modules\RaidTimeline\RaidTimeline.lua
|
Modules\RaidTimeline\RaidTimeline.lua
|
||||||
Modules\RaidTimeline\RaidTimelineBigWigs.lua
|
Modules\RaidTimeline\RaidTimelineBigWigs.lua
|
||||||
Modules\RaidTimeline\RaidTimelineDBM.lua
|
Modules\RaidTimeline\RaidTimelineOptions.lua
|
||||||
Modules\RaidTimeline\RaidTimelineOptions.lua
|
|
||||||
|
# EncounterAlerts
|
||||||
|
Modules\EncounterAlerts\EncounterAlerts.lua
|
||||||
|
Modules\EncounterAlerts\LuraRunes.lua
|
||||||
|
Modules\EncounterAlerts\EncounterAlertsOptions.lua
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ function HMGT_Config:RegisterOptionsProvider(id, provider)
|
|||||||
if type(id) ~= "string" or id == "" then return false end
|
if type(id) ~= "string" or id == "" then return false end
|
||||||
if type(provider) ~= "function" then return false end
|
if type(provider) ~= "function" then return false end
|
||||||
self._optionProviders[id] = provider
|
self._optionProviders[id] = provider
|
||||||
|
if type(self.RebuildRootOptions) == "function" then
|
||||||
|
self:RebuildRootOptions()
|
||||||
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1835,6 +1838,88 @@ function HMGT_Config:Initialize()
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
devToolsEnabled = {
|
||||||
|
type = "toggle",
|
||||||
|
order = 2,
|
||||||
|
width = "full",
|
||||||
|
name = L["OPT_DEVTOOLS_MODE"] or L["OPT_DEBUG_MODE"] or "Debug console",
|
||||||
|
desc = L["OPT_DEVTOOLS_MODE_DESC"] or L["OPT_DEBUG_MODE_DESC"] or "Enable the debug console.",
|
||||||
|
get = function()
|
||||||
|
return HMGT.GetDevToolsSettings and HMGT:GetDevToolsSettings().enabled == true
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
if not HMGT.GetDevToolsSettings then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
HMGT:GetDevToolsSettings().enabled = value == true
|
||||||
|
if HMGT.UpdateDevToolsWindowVisibility then
|
||||||
|
HMGT:UpdateDevToolsWindowVisibility()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
debugLevel = {
|
||||||
|
type = "select",
|
||||||
|
order = 3,
|
||||||
|
width = "full",
|
||||||
|
name = L["OPT_DEBUG_LEVEL"] or "Debug level",
|
||||||
|
values = function()
|
||||||
|
return HMGT.GetDebugLevelOptions and HMGT:GetDebugLevelOptions() or {}
|
||||||
|
end,
|
||||||
|
get = function()
|
||||||
|
return HMGT.GetConfiguredDebugLevel and HMGT:GetConfiguredDebugLevel() or "info"
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
if HMGT.GetDevToolsSettings then
|
||||||
|
HMGT:GetDevToolsSettings().level = value or "info"
|
||||||
|
end
|
||||||
|
if HMGT.RefreshDevToolsWindow then
|
||||||
|
HMGT:RefreshDevToolsWindow()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
debugScope = {
|
||||||
|
type = "select",
|
||||||
|
order = 4,
|
||||||
|
width = "full",
|
||||||
|
name = L["OPT_DEBUG_SCOPE"] or "Module filter",
|
||||||
|
values = function()
|
||||||
|
return HMGT.GetDebugScopeOptions and HMGT:GetDebugScopeOptions() or {}
|
||||||
|
end,
|
||||||
|
get = function()
|
||||||
|
local settings = HMGT.GetDevToolsSettings and HMGT:GetDevToolsSettings() or {}
|
||||||
|
return settings.scope or "ALL"
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
if HMGT.GetDevToolsSettings then
|
||||||
|
HMGT:GetDevToolsSettings().scope = value or "ALL"
|
||||||
|
end
|
||||||
|
if HMGT.RefreshDevToolsWindow then
|
||||||
|
HMGT:RefreshDevToolsWindow()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
openDebug = {
|
||||||
|
type = "execute",
|
||||||
|
order = 5,
|
||||||
|
width = "half",
|
||||||
|
name = L["OPT_DEVTOOLS_OPEN"] or L["OPT_DEBUG_OPEN"] or "Open debug console",
|
||||||
|
func = function()
|
||||||
|
if HMGT.OpenDevToolsWindow then
|
||||||
|
HMGT:OpenDevToolsWindow()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
clearDebug = {
|
||||||
|
type = "execute",
|
||||||
|
order = 6,
|
||||||
|
width = "half",
|
||||||
|
name = L["OPT_DEVTOOLS_CLEAR"] or L["OPT_DEBUG_CLEAR"] or "Clear debug log",
|
||||||
|
func = function()
|
||||||
|
if HMGT.ClearDevToolsLog then
|
||||||
|
HMGT:ClearDevToolsLog()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
commands = {
|
commands = {
|
||||||
@@ -1850,6 +1935,8 @@ function HMGT_Config:Initialize()
|
|||||||
name = table.concat({
|
name = table.concat({
|
||||||
"|cffffd100/hmgt|r",
|
"|cffffd100/hmgt|r",
|
||||||
"|cffffd100/hmgt debug|r",
|
"|cffffd100/hmgt debug|r",
|
||||||
|
"|cffffd100/hmgt status|r",
|
||||||
|
"|cffffd100/hmgt lura|r",
|
||||||
"|cffffd100/hmgt version|r",
|
"|cffffd100/hmgt version|r",
|
||||||
}, "\n"),
|
}, "\n"),
|
||||||
},
|
},
|
||||||
@@ -2000,6 +2087,15 @@ function HMGT_Config:Initialize()
|
|||||||
modulesGroup.args.raidTimeline = raidTimelineGroup
|
modulesGroup.args.raidTimeline = raidTimelineGroup
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local encounterAlertsGroup = BuildNamedModuleGroup(
|
||||||
|
"encounterAlerts",
|
||||||
|
L["OPT_MODULE_ENCOUNTER_ALERTS"] or "Encounter Alerts",
|
||||||
|
50
|
||||||
|
)
|
||||||
|
if encounterAlertsGroup then
|
||||||
|
modulesGroup.args.encounterAlerts = encounterAlertsGroup
|
||||||
|
end
|
||||||
|
|
||||||
if next(modulesGroup.args) == nil then
|
if next(modulesGroup.args) == nil then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -2037,12 +2133,20 @@ function HMGT_Config:Initialize()
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local modulesGroup = BuildModulesGroup()
|
function HMGT_Config:RebuildRootOptions()
|
||||||
if modulesGroup then
|
local modulesGroup = BuildModulesGroup()
|
||||||
rootOptions.args.modules = modulesGroup
|
if modulesGroup then
|
||||||
|
rootOptions.args.modules = modulesGroup
|
||||||
|
else
|
||||||
|
rootOptions.args.modules = nil
|
||||||
|
end
|
||||||
|
NormalizeExecuteButtonWidths(rootOptions)
|
||||||
|
if AceConfigRegistry and type(AceConfigRegistry.NotifyChange) == "function" then
|
||||||
|
AceConfigRegistry:NotifyChange(ADDON_NAME)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
NormalizeExecuteButtonWidths(rootOptions)
|
HMGT_Config:RebuildRootOptions()
|
||||||
|
|
||||||
local aceConfig = LibStub("AceConfig-3.0")
|
local aceConfig = LibStub("AceConfig-3.0")
|
||||||
local aceConfigDialog = LibStub("AceConfigDialog-3.0")
|
local aceConfigDialog = LibStub("AceConfigDialog-3.0")
|
||||||
|
|||||||
@@ -18,12 +18,23 @@ L["SLASH_HINT"] = "/hmgt – Optionen | /hmgt lock/unlock | /hmgt dem
|
|||||||
L["VERSION_MISMATCH_CHAT"] = "Versionskonflikt mit %s: %s"
|
L["VERSION_MISMATCH_CHAT"] = "Versionskonflikt mit %s: %s"
|
||||||
L["VERSION_MISMATCH_POPUP"] = "HailMaryGuildTools Konflikt mit %s.\n%s\nQuelle: %s"
|
L["VERSION_MISMATCH_POPUP"] = "HailMaryGuildTools Konflikt mit %s.\n%s\nQuelle: %s"
|
||||||
L["VERSION_WINDOW_TITLE"] = "HMGT Versionscheck"
|
L["VERSION_WINDOW_TITLE"] = "HMGT Versionscheck"
|
||||||
L["VERSION_WINDOW_MESSAGE"] = "Es gibt eine neue Version von Hail Mary Guild Tools."
|
L["VERSION_WINDOW_MESSAGE"] = "Hail Mary Guild Tools Versionen in deiner aktuellen Gruppe"
|
||||||
L["VERSION_WINDOW_DETAIL"] = "Erkannt ueber %s von %s.\n%s"
|
L["VERSION_WINDOW_DETAIL"] = "Erkannt ueber %s von %s.\n%s"
|
||||||
L["VERSION_WINDOW_NO_MISMATCH"] = "In deiner aktuellen Gruppe wurde keine neuere HMGT-Version erkannt."
|
L["VERSION_WINDOW_NO_MISMATCH"] = "In deiner aktuellen Gruppe wurde keine neuere HMGT-Version erkannt."
|
||||||
L["VERSION_WINDOW_CURRENT"] = "Aktuelle Version: %s | Protokoll: %s"
|
L["VERSION_WINDOW_CURRENT"] = "Aktuelle Version: %s | Protokoll: %s"
|
||||||
|
L["VERSION_WINDOW_STATUS"] = "Addon oder Bridge bei %d/%d Spielern erkannt"
|
||||||
|
L["VERSION_WINDOW_REFRESH"] = "Aktualisieren"
|
||||||
|
L["VERSION_WINDOW_COLUMN_PLAYER"] = "Spieler"
|
||||||
|
L["VERSION_WINDOW_COLUMN_VERSION"] = "Version"
|
||||||
|
L["VERSION_WINDOW_COLUMN_PROTOCOL"] = "Protokoll"
|
||||||
|
L["VERSION_WINDOW_BRIDGE_MODE"] = "Bridge Mode"
|
||||||
|
L["VERSION_WINDOW_MISSING_ADDON"] = "Addon nicht vorhanden"
|
||||||
|
L["VERSION_WINDOW_LEADER_TAG"] = "(Leiter)"
|
||||||
|
L["VERSION_WINDOW_ASSISTANT_TAG"] = "(Assist)"
|
||||||
|
L["VERSION_WINDOW_SELF_TAG"] = "(Du)"
|
||||||
|
L["VERSION_OUTDATED_WHISPER"] = "Deine Hail Mary Guild Tools Version ist veraltet. Du hast %s, der Gruppenleiter hat %s."
|
||||||
L["VERSION_WINDOW_DEBUG_ONLY"] = "HMGT: /hmgt version ist nur bei aktiviertem Debugmodus verfuegbar."
|
L["VERSION_WINDOW_DEBUG_ONLY"] = "HMGT: /hmgt version ist nur bei aktiviertem Debugmodus verfuegbar."
|
||||||
L["VERSION_WINDOW_DEVTOOLS_ONLY"] = "HMGT: /hmgt version ist nur bei aktivierten Entwicklerwerkzeugen verfuegbar."
|
L["VERSION_WINDOW_DEVTOOLS_ONLY"] = "HMGT: /hmgt version ist nur bei aktivierter Debug-Konsole verfuegbar."
|
||||||
|
|
||||||
-- ── Options: general ─────────────────────────────────────────
|
-- ── Options: general ─────────────────────────────────────────
|
||||||
L["OPT_GENERAL"] = "Allgemein"
|
L["OPT_GENERAL"] = "Allgemein"
|
||||||
@@ -53,17 +64,47 @@ L["OPT_DEBUG_CLEAR"] = "Debug-Log leeren"
|
|||||||
L["OPT_DEBUG_SELECT_ALL"] = "Alles markieren"
|
L["OPT_DEBUG_SELECT_ALL"] = "Alles markieren"
|
||||||
L["DEBUG_WINDOW_TITLE"] = "HMGT Debug-Konsole"
|
L["DEBUG_WINDOW_TITLE"] = "HMGT Debug-Konsole"
|
||||||
L["DEBUG_WINDOW_HINT"] = "Mit dem Mausrad scrollen, Strg+A markiert alles, Strg+C kopiert markierten Text"
|
L["DEBUG_WINDOW_HINT"] = "Mit dem Mausrad scrollen, Strg+A markiert alles, Strg+C kopiert markierten Text"
|
||||||
L["OPT_DEVTOOLS_MODE"] = "Entwicklerwerkzeuge"
|
L["OPT_DEVTOOLS_MODE"] = "Debug-Konsole"
|
||||||
L["OPT_DEVTOOLS_MODE_DESC"] = "Aktiviert die strukturierte Entwickler-Konsole."
|
L["OPT_DEVTOOLS_MODE_DESC"] = "Aktiviert das gemeinsame Debug- und Entwickler-Log."
|
||||||
L["OPT_DEVTOOLS_LEVEL"] = "Erfassungsstufe"
|
L["OPT_DEVTOOLS_LEVEL"] = "Debug-Stufe"
|
||||||
L["OPT_DEVTOOLS_LEVEL_ERROR"] = "Fehler"
|
L["OPT_DEVTOOLS_LEVEL_ERROR"] = "Fehler"
|
||||||
L["OPT_DEVTOOLS_LEVEL_TRACE"] = "Trace"
|
L["OPT_DEVTOOLS_LEVEL_TRACE"] = "Ausfuehrlich"
|
||||||
L["OPT_DEVTOOLS_SCOPE"] = "Scope-Filter"
|
L["OPT_DEVTOOLS_SCOPE"] = "Modulfilter"
|
||||||
L["OPT_DEVTOOLS_SCOPE_ALL"] = "Alle Scopes"
|
L["OPT_DEVTOOLS_SCOPE_ALL"] = "Alle Module"
|
||||||
L["OPT_DEVTOOLS_OPEN"] = "Entwickler-Konsole oeffnen"
|
L["OPT_DEVTOOLS_OPEN"] = "Debug-Konsole oeffnen"
|
||||||
L["OPT_DEVTOOLS_CLEAR"] = "Entwickler-Log leeren"
|
L["OPT_DEVTOOLS_CLEAR"] = "Debug-Log leeren"
|
||||||
L["OPT_DEVTOOLS_SELECT_ALL"] = "Alles markieren"
|
L["OPT_DEVTOOLS_SELECT_ALL"] = "Alles markieren"
|
||||||
L["OPT_DEVTOOLS_DISABLED"] = "HMGT: Entwicklerwerkzeuge sind nicht aktiviert."
|
L["OPT_DEVTOOLS_DISABLED"] = "HMGT: Entwicklerwerkzeuge sind nicht aktiviert."
|
||||||
|
L["OPT_MODULE_ENCOUNTER_ALERTS"] = "Encounter Alerts"
|
||||||
|
L["OPT_ENCOUNTER_ALERTS_PLACEHOLDER"] = "Encounter-spezifische Helper-Frames und Warnungen."
|
||||||
|
L["OPT_EA_LURA_TITLE"] = "L'ura Runen"
|
||||||
|
L["OPT_EA_LURA_RUNE_WINDOW"] = "Runen-Fenster"
|
||||||
|
L["OPT_EA_LURA_ENABLED"] = "L'ura Runen aktivieren"
|
||||||
|
L["OPT_EA_LURA_UNLOCK"] = "Runen-Frame entsperren"
|
||||||
|
L["OPT_EA_LURA_HINT"] = "Erste Version: nur Normal/Heroisch Layout. Tank steht unten mittig zwischen Slot 1 und 5."
|
||||||
|
L["OPT_EA_LURA_SHOW"] = "Anzeigen"
|
||||||
|
L["OPT_EA_LURA_TEST"] = "Testmuster"
|
||||||
|
L["OPT_EA_LURA_CLEAR"] = "Leeren"
|
||||||
|
L["OPT_EA_LURA_BROADCAST"] = "Sequenz senden"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR"] = "Runen-Actionbar"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_SHOW"] = "Leiste anzeigen"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_UNLOCK"] = "Leiste entsperren"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_AUTO_SHOW"] = "Automatisch im Bossraum anzeigen"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_ORIENTATION"] = "Ausrichtung"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_HORIZONTAL"] = "Horizontal"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_VERTICAL"] = "Vertikal"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_HINT"] = "Klicke die Runen in beobachteter Reihenfolge. Slot 5 sendet die Sequenz automatisch. Der rote Button leert die lokale Sequenz."
|
||||||
|
L["OPT_EA_LURA_ICON_SIZE"] = "Icongroesse"
|
||||||
|
L["OPT_EA_LURA_BACKGROUND_ALPHA"] = "Hintergrund-Alpha"
|
||||||
|
L["OPT_EA_LURA_ICON_SPACING"] = "Icon-Abstand"
|
||||||
|
L["OPT_EA_LURA_BORDER_ENABLED"] = "Rahmen anzeigen"
|
||||||
|
L["OPT_EA_LURA_BORDER_WIDTH"] = "Rahmenbreite"
|
||||||
|
L["OPT_EA_LURA_BORDER_COLOR"] = "Rahmenfarbe"
|
||||||
|
L["OPT_EA_LURA_SHOW_LABELS"] = "Labels anzeigen"
|
||||||
|
L["OPT_EA_LURA_RUNE_EMPTY"] = "Leer"
|
||||||
|
L["OPT_EA_LURA_DRAG_HINT"] = "Ziehen zum Verschieben"
|
||||||
|
L["OPT_EA_LURA_BOSS"] = "Boss"
|
||||||
|
L["OPT_EA_LURA_TANK"] = "Tank"
|
||||||
L["DEVTOOLS_WINDOW_TITLE"] = "HMGT Entwicklerwerkzeuge"
|
L["DEVTOOLS_WINDOW_TITLE"] = "HMGT Entwicklerwerkzeuge"
|
||||||
L["DEVTOOLS_WINDOW_HINT"] = "Strukturierte Entwickler-Ereignisse fuer die aktuelle Sitzung"
|
L["DEVTOOLS_WINDOW_HINT"] = "Strukturierte Entwickler-Ereignisse fuer die aktuelle Sitzung"
|
||||||
L["OPT_SYNC_REMOTE_CHARGES"] = "Remote-Aufladungen synchronisieren"
|
L["OPT_SYNC_REMOTE_CHARGES"] = "Remote-Aufladungen synchronisieren"
|
||||||
|
|||||||
@@ -18,12 +18,23 @@ L["SLASH_HINT"] = "/hmgt – options | /hmgt lock/unlock | /hmgt demo
|
|||||||
L["VERSION_MISMATCH_CHAT"] = "Version mismatch with %s: %s"
|
L["VERSION_MISMATCH_CHAT"] = "Version mismatch with %s: %s"
|
||||||
L["VERSION_MISMATCH_POPUP"] = "HailMaryGuildTools mismatch with %s.\n%s\nSource: %s"
|
L["VERSION_MISMATCH_POPUP"] = "HailMaryGuildTools mismatch with %s.\n%s\nSource: %s"
|
||||||
L["VERSION_WINDOW_TITLE"] = "HMGT Version Check"
|
L["VERSION_WINDOW_TITLE"] = "HMGT Version Check"
|
||||||
L["VERSION_WINDOW_MESSAGE"] = "A new version of Hail Mary Guild Tools is available."
|
L["VERSION_WINDOW_MESSAGE"] = "Hail Mary Guild Tools versions in your current group"
|
||||||
L["VERSION_WINDOW_DETAIL"] = "Detected via %s from %s.\n%s"
|
L["VERSION_WINDOW_DETAIL"] = "Detected via %s from %s.\n%s"
|
||||||
L["VERSION_WINDOW_NO_MISMATCH"] = "No newer HMGT version has been detected in your current group."
|
L["VERSION_WINDOW_NO_MISMATCH"] = "No newer HMGT version has been detected in your current group."
|
||||||
L["VERSION_WINDOW_CURRENT"] = "Current version: %s | Protocol: %s"
|
L["VERSION_WINDOW_CURRENT"] = "Current version: %s | Protocol: %s"
|
||||||
|
L["VERSION_WINDOW_STATUS"] = "Detected addon or bridge on %d/%d players"
|
||||||
|
L["VERSION_WINDOW_REFRESH"] = "Refresh"
|
||||||
|
L["VERSION_WINDOW_COLUMN_PLAYER"] = "Player"
|
||||||
|
L["VERSION_WINDOW_COLUMN_VERSION"] = "Version"
|
||||||
|
L["VERSION_WINDOW_COLUMN_PROTOCOL"] = "Protocol"
|
||||||
|
L["VERSION_WINDOW_BRIDGE_MODE"] = "Bridge Mode"
|
||||||
|
L["VERSION_WINDOW_MISSING_ADDON"] = "Addon not installed"
|
||||||
|
L["VERSION_WINDOW_LEADER_TAG"] = "(Leader)"
|
||||||
|
L["VERSION_WINDOW_ASSISTANT_TAG"] = "(Assist)"
|
||||||
|
L["VERSION_WINDOW_SELF_TAG"] = "(You)"
|
||||||
|
L["VERSION_OUTDATED_WHISPER"] = "Your Hail Mary Guild Tools version is outdated. You have %s, the group leader has %s."
|
||||||
L["VERSION_WINDOW_DEBUG_ONLY"] = "HMGT: /hmgt version is only available while debug mode is enabled."
|
L["VERSION_WINDOW_DEBUG_ONLY"] = "HMGT: /hmgt version is only available while debug mode is enabled."
|
||||||
L["VERSION_WINDOW_DEVTOOLS_ONLY"] = "HMGT: /hmgt version is only available while developer tools are enabled."
|
L["VERSION_WINDOW_DEVTOOLS_ONLY"] = "HMGT: /hmgt version is only available while the debug console is enabled."
|
||||||
|
|
||||||
-- ── Options: general ─────────────────────────────────────────
|
-- ── Options: general ─────────────────────────────────────────
|
||||||
L["OPT_GENERAL"] = "General"
|
L["OPT_GENERAL"] = "General"
|
||||||
@@ -53,17 +64,47 @@ L["OPT_DEBUG_CLEAR"] = "Clear debug log"
|
|||||||
L["OPT_DEBUG_SELECT_ALL"] = "Select all"
|
L["OPT_DEBUG_SELECT_ALL"] = "Select all"
|
||||||
L["DEBUG_WINDOW_TITLE"] = "HMGT Debug Console"
|
L["DEBUG_WINDOW_TITLE"] = "HMGT Debug Console"
|
||||||
L["DEBUG_WINDOW_HINT"] = "Mouse wheel scrolls, Ctrl+A selects all, Ctrl+C copies selected text"
|
L["DEBUG_WINDOW_HINT"] = "Mouse wheel scrolls, Ctrl+A selects all, Ctrl+C copies selected text"
|
||||||
L["OPT_DEVTOOLS_MODE"] = "Developer tools"
|
L["OPT_DEVTOOLS_MODE"] = "Debug console"
|
||||||
L["OPT_DEVTOOLS_MODE_DESC"] = "Enable the structured developer event console."
|
L["OPT_DEVTOOLS_MODE_DESC"] = "Enable the shared debug and developer log."
|
||||||
L["OPT_DEVTOOLS_LEVEL"] = "Capture level"
|
L["OPT_DEVTOOLS_LEVEL"] = "Debug level"
|
||||||
L["OPT_DEVTOOLS_LEVEL_ERROR"] = "Errors"
|
L["OPT_DEVTOOLS_LEVEL_ERROR"] = "Errors"
|
||||||
L["OPT_DEVTOOLS_LEVEL_TRACE"] = "Trace"
|
L["OPT_DEVTOOLS_LEVEL_TRACE"] = "Verbose"
|
||||||
L["OPT_DEVTOOLS_SCOPE"] = "Scope filter"
|
L["OPT_DEVTOOLS_SCOPE"] = "Module filter"
|
||||||
L["OPT_DEVTOOLS_SCOPE_ALL"] = "All scopes"
|
L["OPT_DEVTOOLS_SCOPE_ALL"] = "All modules"
|
||||||
L["OPT_DEVTOOLS_OPEN"] = "Open developer console"
|
L["OPT_DEVTOOLS_OPEN"] = "Open debug console"
|
||||||
L["OPT_DEVTOOLS_CLEAR"] = "Clear developer log"
|
L["OPT_DEVTOOLS_CLEAR"] = "Clear debug log"
|
||||||
L["OPT_DEVTOOLS_SELECT_ALL"] = "Select all"
|
L["OPT_DEVTOOLS_SELECT_ALL"] = "Select all"
|
||||||
L["OPT_DEVTOOLS_DISABLED"] = "HMGT: developer tools are not enabled."
|
L["OPT_DEVTOOLS_DISABLED"] = "HMGT: developer tools are not enabled."
|
||||||
|
L["OPT_MODULE_ENCOUNTER_ALERTS"] = "Encounter Alerts"
|
||||||
|
L["OPT_ENCOUNTER_ALERTS_PLACEHOLDER"] = "Encounter-specific helper frames and alerts."
|
||||||
|
L["OPT_EA_LURA_TITLE"] = "L'ura Runes"
|
||||||
|
L["OPT_EA_LURA_RUNE_WINDOW"] = "Rune window"
|
||||||
|
L["OPT_EA_LURA_ENABLED"] = "Enable L'ura runes"
|
||||||
|
L["OPT_EA_LURA_UNLOCK"] = "Unlock rune frame"
|
||||||
|
L["OPT_EA_LURA_HINT"] = "First version: normal/heroic layout only. Tank reference is placed bottom-center between slot 1 and 5."
|
||||||
|
L["OPT_EA_LURA_SHOW"] = "Show"
|
||||||
|
L["OPT_EA_LURA_TEST"] = "Test pattern"
|
||||||
|
L["OPT_EA_LURA_CLEAR"] = "Clear"
|
||||||
|
L["OPT_EA_LURA_BROADCAST"] = "Send sequence"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR"] = "Rune action bar"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_SHOW"] = "Show bar"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_UNLOCK"] = "Unlock bar"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_AUTO_SHOW"] = "Auto show in boss room"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_ORIENTATION"] = "Orientation"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_HORIZONTAL"] = "Horizontal"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_VERTICAL"] = "Vertical"
|
||||||
|
L["OPT_EA_LURA_ACTIONBAR_HINT"] = "Click rune buttons in the observed order. Slot 5 sends the sequence automatically. The red button clears the local sequence."
|
||||||
|
L["OPT_EA_LURA_ICON_SIZE"] = "Icon size"
|
||||||
|
L["OPT_EA_LURA_BACKGROUND_ALPHA"] = "Background alpha"
|
||||||
|
L["OPT_EA_LURA_ICON_SPACING"] = "Icon spacing"
|
||||||
|
L["OPT_EA_LURA_BORDER_ENABLED"] = "Show border"
|
||||||
|
L["OPT_EA_LURA_BORDER_WIDTH"] = "Border width"
|
||||||
|
L["OPT_EA_LURA_BORDER_COLOR"] = "Border color"
|
||||||
|
L["OPT_EA_LURA_SHOW_LABELS"] = "Show labels"
|
||||||
|
L["OPT_EA_LURA_RUNE_EMPTY"] = "Empty"
|
||||||
|
L["OPT_EA_LURA_DRAG_HINT"] = "Drag to move"
|
||||||
|
L["OPT_EA_LURA_BOSS"] = "Boss"
|
||||||
|
L["OPT_EA_LURA_TANK"] = "Tank"
|
||||||
L["DEVTOOLS_WINDOW_TITLE"] = "HMGT Developer Tools"
|
L["DEVTOOLS_WINDOW_TITLE"] = "HMGT Developer Tools"
|
||||||
L["DEVTOOLS_WINDOW_HINT"] = "Structured developer events for the current session"
|
L["DEVTOOLS_WINDOW_HINT"] = "Structured developer events for the current session"
|
||||||
L["OPT_SYNC_REMOTE_CHARGES"] = "Sync remote charges"
|
L["OPT_SYNC_REMOTE_CHARGES"] = "Sync remote charges"
|
||||||
|
|||||||
102
Modules/EncounterAlerts/EncounterAlerts.lua
Normal file
102
Modules/EncounterAlerts/EncounterAlerts.lua
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
|
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME, true) or {}
|
||||||
|
|
||||||
|
local EA = HMGT:NewModule("EncounterAlerts", "AceEvent-3.0")
|
||||||
|
HMGT.EncounterAlerts = EA
|
||||||
|
|
||||||
|
EA.runtimeEnabled = false
|
||||||
|
|
||||||
|
function EA:GetSettings()
|
||||||
|
local profile = HMGT.db and HMGT.db.profile
|
||||||
|
profile = profile or {}
|
||||||
|
profile.encounterAlerts = type(profile.encounterAlerts) == "table" and profile.encounterAlerts or {}
|
||||||
|
return profile.encounterAlerts
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:GetLuraRunesSettings()
|
||||||
|
local settings = self:GetSettings()
|
||||||
|
settings.luraRunes = type(settings.luraRunes) == "table" and settings.luraRunes or {}
|
||||||
|
return settings.luraRunes
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:OnEnable()
|
||||||
|
local settings = self:GetSettings()
|
||||||
|
self.runtimeEnabled = settings.enabled == true
|
||||||
|
self:RegisterEvent("PLAYER_ENTERING_WORLD", "RefreshLuraRunesContext")
|
||||||
|
self:RegisterEvent("ZONE_CHANGED", "RefreshLuraRunesContext")
|
||||||
|
self:RegisterEvent("ZONE_CHANGED_INDOORS", "RefreshLuraRunesContext")
|
||||||
|
self:RegisterEvent("ZONE_CHANGED_NEW_AREA", "RefreshLuraRunesContext")
|
||||||
|
self:RegisterEvent("GROUP_ROSTER_UPDATE", "RefreshLuraRunesContext")
|
||||||
|
self:RegisterEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT", "RefreshLuraRunesContext")
|
||||||
|
self:RegisterEvent("PLAYER_TARGET_CHANGED", "RefreshLuraRunesContext")
|
||||||
|
self:RegisterEvent("ENCOUNTER_START", "HandleLuraEncounterStart")
|
||||||
|
self:RegisterEvent("ENCOUNTER_END", "HandleLuraEncounterEnd")
|
||||||
|
self:RegisterEvent("CHAT_MSG_RAID", "HandleLuraRaidChat")
|
||||||
|
self:RegisterEvent("CHAT_MSG_RAID_LEADER", "HandleLuraRaidChat")
|
||||||
|
if self.LuraRunes and self.LuraRunes.Refresh then
|
||||||
|
self.LuraRunes:Refresh()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:Enable()
|
||||||
|
local settings = self:GetSettings()
|
||||||
|
settings.enabled = true
|
||||||
|
self.runtimeEnabled = true
|
||||||
|
if self.LuraRunes and self.LuraRunes.Refresh then
|
||||||
|
self.LuraRunes:Refresh()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:Disable()
|
||||||
|
local settings = self:GetSettings()
|
||||||
|
settings.enabled = false
|
||||||
|
self.runtimeEnabled = false
|
||||||
|
if self.LuraRunes and self.LuraRunes.Hide then
|
||||||
|
self.LuraRunes:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:GetDisplayName()
|
||||||
|
return L["OPT_MODULE_ENCOUNTER_ALERTS"] or "Encounter Alerts"
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:RefreshLuraRunesContext(event)
|
||||||
|
if self.LuraRunes and self.LuraRunes.RefreshContext then
|
||||||
|
self.LuraRunes:RefreshContext(event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:HandleLuraEncounterStart(_, encounterId, encounterName)
|
||||||
|
if self.LuraRunes and self.LuraRunes.OnEncounterStart then
|
||||||
|
self.LuraRunes:OnEncounterStart(encounterId, encounterName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:HandleLuraEncounterEnd(_, encounterId)
|
||||||
|
if self.LuraRunes and self.LuraRunes.OnEncounterEnd then
|
||||||
|
self.LuraRunes:OnEncounterEnd(encounterId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:HandleLuraRunesComm(senderName, payload)
|
||||||
|
if self.LuraRunes and self.LuraRunes.HandleComm then
|
||||||
|
self.LuraRunes:HandleComm(senderName, payload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:HandleLuraRaidChat(event, message, senderName)
|
||||||
|
if self.LuraRunes and self.LuraRunes.HandleRaidChatMessage then
|
||||||
|
self.LuraRunes:HandleRaidChatMessage(message, senderName, event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function EA:HandleSlashCommand(input)
|
||||||
|
if self.LuraRunes and self.LuraRunes.HandleSlashCommand then
|
||||||
|
self.LuraRunes:HandleSlashCommand(input)
|
||||||
|
else
|
||||||
|
HMGT:OpenConfig()
|
||||||
|
end
|
||||||
|
end
|
||||||
414
Modules/EncounterAlerts/EncounterAlertsOptions.lua
Normal file
414
Modules/EncounterAlerts/EncounterAlertsOptions.lua
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
if not HMGT_Config or not HMGT_Config.RegisterOptionsProvider then return end
|
||||||
|
|
||||||
|
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME, true) or {}
|
||||||
|
local AceConfigRegistry = LibStub("AceConfigRegistry-3.0", true)
|
||||||
|
|
||||||
|
local function NotifyOptionsChanged()
|
||||||
|
if AceConfigRegistry and type(AceConfigRegistry.NotifyChange) == "function" then
|
||||||
|
AceConfigRegistry:NotifyChange(ADDON_NAME)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetSettings()
|
||||||
|
local profile = HMGT.db and HMGT.db.profile
|
||||||
|
if not profile then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
profile.encounterAlerts = type(profile.encounterAlerts) == "table" and profile.encounterAlerts or {}
|
||||||
|
return profile.encounterAlerts
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetLuraSettings()
|
||||||
|
local settings = GetSettings()
|
||||||
|
settings.luraRunes = type(settings.luraRunes) == "table" and settings.luraRunes or {}
|
||||||
|
settings.luraRunes.slots = type(settings.luraRunes.slots) == "table" and settings.luraRunes.slots or {}
|
||||||
|
settings.luraRunes.actionBar = type(settings.luraRunes.actionBar) == "table" and settings.luraRunes.actionBar or {}
|
||||||
|
return settings.luraRunes
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetLuraActionBarSettings()
|
||||||
|
local settings = GetLuraSettings()
|
||||||
|
settings.actionBar.shown = settings.actionBar.shown == true
|
||||||
|
settings.actionBar.autoShow = settings.actionBar.autoShow ~= false
|
||||||
|
settings.actionBar.unlocked = settings.actionBar.unlocked == true
|
||||||
|
settings.actionBar.iconSize = tonumber(settings.actionBar.iconSize) or 42
|
||||||
|
settings.actionBar.iconSpacing = tonumber(settings.actionBar.iconSpacing) or 8
|
||||||
|
settings.actionBar.orientation = tostring(settings.actionBar.orientation or "horizontal")
|
||||||
|
if settings.actionBar.orientation ~= "vertical" then
|
||||||
|
settings.actionBar.orientation = "horizontal"
|
||||||
|
end
|
||||||
|
settings.actionBar.border = type(settings.actionBar.border) == "table" and settings.actionBar.border or {}
|
||||||
|
return settings.actionBar
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetLuraBorderSettings()
|
||||||
|
local actionBar = GetLuraActionBarSettings()
|
||||||
|
actionBar.border.enabled = actionBar.border.enabled == true
|
||||||
|
actionBar.border.width = tonumber(actionBar.border.width) or 2
|
||||||
|
actionBar.border.color = type(actionBar.border.color) == "table" and actionBar.border.color or {}
|
||||||
|
actionBar.border.color.r = tonumber(actionBar.border.color.r) or 1
|
||||||
|
actionBar.border.color.g = tonumber(actionBar.border.color.g) or 0.82
|
||||||
|
actionBar.border.color.b = tonumber(actionBar.border.color.b) or 0.1
|
||||||
|
actionBar.border.color.a = tonumber(actionBar.border.color.a) or 0.9
|
||||||
|
return actionBar.border
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetActionBarOrientationValues()
|
||||||
|
return {
|
||||||
|
horizontal = L["OPT_EA_LURA_ACTIONBAR_HORIZONTAL"] or "Horizontal",
|
||||||
|
vertical = L["OPT_EA_LURA_ACTIONBAR_VERTICAL"] or "Vertical",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetLuraRunes()
|
||||||
|
return HMGT.EncounterAlerts and HMGT.EncounterAlerts.LuraRunes or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function RefreshEncounterAlerts()
|
||||||
|
if HMGT.EncounterAlerts then
|
||||||
|
local settings = GetSettings()
|
||||||
|
HMGT.EncounterAlerts.runtimeEnabled = settings.enabled == true
|
||||||
|
if HMGT.EncounterAlerts.LuraRunes and HMGT.EncounterAlerts.LuraRunes.Refresh then
|
||||||
|
HMGT.EncounterAlerts.LuraRunes:Refresh()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
NotifyOptionsChanged()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function BuildRuneWindowOptions()
|
||||||
|
return {
|
||||||
|
type = "group",
|
||||||
|
inline = true,
|
||||||
|
order = 2,
|
||||||
|
name = L["OPT_EA_LURA_RUNE_WINDOW"] or "Rune window",
|
||||||
|
args = {
|
||||||
|
unlocked = {
|
||||||
|
type = "toggle",
|
||||||
|
order = 1,
|
||||||
|
width = "double",
|
||||||
|
name = L["OPT_EA_LURA_UNLOCK"] or "Unlock rune frame",
|
||||||
|
get = function()
|
||||||
|
return GetLuraSettings().unlocked == true
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetSettings().enabled = true
|
||||||
|
local settings = GetLuraSettings()
|
||||||
|
settings.enabled = true
|
||||||
|
settings.unlocked = value == true
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
hint = {
|
||||||
|
type = "description",
|
||||||
|
order = 2,
|
||||||
|
width = "full",
|
||||||
|
name = L["OPT_EA_LURA_HINT"] or "First version: normal/heroic layout only. Tank reference is placed bottom-center between slot 1 and 5.",
|
||||||
|
},
|
||||||
|
show = {
|
||||||
|
type = "execute",
|
||||||
|
order = 3,
|
||||||
|
width = 0.8,
|
||||||
|
name = L["OPT_EA_LURA_SHOW"] or "Show",
|
||||||
|
func = function()
|
||||||
|
local lura = GetLuraRunes()
|
||||||
|
if lura and lura.Show then
|
||||||
|
lura:Show()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
test = {
|
||||||
|
type = "execute",
|
||||||
|
order = 4,
|
||||||
|
width = 0.9,
|
||||||
|
name = L["OPT_EA_LURA_TEST"] or "Test pattern",
|
||||||
|
func = function()
|
||||||
|
local lura = GetLuraRunes()
|
||||||
|
if lura and lura.Show and lura.ApplyTestPattern then
|
||||||
|
lura:Show()
|
||||||
|
lura:ApplyTestPattern()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
clear = {
|
||||||
|
type = "execute",
|
||||||
|
order = 5,
|
||||||
|
width = 0.8,
|
||||||
|
name = L["OPT_EA_LURA_CLEAR"] or "Clear",
|
||||||
|
func = function()
|
||||||
|
local lura = GetLuraRunes()
|
||||||
|
if lura and lura.ClearAssignments then
|
||||||
|
lura:ClearAssignments(false)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
broadcast = {
|
||||||
|
type = "execute",
|
||||||
|
order = 6,
|
||||||
|
width = 1.2,
|
||||||
|
name = L["OPT_EA_LURA_BROADCAST"] or "Broadcast",
|
||||||
|
disabled = function()
|
||||||
|
local lura = GetLuraRunes()
|
||||||
|
return lura and lura.CanBroadcastSequence and not lura:CanBroadcastSequence() or false
|
||||||
|
end,
|
||||||
|
func = function()
|
||||||
|
local lura = GetLuraRunes()
|
||||||
|
if lura and lura.BroadcastAssignments then
|
||||||
|
lura:BroadcastAssignments()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
iconSize = {
|
||||||
|
type = "range",
|
||||||
|
order = 7,
|
||||||
|
width = 1.1,
|
||||||
|
min = 28,
|
||||||
|
max = 80,
|
||||||
|
step = 1,
|
||||||
|
name = L["OPT_EA_LURA_ICON_SIZE"] or "Icon size",
|
||||||
|
get = function()
|
||||||
|
return tonumber(GetLuraSettings().iconSize) or 44
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetLuraSettings().iconSize = tonumber(value) or 44
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
backgroundAlpha = {
|
||||||
|
type = "range",
|
||||||
|
order = 8,
|
||||||
|
width = 1.1,
|
||||||
|
min = 0,
|
||||||
|
max = 0.8,
|
||||||
|
step = 0.01,
|
||||||
|
name = L["OPT_EA_LURA_BACKGROUND_ALPHA"] or "Background alpha",
|
||||||
|
get = function()
|
||||||
|
return tonumber(GetLuraSettings().backgroundAlpha) or 0.14
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetLuraSettings().backgroundAlpha = tonumber(value) or 0.14
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function BuildRuneActionBarOptions()
|
||||||
|
return {
|
||||||
|
type = "group",
|
||||||
|
inline = true,
|
||||||
|
order = 3,
|
||||||
|
name = L["OPT_EA_LURA_ACTIONBAR"] or "Rune action bar",
|
||||||
|
args = {
|
||||||
|
shown = {
|
||||||
|
type = "toggle",
|
||||||
|
order = 1,
|
||||||
|
width = 1.1,
|
||||||
|
name = L["OPT_EA_LURA_ACTIONBAR_SHOW"] or "Show bar",
|
||||||
|
get = function()
|
||||||
|
return GetLuraActionBarSettings().shown == true
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetSettings().enabled = true
|
||||||
|
local settings = GetLuraSettings()
|
||||||
|
settings.enabled = true
|
||||||
|
GetLuraActionBarSettings().shown = value == true
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
unlocked = {
|
||||||
|
type = "toggle",
|
||||||
|
order = 2,
|
||||||
|
width = 1.2,
|
||||||
|
name = L["OPT_EA_LURA_ACTIONBAR_UNLOCK"] or "Unlock bar",
|
||||||
|
get = function()
|
||||||
|
return GetLuraActionBarSettings().unlocked == true
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetSettings().enabled = true
|
||||||
|
local settings = GetLuraSettings()
|
||||||
|
settings.enabled = true
|
||||||
|
local actionBar = GetLuraActionBarSettings()
|
||||||
|
actionBar.shown = true
|
||||||
|
actionBar.unlocked = value == true
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
autoShow = {
|
||||||
|
type = "toggle",
|
||||||
|
order = 2.5,
|
||||||
|
width = 1.4,
|
||||||
|
name = L["OPT_EA_LURA_ACTIONBAR_AUTO_SHOW"] or "Auto show in boss room",
|
||||||
|
get = function()
|
||||||
|
return GetLuraActionBarSettings().autoShow == true
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetLuraActionBarSettings().autoShow = value == true
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
orientation = {
|
||||||
|
type = "select",
|
||||||
|
order = 3,
|
||||||
|
width = 1.2,
|
||||||
|
name = L["OPT_EA_LURA_ACTIONBAR_ORIENTATION"] or "Orientation",
|
||||||
|
values = GetActionBarOrientationValues,
|
||||||
|
get = function()
|
||||||
|
return GetLuraActionBarSettings().orientation
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
local actionBar = GetLuraActionBarSettings()
|
||||||
|
actionBar.orientation = tostring(value or "horizontal")
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
iconSize = {
|
||||||
|
type = "range",
|
||||||
|
order = 5,
|
||||||
|
width = 1.1,
|
||||||
|
min = 28,
|
||||||
|
max = 80,
|
||||||
|
step = 1,
|
||||||
|
name = L["OPT_EA_LURA_ICON_SIZE"] or "Icon size",
|
||||||
|
get = function()
|
||||||
|
return tonumber(GetLuraActionBarSettings().iconSize) or 42
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetLuraActionBarSettings().iconSize = tonumber(value) or 42
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
iconSpacing = {
|
||||||
|
type = "range",
|
||||||
|
order = 6,
|
||||||
|
width = 1.1,
|
||||||
|
min = 0,
|
||||||
|
max = 80,
|
||||||
|
step = 1,
|
||||||
|
name = L["OPT_EA_LURA_ICON_SPACING"] or "Icon spacing",
|
||||||
|
get = function()
|
||||||
|
return tonumber(GetLuraActionBarSettings().iconSpacing) or 8
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetLuraActionBarSettings().iconSpacing = tonumber(value) or 8
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
borderEnabled = {
|
||||||
|
type = "toggle",
|
||||||
|
order = 7,
|
||||||
|
width = 1.1,
|
||||||
|
name = L["OPT_EA_LURA_BORDER_ENABLED"] or "Show border",
|
||||||
|
get = function()
|
||||||
|
return GetLuraBorderSettings().enabled == true
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetLuraBorderSettings().enabled = value == true
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
borderWidth = {
|
||||||
|
type = "range",
|
||||||
|
order = 8,
|
||||||
|
width = 1.1,
|
||||||
|
min = 1,
|
||||||
|
max = 12,
|
||||||
|
step = 1,
|
||||||
|
name = L["OPT_EA_LURA_BORDER_WIDTH"] or "Border width",
|
||||||
|
disabled = function()
|
||||||
|
return GetLuraBorderSettings().enabled ~= true
|
||||||
|
end,
|
||||||
|
get = function()
|
||||||
|
return tonumber(GetLuraBorderSettings().width) or 2
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
GetLuraBorderSettings().width = tonumber(value) or 2
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
borderColor = {
|
||||||
|
type = "color",
|
||||||
|
order = 9,
|
||||||
|
width = 1.1,
|
||||||
|
hasAlpha = true,
|
||||||
|
name = L["OPT_EA_LURA_BORDER_COLOR"] or "Border color",
|
||||||
|
disabled = function()
|
||||||
|
return GetLuraBorderSettings().enabled ~= true
|
||||||
|
end,
|
||||||
|
get = function()
|
||||||
|
local color = GetLuraBorderSettings().color
|
||||||
|
return color.r or 1, color.g or 0.82, color.b or 0.1, color.a or 0.9
|
||||||
|
end,
|
||||||
|
set = function(_, r, g, b, a)
|
||||||
|
local color = GetLuraBorderSettings().color
|
||||||
|
color.r = tonumber(r) or 1
|
||||||
|
color.g = tonumber(g) or 0.82
|
||||||
|
color.b = tonumber(b) or 0.1
|
||||||
|
color.a = tonumber(a) or 0.9
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
hint = {
|
||||||
|
type = "description",
|
||||||
|
order = 10,
|
||||||
|
width = "full",
|
||||||
|
name = L["OPT_EA_LURA_ACTIONBAR_HINT"] or "Click rune buttons in the observed order. Slot 5 sends the sequence automatically.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
HMGT_Config:RegisterOptionsProvider("encounterAlerts", function()
|
||||||
|
return {
|
||||||
|
path = "encounterAlerts",
|
||||||
|
order = 50,
|
||||||
|
group = {
|
||||||
|
type = "group",
|
||||||
|
name = L["OPT_MODULE_ENCOUNTER_ALERTS"] or "Encounter Alerts",
|
||||||
|
order = 50,
|
||||||
|
childGroups = "tab",
|
||||||
|
args = {
|
||||||
|
general = {
|
||||||
|
type = "group",
|
||||||
|
name = L["OPT_GENERAL"] or "General",
|
||||||
|
order = 1,
|
||||||
|
args = {
|
||||||
|
description = {
|
||||||
|
type = "description",
|
||||||
|
order = 1,
|
||||||
|
width = "full",
|
||||||
|
name = L["OPT_ENCOUNTER_ALERTS_PLACEHOLDER"] or "Encounter-specific helper frames and alerts.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
luraRunes = {
|
||||||
|
type = "group",
|
||||||
|
name = L["OPT_EA_LURA_TITLE"] or "L'ura Runes",
|
||||||
|
order = 2,
|
||||||
|
args = {
|
||||||
|
enabled = {
|
||||||
|
type = "toggle",
|
||||||
|
order = 1,
|
||||||
|
width = "double",
|
||||||
|
name = L["OPT_EA_LURA_ENABLED"] or "Enable L'ura runes",
|
||||||
|
get = function()
|
||||||
|
return GetLuraSettings().enabled == true
|
||||||
|
end,
|
||||||
|
set = function(_, value)
|
||||||
|
local enabled = value == true
|
||||||
|
GetSettings().enabled = enabled
|
||||||
|
GetLuraSettings().enabled = enabled
|
||||||
|
RefreshEncounterAlerts()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
runeWindow = BuildRuneWindowOptions(),
|
||||||
|
actionBar = BuildRuneActionBarOptions(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end)
|
||||||
1113
Modules/EncounterAlerts/LuraRunes.lua
Normal file
1113
Modules/EncounterAlerts/LuraRunes.lua
Normal file
File diff suppressed because it is too large
Load Diff
1
Modules/EncounterAlerts/Media/LuraRunes/.gitkeep
Normal file
1
Modules/EncounterAlerts/Media/LuraRunes/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_Circle.tga
Normal file
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_Circle.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_Diamond.tga
Normal file
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_Diamond.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_T.tga
Normal file
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_T.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_Triangle.tga
Normal file
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_Triangle.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_X.tga
Normal file
BIN
Modules/EncounterAlerts/Media/LuraRunes/Rune_X.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
@@ -1,8 +0,0 @@
|
|||||||
local ADDON_NAME = "HailMaryGuildTools"
|
|
||||||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
|
||||||
if not HMGT then return end
|
|
||||||
|
|
||||||
local RT = HMGT.RaidTimeline
|
|
||||||
if not RT then return end
|
|
||||||
|
|
||||||
-- Placeholder for later DBM-specific raid timeline integration.
|
|
||||||
@@ -1,692 +1,69 @@
|
|||||||
-- Modules/GroupCooldownTracker.lua
|
|
||||||
-- Group-Cooldown-Tracker Modul (ein Frame pro Spieler in der Gruppe)
|
|
||||||
|
|
||||||
local ADDON_NAME = "HailMaryGuildTools"
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
if not HMGT then return end
|
if not HMGT then return end
|
||||||
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
|
||||||
|
|
||||||
local GCT = HMGT:NewModule("GroupCooldownTracker")
|
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
||||||
HMGT.GroupCooldownTracker = GCT
|
|
||||||
|
|
||||||
GCT.frame = nil
|
local module = HMGT:NewModule("GroupCooldownTracker")
|
||||||
GCT.frames = {}
|
HMGT.GroupCooldownTracker = module
|
||||||
|
|
||||||
local function SanitizeFrameToken(name)
|
module.definition = {
|
||||||
if not name or name == "" then return "Unknown" end
|
moduleName = "GroupCooldownTracker",
|
||||||
return name:gsub("[^%w_]", "_")
|
dbKey = "groupCooldownTracker",
|
||||||
end
|
trackerType = "group",
|
||||||
|
trackerKey = "groupCooldownTracker",
|
||||||
local function ShortName(name)
|
title = function()
|
||||||
if not name then return "" end
|
return L["GCD_TITLE"]
|
||||||
local short = name:match("^[^-]+")
|
end,
|
||||||
return short or name
|
categories = { "tank", "defensive", "healing", "cc", "utility", "offensive", "lust", "interrupt" },
|
||||||
end
|
|
||||||
|
|
||||||
local function IsUsableAnchorFrame(frame)
|
|
||||||
return frame
|
|
||||||
and frame.IsObjectType
|
|
||||||
and (frame:IsObjectType("Frame") or frame:IsObjectType("Button"))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function GetFrameUnit(frame)
|
|
||||||
if not frame then return nil end
|
|
||||||
local unit = frame.unit
|
|
||||||
if not unit and frame.GetAttribute then
|
|
||||||
unit = frame:GetAttribute("unit")
|
|
||||||
end
|
|
||||||
return unit
|
|
||||||
end
|
|
||||||
|
|
||||||
local function FrameMatchesUnit(frame, unitId)
|
|
||||||
if not IsUsableAnchorFrame(frame) then return false end
|
|
||||||
if not unitId then return true end
|
|
||||||
local unit = GetFrameUnit(frame)
|
|
||||||
return unit == unitId
|
|
||||||
end
|
|
||||||
|
|
||||||
local PLAYER_FRAME_CANDIDATES = {
|
|
||||||
"PlayerFrame",
|
|
||||||
"ElvUF_Player",
|
|
||||||
"NephUI_PlayerFrame",
|
|
||||||
"NephUIPlayerFrame",
|
|
||||||
"oUF_NephUI_Player",
|
|
||||||
"SUFUnitplayer",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local PARTY_FRAME_PATTERNS = {
|
function module:GetDefinition()
|
||||||
"PartyMemberFrame%d", -- Blizzard alt
|
return self.definition
|
||||||
"CompactPartyFrameMember%d", -- Blizzard modern
|
|
||||||
"ElvUF_PartyGroup1UnitButton%d", -- ElvUI
|
|
||||||
"ElvUF_PartyUnitButton%d", -- ElvUI variant
|
|
||||||
"NephUI_PartyUnitButton%d", -- NephUI (common naming variants)
|
|
||||||
"NephUI_PartyFrame%d",
|
|
||||||
"NephUIPartyFrame%d",
|
|
||||||
"oUF_NephUI_PartyUnitButton%d",
|
|
||||||
"SUFUnitparty%d", -- Shadowed Unit Frames
|
|
||||||
}
|
|
||||||
|
|
||||||
local unitFrameCache = {}
|
|
||||||
|
|
||||||
local function EntryNeedsVisualTicker(entry)
|
|
||||||
if type(entry) ~= "table" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local remaining = tonumber(entry.remaining) or 0
|
|
||||||
if remaining > 0 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local maxCharges = tonumber(entry.maxCharges) or 0
|
|
||||||
local currentCharges = tonumber(entry.currentCharges)
|
|
||||||
if maxCharges > 0 and currentCharges ~= nil and currentCharges < maxCharges then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function BuildAnchorLayoutSignature(settings, ordered, unitByPlayer)
|
function module:GetSettings()
|
||||||
local parts = {
|
for _, tracker in ipairs(HMGT:GetTrackerConfigs()) do
|
||||||
settings.attachToPartyFrame == true and "attach" or "stack",
|
if tracker.trackerKey == self.definition.trackerKey then
|
||||||
tostring(settings.partyAttachSide or "RIGHT"),
|
return tracker
|
||||||
tostring(tonumber(settings.partyAttachOffsetX) or 8),
|
|
||||||
tostring(tonumber(settings.partyAttachOffsetY) or 0),
|
|
||||||
tostring(settings.showBar and "bar" or "icon"),
|
|
||||||
tostring(settings.growDirection or "DOWN"),
|
|
||||||
tostring(settings.width or 250),
|
|
||||||
tostring(settings.barHeight or 20),
|
|
||||||
tostring(settings.iconSize or 32),
|
|
||||||
tostring(settings.iconCols or 6),
|
|
||||||
tostring(settings.barSpacing or 2),
|
|
||||||
tostring(settings.locked),
|
|
||||||
tostring(settings.anchorTo or "UIParent"),
|
|
||||||
tostring(settings.anchorPoint or "TOPLEFT"),
|
|
||||||
tostring(settings.anchorRelPoint or "TOPLEFT"),
|
|
||||||
tostring(settings.anchorX or settings.posX or 0),
|
|
||||||
tostring(settings.anchorY or settings.posY or 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, playerName in ipairs(ordered or {}) do
|
|
||||||
parts[#parts + 1] = tostring(playerName)
|
|
||||||
parts[#parts + 1] = tostring(unitByPlayer and unitByPlayer[playerName] or "")
|
|
||||||
end
|
|
||||||
|
|
||||||
return table.concat(parts, "|")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ResolveNamedUnitFrame(unitId)
|
|
||||||
if unitId == "player" then
|
|
||||||
for _, frameName in ipairs(PLAYER_FRAME_CANDIDATES) do
|
|
||||||
local frame = _G[frameName]
|
|
||||||
if FrameMatchesUnit(frame, unitId) or (frameName == "PlayerFrame" and IsUsableAnchorFrame(frame)) then
|
|
||||||
return frame
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local idx = type(unitId) == "string" and unitId:match("^party(%d+)$")
|
|
||||||
if not idx then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
idx = tonumber(idx)
|
|
||||||
for _, pattern in ipairs(PARTY_FRAME_PATTERNS) do
|
|
||||||
local frame = _G[pattern:format(idx)]
|
|
||||||
if FrameMatchesUnit(frame, unitId) then
|
|
||||||
return frame
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ScanUnitFrame(unitId)
|
function module:Enable()
|
||||||
local frame = EnumerateFrames()
|
if HMGT.TrackerManager and HMGT.TrackerManager.Enable then
|
||||||
local scanned = 0
|
HMGT.TrackerManager:Enable()
|
||||||
while frame and scanned < 8000 do
|
|
||||||
if IsUsableAnchorFrame(frame) then
|
|
||||||
local unit = GetFrameUnit(frame)
|
|
||||||
if unit == unitId then
|
|
||||||
HMGT:DebugScoped("verbose", HMGT:GetTrackerDebugScope("Group Cooldowns"), "GroupAttach scan unit=%s scanned=%d found=true", tostring(unitId), scanned)
|
|
||||||
return frame
|
|
||||||
end
|
|
||||||
end
|
|
||||||
scanned = scanned + 1
|
|
||||||
frame = EnumerateFrames(frame)
|
|
||||||
end
|
|
||||||
HMGT:DebugScoped("verbose", HMGT:GetTrackerDebugScope("Group Cooldowns"), "GroupAttach scan unit=%s scanned=%d found=false", tostring(unitId), scanned)
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ResolveUnitAnchorFrame(unitId)
|
|
||||||
if not unitId then return nil end
|
|
||||||
|
|
||||||
local now = GetTime()
|
|
||||||
local cached = unitFrameCache[unitId]
|
|
||||||
if cached and now < (cached.expires or 0) then
|
|
||||||
if cached.frame and cached.frame:IsShown() then
|
|
||||||
return cached.frame
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local frame = ResolveNamedUnitFrame(unitId)
|
|
||||||
if not frame then
|
|
||||||
frame = ScanUnitFrame(unitId)
|
|
||||||
end
|
|
||||||
|
|
||||||
local expiresIn = 1.0
|
|
||||||
if frame and frame:IsShown() then
|
|
||||||
expiresIn = 10.0
|
|
||||||
end
|
|
||||||
unitFrameCache[unitId] = {
|
|
||||||
frame = frame,
|
|
||||||
expires = now + expiresIn,
|
|
||||||
}
|
|
||||||
|
|
||||||
if frame and frame:IsShown() then
|
|
||||||
return frame
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:GetFrameIdForPlayer(playerName)
|
|
||||||
return "GroupCooldownTracker_" .. SanitizeFrameToken(playerName)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:GetPlayerFrame(playerName)
|
|
||||||
if not playerName then return nil end
|
|
||||||
return self.frames[playerName]
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:GetAnchorableFrames()
|
|
||||||
return self.frames
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:EnsurePlayerFrame(playerName)
|
|
||||||
local frame = self.frames[playerName]
|
|
||||||
local s = HMGT.db.profile.groupCooldownTracker
|
|
||||||
if frame then
|
|
||||||
return frame
|
|
||||||
end
|
|
||||||
|
|
||||||
frame = HMGT.TrackerFrame:CreateTrackerFrame(self:GetFrameIdForPlayer(playerName), s)
|
|
||||||
frame._hmgtPlayerName = playerName
|
|
||||||
self.frames[playerName] = frame
|
|
||||||
return frame
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:HideAllFrames()
|
|
||||||
for _, frame in pairs(self.frames) do
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
self.activeOrder = nil
|
|
||||||
self.unitByPlayer = nil
|
|
||||||
self.frame = nil
|
|
||||||
self._lastAnchorLayoutSignature = nil
|
|
||||||
self._nextAnchorRetryAt = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:SetLockedAll(locked)
|
|
||||||
for _, frame in pairs(self.frames) do
|
|
||||||
HMGT.TrackerFrame:SetLocked(frame, locked)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function GCT:EnsureUpdateTicker()
|
function module:Disable()
|
||||||
if self.updateTicker then
|
if HMGT.TrackerManager and HMGT.TrackerManager.UpdateDisplay then
|
||||||
return
|
HMGT.TrackerManager:UpdateDisplay()
|
||||||
end
|
|
||||||
self.updateTicker = C_Timer.NewTicker(0.1, function()
|
|
||||||
self:UpdateDisplay()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:StopUpdateTicker()
|
|
||||||
if self.updateTicker then
|
|
||||||
self.updateTicker:Cancel()
|
|
||||||
self.updateTicker = nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function GCT:SetUpdateTickerEnabled(enabled)
|
function module:SetLockedAll(locked)
|
||||||
if enabled then
|
if HMGT.TrackerManager and HMGT.TrackerManager.SetAllLocked then
|
||||||
self:EnsureUpdateTicker()
|
HMGT.TrackerManager:SetAllLocked(locked)
|
||||||
else
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function GCT:InvalidateAnchorLayout()
|
function module:RefreshAnchors(force)
|
||||||
self._lastAnchorLayoutSignature = nil
|
if HMGT.TrackerManager and HMGT.TrackerManager.RefreshAnchors then
|
||||||
self._nextAnchorRetryAt = nil
|
HMGT.TrackerManager:RefreshAnchors(force)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function GCT:RefreshAnchors(force)
|
function module:InvalidateAnchorLayout()
|
||||||
local s = HMGT.db.profile.groupCooldownTracker
|
if HMGT.TrackerManager and HMGT.TrackerManager.InvalidateAnchorLayout then
|
||||||
if not s then return end
|
HMGT.TrackerManager:InvalidateAnchorLayout()
|
||||||
|
|
||||||
local ordered = {}
|
|
||||||
for _, playerName in ipairs(self.activeOrder or {}) do
|
|
||||||
local frame = self.frames[playerName]
|
|
||||||
if frame and frame:IsShown() then
|
|
||||||
table.insert(ordered, playerName)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if #ordered == 0 then
|
|
||||||
self.frame = nil
|
|
||||||
self._lastAnchorLayoutSignature = nil
|
|
||||||
self._nextAnchorRetryAt = nil
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local now = GetTime()
|
|
||||||
local signature = BuildAnchorLayoutSignature(s, ordered, self.unitByPlayer)
|
|
||||||
if not force and self._lastAnchorLayoutSignature == signature then
|
|
||||||
local retryAt = tonumber(self._nextAnchorRetryAt) or 0
|
|
||||||
if retryAt <= 0 or now < retryAt then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Do not force anchor updates while user is dragging a tracker frame.
|
|
||||||
for _, playerName in ipairs(ordered) do
|
|
||||||
local frame = self.frames[playerName]
|
|
||||||
if frame and frame._hmgtDragging then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local primaryName = ordered[1]
|
|
||||||
local primary = self.frames[primaryName]
|
|
||||||
self.frame = primary
|
|
||||||
|
|
||||||
if s.attachToPartyFrame == true then
|
|
||||||
local side = s.partyAttachSide or "RIGHT"
|
|
||||||
local extraX = tonumber(s.partyAttachOffsetX) or 8
|
|
||||||
local extraY = tonumber(s.partyAttachOffsetY) or 0
|
|
||||||
local growsUp = s.showBar == true and s.growDirection == "UP"
|
|
||||||
local barHeight = tonumber(s.barHeight) or 20
|
|
||||||
local growUpAttachOffset = barHeight + 20
|
|
||||||
local prevPlaced = nil
|
|
||||||
local missingTargets = 0
|
|
||||||
|
|
||||||
for i = 1, #ordered do
|
|
||||||
local playerName = ordered[i]
|
|
||||||
local frame = self.frames[playerName]
|
|
||||||
local unitId = self.unitByPlayer and self.unitByPlayer[playerName]
|
|
||||||
local target = ResolveUnitAnchorFrame(unitId)
|
|
||||||
local contentTopInset = HMGT.TrackerFrame.GetContentTopInset and HMGT.TrackerFrame:GetContentTopInset(frame) or 0
|
|
||||||
|
|
||||||
frame:ClearAllPoints()
|
|
||||||
if target then
|
|
||||||
if side == "LEFT" then
|
|
||||||
if growsUp then
|
|
||||||
frame:SetPoint("BOTTOMRIGHT", target, "TOPLEFT", -extraX, extraY - growUpAttachOffset)
|
|
||||||
else
|
|
||||||
frame:SetPoint("TOPRIGHT", target, "TOPLEFT", -extraX, extraY + contentTopInset)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if growsUp then
|
|
||||||
frame:SetPoint("BOTTOMLEFT", target, "TOPRIGHT", extraX, extraY - growUpAttachOffset)
|
|
||||||
else
|
|
||||||
frame:SetPoint("TOPLEFT", target, "TOPRIGHT", extraX, extraY + contentTopInset)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif prevPlaced then
|
|
||||||
missingTargets = missingTargets + 1
|
|
||||||
HMGT:DebugScoped("verbose", HMGT:GetTrackerDebugScope("Group Cooldowns"), "GroupAttach fallback-stack player=%s unit=%s", tostring(playerName), tostring(unitId))
|
|
||||||
if growsUp then
|
|
||||||
frame:SetPoint("BOTTOMLEFT", prevPlaced, "TOPLEFT", 0, (s.barSpacing or 2) + 10)
|
|
||||||
else
|
|
||||||
frame:SetPoint("TOPLEFT", prevPlaced, "BOTTOMLEFT", 0, -((s.barSpacing or 2) + 10))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
missingTargets = missingTargets + 1
|
|
||||||
HMGT:DebugScoped("info", HMGT:GetTrackerDebugScope("Group Cooldowns"), "GroupAttach fallback-anchor player=%s unit=%s (no party frame found)", tostring(playerName), tostring(unitId))
|
|
||||||
HMGT.TrackerFrame:ApplyAnchor(frame)
|
|
||||||
end
|
|
||||||
|
|
||||||
frame:EnableMouse(false)
|
|
||||||
prevPlaced = frame
|
|
||||||
end
|
|
||||||
if missingTargets > 0 then
|
|
||||||
self._lastAnchorLayoutSignature = nil
|
|
||||||
self._nextAnchorRetryAt = now + 1.0
|
|
||||||
else
|
|
||||||
self._lastAnchorLayoutSignature = signature
|
|
||||||
self._nextAnchorRetryAt = nil
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
HMGT.TrackerFrame:ApplyAnchor(primary)
|
|
||||||
primary:EnableMouse(not s.locked)
|
|
||||||
|
|
||||||
local gap = (s.barSpacing or 2) + 10
|
|
||||||
local growsUp = s.showBar == true and s.growDirection == "UP"
|
|
||||||
for i = 2, #ordered do
|
|
||||||
local prev = self.frames[ordered[i - 1]]
|
|
||||||
local frame = self.frames[ordered[i]]
|
|
||||||
frame:ClearAllPoints()
|
|
||||||
if growsUp then
|
|
||||||
frame:SetPoint("BOTTOMLEFT", prev, "TOPLEFT", 0, gap)
|
|
||||||
else
|
|
||||||
frame:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -gap)
|
|
||||||
end
|
|
||||||
frame:EnableMouse(false)
|
|
||||||
end
|
|
||||||
self._lastAnchorLayoutSignature = signature
|
|
||||||
self._nextAnchorRetryAt = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ============================================================
|
function module:GetAnchorableFrames()
|
||||||
-- ENABLE / DISABLE
|
if HMGT.TrackerManager and HMGT.TrackerManager.GetAnchorableFrames then
|
||||||
-- ============================================================
|
return HMGT.TrackerManager:GetAnchorableFrames()
|
||||||
|
end
|
||||||
function GCT:Enable()
|
return {}
|
||||||
local s = HMGT.db.profile.groupCooldownTracker
|
|
||||||
if not s.enabled and not s.demoMode and not s.testMode then return end
|
|
||||||
|
|
||||||
self:UpdateDisplay()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function GCT:Disable()
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
self:HideAllFrames()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- DISPLAY UPDATE
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
function GCT:UpdateDisplay()
|
|
||||||
local s = HMGT.db.profile.groupCooldownTracker
|
|
||||||
if not s then return end
|
|
||||||
|
|
||||||
if s.testMode then
|
|
||||||
local entries, playerName = HMGT:GetOwnTestEntries(HMGT_SpellData.GroupCooldowns, s, {
|
|
||||||
deferChargeCooldownUntilEmpty = false,
|
|
||||||
})
|
|
||||||
local byPlayer = { [playerName] = {} }
|
|
||||||
for _, entry in ipairs(entries) do
|
|
||||||
entry.playerName = playerName
|
|
||||||
table.insert(byPlayer[playerName], entry)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.activeOrder = { playerName }
|
|
||||||
self.unitByPlayer = { [playerName] = "player" }
|
|
||||||
self.lastEntryCount = 0
|
|
||||||
local active = {}
|
|
||||||
local shownOrder = {}
|
|
||||||
local shouldTick = false
|
|
||||||
for _, pName in ipairs(self.activeOrder) do
|
|
||||||
local frame = self:EnsurePlayerFrame(pName)
|
|
||||||
HMGT.TrackerFrame:SetLocked(frame, s.locked)
|
|
||||||
HMGT.TrackerFrame:SetTitle(frame, string.format("%s - %s", L["GCD_TITLE"], ShortName(pName)))
|
|
||||||
local displayEntries = byPlayer[pName]
|
|
||||||
if HMGT.FilterDisplayEntries then
|
|
||||||
displayEntries = HMGT:FilterDisplayEntries(s, displayEntries) or displayEntries
|
|
||||||
end
|
|
||||||
if HMGT.SortDisplayEntries then
|
|
||||||
HMGT:SortDisplayEntries(displayEntries, "groupCooldownTracker")
|
|
||||||
end
|
|
||||||
if #displayEntries > 0 then
|
|
||||||
HMGT.TrackerFrame:UpdateFrame(frame, displayEntries, true)
|
|
||||||
self.lastEntryCount = self.lastEntryCount + #displayEntries
|
|
||||||
frame:Show()
|
|
||||||
active[pName] = true
|
|
||||||
shownOrder[#shownOrder + 1] = pName
|
|
||||||
for _, entry in ipairs(displayEntries) do
|
|
||||||
if EntryNeedsVisualTicker(entry) then
|
|
||||||
shouldTick = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.activeOrder = shownOrder
|
|
||||||
|
|
||||||
for pn, frame in pairs(self.frames) do
|
|
||||||
if not active[pn] then
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:RefreshAnchors()
|
|
||||||
self:SetUpdateTickerEnabled(shouldTick)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if s.demoMode then
|
|
||||||
local entries = HMGT:GetDemoEntries("groupCooldownTracker", HMGT_SpellData.GroupCooldowns, s)
|
|
||||||
local playerName = HMGT:NormalizePlayerName(UnitName("player")) or "DemoPlayer"
|
|
||||||
local byPlayer = { [playerName] = {} }
|
|
||||||
for _, entry in ipairs(entries) do
|
|
||||||
entry.playerName = playerName
|
|
||||||
table.insert(byPlayer[playerName], entry)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.activeOrder = { playerName }
|
|
||||||
self.unitByPlayer = { [playerName] = "player" }
|
|
||||||
self.lastEntryCount = 0
|
|
||||||
local active = {}
|
|
||||||
local shownOrder = {}
|
|
||||||
local shouldTick = false
|
|
||||||
for _, playerName in ipairs(self.activeOrder) do
|
|
||||||
local frame = self:EnsurePlayerFrame(playerName)
|
|
||||||
HMGT.TrackerFrame:SetLocked(frame, s.locked)
|
|
||||||
HMGT.TrackerFrame:SetTitle(frame, string.format("%s - %s", L["GCD_TITLE"], ShortName(playerName)))
|
|
||||||
local displayEntries = byPlayer[playerName]
|
|
||||||
if HMGT.FilterDisplayEntries then
|
|
||||||
displayEntries = HMGT:FilterDisplayEntries(s, displayEntries) or displayEntries
|
|
||||||
end
|
|
||||||
if HMGT.SortDisplayEntries then
|
|
||||||
HMGT:SortDisplayEntries(displayEntries, "groupCooldownTracker")
|
|
||||||
end
|
|
||||||
if #displayEntries > 0 then
|
|
||||||
HMGT.TrackerFrame:UpdateFrame(frame, displayEntries, true)
|
|
||||||
self.lastEntryCount = self.lastEntryCount + #displayEntries
|
|
||||||
frame:Show()
|
|
||||||
active[playerName] = true
|
|
||||||
shownOrder[#shownOrder + 1] = playerName
|
|
||||||
shouldTick = true
|
|
||||||
else
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.activeOrder = shownOrder
|
|
||||||
|
|
||||||
for pn, frame in pairs(self.frames) do
|
|
||||||
if not active[pn] then
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:RefreshAnchors()
|
|
||||||
self:SetUpdateTickerEnabled(shouldTick)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if IsInRaid() or not IsInGroup() then
|
|
||||||
self.lastEntryCount = 0
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
self:HideAllFrames()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not s.enabled then
|
|
||||||
self.lastEntryCount = 0
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
self:HideAllFrames()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not HMGT:IsVisibleForCurrentGroup(s) then
|
|
||||||
self.lastEntryCount = 0
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
self:HideAllFrames()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local entriesByPlayer, order, unitByPlayer = self:CollectEntriesByPlayer()
|
|
||||||
self.activeOrder = order
|
|
||||||
self.unitByPlayer = unitByPlayer
|
|
||||||
self.lastEntryCount = 0
|
|
||||||
local active = {}
|
|
||||||
local shownOrder = {}
|
|
||||||
local shouldTick = false
|
|
||||||
|
|
||||||
for _, playerName in ipairs(order) do
|
|
||||||
local frame = self:EnsurePlayerFrame(playerName)
|
|
||||||
HMGT.TrackerFrame:SetLocked(frame, s.locked)
|
|
||||||
HMGT.TrackerFrame:SetTitle(frame, string.format("%s - %s", L["GCD_TITLE"], ShortName(playerName)))
|
|
||||||
|
|
||||||
local entries = entriesByPlayer[playerName] or {}
|
|
||||||
if HMGT.FilterDisplayEntries then
|
|
||||||
entries = HMGT:FilterDisplayEntries(s, entries) or entries
|
|
||||||
end
|
|
||||||
if HMGT.SortDisplayEntries then
|
|
||||||
HMGT:SortDisplayEntries(entries, "groupCooldownTracker")
|
|
||||||
end
|
|
||||||
if #entries > 0 then
|
|
||||||
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
|
||||||
self.lastEntryCount = self.lastEntryCount + #entries
|
|
||||||
frame:Show()
|
|
||||||
active[playerName] = true
|
|
||||||
shownOrder[#shownOrder + 1] = playerName
|
|
||||||
for _, entry in ipairs(entries) do
|
|
||||||
if EntryNeedsVisualTicker(entry) then
|
|
||||||
shouldTick = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.activeOrder = shownOrder
|
|
||||||
|
|
||||||
for pn, frame in pairs(self.frames) do
|
|
||||||
if not active[pn] then
|
|
||||||
frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:RefreshAnchors()
|
|
||||||
self:SetUpdateTickerEnabled(shouldTick)
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:CollectEntriesByPlayer()
|
|
||||||
local s = HMGT.db.profile.groupCooldownTracker
|
|
||||||
local byPlayer = {}
|
|
||||||
local playerOrder = {}
|
|
||||||
local unitByPlayer = {}
|
|
||||||
local players = self:GetGroupPlayers()
|
|
||||||
|
|
||||||
for _, playerInfo in ipairs(players) do
|
|
||||||
repeat
|
|
||||||
local name = playerInfo.name
|
|
||||||
if not name then break end
|
|
||||||
|
|
||||||
local pData = HMGT.playerData[name]
|
|
||||||
local class = pData and pData.class or playerInfo.class
|
|
||||||
local specIdx
|
|
||||||
if playerInfo.isOwn then
|
|
||||||
specIdx = GetSpecialization()
|
|
||||||
if not specIdx or specIdx == 0 then break end
|
|
||||||
else
|
|
||||||
specIdx = pData and pData.specIndex or nil
|
|
||||||
if not specIdx or tonumber(specIdx) <= 0 then break end
|
|
||||||
end
|
|
||||||
local talents = pData and pData.talents or {}
|
|
||||||
if not class then break end
|
|
||||||
|
|
||||||
local knownCDs = HMGT_SpellData.GetSpellsForSpec(class, specIdx, HMGT_SpellData.GroupCooldowns)
|
|
||||||
local entries = {}
|
|
||||||
for _, spellEntry in ipairs(knownCDs) do
|
|
||||||
if s.enabledSpells[spellEntry.spellId] ~= false then
|
|
||||||
local remaining, total, curCharges, maxCharges = HMGT:GetCooldownInfo(name, spellEntry.spellId, {
|
|
||||||
deferChargeCooldownUntilEmpty = false,
|
|
||||||
})
|
|
||||||
local isAvailabilitySpell = HMGT.IsAvailabilitySpell and HMGT:IsAvailabilitySpell(spellEntry)
|
|
||||||
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
|
||||||
local hasChargeSpell = (tonumber(maxCharges) or 0) > 1
|
|
||||||
local hasPartialCharges = (tonumber(maxCharges) or 0) > 0 and (tonumber(curCharges) or tonumber(maxCharges) or 0) < (tonumber(maxCharges) or 0)
|
|
||||||
local include = HMGT:ShouldDisplayEntry(s, remaining, curCharges, maxCharges, spellEntry)
|
|
||||||
local spellKnown = HMGT:IsTrackedSpellKnownForPlayer(name, spellEntry.spellId)
|
|
||||||
local hasActiveCd = ((remaining or 0) > 0) or hasPartialCharges
|
|
||||||
if not spellKnown and not hasActiveCd then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
if isAvailabilitySpell and not spellKnown then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
if not playerInfo.isOwn then
|
|
||||||
if isAvailabilitySpell and not HMGT:HasAvailabilityState(name, spellEntry.spellId) then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if include then
|
|
||||||
table.insert(entries, {
|
|
||||||
playerName = name,
|
|
||||||
class = class,
|
|
||||||
spellEntry = spellEntry,
|
|
||||||
remaining = remaining,
|
|
||||||
total = total > 0 and total or effectiveCd,
|
|
||||||
currentCharges = curCharges,
|
|
||||||
maxCharges = maxCharges,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if #entries > 0 then
|
|
||||||
byPlayer[name] = entries
|
|
||||||
table.insert(playerOrder, name)
|
|
||||||
unitByPlayer[name] = playerInfo.unitId
|
|
||||||
end
|
|
||||||
until true
|
|
||||||
end
|
|
||||||
|
|
||||||
table.sort(playerOrder, function(a, b)
|
|
||||||
local own = HMGT:NormalizePlayerName(UnitName("player"))
|
|
||||||
if a == own and b ~= own then return true end
|
|
||||||
if b == own and a ~= own then return false end
|
|
||||||
return a < b
|
|
||||||
end)
|
|
||||||
|
|
||||||
return byPlayer, playerOrder, unitByPlayer
|
|
||||||
end
|
|
||||||
|
|
||||||
function GCT:GetGroupPlayers()
|
|
||||||
local players = {}
|
|
||||||
local ownName = HMGT:NormalizePlayerName(UnitName("player"))
|
|
||||||
local settings = HMGT.db and HMGT.db.profile and HMGT.db.profile.groupCooldownTracker
|
|
||||||
|
|
||||||
if settings and settings.includeSelfFrame == true and ownName then
|
|
||||||
table.insert(players, {
|
|
||||||
name = ownName,
|
|
||||||
class = select(2, UnitClass("player")),
|
|
||||||
unitId = "player",
|
|
||||||
isOwn = true,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
if IsInGroup() and not IsInRaid() then
|
|
||||||
for i = 1, GetNumGroupMembers() - 1 do
|
|
||||||
local unitId = "party" .. i
|
|
||||||
local name = HMGT:NormalizePlayerName(UnitName(unitId))
|
|
||||||
local class = select(2, UnitClass(unitId))
|
|
||||||
if name and name ~= ownName then
|
|
||||||
table.insert(players, {name = name, class = class, unitId = unitId})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return players
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,54 +42,13 @@ function Manager:HidePlayerFrames(frameKey)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Manager:BuildEntriesByPlayerForTracker(tracker)
|
function Manager:BuildEntriesByPlayerForTracker(tracker)
|
||||||
local frameKey = S.GetTrackerFrameKey(tracker.id)
|
return HMGT:BuildEntriesByPlayerForTracker(
|
||||||
local ownName = HMGT:NormalizePlayerName(UnitName("player")) or "Player"
|
tracker,
|
||||||
if tracker.testMode then
|
self:GetTrackerFrameKey(tracker),
|
||||||
local entries = self:CollectTestEntries(tracker)
|
function(unitId)
|
||||||
if S.IsGroupTracker(tracker) and tracker.attachToPartyFrame == true then
|
return S.ResolveUnitAnchorFrame(unitId)
|
||||||
return S.BuildPartyPreviewEntries(entries)
|
|
||||||
end
|
end
|
||||||
local byPlayer, order, unitByPlayer = {}, {}, {}
|
)
|
||||||
if #entries > 0 then
|
|
||||||
byPlayer[ownName] = entries
|
|
||||||
order[1] = ownName
|
|
||||||
unitByPlayer[ownName] = "player"
|
|
||||||
end
|
|
||||||
return byPlayer, order, unitByPlayer, true
|
|
||||||
end
|
|
||||||
if tracker.demoMode then
|
|
||||||
local entries = HMGT:GetDemoEntries(frameKey, S.GetTrackerSpellPool(tracker.categories), tracker)
|
|
||||||
if S.IsGroupTracker(tracker) and tracker.attachToPartyFrame == true then
|
|
||||||
return S.BuildPartyPreviewEntries(entries)
|
|
||||||
end
|
|
||||||
for _, entry in ipairs(entries) do
|
|
||||||
entry.playerName = ownName
|
|
||||||
end
|
|
||||||
local byPlayer, order, unitByPlayer = {}, {}, {}
|
|
||||||
if #entries > 0 then
|
|
||||||
byPlayer[ownName] = entries
|
|
||||||
order[1] = ownName
|
|
||||||
unitByPlayer[ownName] = "player"
|
|
||||||
end
|
|
||||||
return byPlayer, order, unitByPlayer, true
|
|
||||||
end
|
|
||||||
if not tracker.enabled or not HMGT:IsVisibleForCurrentGroup(tracker) then
|
|
||||||
return {}, {}, {}, false
|
|
||||||
end
|
|
||||||
if IsInRaid() or not IsInGroup() then
|
|
||||||
return {}, {}, {}, false
|
|
||||||
end
|
|
||||||
local byPlayer, order, unitByPlayer = {}, {}, {}
|
|
||||||
for _, playerInfo in ipairs(S.GetGroupPlayers(tracker)) do
|
|
||||||
local entries = S.CollectEntriesForPlayer(tracker, playerInfo)
|
|
||||||
if #entries > 0 then
|
|
||||||
local playerName = playerInfo.name
|
|
||||||
byPlayer[playerName] = entries
|
|
||||||
order[#order + 1] = playerName
|
|
||||||
unitByPlayer[playerName] = playerInfo.unitId
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return byPlayer, order, unitByPlayer, true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Manager:RefreshPerGroupAnchors(tracker, force)
|
function Manager:RefreshPerGroupAnchors(tracker, force)
|
||||||
@@ -206,11 +165,10 @@ function Manager:UpdatePerGroupMemberTracker(tracker)
|
|||||||
for _, playerName in ipairs(order) do
|
for _, playerName in ipairs(order) do
|
||||||
local frame = self:EnsurePlayerFrame(tracker, playerName)
|
local frame = self:EnsurePlayerFrame(tracker, playerName)
|
||||||
local entries = byPlayer[playerName] or {}
|
local entries = byPlayer[playerName] or {}
|
||||||
if HMGT.FilterDisplayEntries then
|
local tickThis = false
|
||||||
entries = HMGT:FilterDisplayEntries(tracker, entries) or entries
|
entries, tickThis = HMGT:FinalizeTrackerEntries(tracker, entries, tracker.trackerKey or nil)
|
||||||
end
|
if tickThis then
|
||||||
if HMGT.SortDisplayEntries then
|
shouldTick = true
|
||||||
HMGT:SortDisplayEntries(entries)
|
|
||||||
end
|
end
|
||||||
if #entries > 0 then
|
if #entries > 0 then
|
||||||
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
||||||
@@ -219,12 +177,6 @@ function Manager:UpdatePerGroupMemberTracker(tracker)
|
|||||||
shownOrder[#shownOrder + 1] = playerName
|
shownOrder[#shownOrder + 1] = playerName
|
||||||
shownByPlayer[playerName] = entries
|
shownByPlayer[playerName] = entries
|
||||||
entryCount = entryCount + #entries
|
entryCount = entryCount + #entries
|
||||||
for _, entry in ipairs(entries) do
|
|
||||||
if S.EntryNeedsVisualTicker(entry) then
|
|
||||||
shouldTick = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
frame:Hide()
|
frame:Hide()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -97,14 +97,23 @@ HMGT_SpellData.Interrupts = {
|
|||||||
-- WARLOCK
|
-- WARLOCK
|
||||||
Spell(19647, "Spell Lock", {
|
Spell(19647, "Spell Lock", {
|
||||||
classes = {"WARLOCK"},
|
classes = {"WARLOCK"},
|
||||||
specs = {2},
|
specs = {1, 3},
|
||||||
category = "interrupt",
|
category = "interrupt",
|
||||||
state = { kind = "cooldown", cooldown = 24 },
|
state = { kind = "cooldown", cooldown = 24 },
|
||||||
}),
|
}),
|
||||||
|
Spell(119914, "Axe Toss", {
|
||||||
|
classes = {"WARLOCK"},
|
||||||
|
specs = {2},
|
||||||
|
category = "interrupt",
|
||||||
|
aliases = { 89766 },
|
||||||
|
petSpellId = 89766,
|
||||||
|
state = { kind = "cooldown", cooldown = 30 },
|
||||||
|
}),
|
||||||
Spell(132409, "Spell Lock (Grimoire)", {
|
Spell(132409, "Spell Lock (Grimoire)", {
|
||||||
classes = {"WARLOCK"},
|
classes = {"WARLOCK"},
|
||||||
specs = {1, 3},
|
specs = {1, 3},
|
||||||
category = "interrupt",
|
category = "interrupt",
|
||||||
|
aliases = { 1276467 },
|
||||||
state = { kind = "cooldown", cooldown = 24 },
|
state = { kind = "cooldown", cooldown = 24 },
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,44 @@
|
|||||||
-- Modules/InterruptTracker.lua
|
|
||||||
-- Interrupt tracker based on the shared single-frame tracker base.
|
|
||||||
|
|
||||||
local ADDON_NAME = "HailMaryGuildTools"
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
||||||
|
|
||||||
local Base = HMGT.SingleFrameTrackerBase
|
local module = HMGT:NewModule("InterruptTracker")
|
||||||
if not Base then return end
|
HMGT.InterruptTracker = module
|
||||||
|
|
||||||
Base:CreateModule("InterruptTracker", {
|
module.definition = {
|
||||||
profileKey = "interruptTracker",
|
moduleName = "InterruptTracker",
|
||||||
frameName = "InterruptTracker",
|
dbKey = "interruptTracker",
|
||||||
|
trackerType = "normal",
|
||||||
|
trackerKey = "interruptTracker",
|
||||||
title = function()
|
title = function()
|
||||||
return L["IT_TITLE"]
|
return L["IT_TITLE"]
|
||||||
end,
|
end,
|
||||||
demoKey = "interruptTracker",
|
categories = { "interrupt" },
|
||||||
database = function()
|
}
|
||||||
return HMGT_SpellData.Interrupts
|
|
||||||
end,
|
function module:GetDefinition()
|
||||||
})
|
return self.definition
|
||||||
|
end
|
||||||
|
|
||||||
|
function module:GetSettings()
|
||||||
|
for _, tracker in ipairs(HMGT:GetTrackerConfigs()) do
|
||||||
|
if tracker.trackerKey == self.definition.trackerKey then
|
||||||
|
return tracker
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function module:Enable()
|
||||||
|
if HMGT.TrackerManager and HMGT.TrackerManager.Enable then
|
||||||
|
HMGT.TrackerManager:Enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function module:Disable()
|
||||||
|
if HMGT.TrackerManager and HMGT.TrackerManager.UpdateDisplay then
|
||||||
|
HMGT.TrackerManager:UpdateDisplay()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -3,66 +3,15 @@ local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
|||||||
if not HMGT or not HMGT.TrackerManager then return end
|
if not HMGT or not HMGT.TrackerManager then return end
|
||||||
|
|
||||||
local Manager = HMGT.TrackerManager
|
local Manager = HMGT.TrackerManager
|
||||||
local S = Manager._shared or {}
|
|
||||||
|
|
||||||
function Manager:CollectEntries(tracker)
|
function Manager:CollectEntries(tracker)
|
||||||
local entries = {}
|
return HMGT:CollectTrackerEntries(tracker)
|
||||||
local players = S.GetGroupPlayers(tracker)
|
|
||||||
for _, playerInfo in ipairs(players) do
|
|
||||||
local playerEntries = S.CollectEntriesForPlayer(tracker, playerInfo)
|
|
||||||
for _, entry in ipairs(playerEntries) do
|
|
||||||
entries[#entries + 1] = entry
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return entries
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Manager:CollectTestEntries(tracker)
|
function Manager:CollectTestEntries(tracker)
|
||||||
local playerName = HMGT:NormalizePlayerName(UnitName("player")) or "Player"
|
return HMGT:CollectTrackerTestEntries(tracker)
|
||||||
local classToken = select(2, UnitClass("player"))
|
|
||||||
if not classToken then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local entries = {}
|
|
||||||
local pData = HMGT.playerData[playerName]
|
|
||||||
local talents = pData and pData.talents or {}
|
|
||||||
local spells = S.GetTrackerSpellsForPlayer(classToken, GetSpecialization() or 0, tracker.categories)
|
|
||||||
for _, spellEntry in ipairs(spells) do
|
|
||||||
if tracker.enabledSpells[spellEntry.spellId] ~= false then
|
|
||||||
local remaining, total, currentCharges, maxCharges = HMGT:GetCooldownInfo(playerName, spellEntry.spellId)
|
|
||||||
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
|
||||||
local isAvailabilitySpell = HMGT:IsAvailabilitySpell(spellEntry)
|
|
||||||
local spellKnown = HMGT:IsTrackedSpellKnownForPlayer(playerName, spellEntry.spellId)
|
|
||||||
local hasPartialCharges = (tonumber(maxCharges) or 0) > 0
|
|
||||||
and (tonumber(currentCharges) or tonumber(maxCharges) or 0) < (tonumber(maxCharges) or 0)
|
|
||||||
local hasActiveCd = ((remaining or 0) > 0) or hasPartialCharges
|
|
||||||
local hasAvailabilityState = isAvailabilitySpell and HMGT:HasAvailabilityState(playerName, spellEntry.spellId)
|
|
||||||
if spellKnown or hasActiveCd or hasAvailabilityState then
|
|
||||||
entries[#entries + 1] = {
|
|
||||||
playerName = playerName,
|
|
||||||
class = classToken,
|
|
||||||
spellEntry = spellEntry,
|
|
||||||
remaining = remaining,
|
|
||||||
total = total > 0 and total or effectiveCd,
|
|
||||||
currentCharges = currentCharges,
|
|
||||||
maxCharges = maxCharges,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return entries
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Manager:BuildEntriesForTracker(tracker)
|
function Manager:BuildEntriesForTracker(tracker)
|
||||||
if tracker.testMode then
|
return HMGT:BuildEntriesForTracker(tracker, self:GetTrackerFrameKey(tracker))
|
||||||
return self:CollectTestEntries(tracker), true
|
|
||||||
end
|
|
||||||
if tracker.demoMode then
|
|
||||||
return HMGT:GetDemoEntries(S.GetTrackerFrameKey(tracker.id), S.GetTrackerSpellPool(tracker.categories), tracker), true
|
|
||||||
end
|
|
||||||
if not tracker.enabled or not HMGT:IsVisibleForCurrentGroup(tracker) then
|
|
||||||
return {}, false
|
|
||||||
end
|
|
||||||
return self:CollectEntries(tracker), true
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,21 +1,44 @@
|
|||||||
-- Modules/RaidCooldownTracker.lua
|
|
||||||
-- Raid cooldown tracker based on the shared single-frame tracker base.
|
|
||||||
|
|
||||||
local ADDON_NAME = "HailMaryGuildTools"
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME)
|
||||||
|
|
||||||
local Base = HMGT.SingleFrameTrackerBase
|
local module = HMGT:NewModule("RaidCooldownTracker")
|
||||||
if not Base then return end
|
HMGT.RaidCooldownTracker = module
|
||||||
|
|
||||||
Base:CreateModule("RaidCooldownTracker", {
|
module.definition = {
|
||||||
profileKey = "raidCooldownTracker",
|
moduleName = "RaidCooldownTracker",
|
||||||
frameName = "RaidCooldownTracker",
|
dbKey = "raidCooldownTracker",
|
||||||
|
trackerType = "normal",
|
||||||
|
trackerKey = "raidCooldownTracker",
|
||||||
title = function()
|
title = function()
|
||||||
return L["RCD_TITLE"]
|
return L["RCD_TITLE"]
|
||||||
end,
|
end,
|
||||||
demoKey = "raidCooldownTracker",
|
categories = { "lust", "defensive", "healing", "tank", "utility", "offensive", "cc", "interrupt" },
|
||||||
database = function()
|
}
|
||||||
return HMGT_SpellData.RaidCooldowns
|
|
||||||
end,
|
function module:GetDefinition()
|
||||||
})
|
return self.definition
|
||||||
|
end
|
||||||
|
|
||||||
|
function module:GetSettings()
|
||||||
|
for _, tracker in ipairs(HMGT:GetTrackerConfigs()) do
|
||||||
|
if tracker.trackerKey == self.definition.trackerKey then
|
||||||
|
return tracker
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function module:Enable()
|
||||||
|
if HMGT.TrackerManager and HMGT.TrackerManager.Enable then
|
||||||
|
HMGT.TrackerManager:Enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function module:Disable()
|
||||||
|
if HMGT.TrackerManager and HMGT.TrackerManager.UpdateDisplay then
|
||||||
|
HMGT.TrackerManager:UpdateDisplay()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,305 +0,0 @@
|
|||||||
-- Modules/Tracker/SingleFrameTrackerBase.lua
|
|
||||||
-- Shared implementation for single-frame tracker modules.
|
|
||||||
|
|
||||||
local ADDON_NAME = "HailMaryGuildTools"
|
|
||||||
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
|
||||||
if not HMGT then return end
|
|
||||||
|
|
||||||
HMGT.SingleFrameTrackerBase = HMGT.SingleFrameTrackerBase or {}
|
|
||||||
local Base = HMGT.SingleFrameTrackerBase
|
|
||||||
|
|
||||||
local function GetDefaultGroupPlayers()
|
|
||||||
local players = {}
|
|
||||||
|
|
||||||
local ownName = HMGT:NormalizePlayerName(UnitName("player"))
|
|
||||||
local ownClass = select(2, UnitClass("player"))
|
|
||||||
table.insert(players, { name = ownName, class = ownClass, isOwn = true, unitId = "player" })
|
|
||||||
|
|
||||||
if IsInRaid() then
|
|
||||||
for i = 1, GetNumGroupMembers() do
|
|
||||||
local unitId = "raid" .. i
|
|
||||||
local name = HMGT:NormalizePlayerName(UnitName(unitId))
|
|
||||||
local class = select(2, UnitClass(unitId))
|
|
||||||
if name and name ~= ownName then
|
|
||||||
table.insert(players, { name = name, class = class, unitId = unitId })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif IsInGroup() then
|
|
||||||
for i = 1, GetNumGroupMembers() - 1 do
|
|
||||||
local unitId = "party" .. i
|
|
||||||
local name = HMGT:NormalizePlayerName(UnitName(unitId))
|
|
||||||
local class = select(2, UnitClass(unitId))
|
|
||||||
if name and name ~= ownName then
|
|
||||||
table.insert(players, { name = name, class = class, unitId = unitId })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return players
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ResolveConfigValue(configValue, self)
|
|
||||||
if type(configValue) == "function" then
|
|
||||||
return configValue(self)
|
|
||||||
end
|
|
||||||
return configValue
|
|
||||||
end
|
|
||||||
|
|
||||||
local function EntryNeedsVisualTicker(entry)
|
|
||||||
if type(entry) ~= "table" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local remaining = tonumber(entry.remaining) or 0
|
|
||||||
if remaining > 0 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local maxCharges = tonumber(entry.maxCharges) or 0
|
|
||||||
local currentCharges = tonumber(entry.currentCharges)
|
|
||||||
if maxCharges > 0 and currentCharges ~= nil and currentCharges < maxCharges then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function Base:Create(config)
|
|
||||||
local tracker = {
|
|
||||||
frame = nil,
|
|
||||||
updateTicker = nil,
|
|
||||||
lastEntryCount = 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
function tracker:GetSettings()
|
|
||||||
return HMGT.db.profile[config.profileKey]
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:GetDatabase()
|
|
||||||
return ResolveConfigValue(config.database, self) or {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:GetTitle()
|
|
||||||
return ResolveConfigValue(config.title, self) or config.frameName
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:GetDemoKey()
|
|
||||||
return ResolveConfigValue(config.demoKey, self) or config.profileKey
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:GetCooldownInfoOpts()
|
|
||||||
return ResolveConfigValue(config.cooldownInfoOpts, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:GetGroupPlayers()
|
|
||||||
local custom = ResolveConfigValue(config.groupPlayersProvider, self)
|
|
||||||
if type(custom) == "table" then
|
|
||||||
return custom
|
|
||||||
end
|
|
||||||
return GetDefaultGroupPlayers()
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:EnsureUpdateTicker()
|
|
||||||
if self.updateTicker then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self.updateTicker = C_Timer.NewTicker(0.1, function()
|
|
||||||
self:UpdateDisplay()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:StopUpdateTicker()
|
|
||||||
if self.updateTicker then
|
|
||||||
self.updateTicker:Cancel()
|
|
||||||
self.updateTicker = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:SetUpdateTickerEnabled(enabled)
|
|
||||||
if enabled then
|
|
||||||
self:EnsureUpdateTicker()
|
|
||||||
else
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:Enable()
|
|
||||||
local s = self:GetSettings()
|
|
||||||
if not s.enabled and not s.demoMode and not s.testMode then return end
|
|
||||||
|
|
||||||
if not self.frame then
|
|
||||||
self.frame = HMGT.TrackerFrame:CreateTrackerFrame(config.frameName, s)
|
|
||||||
HMGT.TrackerFrame:SetTitle(self.frame, self:GetTitle())
|
|
||||||
end
|
|
||||||
|
|
||||||
if HMGT:IsVisibleForCurrentGroup(s) then
|
|
||||||
self.frame:Show()
|
|
||||||
else
|
|
||||||
self.frame:Hide()
|
|
||||||
end
|
|
||||||
self:UpdateDisplay()
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:Disable()
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
if self.frame then
|
|
||||||
self.frame:Hide()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:UpdateDisplay()
|
|
||||||
if not self.frame then
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local s = self:GetSettings()
|
|
||||||
local database = self:GetDatabase()
|
|
||||||
local cooldownInfoOpts = self:GetCooldownInfoOpts()
|
|
||||||
|
|
||||||
if s.testMode then
|
|
||||||
HMGT.TrackerFrame:SetLocked(self.frame, s.locked)
|
|
||||||
local entries = HMGT:GetOwnTestEntries(database, s, cooldownInfoOpts)
|
|
||||||
self.lastEntryCount = #entries
|
|
||||||
HMGT.TrackerFrame:UpdateFrame(self.frame, entries)
|
|
||||||
self.frame:Show()
|
|
||||||
local shouldTick = false
|
|
||||||
for _, entry in ipairs(entries) do
|
|
||||||
if EntryNeedsVisualTicker(entry) then
|
|
||||||
shouldTick = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:SetUpdateTickerEnabled(shouldTick)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if s.demoMode then
|
|
||||||
HMGT.TrackerFrame:SetLocked(self.frame, s.locked)
|
|
||||||
local entries = HMGT:GetDemoEntries(self:GetDemoKey(), database, s)
|
|
||||||
self.lastEntryCount = #entries
|
|
||||||
HMGT.TrackerFrame:UpdateFrame(self.frame, entries)
|
|
||||||
self.frame:Show()
|
|
||||||
self:SetUpdateTickerEnabled(#entries > 0)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if not s.enabled then
|
|
||||||
self.lastEntryCount = 0
|
|
||||||
self.frame:Hide()
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if not HMGT:IsVisibleForCurrentGroup(s) then
|
|
||||||
self.lastEntryCount = 0
|
|
||||||
self.frame:Hide()
|
|
||||||
self:StopUpdateTicker()
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
HMGT.TrackerFrame:SetLocked(self.frame, s.locked)
|
|
||||||
|
|
||||||
local entries = self:CollectEntries()
|
|
||||||
self.lastEntryCount = #entries
|
|
||||||
if HMGT.SortDisplayEntries then
|
|
||||||
HMGT:SortDisplayEntries(entries, config.profileKey)
|
|
||||||
end
|
|
||||||
|
|
||||||
HMGT.TrackerFrame:UpdateFrame(self.frame, entries)
|
|
||||||
self.frame:Show()
|
|
||||||
|
|
||||||
local shouldTick = false
|
|
||||||
for _, entry in ipairs(entries) do
|
|
||||||
if EntryNeedsVisualTicker(entry) then
|
|
||||||
shouldTick = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:SetUpdateTickerEnabled(shouldTick)
|
|
||||||
end
|
|
||||||
|
|
||||||
function tracker:CollectEntries()
|
|
||||||
local entries = {}
|
|
||||||
local s = self:GetSettings()
|
|
||||||
local database = self:GetDatabase()
|
|
||||||
local cooldownInfoOpts = self:GetCooldownInfoOpts()
|
|
||||||
local players = self:GetGroupPlayers()
|
|
||||||
|
|
||||||
for _, playerInfo in ipairs(players) do
|
|
||||||
repeat
|
|
||||||
local name = playerInfo.name
|
|
||||||
local pData = HMGT.playerData[name]
|
|
||||||
local class = pData and pData.class or playerInfo.class
|
|
||||||
local specIdx
|
|
||||||
if playerInfo.isOwn then
|
|
||||||
specIdx = GetSpecialization()
|
|
||||||
if not specIdx or specIdx == 0 then break end
|
|
||||||
else
|
|
||||||
specIdx = pData and pData.specIndex or nil
|
|
||||||
if not specIdx or tonumber(specIdx) <= 0 then break end
|
|
||||||
end
|
|
||||||
local talents = pData and pData.talents or {}
|
|
||||||
|
|
||||||
if not class then break end
|
|
||||||
|
|
||||||
local knownSpells = HMGT_SpellData.GetSpellsForSpec(class, specIdx, database)
|
|
||||||
|
|
||||||
for _, spellEntry in ipairs(knownSpells) do
|
|
||||||
if s.enabledSpells[spellEntry.spellId] ~= false then
|
|
||||||
local remaining, total, curCharges, maxCharges = HMGT:GetCooldownInfo(name, spellEntry.spellId, cooldownInfoOpts)
|
|
||||||
local isAvailabilitySpell = HMGT.IsAvailabilitySpell and HMGT:IsAvailabilitySpell(spellEntry)
|
|
||||||
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
|
||||||
local include = HMGT:ShouldDisplayEntry(s, remaining, curCharges, maxCharges, spellEntry)
|
|
||||||
local spellKnown = HMGT:IsTrackedSpellKnownForPlayer(name, spellEntry.spellId)
|
|
||||||
local hasPartialCharges = (tonumber(maxCharges) or 0) > 0
|
|
||||||
and (tonumber(curCharges) or tonumber(maxCharges) or 0) < (tonumber(maxCharges) or 0)
|
|
||||||
local hasActiveCd = ((remaining or 0) > 0) or hasPartialCharges
|
|
||||||
if not spellKnown and not hasActiveCd then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
if isAvailabilitySpell and not spellKnown then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if not playerInfo.isOwn then
|
|
||||||
if isAvailabilitySpell and not HMGT:HasAvailabilityState(name, spellEntry.spellId) then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if include then
|
|
||||||
entries[#entries + 1] = {
|
|
||||||
playerName = name,
|
|
||||||
class = class,
|
|
||||||
spellEntry = spellEntry,
|
|
||||||
remaining = remaining,
|
|
||||||
total = total > 0 and total or effectiveCd,
|
|
||||||
currentCharges = curCharges,
|
|
||||||
maxCharges = maxCharges,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
until true
|
|
||||||
end
|
|
||||||
|
|
||||||
return entries
|
|
||||||
end
|
|
||||||
|
|
||||||
return tracker
|
|
||||||
end
|
|
||||||
|
|
||||||
function Base:CreateModule(moduleName, config, ...)
|
|
||||||
if type(moduleName) ~= "string" or moduleName == "" then
|
|
||||||
return self:Create(config)
|
|
||||||
end
|
|
||||||
|
|
||||||
local module = HMGT:NewModule(moduleName, ...)
|
|
||||||
local tracker = self:Create(config)
|
|
||||||
for key, value in pairs(tracker) do
|
|
||||||
module[key] = value
|
|
||||||
end
|
|
||||||
HMGT[moduleName] = module
|
|
||||||
return module
|
|
||||||
end
|
|
||||||
@@ -1077,6 +1077,18 @@ function HMGT_SpellData.RebuildLookups()
|
|||||||
for _, entry in ipairs(HMGT_SpellData.Interrupts or {}) do
|
for _, entry in ipairs(HMGT_SpellData.Interrupts or {}) do
|
||||||
entry._hmgtDataset = "Interrupts"
|
entry._hmgtDataset = "Interrupts"
|
||||||
HMGT_SpellData.InterruptLookup[entry.spellId] = entry
|
HMGT_SpellData.InterruptLookup[entry.spellId] = entry
|
||||||
|
if type(entry.aliases) == "table" then
|
||||||
|
for _, aliasId in ipairs(entry.aliases) do
|
||||||
|
local sid = tonumber(aliasId)
|
||||||
|
if sid and sid > 0 then
|
||||||
|
HMGT_SpellData.InterruptLookup[sid] = entry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local petSpellId = tonumber(entry.petSpellId)
|
||||||
|
if petSpellId and petSpellId > 0 then
|
||||||
|
HMGT_SpellData.InterruptLookup[petSpellId] = entry
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
HMGT_SpellData.CooldownLookup = {}
|
HMGT_SpellData.CooldownLookup = {}
|
||||||
|
|||||||
169
Modules/Tracker/TrackerAvailability.lua
Normal file
169
Modules/Tracker/TrackerAvailability.lua
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
|
HMGT.TrackerAvailability = HMGT.TrackerAvailability or {}
|
||||||
|
|
||||||
|
local internals = HMGT.TrackerInternals or {}
|
||||||
|
local GetPlayerAuraApplications = internals.GetPlayerAuraApplications
|
||||||
|
local GetSpellCastCountInfo = internals.GetSpellCastCountInfo
|
||||||
|
|
||||||
|
function HMGT:GetOwnAvailabilityProgress(spellEntry)
|
||||||
|
local availability = self:GetAvailabilityConfig(spellEntry)
|
||||||
|
if not availability then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local required = self:GetAvailabilityRequiredCount(spellEntry)
|
||||||
|
if required <= 0 then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local current = 0
|
||||||
|
if availability.type == "auraStacks" then
|
||||||
|
current = GetPlayerAuraApplications and GetPlayerAuraApplications(availability.auraSpellId) or 0
|
||||||
|
if current <= 0 then
|
||||||
|
local fallbackSpellId = tonumber(availability.fallbackSpellCountId)
|
||||||
|
or tonumber(availability.progressSpellId)
|
||||||
|
or tonumber(spellEntry and spellEntry.spellId)
|
||||||
|
if fallbackSpellId and fallbackSpellId > 0 and GetSpellCastCountInfo then
|
||||||
|
current = GetSpellCastCountInfo(fallbackSpellId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
current = math.max(0, math.min(required, tonumber(current) or 0))
|
||||||
|
return current, required
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetAvailabilityState(playerName, spellId)
|
||||||
|
local state = self:GetAvailabilityStateEntry(playerName, spellId)
|
||||||
|
if not state then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
return tonumber(state.current) or 0, tonumber(state.max) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:HasAvailabilityState(playerName, spellId)
|
||||||
|
local _, max = self:GetAvailabilityState(playerName, spellId)
|
||||||
|
return (tonumber(max) or 0) > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:StoreAvailabilityState(playerName, spellId, current, max, spellEntry)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not normalizedName or not sid or sid <= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local maxCount = math.max(0, math.floor((tonumber(max) or 0) + 0.5))
|
||||||
|
if maxCount <= 0 then
|
||||||
|
return self:ClearAvailabilityState(normalizedName, sid)
|
||||||
|
end
|
||||||
|
|
||||||
|
local currentCount = math.max(0, math.min(maxCount, math.floor((tonumber(current) or 0) + 0.5)))
|
||||||
|
local previous = self:GetAvailabilityStateEntry(normalizedName, sid)
|
||||||
|
local changed = (not previous)
|
||||||
|
or (tonumber(previous.current) or -1) ~= currentCount
|
||||||
|
or (tonumber(previous.max) or -1) ~= maxCount
|
||||||
|
|
||||||
|
self:SetAvailabilityStateEntry(normalizedName, sid, {
|
||||||
|
current = currentCount,
|
||||||
|
max = maxCount,
|
||||||
|
spellEntry = spellEntry,
|
||||||
|
updatedAt = GetTime(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return changed
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:RefreshOwnAvailabilitySpell(spellEntry)
|
||||||
|
if not self:IsAvailabilitySpell(spellEntry) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local playerName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
if not playerName then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local current, max = self:GetOwnAvailabilityProgress(spellEntry)
|
||||||
|
if (tonumber(max) or 0) > 0 then
|
||||||
|
local pData = self.playerData[playerName]
|
||||||
|
if pData and type(pData.knownSpells) == "table" then
|
||||||
|
pData.knownSpells[tonumber(spellEntry.spellId)] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return self:StoreAvailabilityState(playerName, spellEntry.spellId, current, max, spellEntry)
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:RefreshOwnAvailabilityStates()
|
||||||
|
local playerName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local pData = playerName and self.playerData[playerName]
|
||||||
|
if not pData or not pData.class or not pData.specIndex then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local changed = false
|
||||||
|
local groupCooldowns = HMGT_SpellData.GetSpellsForSpec(pData.class, pData.specIndex, HMGT_SpellData.GroupCooldowns)
|
||||||
|
for _, spellEntry in ipairs(groupCooldowns or {}) do
|
||||||
|
if self:IsAvailabilitySpell(spellEntry) and self:RefreshOwnAvailabilitySpell(spellEntry) then
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self:PruneAvailabilityStates(playerName, pData.knownSpells or {}) then
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return changed
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:RefreshAndPublishOwnAvailabilityStates()
|
||||||
|
local playerName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local pData = playerName and self.playerData[playerName]
|
||||||
|
if not pData or not pData.class or not pData.specIndex then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local changed = false
|
||||||
|
local groupCooldowns = HMGT_SpellData.GetSpellsForSpec(pData.class, pData.specIndex, HMGT_SpellData.GroupCooldowns)
|
||||||
|
for _, spellEntry in ipairs(groupCooldowns or {}) do
|
||||||
|
if self:IsAvailabilitySpell(spellEntry) and self:RefreshOwnAvailabilitySpell(spellEntry) then
|
||||||
|
self:PublishOwnSpellState(spellEntry.spellId, { sendLegacy = true })
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if self:PruneAvailabilityStates(playerName, pData.knownSpells or {}) then
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return changed
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:SendOwnAvailabilityStates(target)
|
||||||
|
local playerName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local pData = playerName and self.playerData[playerName]
|
||||||
|
if not pData or not pData.class or not pData.specIndex then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
self:RefreshOwnAvailabilityStates()
|
||||||
|
|
||||||
|
local sent = 0
|
||||||
|
local groupCooldowns = HMGT_SpellData.GetSpellsForSpec(pData.class, pData.specIndex, HMGT_SpellData.GroupCooldowns)
|
||||||
|
for _, spellEntry in ipairs(groupCooldowns or {}) do
|
||||||
|
if self:IsAvailabilitySpell(spellEntry) and self:IsTrackedSpellKnownForPlayer(playerName, spellEntry.spellId) then
|
||||||
|
local current, max = self:GetAvailabilityState(playerName, spellEntry.spellId)
|
||||||
|
if (tonumber(max) or 0) > 0 then
|
||||||
|
self:BroadcastAvailabilityState(spellEntry.spellId, current, max, target)
|
||||||
|
sent = sent + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return sent
|
||||||
|
end
|
||||||
191
Modules/Tracker/TrackerBridge.lua
Normal file
191
Modules/Tracker/TrackerBridge.lua
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
|
HMGT.TrackerBridge = HMGT.TrackerBridge or {}
|
||||||
|
|
||||||
|
function HMGT:RegisterExternalAddonSource(sourceName)
|
||||||
|
local source = tostring(sourceName or "")
|
||||||
|
if source == "" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
self.externalAddonSources = self.externalAddonSources or {}
|
||||||
|
self.externalAddonSources[source] = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetCanonicalExternalSpellEntry(spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not sid or sid <= 0 or not HMGT_SpellData then
|
||||||
|
return nil, sid
|
||||||
|
end
|
||||||
|
|
||||||
|
local spellEntry = HMGT_SpellData.InterruptLookup and HMGT_SpellData.InterruptLookup[sid]
|
||||||
|
or HMGT_SpellData.CooldownLookup and HMGT_SpellData.CooldownLookup[sid]
|
||||||
|
if not spellEntry then
|
||||||
|
return nil, sid
|
||||||
|
end
|
||||||
|
|
||||||
|
return spellEntry, tonumber(spellEntry.spellId) or sid
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:InferClassFromSpellEntry(spellEntry)
|
||||||
|
if type(spellEntry) ~= "table" or type(spellEntry.classes) ~= "table" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local foundClass
|
||||||
|
for key, value in pairs(spellEntry.classes) do
|
||||||
|
local classToken = type(value) == "string" and value or key
|
||||||
|
if foundClass and foundClass ~= classToken then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
foundClass = classToken
|
||||||
|
end
|
||||||
|
return foundClass
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ApplyExternalKnownSpell(sourceName, playerName, spellId, class, cooldown)
|
||||||
|
local source = tostring(sourceName or "External")
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not normalizedName or normalizedName == "" or not sid or sid <= 0 then
|
||||||
|
return false, "invalid_args"
|
||||||
|
end
|
||||||
|
if not self:IsPlayerInCurrentGroup(normalizedName) then
|
||||||
|
return false, "not_in_group"
|
||||||
|
end
|
||||||
|
|
||||||
|
local spellEntry, canonicalSid = self:GetCanonicalExternalSpellEntry(sid)
|
||||||
|
if not spellEntry or not canonicalSid or canonicalSid <= 0 then
|
||||||
|
return false, "unknown_spell"
|
||||||
|
end
|
||||||
|
sid = canonicalSid
|
||||||
|
|
||||||
|
self:RegisterExternalAddonSource(source)
|
||||||
|
local previous = self.playerData[normalizedName] or {}
|
||||||
|
local knownSpells = previous.knownSpells
|
||||||
|
if type(knownSpells) ~= "table" then
|
||||||
|
knownSpells = {}
|
||||||
|
end
|
||||||
|
knownSpells[sid] = true
|
||||||
|
|
||||||
|
local classToken = class or previous.class or self:InferClassFromSpellEntry(spellEntry)
|
||||||
|
|
||||||
|
self.playerData[normalizedName] = {
|
||||||
|
class = classToken,
|
||||||
|
specIndex = previous.specIndex,
|
||||||
|
talentHash = previous.talentHash,
|
||||||
|
talents = previous.talents or {},
|
||||||
|
knownSpells = knownSpells,
|
||||||
|
externalSource = source,
|
||||||
|
}
|
||||||
|
self:SetPlayerBridgeStatus(normalizedName, source)
|
||||||
|
self:DebugScoped("verbose", "TrackerBridge", "Bridge known spell source=%s player=%s spellId=%s", tostring(source), tostring(normalizedName), tostring(sid))
|
||||||
|
|
||||||
|
if tonumber(cooldown) and tonumber(cooldown) > 0 then
|
||||||
|
spellEntry._hmgtExternalBaseCd = tonumber(cooldown)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:TriggerTrackerUpdate("trackers")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ApplyExternalSpecInfo(sourceName, playerName, class, specId, talentHash)
|
||||||
|
local source = tostring(sourceName or "External")
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
local spec = tonumber(specId)
|
||||||
|
local classToken = class and tostring(class) or self:GetClassTokenForSpecId(spec)
|
||||||
|
if not normalizedName or normalizedName == "" or not classToken or classToken == "" or not spec or spec <= 0 then
|
||||||
|
return false, "invalid_args"
|
||||||
|
end
|
||||||
|
if not self:IsPlayerInCurrentGroup(normalizedName) then
|
||||||
|
return false, "not_in_group"
|
||||||
|
end
|
||||||
|
|
||||||
|
self:RegisterExternalAddonSource(source)
|
||||||
|
local previous = self.playerData[normalizedName] or {}
|
||||||
|
local knownSpells = previous.knownSpells
|
||||||
|
if type(knownSpells) ~= "table" then
|
||||||
|
knownSpells = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if HMGT_SpellData and type(HMGT_SpellData.GetSpellsForSpec) == "function" then
|
||||||
|
for _, datasetName in ipairs({ "Interrupts", "RaidCooldowns", "GroupCooldowns" }) do
|
||||||
|
local dataset = HMGT_SpellData[datasetName]
|
||||||
|
for _, spellEntry in ipairs(HMGT_SpellData.GetSpellsForSpec(classToken, spec, dataset)) do
|
||||||
|
local sid = tonumber(spellEntry and spellEntry.spellId)
|
||||||
|
if sid and sid > 0 then
|
||||||
|
knownSpells[sid] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.playerData[normalizedName] = {
|
||||||
|
class = classToken,
|
||||||
|
specIndex = spec,
|
||||||
|
talentHash = talentHash or previous.talentHash,
|
||||||
|
talents = self:ParseTalentHash(talentHash or previous.talentHash),
|
||||||
|
knownSpells = knownSpells,
|
||||||
|
externalSource = source,
|
||||||
|
}
|
||||||
|
self:SetPlayerBridgeStatus(normalizedName, source)
|
||||||
|
self:DebugScoped("info", "TrackerBridge", "Bridge spec sync source=%s player=%s class=%s spec=%s", tostring(source), tostring(normalizedName), tostring(classToken), tostring(spec))
|
||||||
|
|
||||||
|
self:PruneAvailabilityStates(normalizedName, knownSpells)
|
||||||
|
self:TriggerTrackerUpdate("trackers")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ApplyExternalCooldown(sourceName, playerName, spellId, cooldown)
|
||||||
|
local source = tostring(sourceName or "External")
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
local cd = tonumber(cooldown)
|
||||||
|
if not normalizedName or normalizedName == "" or not sid or sid <= 0 or not cd or cd <= 0 then
|
||||||
|
return false, "invalid_args"
|
||||||
|
end
|
||||||
|
if not self:IsPlayerInCurrentGroup(normalizedName) then
|
||||||
|
return false, "not_in_group"
|
||||||
|
end
|
||||||
|
|
||||||
|
local spellEntry, canonicalSid = self:GetCanonicalExternalSpellEntry(sid)
|
||||||
|
if not spellEntry or not canonicalSid or canonicalSid <= 0 then
|
||||||
|
return false, "unknown_spell"
|
||||||
|
end
|
||||||
|
sid = canonicalSid
|
||||||
|
|
||||||
|
self:RegisterExternalAddonSource(source)
|
||||||
|
self:ApplyExternalKnownSpell(source, normalizedName, sid, nil, cd)
|
||||||
|
self:DebugScoped("info", "TrackerBridge", "Bridge cooldown source=%s player=%s spellId=%s cooldown=%.1f", tostring(source), tostring(normalizedName), tostring(sid), cd)
|
||||||
|
self:HandleRemoteSpellCast(normalizedName, sid, GetServerTime(), nil, nil, nil, cd)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:IsPlayerInCurrentGroup(playerName)
|
||||||
|
local target = self:NormalizePlayerName(playerName)
|
||||||
|
if not target then return false end
|
||||||
|
local own = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
if target == own then return true end
|
||||||
|
|
||||||
|
if IsInRaid() then
|
||||||
|
for i = 1, GetNumGroupMembers() do
|
||||||
|
local n = self:NormalizePlayerName(UnitName("raid" .. i))
|
||||||
|
if n == target then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if IsInGroup() then
|
||||||
|
for i = 1, GetNumSubgroupMembers() do
|
||||||
|
local n = self:NormalizePlayerName(UnitName("party" .. i))
|
||||||
|
if n == target then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
576
Modules/Tracker/TrackerCore.lua
Normal file
576
Modules/Tracker/TrackerCore.lua
Normal file
@@ -0,0 +1,576 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
|
HMGT.TrackerCore = HMGT.TrackerCore or {}
|
||||||
|
|
||||||
|
HMGT.TRACKER_PRESET_DEFINITIONS = HMGT.TRACKER_PRESET_DEFINITIONS or {
|
||||||
|
interruptTracker = {
|
||||||
|
moduleName = "InterruptTracker",
|
||||||
|
dbKey = "interruptTracker",
|
||||||
|
trackerType = "normal",
|
||||||
|
trackerKey = "interruptTracker",
|
||||||
|
categories = { "interrupt" },
|
||||||
|
defaultName = function(L)
|
||||||
|
return (L and L["IT_NAME"]) or "Interrupts"
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
raidCooldownTracker = {
|
||||||
|
moduleName = "RaidCooldownTracker",
|
||||||
|
dbKey = "raidCooldownTracker",
|
||||||
|
trackerType = "normal",
|
||||||
|
trackerKey = "raidCooldownTracker",
|
||||||
|
categories = { "lust", "defensive", "healing", "tank", "utility", "offensive", "cc", "interrupt" },
|
||||||
|
defaultName = function(L)
|
||||||
|
return (L and L["RCD_NAME"]) or "Raid Cooldowns"
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
groupCooldownTracker = {
|
||||||
|
moduleName = "GroupCooldownTracker",
|
||||||
|
dbKey = "groupCooldownTracker",
|
||||||
|
trackerType = "group",
|
||||||
|
trackerKey = "groupCooldownTracker",
|
||||||
|
categories = { "tank", "defensive", "healing", "cc", "utility", "offensive", "lust", "interrupt" },
|
||||||
|
includeSelfFrame = false,
|
||||||
|
showChargesOnIcon = true,
|
||||||
|
defaultName = function(L)
|
||||||
|
return (L and L["GCD_NAME"]) or "Cooldowns"
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function HMGT:GetTrackerPresetDefinitions()
|
||||||
|
return self.TRACKER_PRESET_DEFINITIONS or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetTrackerPresetDefinition(key)
|
||||||
|
local definitions = self:GetTrackerPresetDefinitions()
|
||||||
|
return definitions and definitions[tostring(key or "")]
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetTrackerPresetDefinitionByModule(moduleName)
|
||||||
|
local target = tostring(moduleName or "")
|
||||||
|
for _, definition in pairs(self:GetTrackerPresetDefinitions()) do
|
||||||
|
if tostring(definition.moduleName or "") == target then
|
||||||
|
return definition
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetTrackerTypeOptions()
|
||||||
|
local L = self.L
|
||||||
|
return {
|
||||||
|
normal = (L and L["OPT_TRACKER_TYPE_NORMAL"]) or "Normal tracker",
|
||||||
|
group = (L and L["OPT_TRACKER_TYPE_GROUP"]) or "Group-based tracker",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:BuildTrackerConfigFromPreset(presetKey, trackerId, overrides)
|
||||||
|
local definition = self:GetTrackerPresetDefinition(presetKey)
|
||||||
|
local config = overrides or {}
|
||||||
|
if not definition then
|
||||||
|
return self:CreateTrackerConfig(trackerId, config)
|
||||||
|
end
|
||||||
|
|
||||||
|
local base = {
|
||||||
|
name = type(definition.defaultName) == "function" and definition.defaultName(self.L) or tostring(definition.defaultName or ""),
|
||||||
|
trackerType = definition.trackerType,
|
||||||
|
trackerKey = definition.trackerKey,
|
||||||
|
categories = definition.categories,
|
||||||
|
includeSelfFrame = definition.includeSelfFrame,
|
||||||
|
showChargesOnIcon = definition.showChargesOnIcon,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in pairs(config) do
|
||||||
|
base[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
return self:CreateTrackerConfig(trackerId, base)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function EntryNeedsVisualTicker(entry)
|
||||||
|
if type(entry) ~= "table" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local remaining = tonumber(entry.remaining) or 0
|
||||||
|
if remaining > 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local maxCharges = tonumber(entry.maxCharges) or 0
|
||||||
|
local currentCharges = tonumber(entry.currentCharges)
|
||||||
|
if maxCharges > 0 and currentCharges ~= nil and currentCharges < maxCharges then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:IsGroupTrackerConfig(tracker)
|
||||||
|
return type(tracker) == "table" and tracker.trackerType == "group"
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetTrackerSpellPool(categories)
|
||||||
|
if HMGT_SpellData and type(HMGT_SpellData.GetSpellPoolForCategories) == "function" then
|
||||||
|
return HMGT_SpellData.GetSpellPoolForCategories(categories)
|
||||||
|
end
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetTrackerSpellsForPlayer(classToken, specIndex, categories)
|
||||||
|
if HMGT_SpellData and type(HMGT_SpellData.GetSpellsForCategories) == "function" then
|
||||||
|
return HMGT_SpellData.GetSpellsForCategories(classToken, specIndex, categories)
|
||||||
|
end
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetTrackerPlayers(tracker)
|
||||||
|
local players = {}
|
||||||
|
|
||||||
|
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local ownClass = select(2, UnitClass("player"))
|
||||||
|
local includeOwnPlayer = true
|
||||||
|
if self:IsGroupTrackerConfig(tracker) then
|
||||||
|
includeOwnPlayer = tracker.includeSelfFrame == true
|
||||||
|
end
|
||||||
|
if includeOwnPlayer then
|
||||||
|
players[#players + 1] = {
|
||||||
|
name = ownName,
|
||||||
|
class = ownClass,
|
||||||
|
isOwn = true,
|
||||||
|
unitId = "player",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if IsInRaid() then
|
||||||
|
for i = 1, GetNumGroupMembers() do
|
||||||
|
local unitId = "raid" .. i
|
||||||
|
local name = self:NormalizePlayerName(UnitName(unitId))
|
||||||
|
local class = select(2, UnitClass(unitId))
|
||||||
|
if name and name ~= ownName then
|
||||||
|
players[#players + 1] = {
|
||||||
|
name = name,
|
||||||
|
class = class,
|
||||||
|
unitId = unitId,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif IsInGroup() then
|
||||||
|
for i = 1, GetNumGroupMembers() - 1 do
|
||||||
|
local unitId = "party" .. i
|
||||||
|
local name = self:NormalizePlayerName(UnitName(unitId))
|
||||||
|
local class = select(2, UnitClass(unitId))
|
||||||
|
if name and name ~= ownName then
|
||||||
|
players[#players + 1] = {
|
||||||
|
name = name,
|
||||||
|
class = class,
|
||||||
|
unitId = unitId,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return players
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:CollectTrackerEntriesForPlayer(tracker, playerInfo)
|
||||||
|
local entries = {}
|
||||||
|
if type(tracker) ~= "table" or type(playerInfo) ~= "table" then
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local playerName = playerInfo.name
|
||||||
|
if not playerName then
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local pData = self.playerData[playerName]
|
||||||
|
local classToken = pData and pData.class or playerInfo.class
|
||||||
|
if not classToken then
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local specIndex
|
||||||
|
if playerInfo.isOwn then
|
||||||
|
specIndex = GetSpecialization()
|
||||||
|
if not specIndex or specIndex == 0 then
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
else
|
||||||
|
specIndex = pData and pData.specIndex or nil
|
||||||
|
if not specIndex or tonumber(specIndex) <= 0 then
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local talents = pData and pData.talents or {}
|
||||||
|
local spells = self:GetTrackerSpellsForPlayer(classToken, specIndex, tracker.categories)
|
||||||
|
for _, spellEntry in ipairs(spells) do
|
||||||
|
if tracker.enabledSpells[spellEntry.spellId] ~= false then
|
||||||
|
local remaining, total, currentCharges, maxCharges = self:GetCooldownInfo(playerName, spellEntry.spellId)
|
||||||
|
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||||
|
local isAvailabilitySpell = self:IsAvailabilitySpell(spellEntry)
|
||||||
|
local include = self:ShouldDisplayEntry(tracker, remaining, currentCharges, maxCharges, spellEntry)
|
||||||
|
local spellKnown = self:IsTrackedSpellKnownForPlayer(playerName, spellEntry.spellId)
|
||||||
|
local hasPartialCharges = (tonumber(maxCharges) or 0) > 0
|
||||||
|
and (tonumber(currentCharges) or tonumber(maxCharges) or 0) < (tonumber(maxCharges) or 0)
|
||||||
|
local hasActiveCd = ((remaining or 0) > 0) or hasPartialCharges
|
||||||
|
|
||||||
|
if not spellKnown and not hasActiveCd then
|
||||||
|
include = false
|
||||||
|
end
|
||||||
|
if isAvailabilitySpell and not spellKnown then
|
||||||
|
include = false
|
||||||
|
end
|
||||||
|
if not playerInfo.isOwn and isAvailabilitySpell and not self:HasAvailabilityState(playerName, spellEntry.spellId) then
|
||||||
|
include = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if include then
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
playerName = playerName,
|
||||||
|
class = classToken,
|
||||||
|
spellEntry = spellEntry,
|
||||||
|
remaining = remaining,
|
||||||
|
total = total > 0 and total or effectiveCd,
|
||||||
|
currentCharges = currentCharges,
|
||||||
|
maxCharges = maxCharges,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CopyEntriesForPreview(entries, playerName)
|
||||||
|
local copies = {}
|
||||||
|
for _, entry in ipairs(entries or {}) do
|
||||||
|
local nextEntry = {}
|
||||||
|
for key, value in pairs(entry) do
|
||||||
|
nextEntry[key] = value
|
||||||
|
end
|
||||||
|
nextEntry.playerName = playerName
|
||||||
|
copies[#copies + 1] = nextEntry
|
||||||
|
end
|
||||||
|
return copies
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:BuildPartyPreviewEntries(entries, resolveUnitAnchorFrame)
|
||||||
|
local byPlayer = {}
|
||||||
|
local order = {}
|
||||||
|
local unitByPlayer = {}
|
||||||
|
for index = 1, 4 do
|
||||||
|
local unitId = "party" .. index
|
||||||
|
if not resolveUnitAnchorFrame or resolveUnitAnchorFrame(unitId) then
|
||||||
|
local playerName = string.format("Party %d", index)
|
||||||
|
local playerEntries = CopyEntriesForPreview(entries, playerName)
|
||||||
|
if #playerEntries > 0 then
|
||||||
|
byPlayer[playerName] = playerEntries
|
||||||
|
order[#order + 1] = playerName
|
||||||
|
unitByPlayer[playerName] = unitId
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return byPlayer, order, unitByPlayer, #order > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:CollectTrackerEntries(tracker)
|
||||||
|
local entries = {}
|
||||||
|
local players = self:GetTrackerPlayers(tracker)
|
||||||
|
for _, playerInfo in ipairs(players) do
|
||||||
|
local playerEntries = self:CollectTrackerEntriesForPlayer(tracker, playerInfo)
|
||||||
|
for _, entry in ipairs(playerEntries) do
|
||||||
|
entries[#entries + 1] = entry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetDemoEntries(trackerKey, database, settings)
|
||||||
|
local pool = {}
|
||||||
|
local poolByClass = {}
|
||||||
|
for _, entry in ipairs(database or {}) do
|
||||||
|
if settings.enabledSpells[entry.spellId] ~= false then
|
||||||
|
pool[#pool + 1] = entry
|
||||||
|
for _, classToken in ipairs(entry.classes or {}) do
|
||||||
|
poolByClass[classToken] = poolByClass[classToken] or {}
|
||||||
|
poolByClass[classToken][#poolByClass[classToken] + 1] = entry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #pool == 0 then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local classKeys = {}
|
||||||
|
for classToken in pairs(poolByClass) do
|
||||||
|
classKeys[#classKeys + 1] = classToken
|
||||||
|
end
|
||||||
|
if #classKeys == 0 then
|
||||||
|
classKeys[1] = "WARRIOR"
|
||||||
|
end
|
||||||
|
|
||||||
|
local count = settings.showBar and math.min(8, #pool) or math.min(12, #pool)
|
||||||
|
local names = { "Alice", "Bob", "Clara", "Duke", "Elli", "Fynn", "Gina", "Hektor", "Ivo", "Jana", "Kira", "Lio" }
|
||||||
|
|
||||||
|
local spellIds = {}
|
||||||
|
for _, entry in ipairs(pool) do
|
||||||
|
spellIds[#spellIds + 1] = tostring(entry.spellId)
|
||||||
|
end
|
||||||
|
table.sort(spellIds)
|
||||||
|
local signature = table.concat(spellIds, ",") .. "|" .. tostring(settings.showBar and 1 or 0) .. "|" .. tostring(count)
|
||||||
|
|
||||||
|
local now = GetTime()
|
||||||
|
local cache = self.demoModeData[trackerKey]
|
||||||
|
if (not cache) or cache.signature ~= signature or (not cache.entries) or #cache.entries ~= count then
|
||||||
|
local cachedEntries = {}
|
||||||
|
for index = 1, count do
|
||||||
|
local classToken = classKeys[math.random(1, #classKeys)]
|
||||||
|
local classPool = poolByClass[classToken]
|
||||||
|
local spellEntry = (classPool and classPool[math.random(1, #classPool)]) or pool[math.random(1, #pool)]
|
||||||
|
local duration = math.max(
|
||||||
|
1,
|
||||||
|
tonumber(HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(spellEntry)) or tonumber(spellEntry.cooldown) or 60
|
||||||
|
)
|
||||||
|
local offset = math.random() * math.min(duration * 0.85, duration - 0.1)
|
||||||
|
cachedEntries[#cachedEntries + 1] = {
|
||||||
|
playerName = names[((index - 1) % #names) + 1],
|
||||||
|
class = classToken or ((spellEntry.classes and spellEntry.classes[1]) or "WARRIOR"),
|
||||||
|
spellEntry = spellEntry,
|
||||||
|
total = duration,
|
||||||
|
cycleStart = now - offset,
|
||||||
|
currentCharges = nil,
|
||||||
|
maxCharges = nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
cache = {
|
||||||
|
signature = signature,
|
||||||
|
entries = cachedEntries,
|
||||||
|
}
|
||||||
|
self.demoModeData[trackerKey] = cache
|
||||||
|
end
|
||||||
|
|
||||||
|
local entries = {}
|
||||||
|
for _, entry in ipairs(cache.entries) do
|
||||||
|
local total = math.max(1, tonumber(entry.total) or 1)
|
||||||
|
local elapsed = math.max(0, now - (entry.cycleStart or now))
|
||||||
|
local phase = math.fmod(elapsed, total)
|
||||||
|
local remaining = total - phase
|
||||||
|
if elapsed > 0 and phase < 0.05 then
|
||||||
|
remaining = 0
|
||||||
|
end
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
playerName = entry.playerName,
|
||||||
|
class = entry.class,
|
||||||
|
spellEntry = entry.spellEntry,
|
||||||
|
remaining = remaining,
|
||||||
|
total = total,
|
||||||
|
currentCharges = entry.currentCharges,
|
||||||
|
maxCharges = entry.maxCharges,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:CollectTrackerTestEntries(tracker)
|
||||||
|
local playerName = self:NormalizePlayerName(UnitName("player")) or "Player"
|
||||||
|
local classToken = select(2, UnitClass("player"))
|
||||||
|
if not classToken then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local entries = {}
|
||||||
|
local pData = self.playerData[playerName]
|
||||||
|
local talents = pData and pData.talents or {}
|
||||||
|
local spells = self:GetTrackerSpellsForPlayer(classToken, GetSpecialization() or 0, tracker.categories)
|
||||||
|
for _, spellEntry in ipairs(spells) do
|
||||||
|
if tracker.enabledSpells[spellEntry.spellId] ~= false then
|
||||||
|
local remaining, total, currentCharges, maxCharges = self:GetCooldownInfo(playerName, spellEntry.spellId)
|
||||||
|
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||||
|
local isAvailabilitySpell = self:IsAvailabilitySpell(spellEntry)
|
||||||
|
local spellKnown = self:IsTrackedSpellKnownForPlayer(playerName, spellEntry.spellId)
|
||||||
|
local hasPartialCharges = (tonumber(maxCharges) or 0) > 0
|
||||||
|
and (tonumber(currentCharges) or tonumber(maxCharges) or 0) < (tonumber(maxCharges) or 0)
|
||||||
|
local hasActiveCd = ((remaining or 0) > 0) or hasPartialCharges
|
||||||
|
local hasAvailabilityState = isAvailabilitySpell and self:HasAvailabilityState(playerName, spellEntry.spellId)
|
||||||
|
if spellKnown or hasActiveCd or hasAvailabilityState then
|
||||||
|
entries[#entries + 1] = {
|
||||||
|
playerName = playerName,
|
||||||
|
class = classToken,
|
||||||
|
spellEntry = spellEntry,
|
||||||
|
remaining = remaining,
|
||||||
|
total = total > 0 and total or effectiveCd,
|
||||||
|
currentCharges = currentCharges,
|
||||||
|
maxCharges = maxCharges,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:BuildEntriesForTracker(tracker, trackerKey)
|
||||||
|
local key = trackerKey or tostring(tonumber(tracker and tracker.id) or 0)
|
||||||
|
if tracker and tracker.testMode then
|
||||||
|
return self:CollectTrackerTestEntries(tracker), true
|
||||||
|
end
|
||||||
|
if tracker and tracker.demoMode then
|
||||||
|
return self:GetDemoEntries(key, self:GetTrackerSpellPool(tracker.categories), tracker), true
|
||||||
|
end
|
||||||
|
if not tracker or not tracker.enabled or not self:IsVisibleForCurrentGroup(tracker) then
|
||||||
|
return {}, false
|
||||||
|
end
|
||||||
|
return self:CollectTrackerEntries(tracker), true
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:BuildEntriesByPlayerForTracker(tracker, trackerKey, resolveUnitAnchorFrame)
|
||||||
|
local key = trackerKey or tostring(tonumber(tracker and tracker.id) or 0)
|
||||||
|
local ownName = self:NormalizePlayerName(UnitName("player")) or "Player"
|
||||||
|
if tracker.testMode then
|
||||||
|
local entries = self:CollectTrackerTestEntries(tracker)
|
||||||
|
if self:IsGroupTrackerConfig(tracker) and tracker.attachToPartyFrame == true then
|
||||||
|
return self:BuildPartyPreviewEntries(entries, resolveUnitAnchorFrame)
|
||||||
|
end
|
||||||
|
local byPlayer, order, unitByPlayer = {}, {}, {}
|
||||||
|
if #entries > 0 then
|
||||||
|
byPlayer[ownName] = entries
|
||||||
|
order[1] = ownName
|
||||||
|
unitByPlayer[ownName] = "player"
|
||||||
|
end
|
||||||
|
return byPlayer, order, unitByPlayer, true
|
||||||
|
end
|
||||||
|
if tracker.demoMode then
|
||||||
|
local entries = self:GetDemoEntries(key, self:GetTrackerSpellPool(tracker.categories), tracker)
|
||||||
|
if self:IsGroupTrackerConfig(tracker) and tracker.attachToPartyFrame == true then
|
||||||
|
return self:BuildPartyPreviewEntries(entries, resolveUnitAnchorFrame)
|
||||||
|
end
|
||||||
|
for _, entry in ipairs(entries) do
|
||||||
|
entry.playerName = ownName
|
||||||
|
end
|
||||||
|
local byPlayer, order, unitByPlayer = {}, {}, {}
|
||||||
|
if #entries > 0 then
|
||||||
|
byPlayer[ownName] = entries
|
||||||
|
order[1] = ownName
|
||||||
|
unitByPlayer[ownName] = "player"
|
||||||
|
end
|
||||||
|
return byPlayer, order, unitByPlayer, true
|
||||||
|
end
|
||||||
|
if not tracker.enabled or not self:IsVisibleForCurrentGroup(tracker) then
|
||||||
|
return {}, {}, {}, false
|
||||||
|
end
|
||||||
|
if IsInRaid() or not IsInGroup() then
|
||||||
|
return {}, {}, {}, false
|
||||||
|
end
|
||||||
|
local byPlayer, order, unitByPlayer = {}, {}, {}
|
||||||
|
for _, playerInfo in ipairs(self:GetTrackerPlayers(tracker)) do
|
||||||
|
local entries = self:CollectTrackerEntriesForPlayer(tracker, playerInfo)
|
||||||
|
if #entries > 0 then
|
||||||
|
local playerName = playerInfo.name
|
||||||
|
byPlayer[playerName] = entries
|
||||||
|
order[#order + 1] = playerName
|
||||||
|
unitByPlayer[playerName] = playerInfo.unitId
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return byPlayer, order, unitByPlayer, true
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:FinalizeTrackerEntries(tracker, entries, trackerKey)
|
||||||
|
local result = entries or {}
|
||||||
|
if self.FilterDisplayEntries then
|
||||||
|
result = self:FilterDisplayEntries(tracker, result) or result
|
||||||
|
end
|
||||||
|
if self.SortDisplayEntries then
|
||||||
|
self:SortDisplayEntries(result, trackerKey)
|
||||||
|
end
|
||||||
|
|
||||||
|
local shouldTick = false
|
||||||
|
for _, entry in ipairs(result) do
|
||||||
|
if EntryNeedsVisualTicker(entry) then
|
||||||
|
shouldTick = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result, shouldTick
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:TriggerTrackerUpdate(reason)
|
||||||
|
local function normalizeReason(value)
|
||||||
|
if value == true then
|
||||||
|
return "trackers"
|
||||||
|
elseif value == "trackers" or value == "layout" or value == "visual" then
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
return "full"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function mergeReasons(current, incoming)
|
||||||
|
local priority = {
|
||||||
|
visual = 1,
|
||||||
|
layout = 2,
|
||||||
|
trackers = 3,
|
||||||
|
full = 4,
|
||||||
|
}
|
||||||
|
current = normalizeReason(current)
|
||||||
|
incoming = normalizeReason(incoming)
|
||||||
|
if (priority[incoming] or 4) >= (priority[current] or 4) then
|
||||||
|
return incoming
|
||||||
|
end
|
||||||
|
return current
|
||||||
|
end
|
||||||
|
|
||||||
|
self._trackerUpdateMinDelay = self._trackerUpdateMinDelay or 0.08
|
||||||
|
self._trackerUpdatePending = true
|
||||||
|
self._trackerUpdateReason = mergeReasons(self._trackerUpdateReason, reason)
|
||||||
|
if HMGT.TrackerManager then
|
||||||
|
local normalizedReason = normalizeReason(reason)
|
||||||
|
if normalizedReason == "trackers" then
|
||||||
|
HMGT.TrackerManager:MarkTrackersDirty()
|
||||||
|
elseif normalizedReason == "layout" then
|
||||||
|
HMGT.TrackerManager:MarkLayoutDirty()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self._updateScheduled then return end
|
||||||
|
|
||||||
|
local now = GetTime()
|
||||||
|
local last = self._lastTrackerUpdateAt or 0
|
||||||
|
local delay = math.max(0, self._trackerUpdateMinDelay - (now - last))
|
||||||
|
self._updateScheduled = true
|
||||||
|
|
||||||
|
self:ScheduleTimer(function()
|
||||||
|
self._updateScheduled = nil
|
||||||
|
if not self._trackerUpdatePending then return end
|
||||||
|
self._trackerUpdatePending = nil
|
||||||
|
self._lastTrackerUpdateAt = GetTime()
|
||||||
|
local pendingReason = self._trackerUpdateReason
|
||||||
|
self._trackerUpdateReason = nil
|
||||||
|
|
||||||
|
local function profileModule(name, fn)
|
||||||
|
if not fn then return end
|
||||||
|
local t0 = debugprofilestop and debugprofilestop() or nil
|
||||||
|
fn()
|
||||||
|
local t1 = debugprofilestop and debugprofilestop() or nil
|
||||||
|
if t0 and t1 then
|
||||||
|
local mod = HMGT[name]
|
||||||
|
local count = mod and mod.lastEntryCount or 0
|
||||||
|
self:DebugScoped("verbose", "TrackerUI", "UIUpdate %s took %.2fms entries=%s", tostring(name), t1 - t0, tostring(count))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
profileModule("TrackerManager", HMGT.TrackerManager and function()
|
||||||
|
if pendingReason == "visual" and HMGT.TrackerManager.RefreshVisibleVisuals then
|
||||||
|
HMGT.TrackerManager:RefreshVisibleVisuals()
|
||||||
|
else
|
||||||
|
HMGT.TrackerManager:UpdateDisplay()
|
||||||
|
end
|
||||||
|
end or nil)
|
||||||
|
|
||||||
|
if self._trackerUpdatePending then
|
||||||
|
self:TriggerTrackerUpdate()
|
||||||
|
end
|
||||||
|
end, delay)
|
||||||
|
end
|
||||||
268
Modules/Tracker/TrackerDataProvider.lua
Normal file
268
Modules/Tracker/TrackerDataProvider.lua
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
|
HMGT.TrackerDataProvider = HMGT.TrackerDataProvider or {}
|
||||||
|
|
||||||
|
local internals = HMGT.TrackerInternals or {}
|
||||||
|
local SafeApiNumber = internals.SafeApiNumber
|
||||||
|
local GetSpellChargesInfo = internals.GetSpellChargesInfo
|
||||||
|
local GetSpellCooldownInfo = internals.GetSpellCooldownInfo
|
||||||
|
|
||||||
|
function HMGT:GetCooldownInfo(playerName, spellId, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local deferUntilEmpty = opts.deferChargeCooldownUntilEmpty and true or false
|
||||||
|
local spellEntry = HMGT_SpellData.InterruptLookup[spellId]
|
||||||
|
or HMGT_SpellData.CooldownLookup[spellId]
|
||||||
|
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local isOwnPlayer = playerName == ownName
|
||||||
|
local pData = isOwnPlayer and self.playerData[ownName] or nil
|
||||||
|
local talents = pData and pData.talents or {}
|
||||||
|
local effectiveCd = spellEntry and HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents) or 0
|
||||||
|
local knownMaxCharges, knownChargeDuration = 0, 0
|
||||||
|
if spellEntry and isOwnPlayer then
|
||||||
|
knownMaxCharges, knownChargeDuration = self:GetKnownChargeInfo(spellEntry, talents, spellId, effectiveCd)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self:IsAvailabilitySpell(spellEntry) then
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
if normalizedName == ownName then
|
||||||
|
local current, max = self:GetOwnAvailabilityProgress(spellEntry)
|
||||||
|
if (tonumber(max) or 0) > 0 then
|
||||||
|
self:StoreAvailabilityState(ownName, spellId, current, max, spellEntry)
|
||||||
|
return 0, 0, current, max
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local current, max = self:GetAvailabilityState(normalizedName, spellId)
|
||||||
|
if (tonumber(max) or 0) > 0 then
|
||||||
|
return 0, 0, current, max
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 0, 0, nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local cdData = self:GetActiveCooldown(playerName, spellId)
|
||||||
|
|
||||||
|
if isOwnPlayer and not (InCombatLockdown and InCombatLockdown()) then
|
||||||
|
local charges, maxCharges, chargeStart, chargeDuration = nil, nil, nil, nil
|
||||||
|
if GetSpellChargesInfo then
|
||||||
|
charges, maxCharges, chargeStart, chargeDuration = GetSpellChargesInfo(spellId)
|
||||||
|
end
|
||||||
|
charges = SafeApiNumber and SafeApiNumber(charges, 0) or tonumber(charges) or 0
|
||||||
|
maxCharges = SafeApiNumber and SafeApiNumber(maxCharges, 0) or tonumber(maxCharges) or 0
|
||||||
|
chargeStart = SafeApiNumber and SafeApiNumber(chargeStart) or tonumber(chargeStart)
|
||||||
|
chargeDuration = SafeApiNumber and SafeApiNumber(chargeDuration, 0) or tonumber(chargeDuration) or 0
|
||||||
|
|
||||||
|
if maxCharges > 0 then
|
||||||
|
local tempChargeState = {
|
||||||
|
currentCharges = charges,
|
||||||
|
maxCharges = maxCharges,
|
||||||
|
chargeStart = chargeStart,
|
||||||
|
chargeDuration = chargeDuration,
|
||||||
|
duration = chargeDuration,
|
||||||
|
}
|
||||||
|
local remaining, total, curCharges, maxChargeCount = self:ResolveChargeState(tempChargeState)
|
||||||
|
self:StoreKnownChargeInfo(spellId, maxChargeCount, total > 0 and total or chargeDuration)
|
||||||
|
if (curCharges or 0) < maxChargeCount and remaining <= 0 and GetSpellCooldownInfo then
|
||||||
|
local cdStart, cdDuration = GetSpellCooldownInfo(spellId)
|
||||||
|
if cdDuration > 0 then
|
||||||
|
remaining = math.max(0, cdDuration - (GetTime() - cdStart))
|
||||||
|
total = math.max(total or 0, cdDuration)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if deferUntilEmpty and (curCharges or 0) > 0 then
|
||||||
|
remaining = 0
|
||||||
|
end
|
||||||
|
return remaining, total, curCharges, maxChargeCount
|
||||||
|
end
|
||||||
|
|
||||||
|
if GetSpellCooldownInfo then
|
||||||
|
local cdStart, cdDuration = GetSpellCooldownInfo(spellId)
|
||||||
|
cdStart = tonumber(cdStart) or 0
|
||||||
|
cdDuration = tonumber(cdDuration) or 0
|
||||||
|
if cdDuration > 0 then
|
||||||
|
local remaining = math.max(0, cdDuration - (GetTime() - cdStart))
|
||||||
|
remaining = math.max(0, math.min(cdDuration, remaining))
|
||||||
|
if cdData and (tonumber(cdData.maxCharges) or 0) <= 0 then
|
||||||
|
local cachedRemaining = (tonumber(cdData.duration) or 0) - (GetTime() - (tonumber(cdData.startTime) or GetTime()))
|
||||||
|
cachedRemaining = math.max(0, math.min(tonumber(cdData.duration) or cachedRemaining, cachedRemaining))
|
||||||
|
local cachedDuration = math.max(0, tonumber(cdData.duration) or 0)
|
||||||
|
if cachedDuration > 2.0 and cachedRemaining > 2.0 and cdDuration < math.max(2.0, cachedDuration * 0.35) then
|
||||||
|
return cachedRemaining, cachedDuration, nil, nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return remaining, cdDuration, nil, nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not cdData then
|
||||||
|
if isOwnPlayer and knownMaxCharges > 1 then
|
||||||
|
return 0, math.max(0, knownChargeDuration or effectiveCd or 0), knownMaxCharges, knownMaxCharges
|
||||||
|
end
|
||||||
|
return 0, 0, nil, nil
|
||||||
|
end
|
||||||
|
if (tonumber(cdData.maxCharges) or 0) > 0 then
|
||||||
|
local remaining, chargeDur, charges, maxCharges = self:ResolveChargeState(cdData)
|
||||||
|
self:StoreKnownChargeInfo(spellId, maxCharges, chargeDur)
|
||||||
|
if deferUntilEmpty and charges > 0 then
|
||||||
|
remaining = 0
|
||||||
|
end
|
||||||
|
return remaining, chargeDur, charges, maxCharges
|
||||||
|
end
|
||||||
|
if isOwnPlayer and knownMaxCharges > 1 then
|
||||||
|
local remaining = (tonumber(cdData.duration) or 0) - (GetTime() - (tonumber(cdData.startTime) or GetTime()))
|
||||||
|
remaining = math.max(0, math.min(tonumber(cdData.duration) or remaining, remaining))
|
||||||
|
local currentCharges = knownMaxCharges
|
||||||
|
if remaining > 0 then
|
||||||
|
currentCharges = math.max(0, knownMaxCharges - 1)
|
||||||
|
end
|
||||||
|
if deferUntilEmpty and currentCharges > 0 then
|
||||||
|
remaining = 0
|
||||||
|
end
|
||||||
|
return remaining, math.max(0, knownChargeDuration or effectiveCd or 0), currentCharges, knownMaxCharges
|
||||||
|
end
|
||||||
|
local remaining = cdData.duration - (GetTime() - cdData.startTime)
|
||||||
|
remaining = math.max(0, math.min(cdData.duration, remaining))
|
||||||
|
return remaining, cdData.duration, nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ShouldDisplayEntry(settings, remaining, currentCharges, maxCharges, spellEntry)
|
||||||
|
local rem = tonumber(remaining) or 0
|
||||||
|
local cur = tonumber(currentCharges) or 0
|
||||||
|
local max = tonumber(maxCharges) or 0
|
||||||
|
local soon = tonumber(settings.readySoonSec) or 0
|
||||||
|
local isAvailabilitySpell = spellEntry and self:IsAvailabilitySpell(spellEntry) or false
|
||||||
|
local isReady
|
||||||
|
|
||||||
|
if isAvailabilitySpell then
|
||||||
|
isReady = max > 0 and cur >= max
|
||||||
|
else
|
||||||
|
isReady = rem <= 0 or (max > 0 and cur > 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if settings.showOnlyReady then
|
||||||
|
return isReady
|
||||||
|
end
|
||||||
|
if soon > 0 then
|
||||||
|
if isAvailabilitySpell then
|
||||||
|
return isReady
|
||||||
|
end
|
||||||
|
return isReady or rem <= soon
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local DEFAULT_CATEGORY_PRIORITY = {
|
||||||
|
interrupt = 1,
|
||||||
|
lust = 2,
|
||||||
|
defensive = 3,
|
||||||
|
tank = 4,
|
||||||
|
healing = 5,
|
||||||
|
offensive = 6,
|
||||||
|
utility = 7,
|
||||||
|
cc = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
local TRACKER_CATEGORY_PRIORITY = {
|
||||||
|
interruptTracker = {
|
||||||
|
interrupt = 1,
|
||||||
|
defensive = 2,
|
||||||
|
utility = 3,
|
||||||
|
cc = 4,
|
||||||
|
healing = 5,
|
||||||
|
tank = 6,
|
||||||
|
offensive = 7,
|
||||||
|
lust = 8,
|
||||||
|
},
|
||||||
|
raidCooldownTracker = {
|
||||||
|
lust = 1,
|
||||||
|
defensive = 2,
|
||||||
|
healing = 3,
|
||||||
|
tank = 4,
|
||||||
|
utility = 5,
|
||||||
|
offensive = 6,
|
||||||
|
cc = 7,
|
||||||
|
interrupt = 8,
|
||||||
|
},
|
||||||
|
groupCooldownTracker = {
|
||||||
|
tank = 1,
|
||||||
|
defensive = 2,
|
||||||
|
healing = 3,
|
||||||
|
cc = 4,
|
||||||
|
utility = 5,
|
||||||
|
offensive = 6,
|
||||||
|
lust = 7,
|
||||||
|
interrupt = 8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local function GetCategoryPriority(category, trackerKey)
|
||||||
|
local cat = tostring(category or "utility")
|
||||||
|
local trackerOrder = trackerKey and TRACKER_CATEGORY_PRIORITY[trackerKey]
|
||||||
|
if trackerOrder and trackerOrder[cat] then
|
||||||
|
return trackerOrder[cat]
|
||||||
|
end
|
||||||
|
local order = HMGT_SpellData and HMGT_SpellData.CategoryOrder
|
||||||
|
if type(order) == "table" then
|
||||||
|
for idx, key in ipairs(order) do
|
||||||
|
if key == cat then
|
||||||
|
return idx
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return #order + 10
|
||||||
|
end
|
||||||
|
return DEFAULT_CATEGORY_PRIORITY[cat] or 99
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:SortDisplayEntries(entries, trackerKey)
|
||||||
|
if type(entries) ~= "table" then return end
|
||||||
|
table.sort(entries, function(a, b)
|
||||||
|
local aRemaining = tonumber(a and a.remaining) or 0
|
||||||
|
local bRemaining = tonumber(b and b.remaining) or 0
|
||||||
|
local aActive = aRemaining > 0
|
||||||
|
local bActive = bRemaining > 0
|
||||||
|
if aActive ~= bActive then
|
||||||
|
return aActive
|
||||||
|
end
|
||||||
|
|
||||||
|
local aEntry = a and a.spellEntry
|
||||||
|
local bEntry = b and b.spellEntry
|
||||||
|
|
||||||
|
local aPriority = tonumber(aEntry and aEntry.priority) or GetCategoryPriority(aEntry and aEntry.category, trackerKey)
|
||||||
|
local bPriority = tonumber(bEntry and bEntry.priority) or GetCategoryPriority(bEntry and bEntry.category, trackerKey)
|
||||||
|
if aPriority ~= bPriority then
|
||||||
|
return aPriority < bPriority
|
||||||
|
end
|
||||||
|
|
||||||
|
if aActive and aRemaining ~= bRemaining then
|
||||||
|
return aRemaining < bRemaining
|
||||||
|
end
|
||||||
|
|
||||||
|
local aTotal = tonumber(a and a.total)
|
||||||
|
or tonumber(aEntry and HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(aEntry))
|
||||||
|
or tonumber(aEntry and aEntry.cooldown)
|
||||||
|
or 0
|
||||||
|
local bTotal = tonumber(b and b.total)
|
||||||
|
or tonumber(bEntry and HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(bEntry))
|
||||||
|
or tonumber(bEntry and bEntry.cooldown)
|
||||||
|
or 0
|
||||||
|
if (not aActive) and aTotal ~= bTotal then
|
||||||
|
return aTotal > bTotal
|
||||||
|
end
|
||||||
|
|
||||||
|
if aRemaining ~= bRemaining then
|
||||||
|
return aRemaining < bRemaining
|
||||||
|
end
|
||||||
|
|
||||||
|
local aName = tostring(a and a.playerName or "")
|
||||||
|
local bName = tostring(b and b.playerName or "")
|
||||||
|
if aName ~= bName then
|
||||||
|
return aName < bName
|
||||||
|
end
|
||||||
|
|
||||||
|
local aSpell = tonumber(aEntry and aEntry.spellId) or 0
|
||||||
|
local bSpell = tonumber(bEntry and bEntry.spellId) or 0
|
||||||
|
return aSpell < bSpell
|
||||||
|
end)
|
||||||
|
end
|
||||||
524
Modules/Tracker/TrackerDetection.lua
Normal file
524
Modules/Tracker/TrackerDetection.lua
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
|
HMGT.TrackerDetection = HMGT.TrackerDetection or {}
|
||||||
|
|
||||||
|
local internals = HMGT.TrackerInternals or {}
|
||||||
|
local GetSpellChargesInfo = internals.GetSpellChargesInfo
|
||||||
|
local GetSpellCooldownInfo = internals.GetSpellCooldownInfo
|
||||||
|
local GetGlobalCooldownInfo = internals.GetGlobalCooldownInfo
|
||||||
|
local GetSpellDebugLabel = internals.GetSpellDebugLabel
|
||||||
|
local BuildCooldownStateFingerprint = internals.BuildCooldownStateFingerprint
|
||||||
|
local ApplyOwnCooldownReducers = internals.ApplyOwnCooldownReducers
|
||||||
|
local ApplyObservedCooldownReducers = internals.ApplyObservedCooldownReducers
|
||||||
|
|
||||||
|
function HMGT:HandleOwnSpellCast(spellId)
|
||||||
|
local isInterrupt = HMGT_SpellData.InterruptLookup[spellId] ~= nil
|
||||||
|
local isCooldown = HMGT_SpellData.CooldownLookup[spellId] ~= nil
|
||||||
|
if not isInterrupt and not isCooldown then return end
|
||||||
|
|
||||||
|
local spellEntry = HMGT_SpellData.InterruptLookup[spellId]
|
||||||
|
or HMGT_SpellData.CooldownLookup[spellId]
|
||||||
|
spellId = tonumber(spellEntry and spellEntry.spellId) or spellId
|
||||||
|
local name = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local pData = self.playerData[name]
|
||||||
|
local talents = pData and pData.talents or {}
|
||||||
|
if self:IsAvailabilitySpell(spellEntry) then
|
||||||
|
self:LogTrackedSpellCast(name, spellEntry, {
|
||||||
|
stateKind = "availability",
|
||||||
|
required = HMGT_SpellData.GetEffectiveAvailabilityRequired(spellEntry, talents),
|
||||||
|
})
|
||||||
|
if self:RefreshOwnAvailabilitySpell(spellEntry) then
|
||||||
|
self:PublishOwnSpellState(spellId, { sendLegacy = true })
|
||||||
|
end
|
||||||
|
self:TriggerTrackerUpdate()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||||
|
local now = GetTime()
|
||||||
|
local inCombat = InCombatLockdown and InCombatLockdown()
|
||||||
|
local cur, max, chargeStart, chargeDuration = nil, nil, nil, nil
|
||||||
|
if not inCombat and GetSpellChargesInfo then
|
||||||
|
cur, max, chargeStart, chargeDuration = GetSpellChargesInfo(spellId)
|
||||||
|
end
|
||||||
|
local cachedMaxCharges, cachedChargeDuration = self:GetKnownChargeInfo(
|
||||||
|
spellEntry,
|
||||||
|
talents,
|
||||||
|
spellId,
|
||||||
|
(not inCombat and tonumber(chargeDuration) and tonumber(chargeDuration) > 0) and tonumber(chargeDuration) or effectiveCd
|
||||||
|
)
|
||||||
|
local inferredMaxCharges, inferredChargeDuration = HMGT_SpellData.GetEffectiveChargeInfo(
|
||||||
|
spellEntry,
|
||||||
|
talents,
|
||||||
|
(not inCombat and tonumber(max) and tonumber(max) > 0) and tonumber(max) or ((cachedMaxCharges > 0) and cachedMaxCharges or nil),
|
||||||
|
(not inCombat and tonumber(chargeDuration) and tonumber(chargeDuration) > 0) and tonumber(chargeDuration)
|
||||||
|
or ((cachedChargeDuration > 0) and cachedChargeDuration or effectiveCd)
|
||||||
|
)
|
||||||
|
|
||||||
|
local hasCharges = ((tonumber(max) or 0) > 1) or (tonumber(inferredMaxCharges) or 0) > 1
|
||||||
|
local currentCharges = 0
|
||||||
|
local maxCharges = 0
|
||||||
|
local chargeDur = 0
|
||||||
|
local chargeStartTime = nil
|
||||||
|
|
||||||
|
local startTime = now
|
||||||
|
local duration = effectiveCd
|
||||||
|
local expiresIn = effectiveCd
|
||||||
|
|
||||||
|
local existingCd = self:GetActiveCooldown(name, spellId)
|
||||||
|
if existingCd and (tonumber(existingCd.maxCharges) or 0) > 0 then
|
||||||
|
self:ResolveChargeState(existingCd, now)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasCharges then
|
||||||
|
maxCharges = math.max(1, tonumber(max) or cachedMaxCharges or tonumber(inferredMaxCharges) or 1)
|
||||||
|
currentCharges = tonumber(cur)
|
||||||
|
if currentCharges == nil then
|
||||||
|
local prevCharges = existingCd and tonumber(existingCd.currentCharges)
|
||||||
|
local prevMax = existingCd and tonumber(existingCd.maxCharges)
|
||||||
|
if prevCharges and prevMax and prevMax == maxCharges then
|
||||||
|
currentCharges = math.max(0, prevCharges - 1)
|
||||||
|
else
|
||||||
|
currentCharges = math.max(0, maxCharges - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
currentCharges = math.max(0, math.min(maxCharges, currentCharges))
|
||||||
|
|
||||||
|
chargeDur = tonumber(chargeDuration)
|
||||||
|
or cachedChargeDuration
|
||||||
|
or tonumber(inferredChargeDuration)
|
||||||
|
or tonumber(effectiveCd)
|
||||||
|
or 0
|
||||||
|
chargeDur = math.max(0, chargeDur)
|
||||||
|
self:StoreKnownChargeInfo(spellId, maxCharges, chargeDur)
|
||||||
|
|
||||||
|
if currentCharges < maxCharges and chargeDur > 0 then
|
||||||
|
chargeStartTime = tonumber(chargeStart) or now
|
||||||
|
local missing = maxCharges - currentCharges
|
||||||
|
startTime = chargeStartTime
|
||||||
|
duration = missing * chargeDur
|
||||||
|
expiresIn = math.max(0, duration - (now - startTime))
|
||||||
|
else
|
||||||
|
startTime = now
|
||||||
|
duration = 0
|
||||||
|
expiresIn = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:Debug(
|
||||||
|
"verbose",
|
||||||
|
"HandleOwnSpellCast name=%s spellId=%s cd=%.2f charges=%s/%s",
|
||||||
|
tostring(name),
|
||||||
|
tostring(spellId),
|
||||||
|
tonumber(effectiveCd) or 0,
|
||||||
|
hasCharges and tostring(currentCharges) or "-",
|
||||||
|
hasCharges and tostring(maxCharges) or "-"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._cdNonce = (self._cdNonce or 0) + 1
|
||||||
|
local nonce = self._cdNonce
|
||||||
|
|
||||||
|
self:SetActiveCooldown(name, spellId, {
|
||||||
|
startTime = startTime,
|
||||||
|
duration = duration,
|
||||||
|
spellEntry = spellEntry,
|
||||||
|
currentCharges = hasCharges and currentCharges or nil,
|
||||||
|
maxCharges = hasCharges and maxCharges or nil,
|
||||||
|
chargeStart = hasCharges and chargeStartTime or nil,
|
||||||
|
chargeDuration = hasCharges and chargeDur or nil,
|
||||||
|
_nonce = nonce,
|
||||||
|
})
|
||||||
|
|
||||||
|
self:LogTrackedSpellCast(name, spellEntry, {
|
||||||
|
cooldown = effectiveCd,
|
||||||
|
currentCharges = hasCharges and currentCharges or nil,
|
||||||
|
maxCharges = hasCharges and maxCharges or nil,
|
||||||
|
chargeCooldown = hasCharges and chargeDur or nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
if expiresIn > 0 then
|
||||||
|
self:ScheduleTimer(function()
|
||||||
|
local current = self:GetActiveCooldown(name, spellId)
|
||||||
|
if current and current._nonce == nonce then
|
||||||
|
self:ClearActiveCooldown(name, spellId)
|
||||||
|
self:PublishOwnSpellState(spellId)
|
||||||
|
self:TriggerTrackerUpdate()
|
||||||
|
end
|
||||||
|
end, expiresIn)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:PublishOwnSpellState(spellId, { sendLegacy = true })
|
||||||
|
self:TriggerTrackerUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:RefreshOwnCooldownStateFromGame(spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not sid then return false end
|
||||||
|
if InCombatLockdown and InCombatLockdown() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
if not ownName then return false end
|
||||||
|
|
||||||
|
local spellEntry = HMGT_SpellData.InterruptLookup[sid]
|
||||||
|
or HMGT_SpellData.CooldownLookup[sid]
|
||||||
|
if not spellEntry or self:IsAvailabilitySpell(spellEntry) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
sid = tonumber(spellEntry.spellId) or sid
|
||||||
|
|
||||||
|
local existing = self:GetActiveCooldown(ownName, sid)
|
||||||
|
local before = BuildCooldownStateFingerprint and BuildCooldownStateFingerprint(existing) or "nil"
|
||||||
|
local now = GetTime()
|
||||||
|
local pData = self.playerData[ownName]
|
||||||
|
local talents = pData and pData.talents or {}
|
||||||
|
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||||
|
local cur, max, chargeStart, chargeDuration = nil, nil, nil, nil
|
||||||
|
if GetSpellChargesInfo then
|
||||||
|
cur, max, chargeStart, chargeDuration = GetSpellChargesInfo(sid)
|
||||||
|
end
|
||||||
|
local inferredMaxCharges, inferredChargeDuration = HMGT_SpellData.GetEffectiveChargeInfo(
|
||||||
|
spellEntry,
|
||||||
|
talents,
|
||||||
|
(tonumber(max) and tonumber(max) > 0) and tonumber(max) or nil,
|
||||||
|
(tonumber(chargeDuration) and tonumber(chargeDuration) > 0) and tonumber(chargeDuration) or effectiveCd
|
||||||
|
)
|
||||||
|
|
||||||
|
local hasCharges = ((tonumber(max) or 0) > 1) or (tonumber(inferredMaxCharges) or 0) > 1
|
||||||
|
|
||||||
|
if hasCharges then
|
||||||
|
local maxCharges = math.max(1, tonumber(max) or tonumber(inferredMaxCharges) or 1)
|
||||||
|
local currentCharges = tonumber(cur)
|
||||||
|
if currentCharges == nil then
|
||||||
|
currentCharges = maxCharges
|
||||||
|
end
|
||||||
|
currentCharges = math.max(0, math.min(maxCharges, currentCharges))
|
||||||
|
|
||||||
|
local chargeDur = tonumber(chargeDuration) or tonumber(inferredChargeDuration) or tonumber(effectiveCd) or 0
|
||||||
|
chargeDur = math.max(0, chargeDur)
|
||||||
|
|
||||||
|
if currentCharges < maxCharges and chargeDur > 0 then
|
||||||
|
local chargeStartTime = tonumber(chargeStart) or now
|
||||||
|
local missing = maxCharges - currentCharges
|
||||||
|
local updatedEntry = self:SetActiveCooldown(ownName, sid, {
|
||||||
|
startTime = chargeStartTime,
|
||||||
|
duration = missing * chargeDur,
|
||||||
|
spellEntry = spellEntry,
|
||||||
|
currentCharges = currentCharges,
|
||||||
|
maxCharges = maxCharges,
|
||||||
|
chargeStart = chargeStartTime,
|
||||||
|
chargeDuration = chargeDur,
|
||||||
|
})
|
||||||
|
self:RefreshCooldownExpiryTimer(ownName, sid, updatedEntry)
|
||||||
|
else
|
||||||
|
self:ClearActiveCooldown(ownName, sid)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local cooldownStart, cooldownDuration = 0, 0
|
||||||
|
if GetSpellCooldownInfo then
|
||||||
|
cooldownStart, cooldownDuration = GetSpellCooldownInfo(sid)
|
||||||
|
end
|
||||||
|
cooldownStart = tonumber(cooldownStart) or 0
|
||||||
|
cooldownDuration = tonumber(cooldownDuration) or 0
|
||||||
|
local gcdStart, gcdDuration = 0, 0
|
||||||
|
if GetGlobalCooldownInfo then
|
||||||
|
gcdStart, gcdDuration = GetGlobalCooldownInfo()
|
||||||
|
end
|
||||||
|
gcdStart = tonumber(gcdStart) or 0
|
||||||
|
gcdDuration = tonumber(gcdDuration) or 0
|
||||||
|
local existingDuration = tonumber(existing and existing.duration) or 0
|
||||||
|
local existingStart = tonumber(existing and existing.startTime) or now
|
||||||
|
local existingRemaining = math.max(0, existingDuration - (now - existingStart))
|
||||||
|
|
||||||
|
local isLikelyGlobalCooldown = cooldownDuration > 0
|
||||||
|
and gcdDuration > 0
|
||||||
|
and math.abs(cooldownDuration - gcdDuration) <= 0.15
|
||||||
|
and (tonumber(effectiveCd) or 0) > (gcdDuration + 1.0)
|
||||||
|
|
||||||
|
local isSuspiciousShortRefresh = cooldownDuration > 0
|
||||||
|
and existingRemaining > 2.0
|
||||||
|
and existingDuration > 2.0
|
||||||
|
and cooldownDuration < math.max(2.0, existingDuration * 0.35)
|
||||||
|
and cooldownDuration < math.max(2.0, (tonumber(effectiveCd) or 0) * 0.35)
|
||||||
|
|
||||||
|
if isLikelyGlobalCooldown or isSuspiciousShortRefresh then
|
||||||
|
self:DebugScoped(
|
||||||
|
"verbose",
|
||||||
|
"TrackerState",
|
||||||
|
"Ignore suspicious refresh for %s: spellCD=%.3f gcd=%.3f existing=%.3f remaining=%.3f effective=%.3f",
|
||||||
|
GetSpellDebugLabel and GetSpellDebugLabel(sid) or tostring(sid),
|
||||||
|
cooldownDuration,
|
||||||
|
gcdDuration,
|
||||||
|
existingDuration,
|
||||||
|
existingRemaining,
|
||||||
|
tonumber(effectiveCd) or 0
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if cooldownDuration > 0 then
|
||||||
|
local updatedEntry = self:SetActiveCooldown(ownName, sid, {
|
||||||
|
startTime = cooldownStart,
|
||||||
|
duration = cooldownDuration,
|
||||||
|
spellEntry = spellEntry,
|
||||||
|
})
|
||||||
|
self:RefreshCooldownExpiryTimer(ownName, sid, updatedEntry)
|
||||||
|
else
|
||||||
|
self:ClearActiveCooldown(ownName, sid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local after = BuildCooldownStateFingerprint and BuildCooldownStateFingerprint(self:GetActiveCooldown(ownName, sid)) or "nil"
|
||||||
|
return before ~= after
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:DidOwnInterruptSucceed(triggerSpellId, talents)
|
||||||
|
local sid = tonumber(triggerSpellId)
|
||||||
|
if not sid then return false end
|
||||||
|
|
||||||
|
local spellEntry = HMGT_SpellData and HMGT_SpellData.InterruptLookup and HMGT_SpellData.InterruptLookup[sid]
|
||||||
|
if not spellEntry then return false end
|
||||||
|
sid = tonumber(spellEntry.spellId) or sid
|
||||||
|
|
||||||
|
local observedDuration = 0
|
||||||
|
if GetSpellCooldownInfo then
|
||||||
|
local _, duration = GetSpellCooldownInfo(sid)
|
||||||
|
observedDuration = duration
|
||||||
|
end
|
||||||
|
observedDuration = tonumber(observedDuration) or 0
|
||||||
|
if observedDuration <= 0 then return false end
|
||||||
|
|
||||||
|
local expectedDuration = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||||
|
expectedDuration = tonumber(expectedDuration) or 0
|
||||||
|
if expectedDuration <= 0 then return false end
|
||||||
|
|
||||||
|
return observedDuration < (expectedDuration - 0.05)
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:HandleOwnCooldownReductionTrigger(triggerSpellId)
|
||||||
|
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
if not ownName then return end
|
||||||
|
|
||||||
|
local pData = self.playerData[ownName]
|
||||||
|
local classToken = pData and pData.class or select(2, UnitClass("player"))
|
||||||
|
local specIndex = pData and pData.specIndex or GetSpecialization()
|
||||||
|
local talents = pData and pData.talents or {}
|
||||||
|
if not classToken or not specIndex then return end
|
||||||
|
|
||||||
|
local reducers = HMGT_SpellData.GetCooldownReducersForCast(classToken, specIndex, triggerSpellId, talents)
|
||||||
|
if not reducers or #reducers == 0 then return end
|
||||||
|
|
||||||
|
local instantReducers = {}
|
||||||
|
local observedInstantReducers = {}
|
||||||
|
local successReducers = {}
|
||||||
|
local observedSuccessReducers = {}
|
||||||
|
for _, reducer in ipairs(reducers) do
|
||||||
|
local observed = type(reducer.observe) == "table"
|
||||||
|
if reducer.requireInterruptSuccess then
|
||||||
|
if observed then
|
||||||
|
observedSuccessReducers[#observedSuccessReducers + 1] = reducer
|
||||||
|
else
|
||||||
|
successReducers[#successReducers + 1] = reducer
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if observed then
|
||||||
|
observedInstantReducers[#observedInstantReducers + 1] = reducer
|
||||||
|
else
|
||||||
|
instantReducers[#instantReducers + 1] = reducer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local castTs = GetServerTime()
|
||||||
|
if #instantReducers > 0 and ApplyOwnCooldownReducers then
|
||||||
|
ApplyOwnCooldownReducers(self, ownName, triggerSpellId, instantReducers, castTs)
|
||||||
|
end
|
||||||
|
if #observedInstantReducers > 0 and ApplyObservedCooldownReducers then
|
||||||
|
ApplyObservedCooldownReducers(self, ownName, observedInstantReducers)
|
||||||
|
end
|
||||||
|
|
||||||
|
if #successReducers > 0 or #observedSuccessReducers > 0 then
|
||||||
|
local function ApplySuccessReducers()
|
||||||
|
if not self:DidOwnInterruptSucceed(triggerSpellId, talents) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if #successReducers > 0 and ApplyOwnCooldownReducers then
|
||||||
|
ApplyOwnCooldownReducers(self, ownName, triggerSpellId, successReducers, castTs)
|
||||||
|
end
|
||||||
|
if #observedSuccessReducers > 0 and ApplyObservedCooldownReducers then
|
||||||
|
ApplyObservedCooldownReducers(self, ownName, observedSuccessReducers)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ApplySuccessReducers() then
|
||||||
|
C_Timer.After(0.12, function()
|
||||||
|
if not self or not self.playerData or not self.playerData[ownName] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
ApplySuccessReducers()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:HandleRemoteSpellCast(playerName, spellId, castTimestamp, curCharges, maxCharges, chargeRemaining, chargeDuration)
|
||||||
|
local spellEntry = HMGT_SpellData.InterruptLookup[spellId]
|
||||||
|
or HMGT_SpellData.CooldownLookup[spellId]
|
||||||
|
if not spellEntry then return end
|
||||||
|
spellId = tonumber(spellEntry.spellId) or spellId
|
||||||
|
if self:IsAvailabilitySpell(spellEntry) then return end
|
||||||
|
|
||||||
|
local pData = self.playerData[playerName]
|
||||||
|
local talents = pData and pData.talents or {}
|
||||||
|
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
||||||
|
|
||||||
|
castTimestamp = tonumber(castTimestamp) or GetServerTime()
|
||||||
|
local existingEntry = self:GetActiveCooldown(playerName, spellId)
|
||||||
|
if (tonumber(maxCharges) or 0) <= 0 and existingEntry and existingEntry.lastCastTimestamp then
|
||||||
|
local prevTs = tonumber(existingEntry.lastCastTimestamp) or 0
|
||||||
|
if math.abs(prevTs - castTimestamp) <= 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local now = GetTime()
|
||||||
|
local elapsed = math.max(0, GetServerTime() - castTimestamp)
|
||||||
|
|
||||||
|
local incomingCur = tonumber(curCharges) or 0
|
||||||
|
local incomingMax = tonumber(maxCharges) or 0
|
||||||
|
local incomingChargeRemaining = tonumber(chargeRemaining) or 0
|
||||||
|
local incomingChargeDuration = tonumber(chargeDuration) or 0
|
||||||
|
|
||||||
|
local inferredMaxCharges, inferredChargeDuration = HMGT_SpellData.GetEffectiveChargeInfo(
|
||||||
|
spellEntry,
|
||||||
|
talents,
|
||||||
|
(incomingMax > 0) and incomingMax or nil,
|
||||||
|
(incomingChargeDuration > 0) and incomingChargeDuration or effectiveCd
|
||||||
|
)
|
||||||
|
local hasCharges = (incomingMax > 1) or (tonumber(inferredMaxCharges) or 0) > 1
|
||||||
|
|
||||||
|
local currentCharges = 0
|
||||||
|
local maxChargeCount = 0
|
||||||
|
local chargeDur = 0
|
||||||
|
local nextChargeRemaining = 0
|
||||||
|
local chargeStartTime = nil
|
||||||
|
local startTime, duration, expiresIn
|
||||||
|
|
||||||
|
if hasCharges then
|
||||||
|
maxChargeCount = math.max(1, (incomingMax > 0 and incomingMax) or tonumber(inferredMaxCharges) or 1)
|
||||||
|
chargeDur = tonumber(incomingChargeDuration) or tonumber(inferredChargeDuration) or tonumber(effectiveCd) or 0
|
||||||
|
chargeDur = math.max(0, chargeDur)
|
||||||
|
if chargeDur <= 0 then
|
||||||
|
chargeDur = math.max(0, tonumber(effectiveCd) or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if incomingMax > 0 then
|
||||||
|
currentCharges = math.max(0, math.min(maxChargeCount, incomingCur))
|
||||||
|
nextChargeRemaining = math.max(0, math.min(chargeDur, incomingChargeRemaining - elapsed))
|
||||||
|
if currentCharges < maxChargeCount and chargeDur > 0 then
|
||||||
|
chargeStartTime = now - math.max(0, chargeDur - nextChargeRemaining)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local existing = self:GetActiveCooldown(playerName, spellId)
|
||||||
|
if existing and (tonumber(existing.maxCharges) or 0) == maxChargeCount then
|
||||||
|
self:ResolveChargeState(existing, now)
|
||||||
|
local prevCharges = tonumber(existing.currentCharges) or maxChargeCount
|
||||||
|
local prevStart = tonumber(existing.chargeStart)
|
||||||
|
local prevDur = tonumber(existing.chargeDuration) or chargeDur
|
||||||
|
if prevDur > 0 then
|
||||||
|
chargeDur = prevDur
|
||||||
|
end
|
||||||
|
|
||||||
|
currentCharges = math.max(0, prevCharges - 1)
|
||||||
|
if currentCharges < maxChargeCount and chargeDur > 0 then
|
||||||
|
if prevCharges >= maxChargeCount then
|
||||||
|
chargeStartTime = now
|
||||||
|
else
|
||||||
|
chargeStartTime = prevStart or now
|
||||||
|
end
|
||||||
|
nextChargeRemaining = math.max(0, chargeDur - (now - chargeStartTime))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
currentCharges = math.max(0, maxChargeCount - 1)
|
||||||
|
if currentCharges < maxChargeCount and chargeDur > 0 then
|
||||||
|
chargeStartTime = now
|
||||||
|
nextChargeRemaining = chargeDur
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentCharges >= maxChargeCount and maxChargeCount > 0 then
|
||||||
|
currentCharges = math.max(0, maxChargeCount - 1)
|
||||||
|
if chargeDur > 0 then
|
||||||
|
chargeStartTime = now
|
||||||
|
nextChargeRemaining = chargeDur
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentCharges < maxChargeCount and chargeDur > 0 then
|
||||||
|
chargeStartTime = chargeStartTime or now
|
||||||
|
local missing = maxChargeCount - currentCharges
|
||||||
|
startTime = chargeStartTime
|
||||||
|
duration = missing * chargeDur
|
||||||
|
expiresIn = math.max(0, duration - (now - startTime))
|
||||||
|
else
|
||||||
|
startTime = now
|
||||||
|
duration = 0
|
||||||
|
expiresIn = 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local remaining = effectiveCd - elapsed
|
||||||
|
if remaining <= 0 then return end
|
||||||
|
startTime = now - elapsed
|
||||||
|
duration = effectiveCd
|
||||||
|
expiresIn = remaining
|
||||||
|
end
|
||||||
|
|
||||||
|
self:Debug(
|
||||||
|
"verbose",
|
||||||
|
"HandleRemoteSpellCast name=%s spellId=%s elapsed=%.2f expiresIn=%.2f charges=%s/%s",
|
||||||
|
tostring(playerName),
|
||||||
|
tostring(spellId),
|
||||||
|
tonumber(elapsed) or 0,
|
||||||
|
tonumber(expiresIn) or 0,
|
||||||
|
hasCharges and tostring(currentCharges) or "-",
|
||||||
|
hasCharges and tostring(maxChargeCount) or "-"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._cdNonce = (self._cdNonce or 0) + 1
|
||||||
|
local nonce = self._cdNonce
|
||||||
|
|
||||||
|
self:SetActiveCooldown(playerName, spellId, {
|
||||||
|
startTime = startTime,
|
||||||
|
duration = duration,
|
||||||
|
spellEntry = spellEntry,
|
||||||
|
currentCharges = hasCharges and currentCharges or nil,
|
||||||
|
maxCharges = hasCharges and maxChargeCount or nil,
|
||||||
|
chargeStart = hasCharges and chargeStartTime or nil,
|
||||||
|
chargeDuration = hasCharges and chargeDur or nil,
|
||||||
|
lastCastTimestamp = castTimestamp,
|
||||||
|
_nonce = nonce,
|
||||||
|
})
|
||||||
|
|
||||||
|
self:LogTrackedSpellCast(playerName, spellEntry, {
|
||||||
|
cooldown = effectiveCd,
|
||||||
|
currentCharges = hasCharges and currentCharges or nil,
|
||||||
|
maxCharges = hasCharges and maxChargeCount or nil,
|
||||||
|
chargeCooldown = hasCharges and chargeDur or nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
if expiresIn > 0 then
|
||||||
|
self:ScheduleTimer(function()
|
||||||
|
local current = self:GetActiveCooldown(playerName, spellId)
|
||||||
|
if current and current._nonce == nonce then
|
||||||
|
self:ClearActiveCooldown(playerName, spellId)
|
||||||
|
self:TriggerTrackerUpdate()
|
||||||
|
end
|
||||||
|
end, expiresIn)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:TriggerTrackerUpdate()
|
||||||
|
end
|
||||||
@@ -94,25 +94,6 @@ local PARTY_FRAME_PATTERNS = {
|
|||||||
|
|
||||||
local unitFrameCache = {}
|
local unitFrameCache = {}
|
||||||
|
|
||||||
local function EntryNeedsVisualTicker(entry)
|
|
||||||
if type(entry) ~= "table" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local remaining = tonumber(entry.remaining) or 0
|
|
||||||
if remaining > 0 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local maxCharges = tonumber(entry.maxCharges) or 0
|
|
||||||
local currentCharges = tonumber(entry.currentCharges)
|
|
||||||
if maxCharges > 0 and currentCharges ~= nil and currentCharges < maxCharges then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function BuildAnchorLayoutSignature(settings, ordered, unitByPlayer)
|
local function BuildAnchorLayoutSignature(settings, ordered, unitByPlayer)
|
||||||
local parts = {
|
local parts = {
|
||||||
settings.attachToPartyFrame == true and "attach" or "stack",
|
settings.attachToPartyFrame == true and "attach" or "stack",
|
||||||
@@ -222,55 +203,6 @@ local function ResolveUnitAnchorFrame(unitId)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function GetGroupPlayers(tracker)
|
|
||||||
local players = {}
|
|
||||||
|
|
||||||
local ownName = HMGT:NormalizePlayerName(UnitName("player"))
|
|
||||||
local ownClass = select(2, UnitClass("player"))
|
|
||||||
local includeOwnPlayer = true
|
|
||||||
if IsGroupTracker(tracker) then
|
|
||||||
includeOwnPlayer = tracker.includeSelfFrame == true
|
|
||||||
end
|
|
||||||
if includeOwnPlayer then
|
|
||||||
players[#players + 1] = {
|
|
||||||
name = ownName,
|
|
||||||
class = ownClass,
|
|
||||||
isOwn = true,
|
|
||||||
unitId = "player",
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if IsInRaid() then
|
|
||||||
for i = 1, GetNumGroupMembers() do
|
|
||||||
local unitId = "raid" .. i
|
|
||||||
local name = HMGT:NormalizePlayerName(UnitName(unitId))
|
|
||||||
local class = select(2, UnitClass(unitId))
|
|
||||||
if name and name ~= ownName then
|
|
||||||
players[#players + 1] = {
|
|
||||||
name = name,
|
|
||||||
class = class,
|
|
||||||
unitId = unitId,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif IsInGroup() then
|
|
||||||
for i = 1, GetNumGroupMembers() - 1 do
|
|
||||||
local unitId = "party" .. i
|
|
||||||
local name = HMGT:NormalizePlayerName(UnitName(unitId))
|
|
||||||
local class = select(2, UnitClass(unitId))
|
|
||||||
if name and name ~= ownName then
|
|
||||||
players[#players + 1] = {
|
|
||||||
name = name,
|
|
||||||
class = class,
|
|
||||||
unitId = unitId,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return players
|
|
||||||
end
|
|
||||||
|
|
||||||
local function GetTrackerLabel(tracker)
|
local function GetTrackerLabel(tracker)
|
||||||
if type(tracker) ~= "table" then
|
if type(tracker) ~= "table" then
|
||||||
return "Tracker"
|
return "Tracker"
|
||||||
@@ -287,136 +219,6 @@ local function GetTrackerLabel(tracker)
|
|||||||
return "Tracker"
|
return "Tracker"
|
||||||
end
|
end
|
||||||
|
|
||||||
local function GetTrackerSpellPool(categories)
|
|
||||||
if HMGT_SpellData and type(HMGT_SpellData.GetSpellPoolForCategories) == "function" then
|
|
||||||
return HMGT_SpellData.GetSpellPoolForCategories(categories)
|
|
||||||
end
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function GetTrackerSpellsForPlayer(classToken, specIndex, categories)
|
|
||||||
if HMGT_SpellData and type(HMGT_SpellData.GetSpellsForCategories) == "function" then
|
|
||||||
return HMGT_SpellData.GetSpellsForCategories(classToken, specIndex, categories)
|
|
||||||
end
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function CollectEntriesForPlayer(tracker, playerInfo)
|
|
||||||
local entries = {}
|
|
||||||
if type(tracker) ~= "table" or type(playerInfo) ~= "table" then
|
|
||||||
return entries
|
|
||||||
end
|
|
||||||
|
|
||||||
local playerName = playerInfo.name
|
|
||||||
if not playerName then
|
|
||||||
return entries
|
|
||||||
end
|
|
||||||
|
|
||||||
local pData = HMGT.playerData[playerName]
|
|
||||||
local classToken = pData and pData.class or playerInfo.class
|
|
||||||
if not classToken then
|
|
||||||
return entries
|
|
||||||
end
|
|
||||||
|
|
||||||
local specIndex
|
|
||||||
if playerInfo.isOwn then
|
|
||||||
specIndex = GetSpecialization()
|
|
||||||
if not specIndex or specIndex == 0 then
|
|
||||||
return entries
|
|
||||||
end
|
|
||||||
else
|
|
||||||
specIndex = pData and pData.specIndex or nil
|
|
||||||
if not specIndex or tonumber(specIndex) <= 0 then
|
|
||||||
return entries
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local talents = pData and pData.talents or {}
|
|
||||||
local spells = GetTrackerSpellsForPlayer(classToken, specIndex, tracker.categories)
|
|
||||||
for _, spellEntry in ipairs(spells) do
|
|
||||||
if tracker.enabledSpells[spellEntry.spellId] ~= false then
|
|
||||||
local remaining, total, currentCharges, maxCharges = HMGT:GetCooldownInfo(playerName, spellEntry.spellId)
|
|
||||||
local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents)
|
|
||||||
local isAvailabilitySpell = HMGT:IsAvailabilitySpell(spellEntry)
|
|
||||||
local include = HMGT:ShouldDisplayEntry(tracker, remaining, currentCharges, maxCharges, spellEntry)
|
|
||||||
local spellKnown = HMGT:IsTrackedSpellKnownForPlayer(playerName, spellEntry.spellId)
|
|
||||||
local hasPartialCharges = (tonumber(maxCharges) or 0) > 0
|
|
||||||
and (tonumber(currentCharges) or tonumber(maxCharges) or 0) < (tonumber(maxCharges) or 0)
|
|
||||||
local hasActiveCd = ((remaining or 0) > 0) or hasPartialCharges
|
|
||||||
|
|
||||||
if not spellKnown and not hasActiveCd then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
if isAvailabilitySpell and not spellKnown then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
if not playerInfo.isOwn and isAvailabilitySpell and not HMGT:HasAvailabilityState(playerName, spellEntry.spellId) then
|
|
||||||
include = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if include then
|
|
||||||
entries[#entries + 1] = {
|
|
||||||
playerName = playerName,
|
|
||||||
class = classToken,
|
|
||||||
spellEntry = spellEntry,
|
|
||||||
remaining = remaining,
|
|
||||||
total = total > 0 and total or effectiveCd,
|
|
||||||
currentCharges = currentCharges,
|
|
||||||
maxCharges = maxCharges,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return entries
|
|
||||||
end
|
|
||||||
|
|
||||||
local function CopyEntriesForPreview(entries, playerName)
|
|
||||||
local copies = {}
|
|
||||||
for _, entry in ipairs(entries or {}) do
|
|
||||||
local nextEntry = {}
|
|
||||||
for key, value in pairs(entry) do
|
|
||||||
nextEntry[key] = value
|
|
||||||
end
|
|
||||||
nextEntry.playerName = playerName
|
|
||||||
copies[#copies + 1] = nextEntry
|
|
||||||
end
|
|
||||||
return copies
|
|
||||||
end
|
|
||||||
|
|
||||||
local function GetAvailablePartyPreviewUnits()
|
|
||||||
local units = {}
|
|
||||||
for index = 1, 4 do
|
|
||||||
local unitId = "party" .. index
|
|
||||||
if ResolveUnitAnchorFrame(unitId) then
|
|
||||||
units[#units + 1] = {
|
|
||||||
playerName = string.format("Party %d", index),
|
|
||||||
unitId = unitId,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return units
|
|
||||||
end
|
|
||||||
|
|
||||||
local function BuildPartyPreviewEntries(entries)
|
|
||||||
local byPlayer = {}
|
|
||||||
local order = {}
|
|
||||||
local unitByPlayer = {}
|
|
||||||
local previewUnits = GetAvailablePartyPreviewUnits()
|
|
||||||
|
|
||||||
for _, previewUnit in ipairs(previewUnits) do
|
|
||||||
local playerName = previewUnit.playerName
|
|
||||||
local playerEntries = CopyEntriesForPreview(entries, playerName)
|
|
||||||
if #playerEntries > 0 then
|
|
||||||
byPlayer[playerName] = playerEntries
|
|
||||||
order[#order + 1] = playerName
|
|
||||||
unitByPlayer[playerName] = previewUnit.unitId
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return byPlayer, order, unitByPlayer, #order > 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local function SortTrackers(trackers)
|
local function SortTrackers(trackers)
|
||||||
table.sort(trackers, function(a, b)
|
table.sort(trackers, function(a, b)
|
||||||
local aId = tonumber(a and a.id) or 0
|
local aId = tonumber(a and a.id) or 0
|
||||||
@@ -467,13 +269,7 @@ Manager._shared.ShortName = ShortName
|
|||||||
Manager._shared.BuildAnchorLayoutSignature = BuildAnchorLayoutSignature
|
Manager._shared.BuildAnchorLayoutSignature = BuildAnchorLayoutSignature
|
||||||
Manager._shared.IsGroupTracker = IsGroupTracker
|
Manager._shared.IsGroupTracker = IsGroupTracker
|
||||||
Manager._shared.ResolveUnitAnchorFrame = ResolveUnitAnchorFrame
|
Manager._shared.ResolveUnitAnchorFrame = ResolveUnitAnchorFrame
|
||||||
Manager._shared.GetGroupPlayers = GetGroupPlayers
|
|
||||||
Manager._shared.GetTrackerLabel = GetTrackerLabel
|
Manager._shared.GetTrackerLabel = GetTrackerLabel
|
||||||
Manager._shared.GetTrackerSpellPool = GetTrackerSpellPool
|
|
||||||
Manager._shared.GetTrackerSpellsForPlayer = GetTrackerSpellsForPlayer
|
|
||||||
Manager._shared.CollectEntriesForPlayer = CollectEntriesForPlayer
|
|
||||||
Manager._shared.BuildPartyPreviewEntries = BuildPartyPreviewEntries
|
|
||||||
Manager._shared.EntryNeedsVisualTicker = EntryNeedsVisualTicker
|
|
||||||
Manager._shared.BuildGroupDisplaySignature = BuildGroupDisplaySignature
|
Manager._shared.BuildGroupDisplaySignature = BuildGroupDisplaySignature
|
||||||
|
|
||||||
function Manager:GetTrackers()
|
function Manager:GetTrackers()
|
||||||
@@ -492,6 +288,13 @@ function Manager:GetTrackers()
|
|||||||
return self._trackerCache
|
return self._trackerCache
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Manager:GetTrackerFrameKey(tracker)
|
||||||
|
if type(tracker) == "table" then
|
||||||
|
return GetTrackerFrameKey(tracker.id)
|
||||||
|
end
|
||||||
|
return GetTrackerFrameKey(tracker)
|
||||||
|
end
|
||||||
|
|
||||||
function Manager:MarkTrackersDirty()
|
function Manager:MarkTrackersDirty()
|
||||||
self._trackerCache = nil
|
self._trackerCache = nil
|
||||||
self._trackerCacheSignature = nil
|
self._trackerCacheSignature = nil
|
||||||
@@ -648,12 +451,8 @@ function Manager:RefreshVisibleVisuals()
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
local entries = byPlayer[playerName] or {}
|
local entries = byPlayer[playerName] or {}
|
||||||
if HMGT.FilterDisplayEntries then
|
local tickThis = false
|
||||||
entries = HMGT:FilterDisplayEntries(tracker, entries) or entries
|
entries, tickThis = HMGT:FinalizeTrackerEntries(tracker, entries, tracker.trackerKey or nil)
|
||||||
end
|
|
||||||
if HMGT.SortDisplayEntries then
|
|
||||||
HMGT:SortDisplayEntries(entries)
|
|
||||||
end
|
|
||||||
if #entries == 0 then
|
if #entries == 0 then
|
||||||
needsFullRefresh = true
|
needsFullRefresh = true
|
||||||
break
|
break
|
||||||
@@ -666,11 +465,8 @@ function Manager:RefreshVisibleVisuals()
|
|||||||
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
||||||
totalEntries = totalEntries + #entries
|
totalEntries = totalEntries + #entries
|
||||||
byPlayerFiltered[playerName] = entries
|
byPlayerFiltered[playerName] = entries
|
||||||
for _, entry in ipairs(entries) do
|
if tickThis then
|
||||||
if EntryNeedsVisualTicker(entry) then
|
shouldTick = true
|
||||||
shouldTick = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local newSignature = BuildGroupDisplaySignature(currentOrder, byPlayerFiltered)
|
local newSignature = BuildGroupDisplaySignature(currentOrder, byPlayerFiltered)
|
||||||
@@ -680,36 +476,29 @@ function Manager:RefreshVisibleVisuals()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local frame = self.frames[frameKey]
|
local frame = self.frames[frameKey]
|
||||||
if frame and frame:IsShown() then
|
if frame and frame:IsShown() then
|
||||||
local entries, shouldShow = self:BuildEntriesForTracker(tracker)
|
local entries, shouldShow = self:BuildEntriesForTracker(tracker)
|
||||||
if not shouldShow then
|
if not shouldShow then
|
||||||
needsFullRefresh = true
|
|
||||||
else
|
|
||||||
if HMGT.FilterDisplayEntries then
|
|
||||||
entries = HMGT:FilterDisplayEntries(tracker, entries) or entries
|
|
||||||
end
|
|
||||||
if HMGT.SortDisplayEntries then
|
|
||||||
HMGT:SortDisplayEntries(entries)
|
|
||||||
end
|
|
||||||
if #entries == 0 then
|
|
||||||
needsFullRefresh = true
|
needsFullRefresh = true
|
||||||
else
|
else
|
||||||
|
local tickThis = false
|
||||||
|
entries, tickThis = HMGT:FinalizeTrackerEntries(tracker, entries, tracker.trackerKey or nil)
|
||||||
|
if #entries == 0 then
|
||||||
|
needsFullRefresh = true
|
||||||
|
else
|
||||||
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
||||||
totalEntries = totalEntries + #entries
|
totalEntries = totalEntries + #entries
|
||||||
local newSignature = BuildNormalDisplaySignature(true, entries)
|
local newSignature = BuildNormalDisplaySignature(true, entries)
|
||||||
if self._displaySignatures[frameKey] ~= newSignature then
|
if self._displaySignatures[frameKey] ~= newSignature then
|
||||||
needsFullRefresh = true
|
needsFullRefresh = true
|
||||||
end
|
end
|
||||||
for _, entry in ipairs(entries) do
|
if tickThis then
|
||||||
if EntryNeedsVisualTicker(entry) then
|
|
||||||
shouldTick = true
|
shouldTick = true
|
||||||
break
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -751,12 +540,8 @@ function Manager:UpdateDisplay()
|
|||||||
local entries, shouldShow = self:BuildEntriesForTracker(tracker)
|
local entries, shouldShow = self:BuildEntriesForTracker(tracker)
|
||||||
|
|
||||||
if shouldShow then
|
if shouldShow then
|
||||||
if HMGT.FilterDisplayEntries then
|
local tickThis = false
|
||||||
entries = HMGT:FilterDisplayEntries(tracker, entries) or entries
|
entries, tickThis = HMGT:FinalizeTrackerEntries(tracker, entries, tracker.trackerKey or nil)
|
||||||
end
|
|
||||||
if HMGT.SortDisplayEntries then
|
|
||||||
HMGT:SortDisplayEntries(entries)
|
|
||||||
end
|
|
||||||
|
|
||||||
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
HMGT.TrackerFrame:UpdateFrame(frame, entries, true)
|
||||||
frame:Show()
|
frame:Show()
|
||||||
@@ -769,11 +554,8 @@ function Manager:UpdateDisplay()
|
|||||||
layoutDirty = true
|
layoutDirty = true
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, entry in ipairs(entries) do
|
if tickThis then
|
||||||
if EntryNeedsVisualTicker(entry) then
|
shouldTick = true
|
||||||
shouldTick = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
frame:Hide()
|
frame:Hide()
|
||||||
|
|||||||
@@ -142,13 +142,26 @@ local function IsPartyAttachMode(tracker)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function IsGroupTracker(tracker)
|
local function IsGroupTracker(tracker)
|
||||||
return type(tracker) == "table" and tracker.trackerType == "group"
|
return HMGT.IsGroupTrackerConfig and HMGT:IsGroupTrackerConfig(tracker) or (type(tracker) == "table" and tracker.trackerType == "group")
|
||||||
end
|
end
|
||||||
|
|
||||||
local TRACKER_TYPE_VALUES = {
|
local function GetTrackerTypeValues()
|
||||||
normal = L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker",
|
return HMGT.GetTrackerTypeOptions and HMGT:GetTrackerTypeOptions() or {
|
||||||
group = L["OPT_TRACKER_TYPE_GROUP"] or "Group-based tracker",
|
normal = L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker",
|
||||||
}
|
group = L["OPT_TRACKER_TYPE_GROUP"] or "Group-based tracker",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetPresetLabel(presetKey)
|
||||||
|
local definition = HMGT.GetTrackerPresetDefinition and HMGT:GetTrackerPresetDefinition(presetKey) or nil
|
||||||
|
if not definition then
|
||||||
|
return tostring(presetKey or (L["OPT_TRACKER"] or "Tracker"))
|
||||||
|
end
|
||||||
|
if type(definition.defaultName) == "function" then
|
||||||
|
return tostring(definition.defaultName(L))
|
||||||
|
end
|
||||||
|
return tostring(definition.defaultName or definition.moduleName or presetKey)
|
||||||
|
end
|
||||||
|
|
||||||
local function GetTrackerVisibilitySummary(tracker)
|
local function GetTrackerVisibilitySummary(tracker)
|
||||||
local parts = {}
|
local parts = {}
|
||||||
@@ -180,7 +193,7 @@ local function GetTrackerSummaryText(tracker)
|
|||||||
local display = tracker.showBar and (L["OPT_DISPLAY_BAR"] or "Progress bars") or (L["OPT_DISPLAY_ICON"] or "Icons")
|
local display = tracker.showBar and (L["OPT_DISPLAY_BAR"] or "Progress bars") or (L["OPT_DISPLAY_ICON"] or "Icons")
|
||||||
|
|
||||||
return table.concat({
|
return table.concat({
|
||||||
string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_TYPE"] or "Tracker type", TRACKER_TYPE_VALUES[tracker.trackerType or "normal"] or (L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker")),
|
string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_TYPE"] or "Tracker type", GetTrackerTypeValues()[tracker.trackerType or "normal"] or (L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker")),
|
||||||
string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_CATEGORIES"] or "Categories", GetTrackerCategoriesSummary(tracker)),
|
string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_CATEGORIES"] or "Categories", GetTrackerCategoriesSummary(tracker)),
|
||||||
string.format("|cffffd100%s|r: %s", L["OPT_STATUS_MODE"] or "Mode", modeLabel),
|
string.format("|cffffd100%s|r: %s", L["OPT_STATUS_MODE"] or "Mode", modeLabel),
|
||||||
string.format("|cffffd100%s|r: %s", L["OPT_STATUS_DISPLAY"] or "Display", display),
|
string.format("|cffffd100%s|r: %s", L["OPT_STATUS_DISPLAY"] or "Display", display),
|
||||||
@@ -814,7 +827,7 @@ local function BuildGlobalSpellBrowserArgs()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function BuildTrackerOverviewArgs()
|
local function BuildTrackerOverviewArgs()
|
||||||
return {
|
local args = {
|
||||||
description = {
|
description = {
|
||||||
type = "description",
|
type = "description",
|
||||||
order = 1,
|
order = 1,
|
||||||
@@ -833,22 +846,36 @@ local function BuildTrackerOverviewArgs()
|
|||||||
return string.format("%s\n\n%s (%d): %s", body, L["OPT_TRACKERS"] or "Tracker Bars", #trackers, table.concat(names, ", "))
|
return string.format("%s\n\n%s (%d): %s", body, L["OPT_TRACKERS"] or "Tracker Bars", #trackers, table.concat(names, ", "))
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
addTracker = {
|
}
|
||||||
|
|
||||||
|
local definitions = HMGT.GetTrackerPresetDefinitions and HMGT:GetTrackerPresetDefinitions() or {}
|
||||||
|
local presetKeys = {}
|
||||||
|
for presetKey in pairs(definitions) do
|
||||||
|
presetKeys[#presetKeys + 1] = presetKey
|
||||||
|
end
|
||||||
|
table.sort(presetKeys, function(a, b)
|
||||||
|
return GetPresetLabel(a) < GetPresetLabel(b)
|
||||||
|
end)
|
||||||
|
|
||||||
|
for index, presetKey in ipairs(presetKeys) do
|
||||||
|
args["addPreset_" .. presetKey] = {
|
||||||
type = "execute",
|
type = "execute",
|
||||||
order = 2,
|
order = 2 + index,
|
||||||
width = "full",
|
width = "full",
|
||||||
name = L["OPT_ADD_TRACKER"] or "Add tracker",
|
name = function()
|
||||||
|
return string.format("%s: %s", L["OPT_ADD_TRACKER"] or "Add tracker", GetPresetLabel(presetKey))
|
||||||
|
end,
|
||||||
func = function()
|
func = function()
|
||||||
local nextId = HMGT:GetNextTrackerId()
|
local nextId = HMGT:GetNextTrackerId()
|
||||||
local tracker = HMGT:CreateTrackerConfig(nextId, {
|
local tracker = HMGT:BuildTrackerConfigFromPreset(presetKey, nextId)
|
||||||
name = string.format("%s %d", L["OPT_TRACKER"] or "Tracker", nextId),
|
|
||||||
})
|
|
||||||
HMGT.db.profile.trackers = HMGT.db.profile.trackers or {}
|
HMGT.db.profile.trackers = HMGT.db.profile.trackers or {}
|
||||||
HMGT.db.profile.trackers[#HMGT.db.profile.trackers + 1] = tracker
|
HMGT.db.profile.trackers[#HMGT.db.profile.trackers + 1] = tracker
|
||||||
TriggerTrackerUpdate(true)
|
TriggerTrackerUpdate(true)
|
||||||
end,
|
end,
|
||||||
},
|
}
|
||||||
}
|
end
|
||||||
|
|
||||||
|
return args
|
||||||
end
|
end
|
||||||
|
|
||||||
local function BuildTrackerGroup(trackerId, order)
|
local function BuildTrackerGroup(trackerId, order)
|
||||||
@@ -1008,7 +1035,7 @@ local function BuildTrackerGroup(trackerId, order)
|
|||||||
width = "full",
|
width = "full",
|
||||||
name = L["OPT_TRACKER_TYPE"] or "Tracker type",
|
name = L["OPT_TRACKER_TYPE"] or "Tracker type",
|
||||||
desc = L["OPT_TRACKER_TYPE_DESC"] or "Choose whether this tracker uses one shared frame or separate frames per group member.",
|
desc = L["OPT_TRACKER_TYPE_DESC"] or "Choose whether this tracker uses one shared frame or separate frames per group member.",
|
||||||
values = TRACKER_TYPE_VALUES,
|
values = GetTrackerTypeValues,
|
||||||
get = function()
|
get = function()
|
||||||
local tracker = s()
|
local tracker = s()
|
||||||
return (tracker and tracker.trackerType) or "normal"
|
return (tracker and tracker.trackerType) or "normal"
|
||||||
|
|||||||
65
Modules/Tracker/TrackerPlayerState.lua
Normal file
65
Modules/Tracker/TrackerPlayerState.lua
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
|
HMGT.TrackerPlayerState = HMGT.TrackerPlayerState or {}
|
||||||
|
|
||||||
|
local internals = HMGT.TrackerInternals or {}
|
||||||
|
local IsSpellKnownLocally = internals.IsSpellKnownLocally
|
||||||
|
|
||||||
|
function HMGT:CollectOwnAvailableTrackerSpells(classToken, specIndex)
|
||||||
|
local class = classToken or select(2, UnitClass("player"))
|
||||||
|
local spec = tonumber(specIndex) or tonumber(GetSpecialization())
|
||||||
|
if not class or not spec or spec <= 0 then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
if not HMGT_SpellData or type(HMGT_SpellData.GetSpellsForSpec) ~= "function" then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local knownSpells = {}
|
||||||
|
for _, datasetName in ipairs({ "Interrupts", "RaidCooldowns", "GroupCooldowns" }) do
|
||||||
|
local dataset = HMGT_SpellData[datasetName]
|
||||||
|
if type(dataset) == "table" then
|
||||||
|
local spells = HMGT_SpellData.GetSpellsForSpec(class, spec, dataset)
|
||||||
|
for _, entry in ipairs(spells) do
|
||||||
|
local sid = tonumber(entry.spellId)
|
||||||
|
if sid and sid > 0 and IsSpellKnownLocally and IsSpellKnownLocally(sid) then
|
||||||
|
knownSpells[sid] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local ownCDs = ownName and self:GetPlayerCooldownMap(ownName, false)
|
||||||
|
if ownCDs then
|
||||||
|
for sid in pairs(ownCDs) do
|
||||||
|
sid = tonumber(sid)
|
||||||
|
if sid and sid > 0 then
|
||||||
|
knownSpells[sid] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return knownSpells
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:IsTrackedSpellKnownForPlayer(playerName, spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not sid or sid <= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local pData = normalizedName and self.playerData[normalizedName]
|
||||||
|
if pData and type(pData.knownSpells) == "table" and pData.knownSpells[sid] == true then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if normalizedName and ownName and normalizedName == ownName and IsSpellKnownLocally then
|
||||||
|
return IsSpellKnownLocally(sid)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
410
Modules/Tracker/TrackerState.lua
Normal file
410
Modules/Tracker/TrackerState.lua
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
local ADDON_NAME = "HailMaryGuildTools"
|
||||||
|
local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME)
|
||||||
|
if not HMGT then return end
|
||||||
|
|
||||||
|
HMGT.TrackerState = HMGT.TrackerState or {}
|
||||||
|
|
||||||
|
function HMGT:EnsureTrackerStateTables()
|
||||||
|
self.playerData = self.playerData or {}
|
||||||
|
self.activeCDs = self.activeCDs or {}
|
||||||
|
self.availabilityStates = self.availabilityStates or {}
|
||||||
|
self.localSpellStateRevisions = self.localSpellStateRevisions or {}
|
||||||
|
self.remoteSpellStateRevisions = self.remoteSpellStateRevisions or {}
|
||||||
|
self.knownChargeInfo = self.knownChargeInfo or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ResetTrackerState()
|
||||||
|
self.playerData = {}
|
||||||
|
self.activeCDs = {}
|
||||||
|
self.availabilityStates = {}
|
||||||
|
self.localSpellStateRevisions = {}
|
||||||
|
self.remoteSpellStateRevisions = {}
|
||||||
|
self.knownChargeInfo = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetPlayerCooldownMap(playerName, create)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
if not normalizedName then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
self:EnsureTrackerStateTables()
|
||||||
|
if create then
|
||||||
|
self.activeCDs[normalizedName] = self.activeCDs[normalizedName] or {}
|
||||||
|
end
|
||||||
|
return self.activeCDs[normalizedName]
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetAvailabilityStateMap(playerName, create)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
if not normalizedName then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
self:EnsureTrackerStateTables()
|
||||||
|
if create then
|
||||||
|
self.availabilityStates[normalizedName] = self.availabilityStates[normalizedName] or {}
|
||||||
|
end
|
||||||
|
return self.availabilityStates[normalizedName]
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetAvailabilityStateEntry(playerName, spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
local states = self:GetAvailabilityStateMap(playerName, false)
|
||||||
|
return states and sid and states[sid] or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:SetAvailabilityStateEntry(playerName, spellId, stateData)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not sid or sid <= 0 or type(stateData) ~= "table" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local states = self:GetAvailabilityStateMap(playerName, true)
|
||||||
|
if not states then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
states[sid] = stateData
|
||||||
|
return stateData
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ClearAvailabilityState(playerName, spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
if not normalizedName or not sid or sid <= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local states = self.availabilityStates and self.availabilityStates[normalizedName]
|
||||||
|
if not states or not states[sid] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
states[sid] = nil
|
||||||
|
if not next(states) then
|
||||||
|
self.availabilityStates[normalizedName] = nil
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetActiveCooldown(playerName, spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
local cooldowns = self:GetPlayerCooldownMap(playerName, false)
|
||||||
|
return cooldowns and sid and cooldowns[sid] or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:SetActiveCooldown(playerName, spellId, cdData)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not sid or sid <= 0 or type(cdData) ~= "table" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local cooldowns = self:GetPlayerCooldownMap(playerName, true)
|
||||||
|
if not cooldowns then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
cooldowns[sid] = cdData
|
||||||
|
return cdData
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ClearActiveCooldown(playerName, spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
if not normalizedName or not sid or sid <= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local cooldowns = self.activeCDs and self.activeCDs[normalizedName]
|
||||||
|
if not cooldowns or not cooldowns[sid] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
cooldowns[sid] = nil
|
||||||
|
if not next(cooldowns) then
|
||||||
|
self.activeCDs[normalizedName] = nil
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ClearPlayerCooldowns(playerName)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
if not normalizedName then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if self.activeCDs and self.activeCDs[normalizedName] then
|
||||||
|
self.activeCDs[normalizedName] = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetLocalSpellStateRevision(spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not sid or sid <= 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
self:EnsureTrackerStateTables()
|
||||||
|
return tonumber(self.localSpellStateRevisions[sid]) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:EnsureLocalSpellStateRevision(spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not sid or sid <= 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
self:EnsureTrackerStateTables()
|
||||||
|
local current = tonumber(self.localSpellStateRevisions[sid]) or 0
|
||||||
|
if current <= 0 then
|
||||||
|
current = 1
|
||||||
|
self.localSpellStateRevisions[sid] = current
|
||||||
|
end
|
||||||
|
return current
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:NextLocalSpellStateRevision(spellId)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
if not sid or sid <= 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
self:EnsureTrackerStateTables()
|
||||||
|
local nextRevision = (tonumber(self.localSpellStateRevisions[sid]) or 0) + 1
|
||||||
|
self.localSpellStateRevisions[sid] = nextRevision
|
||||||
|
return nextRevision
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetRemoteSpellStateRevision(playerName, spellId)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
local bySpell = normalizedName and self.remoteSpellStateRevisions[normalizedName]
|
||||||
|
return tonumber(bySpell and bySpell[sid]) or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:SetRemoteSpellStateRevision(playerName, spellId, revision)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
local rev = tonumber(revision) or 0
|
||||||
|
if not normalizedName or not sid or sid <= 0 or rev <= 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self:EnsureTrackerStateTables()
|
||||||
|
self.remoteSpellStateRevisions[normalizedName] = self.remoteSpellStateRevisions[normalizedName] or {}
|
||||||
|
self.remoteSpellStateRevisions[normalizedName][sid] = rev
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ClearRemoteSpellStateRevisions(playerName)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
if not normalizedName then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if self.remoteSpellStateRevisions and self.remoteSpellStateRevisions[normalizedName] then
|
||||||
|
self.remoteSpellStateRevisions[normalizedName] = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ClearTrackerStateForPlayer(playerName)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
if not normalizedName then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local changed = false
|
||||||
|
if self.activeCDs and self.activeCDs[normalizedName] then
|
||||||
|
self.activeCDs[normalizedName] = nil
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
if self.availabilityStates and self.availabilityStates[normalizedName] then
|
||||||
|
self.availabilityStates[normalizedName] = nil
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
if self.remoteSpellStateRevisions and self.remoteSpellStateRevisions[normalizedName] then
|
||||||
|
self.remoteSpellStateRevisions[normalizedName] = nil
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return changed
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:StoreKnownChargeInfo(spellId, maxCharges, chargeDuration)
|
||||||
|
local sid = tonumber(spellId)
|
||||||
|
local maxCount = tonumber(maxCharges)
|
||||||
|
if not sid or sid <= 0 or not maxCount or maxCount <= 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self:EnsureTrackerStateTables()
|
||||||
|
self.knownChargeInfo[sid] = {
|
||||||
|
maxCharges = math.max(1, math.floor(maxCount + 0.5)),
|
||||||
|
chargeDuration = math.max(0, tonumber(chargeDuration) or 0),
|
||||||
|
updatedAt = GetTime(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:GetKnownChargeInfo(spellEntry, talents, spellId, fallbackChargeDuration)
|
||||||
|
local sid = tonumber(spellId or (spellEntry and spellEntry.spellId))
|
||||||
|
if not sid or sid <= 0 then
|
||||||
|
return 0, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local cached = self.knownChargeInfo and self.knownChargeInfo[sid]
|
||||||
|
local cachedMax = tonumber(cached and cached.maxCharges) or 0
|
||||||
|
local cachedDuration = tonumber(cached and cached.chargeDuration) or 0
|
||||||
|
|
||||||
|
local inferredMax, inferredDuration = HMGT_SpellData.GetEffectiveChargeInfo(
|
||||||
|
spellEntry,
|
||||||
|
talents or {},
|
||||||
|
(cachedMax > 0) and cachedMax or nil,
|
||||||
|
(cachedDuration > 0) and cachedDuration or fallbackChargeDuration
|
||||||
|
)
|
||||||
|
|
||||||
|
local maxCharges = math.max(cachedMax, tonumber(inferredMax) or 0)
|
||||||
|
local chargeDuration = math.max(
|
||||||
|
tonumber(inferredDuration) or 0,
|
||||||
|
cachedDuration,
|
||||||
|
tonumber(fallbackChargeDuration) or 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if maxCharges > 1 then
|
||||||
|
self:StoreKnownChargeInfo(sid, maxCharges, chargeDuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
return maxCharges, chargeDuration
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:PruneAvailabilityStates(playerName, knownSpells)
|
||||||
|
local normalizedName = self:NormalizePlayerName(playerName)
|
||||||
|
local states = normalizedName and self.availabilityStates[normalizedName]
|
||||||
|
if not states or type(knownSpells) ~= "table" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local changed = false
|
||||||
|
for sid in pairs(states) do
|
||||||
|
if not knownSpells[tonumber(sid)] then
|
||||||
|
states[sid] = nil
|
||||||
|
changed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not next(states) then
|
||||||
|
self.availabilityStates[normalizedName] = nil
|
||||||
|
end
|
||||||
|
return changed
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:ResolveChargeState(cdData, now)
|
||||||
|
if type(cdData) ~= "table" then
|
||||||
|
return 0, 0, 0, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
now = tonumber(now) or GetTime()
|
||||||
|
local maxCharges = math.max(0, tonumber(cdData.maxCharges) or 0)
|
||||||
|
local currentCharges = math.max(0, tonumber(cdData.currentCharges) or 0)
|
||||||
|
local chargeDuration = math.max(0, tonumber(cdData.chargeDuration) or 0)
|
||||||
|
local chargeStart = tonumber(cdData.chargeStart)
|
||||||
|
|
||||||
|
if maxCharges <= 0 then
|
||||||
|
return 0, chargeDuration, currentCharges, maxCharges
|
||||||
|
end
|
||||||
|
if currentCharges >= maxCharges or chargeDuration <= 0 or not chargeStart then
|
||||||
|
return 0, chargeDuration, math.min(currentCharges, maxCharges), maxCharges
|
||||||
|
end
|
||||||
|
|
||||||
|
local elapsed = math.max(0, now - chargeStart)
|
||||||
|
local gainedCharges = math.floor(elapsed / chargeDuration)
|
||||||
|
local remaining = chargeDuration - (elapsed % chargeDuration)
|
||||||
|
|
||||||
|
if gainedCharges > 0 then
|
||||||
|
currentCharges = math.min(maxCharges, currentCharges + gainedCharges)
|
||||||
|
if currentCharges >= maxCharges then
|
||||||
|
currentCharges = maxCharges
|
||||||
|
chargeStart = nil
|
||||||
|
remaining = 0
|
||||||
|
else
|
||||||
|
chargeStart = now - (elapsed % chargeDuration)
|
||||||
|
end
|
||||||
|
|
||||||
|
cdData.currentCharges = currentCharges
|
||||||
|
cdData.chargeStart = chargeStart
|
||||||
|
if currentCharges >= maxCharges then
|
||||||
|
cdData.startTime = now
|
||||||
|
cdData.duration = 0
|
||||||
|
else
|
||||||
|
local missing = maxCharges - currentCharges
|
||||||
|
cdData.startTime = chargeStart
|
||||||
|
cdData.duration = missing * chargeDuration
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentCharges >= maxCharges then
|
||||||
|
return 0, chargeDuration, currentCharges, maxCharges
|
||||||
|
end
|
||||||
|
return math.max(0, remaining), chargeDuration, currentCharges, maxCharges
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:RefreshCooldownExpiryTimer(playerName, spellId, cdData)
|
||||||
|
if not cdData then return 0 end
|
||||||
|
local now = GetTime()
|
||||||
|
local duration = tonumber(cdData.duration) or 0
|
||||||
|
local startTime = tonumber(cdData.startTime) or now
|
||||||
|
local expiresIn = math.max(0, duration - (now - startTime))
|
||||||
|
|
||||||
|
self._cdNonce = (self._cdNonce or 0) + 1
|
||||||
|
local nonce = self._cdNonce
|
||||||
|
cdData._nonce = nonce
|
||||||
|
|
||||||
|
if expiresIn > 0 then
|
||||||
|
self:ScheduleTimer(function()
|
||||||
|
local current = self:GetActiveCooldown(playerName, spellId)
|
||||||
|
if current and current._nonce == nonce then
|
||||||
|
self:ClearActiveCooldown(playerName, spellId)
|
||||||
|
if playerName == self:NormalizePlayerName(UnitName("player")) then
|
||||||
|
self:PublishOwnSpellState(spellId)
|
||||||
|
end
|
||||||
|
self:TriggerTrackerUpdate()
|
||||||
|
end
|
||||||
|
end, expiresIn)
|
||||||
|
end
|
||||||
|
return expiresIn
|
||||||
|
end
|
||||||
|
|
||||||
|
function HMGT:CleanupStaleCooldowns()
|
||||||
|
local now = GetTime()
|
||||||
|
local ownName = self:NormalizePlayerName(UnitName("player"))
|
||||||
|
local removed = 0
|
||||||
|
for playerName, spells in pairs(self.activeCDs) do
|
||||||
|
for spellId, cdInfo in pairs(spells) do
|
||||||
|
local duration = tonumber(cdInfo.duration) or 0
|
||||||
|
local startTime = tonumber(cdInfo.startTime) or now
|
||||||
|
local rem = duration - (now - startTime)
|
||||||
|
local hasCharges = (tonumber(cdInfo.maxCharges) or 0) > 0
|
||||||
|
local currentCharges = tonumber(cdInfo.currentCharges) or 0
|
||||||
|
local maxCharges = tonumber(cdInfo.maxCharges) or 0
|
||||||
|
if hasCharges then
|
||||||
|
local _, _, cur, max = self:ResolveChargeState(cdInfo, now)
|
||||||
|
currentCharges = cur
|
||||||
|
maxCharges = max
|
||||||
|
end
|
||||||
|
local shouldDrop = false
|
||||||
|
if hasCharges then
|
||||||
|
if currentCharges >= maxCharges then
|
||||||
|
shouldDrop = true
|
||||||
|
elseif (tonumber(cdInfo.chargeDuration) or 0) <= 0 and rem <= -2 then
|
||||||
|
shouldDrop = true
|
||||||
|
end
|
||||||
|
elseif rem <= -2 then
|
||||||
|
shouldDrop = true
|
||||||
|
end
|
||||||
|
if shouldDrop then
|
||||||
|
spells[spellId] = nil
|
||||||
|
if playerName == ownName then
|
||||||
|
self:PublishOwnSpellState(spellId)
|
||||||
|
end
|
||||||
|
removed = removed + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not next(spells) then
|
||||||
|
self.activeCDs[playerName] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if removed > 0 then
|
||||||
|
self:Debug("verbose", "CleanupStaleCooldowns removed=%d", removed)
|
||||||
|
end
|
||||||
|
end
|
||||||
1046
Modules/Tracker/TrackerSync.lua
Normal file
1046
Modules/Tracker/TrackerSync.lua
Normal file
File diff suppressed because it is too large
Load Diff
23
readme.md
23
readme.md
@@ -5,8 +5,8 @@ It combines cooldown tracking, encounter reminders, notes, and map utilities in
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
- Stable version: `1.3`
|
- Stable version: `2.0.0`
|
||||||
- Current build: `2.0-beta`
|
- Current build: `2.1.0-beta`
|
||||||
- SavedVariables: `HailMaryGuildToolsDB`
|
- SavedVariables: `HailMaryGuildToolsDB`
|
||||||
|
|
||||||
## Main Features
|
## Main Features
|
||||||
@@ -16,7 +16,6 @@ It combines cooldown tracking, encounter reminders, notes, and map utilities in
|
|||||||
- Per-tracker bar and icon layouts
|
- Per-tracker bar and icon layouts
|
||||||
- Aura Expiry for selected buffs and channels
|
- Aura Expiry for selected buffs and channels
|
||||||
- Raid Timeline for encounter-based text reminders and raid cooldown assignments
|
- Raid Timeline for encounter-based text reminders and raid cooldown assignments
|
||||||
- Notes window for raid or personal note management
|
|
||||||
- Map Overlay with custom world map POIs
|
- Map Overlay with custom world map POIs
|
||||||
- Version mismatch detection inside groups and raids
|
- Version mismatch detection inside groups and raids
|
||||||
- Blizzard AddOn options integration with Ace3-based module configuration
|
- Blizzard AddOn options integration with Ace3-based module configuration
|
||||||
@@ -59,12 +58,24 @@ Provides a dedicated notes window for raid notes, personal notes, and drafts.
|
|||||||
Toggles tracker test mode
|
Toggles tracker test mode
|
||||||
- `/hmgt notes`
|
- `/hmgt notes`
|
||||||
Opens the notes window
|
Opens the notes window
|
||||||
|
- `/hmgt lura`
|
||||||
|
Opens the L'ura rune helper
|
||||||
|
- `/hmgt lura circle|x|diamond|t|triangle`
|
||||||
|
Adds one rune to the next L'ura sequence slot; slot 5 sends the sequence for raid leader/assist
|
||||||
|
- `Encounter Alerts > L'ura Runes > Rune action bar`
|
||||||
|
Shows five clickable rune buttons plus a local clear button for building the sequence
|
||||||
|
- `/hmgt lura reset`
|
||||||
|
Clears the local L'ura sequence builder
|
||||||
|
- `/hmgt lura bar`
|
||||||
|
Toggles the L'ura rune action bar
|
||||||
- `/hmgt debug`
|
- `/hmgt debug`
|
||||||
Opens the developer tools window
|
Opens the debug console
|
||||||
- `/hmgt dev`
|
- `/hmgt dev`
|
||||||
Alias for the developer tools window
|
Alias for the debug console
|
||||||
|
- `/hmgt status`
|
||||||
|
Prints a compact addon health check
|
||||||
- `/hmgt version`
|
- `/hmgt version`
|
||||||
Opens the version window when developer tools are enabled
|
Opens the version window when the debug console is enabled
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user