forever-addons/SuffixQuery/SuffixQuery.lua

386 lines
12 KiB
Lua

local addonName = ...
local SuffixQuery = CreateFrame("Frame")
_G[addonName] = SuffixQuery
local defaults = {
mode = "AND",
selectedStats = {},
minimapAngle = 225,
}
local statOptions = {
{ key = "strength", label = "Strength" },
{ key = "agility", label = "Agility" },
{ key = "stamina", label = "Stamina" },
{ key = "intellect", label = "Intellect" },
{ key = "spirit", label = "Spirit" },
{ key = "attackPower", label = "Attack power" },
{ key = "spellPower", label = "Spell power" },
{ key = "healing", label = "Healing" },
{ key = "crit", label = "Critical strike" },
{ key = "hit", label = "Hit" },
{ key = "haste", label = "Haste" },
{ key = "defense", label = "Defense" },
{ key = "dodge", label = "Dodge" },
{ key = "parry", label = "Parry" },
{ key = "block", label = "Block" },
{ key = "expertise", label = "Expertise" },
}
local function Suffix(term, ...)
local stats = {}
for index = 1, select("#", ...) do
stats[select(index, ...)] = true
end
return { term = term, stats = stats }
end
-- Classic-oriented mappings based on the historical Item suffix and
-- ItemRandomSuffix tables. Terms intentionally omit "of the" for AH search.
local suffixes = {
Suffix("Strength", "strength"),
Suffix("Agility", "agility"),
Suffix("Stamina", "stamina"),
Suffix("Intellect", "intellect"),
Suffix("Spirit", "spirit"),
Suffix("Tiger", "strength", "agility"),
Suffix("Beast", "strength", "agility", "stamina"),
Suffix("Grove", "strength", "agility", "stamina"),
Suffix("Striking", "strength", "agility", "stamina"),
Suffix("Bear", "strength", "stamina"),
Suffix("Gorilla", "strength", "intellect"),
Suffix("Boar", "strength", "spirit"),
Suffix("Monkey", "agility", "stamina"),
Suffix("Falcon", "agility", "intellect"),
Suffix("Wolf", "agility", "spirit"),
Suffix("Eagle", "stamina", "intellect"),
Suffix("Whale", "stamina", "spirit"),
Suffix("Owl", "intellect", "spirit"),
Suffix("Ancestor", "strength", "stamina", "crit"),
Suffix("Battle", "strength", "stamina", "crit"),
Suffix("Champion", "strength", "stamina", "dodge"),
Suffix("Crusade", "strength", "stamina", "dodge"),
Suffix("Mercenary", "strength", "stamina", "hit"),
Suffix("Soldier", "strength", "stamina", "crit"),
Suffix("Squire", "strength", "stamina", "hit"),
Suffix("Bandit", "agility", "stamina", "crit"),
Suffix("Hunt", "agility", "stamina", "crit"),
Suffix("Marksman", "agility", "stamina", "hit"),
Suffix("Shadow", "agility", "stamina", "crit"),
Suffix("Thief", "agility", "stamina", "haste"),
Suffix("Wild", "agility", "stamina", "hit"),
Suffix("Elder", "stamina", "intellect", "spirit"),
Suffix("Foreseer", "intellect", "crit", "haste"),
Suffix("Mind", "intellect", "crit", "haste"),
Suffix("Moon", "stamina", "intellect"),
Suffix("Necromancer", "stamina", "intellect", "hit"),
Suffix("Nightmare", "stamina", "intellect", "crit"),
Suffix("Prophet", "stamina", "intellect", "spirit"),
Suffix("Restoration", "stamina", "intellect", "spirit"),
Suffix("Seer", "stamina", "intellect", "crit"),
Suffix("Sorcerer", "stamina", "intellect"),
Suffix("Sun", "stamina", "intellect", "crit"),
Suffix("Vision", "stamina", "intellect", "crit"),
Suffix("Eluding", "agility", "dodge"),
Suffix("Blocking", "strength", "block"),
Suffix("Knight", "stamina", "expertise"),
Suffix("Defender", "stamina", "defense"),
Suffix("Healing", "healing"),
Suffix("Arcane Wrath", "spellPower"),
Suffix("Fiery Wrath", "spellPower"),
Suffix("Frozen Wrath", "spellPower"),
Suffix("Nature's Wrath", "spellPower"),
Suffix("Shadow Wrath", "spellPower"),
}
local function CopyDefaults(target, source)
for key, value in pairs(source) do
if target[key] == nil then
target[key] = type(value) == "table" and CopyDefaults({}, value) or value
end
end
return target
end
local function GetSelectedStats()
local selected = {}
for _, option in ipairs(statOptions) do
if SuffixQuery.db.selectedStats[option.key] then
table.insert(selected, option.key)
end
end
return selected
end
local function MatchesSelection(suffix, selected, mode)
if mode == "OR" then
for _, stat in ipairs(selected) do
if suffix.stats[stat] then
return true
end
end
return false
end
for _, stat in ipairs(selected) do
if not suffix.stats[stat] then
return false
end
end
return true
end
function SuffixQuery:BuildQuery()
local selected = GetSelectedStats()
if #selected == 0 then
return nil, "Select at least one stat."
end
local terms = {}
for _, suffix in ipairs(suffixes) do
if MatchesSelection(suffix, selected, self.db.mode) then
table.insert(terms, suffix.term:lower())
end
end
if #terms == 0 then
return nil, "No mapped suffixes match that selection."
end
return table.concat(terms, "|")
end
local function CreateBackdrop(frame)
frame:SetBackdrop({
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
tile = true,
tileSize = 32,
edgeSize = 32,
insets = { left = 11, right = 11, top = 11, bottom = 11 },
})
frame:SetBackdropColor(0, 0, 0, 0.95)
end
local function CreateText(parent, text, template, point, relativeTo, relativePoint, x, y)
local label = parent:CreateFontString(nil, "OVERLAY", template)
label:SetText(text)
label:SetPoint(point, relativeTo or parent, relativePoint or point, x or 0, y or 0)
return label
end
function SuffixQuery:RefreshModeButtons()
local isAnd = self.db.mode == "AND"
self.andButton:SetText(isAnd and "AND (ON)" or "AND")
self.orButton:SetText(isAnd and "OR" or "OR (ON)")
end
function SuffixQuery:ShowQuery()
local query, message = self:BuildQuery()
if not query then
self.status:SetText(message)
self.status:SetTextColor(1, 0.25, 0.25)
return
end
-- Edit boxes render a literal pipe as "||". The copied value remains a
-- single pipe, matching the auction-house search grammar.
self.result:SetText(query:gsub("|", "||"))
self.result:SetFocus()
self.result:HighlightText()
self.status:SetText(string.format("%d suffixes — Ctrl+C copies and closes.", select(2, query:gsub("|", "")) + 1))
self.status:SetTextColor(0.3, 1, 0.3)
end
function SuffixQuery:ToggleModal()
if self.modal:IsShown() then
self.modal:Hide()
else
self:RefreshModeButtons()
self.modal:Show()
end
end
function SuffixQuery:UpdateMinimapPosition()
local angle = math.rad(self.db.minimapAngle)
self.minimapButton:ClearAllPoints()
self.minimapButton:SetPoint("CENTER", Minimap, "CENTER", math.cos(angle) * 104, math.sin(angle) * 104)
end
function SuffixQuery:CreateMinimapButton()
local button = CreateFrame("Button", "SuffixQueryMinimapButton", Minimap)
button:SetSize(62, 62)
button:SetFrameStrata("MEDIUM")
button:SetFrameLevel(8)
local icon = button:CreateTexture(nil, "ARTWORK")
icon:SetTexture("Interface\\Icons\\INV_Misc_Note_01")
icon:SetSize(24, 24)
icon:SetPoint("CENTER", button, "CENTER", -12, 12)
icon:SetTexCoord(0.12, 0.88, 0.12, 0.88)
local iconMask = button:CreateMaskTexture()
iconMask:SetTexture("Interface\\Minimap\\UI-Minimap-Background", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
iconMask:SetAllPoints(icon)
icon:AddMaskTexture(iconMask)
local border = button:CreateTexture(nil, "OVERLAY")
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
border:SetAllPoints(button)
local highlight = button:CreateTexture(nil, "HIGHLIGHT")
highlight:SetTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
highlight:SetBlendMode("ADD")
highlight:SetSize(62, 62)
highlight:SetPoint("CENTER", button, "CENTER", -12, 12)
button.icon = icon
button:RegisterForClicks("LeftButtonUp")
button:RegisterForDrag("LeftButton")
button:SetScript("OnClick", function()
SuffixQuery:ToggleModal()
end)
button:SetScript("OnDragStart", function(self)
self:SetScript("OnUpdate", function()
local cursorX, cursorY = GetCursorPosition()
local scale = Minimap:GetEffectiveScale()
local centerX, centerY = Minimap:GetCenter()
cursorX, cursorY = cursorX / scale, cursorY / scale
SuffixQuery.db.minimapAngle = math.deg(math.atan2(cursorY - centerY, cursorX - centerX))
SuffixQuery:UpdateMinimapPosition()
end)
end)
button:SetScript("OnDragStop", function(self)
self:SetScript("OnUpdate", nil)
end)
button:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:AddLine("Suffix Query")
GameTooltip:AddLine("Build a random-suffix search string.", 1, 1, 1, true)
GameTooltip:Show()
end)
button:SetScript("OnLeave", GameTooltip_Hide)
self.minimapButton = button
self:UpdateMinimapPosition()
end
function SuffixQuery:CreateModal()
local frame = CreateFrame("Frame", "SuffixQueryModal", UIParent, "BackdropTemplate")
frame:SetSize(460, 510)
frame:SetPoint("CENTER")
frame:SetFrameStrata("DIALOG")
frame:SetToplevel(true)
frame:EnableMouse(true)
frame:SetMovable(true)
frame:SetClampedToScreen(true)
frame:Hide()
CreateBackdrop(frame)
table.insert(UISpecialFrames, frame:GetName())
frame:SetScript("OnMouseDown", function(self, button)
if button == "LeftButton" then
self:StartMoving()
end
end)
frame:SetScript("OnMouseUp", function(self)
self:StopMovingOrSizing()
end)
CreateText(frame, "Suffix Query", "GameFontHighlightLarge", "TOP", frame, "TOP", 0, -22)
CreateText(frame, "Select stats, choose matching logic, then generate a search string.", "GameFontNormalSmall", "TOP", frame, "TOP", 0, -48)
local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
local modeLabel = CreateText(frame, "Match suffixes containing:", "GameFontHighlight", "TOPLEFT", frame, "TOPLEFT", 24, -79)
local andButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
andButton:SetSize(92, 24)
andButton:SetPoint("TOPLEFT", modeLabel, "BOTTOMLEFT", 0, -7)
andButton:SetScript("OnClick", function()
SuffixQuery.db.mode = "AND"
SuffixQuery:RefreshModeButtons()
end)
self.andButton = andButton
local orButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
orButton:SetSize(92, 24)
orButton:SetPoint("LEFT", andButton, "RIGHT", 8, 0)
orButton:SetScript("OnClick", function()
SuffixQuery.db.mode = "OR"
SuffixQuery:RefreshModeButtons()
end)
self.orButton = orButton
CreateText(frame, "Stats", "GameFontHighlight", "TOPLEFT", frame, "TOPLEFT", 24, -145)
for index, option in ipairs(statOptions) do
local column = (index - 1) % 2
local row = math.floor((index - 1) / 2)
local checkbox = CreateFrame("CheckButton", nil, frame, "ChatConfigCheckButtonTemplate")
checkbox:SetPoint("TOPLEFT", frame, "TOPLEFT", 28 + column * 205, -168 - row * 28)
checkbox:SetChecked(self.db.selectedStats[option.key] == true)
checkbox.Text:SetText(option.label)
checkbox.Text:ClearAllPoints()
checkbox.Text:SetPoint("LEFT", checkbox, "RIGHT", 2, 0)
checkbox:SetScript("OnClick", function(self)
SuffixQuery.db.selectedStats[option.key] = self:GetChecked() and true or nil
end)
end
local generate = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
generate:SetSize(160, 28)
generate:SetPoint("BOTTOM", frame, "BOTTOM", 0, 78)
generate:SetText("Generate query")
generate:SetScript("OnClick", function()
SuffixQuery:ShowQuery()
end)
local result = CreateFrame("EditBox", nil, frame, "InputBoxTemplate")
result:SetSize(400, 28)
result:SetPoint("BOTTOM", frame, "BOTTOM", 0, 43)
result:SetAutoFocus(false)
result:SetFontObject("ChatFontNormal")
result:SetScript("OnEscapePressed", function()
frame:Hide()
end)
local ctrlDown = false
result:SetScript("OnKeyDown", function(_, key)
if key == "LCTRL" or key == "RCTRL" or key == "LMETA" or key == "RMETA" then
ctrlDown = true
end
end)
result:SetScript("OnKeyUp", function(_, key)
if key == "LCTRL" or key == "RCTRL" or key == "LMETA" or key == "RMETA" then
C_Timer.After(0.2, function()
ctrlDown = false
end)
elseif ctrlDown and (key == "C" or key == "X") then
C_Timer.After(0.1, function()
frame:Hide()
end)
end
end)
self.result = result
local status = CreateText(frame, "Generate highlights the result for copying.", "GameFontNormalSmall", "BOTTOM", frame, "BOTTOM", 0, 17)
status:SetWidth(400)
status:SetJustifyH("CENTER")
self.status = status
self.modal = frame
self:RefreshModeButtons()
end
function SuffixQuery:Initialize()
SuffixQueryDB = CopyDefaults(SuffixQueryDB or {}, defaults)
self.db = SuffixQueryDB
self:CreateModal()
self:CreateMinimapButton()
SLASH_SUFFIXQUERY1 = "/suffixquery"
SlashCmdList.SUFFIXQUERY = function()
SuffixQuery:ToggleModal()
end
end
SuffixQuery:RegisterEvent("PLAYER_LOGIN")
SuffixQuery:SetScript("OnEvent", function(self, event)
if event == "PLAYER_LOGIN" then
self:Initialize()
end
end)