386 lines
12 KiB
Lua
Executable File
386 lines
12 KiB
Lua
Executable File
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)
|