commit 3329ac83ec26356d12f97270e273c90d059601d0 Author: j2 Date: Mon Sep 21 18:10:20 2026 -0500 Initial Forever beta addons diff --git a/EquippedItemLevel/EquippedItemLevel.lua b/EquippedItemLevel/EquippedItemLevel.lua new file mode 100644 index 0000000..479ee48 --- /dev/null +++ b/EquippedItemLevel/EquippedItemLevel.lua @@ -0,0 +1,265 @@ +local addonName = ... + +local EquippedItemLevel = CreateFrame("Frame") +_G[addonName] = EquippedItemLevel + +local slots = { + { id = 1, frame = "CharacterHeadSlot" }, + { id = 2, frame = "CharacterNeckSlot" }, + { id = 3, frame = "CharacterShoulderSlot" }, + { id = 5, frame = "CharacterChestSlot" }, + { id = 6, frame = "CharacterWaistSlot" }, + { id = 7, frame = "CharacterLegsSlot" }, + { id = 8, frame = "CharacterFeetSlot" }, + { id = 9, frame = "CharacterWristSlot" }, + { id = 10, frame = "CharacterHandsSlot" }, + { id = 11, frame = "CharacterFinger0Slot" }, + { id = 12, frame = "CharacterFinger1Slot" }, + { id = 13, frame = "CharacterTrinket0Slot" }, + { id = 14, frame = "CharacterTrinket1Slot" }, + { id = 15, frame = "CharacterBackSlot" }, + { id = 16, frame = "CharacterMainHandSlot" }, + { id = 17, frame = "CharacterSecondaryHandSlot" }, +} + +local itemLevels = {} +local updateQueued = false +local characterFrameHooked = false +local bagFrameHooks = {} +local equippedLowest, equippedHighest + +local function GetCachedItemInfo(itemLink) + if C_Item and C_Item.GetItemInfo then + return C_Item.GetItemInfo(itemLink) + end + + if GetItemInfo then + return GetItemInfo(itemLink) + end + + return nil +end + +local function GetItemLevel(itemLink) + if not itemLink then + return nil + end + + if C_Item and C_Item.GetDetailedItemLevelInfo then + local level = C_Item.GetDetailedItemLevelInfo(itemLink) + if level and level > 0 then + return level + end + end + + local _, _, _, level = GetCachedItemInfo(itemLink) + return level +end + +local function GetGradientColor(level, lowest, highest) + if lowest == highest then + return 0, 1, 0 + end + + local progress = (level - lowest) / (highest - lowest) + progress = math.max(0, math.min(1, progress)) + if progress <= 0.5 then + return 1, progress * 2, 0 + end + + return 2 - progress * 2, 1, 0 +end + +local function IsGearItem(itemLink) + if not itemLink then + return false + end + + local _, _, _, _, _, _, _, _, equipLocation = GetCachedItemInfo(itemLink) + return equipLocation + and equipLocation ~= "" + and equipLocation ~= "INVTYPE_NON_EQUIP" + and equipLocation ~= "INVTYPE_NON_EQUIP_IGNORE" + and equipLocation ~= "INVTYPE_BAG" + and equipLocation ~= "INVTYPE_TABARD" +end + +local function CreateLabel(slot) + local button = _G[slot.frame] + if not button or button.EquippedItemLevelLabel then + return + end + + local label = button:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + label:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -2, 2) + label:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE") + label:SetJustifyH("RIGHT") + label:Hide() + button.EquippedItemLevelLabel = label +end + +function EquippedItemLevel:Update() + updateQueued = false + + local lowest, highest + for _, slot in ipairs(slots) do + CreateLabel(slot) + + local itemLink = GetInventoryItemLink("player", slot.id) + local level = GetItemLevel(itemLink) + itemLevels[slot.id] = level + + if level then + lowest = not lowest and level or math.min(lowest, level) + highest = not highest and level or math.max(highest, level) + end + end + + equippedLowest, equippedHighest = lowest, highest + + for _, slot in ipairs(slots) do + local button = _G[slot.frame] + local label = button and button.EquippedItemLevelLabel + local level = itemLevels[slot.id] + + if label and level and lowest and highest then + local red, green, blue = GetGradientColor(level, lowest, highest) + label:SetText(string.format("%.0f", level)) + label:SetTextColor(red, green, blue) + label:Show() + elseif label then + label:Hide() + end + end + + self:UpdateBagLabels() +end + +function EquippedItemLevel:QueueUpdate() + if updateQueued then + return + end + + updateQueued = true + C_Timer.After(0.1, function() + EquippedItemLevel:Update() + end) +end + +function EquippedItemLevel:HookCharacterFrame() + if characterFrameHooked or not CharacterFrame then + return + end + + CharacterFrame:HookScript("OnShow", function() + EquippedItemLevel:QueueUpdate() + end) + characterFrameHooked = true +end + +function EquippedItemLevel:UpdateBagButton(button) + if not button or not button.GetBagID then + return + end + + local label = button.EquippedItemLevelBagLabel + if not label then + label = button:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + label:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", -2, 2) + label:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE") + label:SetJustifyH("RIGHT") + button.EquippedItemLevelBagLabel = label + end + + local bag, slot = button:GetBagID(), button:GetID() + local itemLink = bag and slot and C_Container.GetContainerItemLink(bag, slot) + local level = GetItemLevel(itemLink) + if not level or not IsGearItem(itemLink) or not equippedLowest or not equippedHighest then + label:Hide() + return + end + + local red, green, blue = GetGradientColor(level, equippedLowest, equippedHighest) + label:SetText(string.format("%.0f", level)) + label:SetTextColor(red, green, blue) + label:Show() +end + +function EquippedItemLevel:UpdateContainerFrame(frame) + if not frame then + return + end + + if frame.EnumerateValidItems then + for _, button in frame:EnumerateValidItems() do + self:UpdateBagButton(button) + end + elseif frame.Items then + for _, button in ipairs(frame.Items) do + self:UpdateBagButton(button) + end + end +end + +function EquippedItemLevel:HookBagFrame(frame) + if not frame or bagFrameHooks[frame] or not frame.Update then + return + end + + hooksecurefunc(frame, "Update", function(updatedFrame) + EquippedItemLevel:UpdateContainerFrame(updatedFrame) + end) + bagFrameHooks[frame] = true +end + +function EquippedItemLevel:UpdateBagLabels() + local index = 1 + local frame = _G["ContainerFrame" .. index] + while frame do + self:HookBagFrame(frame) + self:UpdateContainerFrame(frame) + index = index + 1 + frame = _G["ContainerFrame" .. index] + end + + if ContainerFrameCombinedBags then + self:HookBagFrame(ContainerFrameCombinedBags) + self:UpdateContainerFrame(ContainerFrameCombinedBags) + end +end + +local function AddTooltipItemLevel(tooltip, data) + -- The same data is supplied for normal and comparison tooltips, including + -- comparison panes that do not implement GameTooltip:GetItem(). + local itemReference = data and (data.hyperlink or data.id) + if not itemReference or not IsGearItem(itemReference) then + return + end + + local level = GetItemLevel(itemReference) + if not level then + return + end + + local red, green, blue = GetGradientColor(level, equippedLowest or level, equippedHighest or level) + tooltip:AddDoubleLine("Item level", string.format("%.0f", level), 1, 0.82, 0, red, green, blue) + tooltip:Show() +end + +function EquippedItemLevel:OnEvent(event) + if event == "PLAYER_LOGIN" then + TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Item, AddTooltipItemLevel) + + end + + self:HookCharacterFrame() + self:QueueUpdate() +end + +EquippedItemLevel:RegisterEvent("PLAYER_LOGIN") +EquippedItemLevel:RegisterEvent("PLAYER_EQUIPMENT_CHANGED") +EquippedItemLevel:RegisterEvent("GET_ITEM_INFO_RECEIVED") +EquippedItemLevel:RegisterEvent("PLAYER_ENTERING_WORLD") +EquippedItemLevel:RegisterEvent("ADDON_LOADED") +EquippedItemLevel:RegisterEvent("BAG_UPDATE_DELAYED") +EquippedItemLevel:SetScript("OnEvent", EquippedItemLevel.OnEvent) diff --git a/EquippedItemLevel/EquippedItemLevel.toc b/EquippedItemLevel/EquippedItemLevel.toc new file mode 100644 index 0000000..b72e32d --- /dev/null +++ b/EquippedItemLevel/EquippedItemLevel.toc @@ -0,0 +1,7 @@ +## Interface: 11508 +## Title: Equipped Item Level +## Notes: Shows equipped item levels on the character pane and in equipped-item tooltips. +## Author: Local +## Version: 1.0 + +EquippedItemLevel.lua diff --git a/SetGuard/SetGuard.lua b/SetGuard/SetGuard.lua new file mode 100644 index 0000000..617aa03 --- /dev/null +++ b/SetGuard/SetGuard.lua @@ -0,0 +1,216 @@ +local addonName = ... + +local SetGuard = CreateFrame("Frame") +_G[addonName] = SetGuard + +local savedSetItems = {} +local hookedFrames = {} +local updateQueued = false +local merchantOpen = false + +function SetGuard:RebuildSetSlots() + wipe(savedSetItems) + if not (C_EquipmentSet and C_EquipmentSet.GetEquipmentSetIDs and C_EquipmentSet.GetItemIDs) then + return + end + + local setIDs = C_EquipmentSet.GetEquipmentSetIDs() + if not setIDs then + return + end + + for _, setID in ipairs(setIDs) do + local itemIDs = C_EquipmentSet.GetItemIDs(setID) + if itemIDs then + for _, itemID in pairs(itemIDs) do + if itemID and itemID > 0 then + savedSetItems[itemID] = true + end + end + end +end +end + +local function GetBagItemID(bag, slot) + if C_Container and C_Container.GetContainerItemID then + return C_Container.GetContainerItemID(bag, slot) + end + + if C_Container and C_Container.GetContainerItemInfo then + local info = C_Container.GetContainerItemInfo(bag, slot) + return info and info.itemID + end + + return nil +end + +local function GetItemIcon(button) + return button.icon or button.Icon or button.ItemIcon or button.IconTexture or button:GetNormalTexture() +end + +local function GetItemTextures(button) + local textures, seen = {}, {} + local candidates = { + button.icon, + button.Icon, + button.ItemIcon, + button.IconTexture, + button:GetNormalTexture(), + } + for _, texture in pairs(candidates) do + if texture and texture.GetVertexColor and not seen[texture] then + seen[texture] = true + table.insert(textures, texture) + end + end + return textures +end + +local function SetButtonDesaturated(button, desaturated) + if SetItemButtonDesaturated then + SetItemButtonDesaturated(button, desaturated) + return + end + + local icon = GetItemIcon(button) + if icon and icon.SetDesaturated then + icon:SetDesaturated(desaturated) + end +end + +local function SetButtonBrightness(button, brightness) + if not button.SetGuardVertexColors then + button.SetGuardVertexColors = {} + for _, texture in ipairs(GetItemTextures(button)) do + local red, green, blue, alpha = texture:GetVertexColor() + button.SetGuardVertexColors[texture] = { red, green, blue, alpha } + end + end + + for texture, color in pairs(button.SetGuardVertexColors) do + texture:SetVertexColor(color[1] * brightness, color[2] * brightness, color[3] * brightness, color[4]) + end +end + +local function RestoreButtonBrightness(button) + if not button.SetGuardVertexColors then + return + end + + for texture, color in pairs(button.SetGuardVertexColors) do + texture:SetVertexColor(color[1], color[2], color[3], color[4]) + end + button.SetGuardVertexColors = nil +end + +function SetGuard:UpdateBagButton(button) + if not button or not button.GetBagID then + return + end + + local label = button.SetGuardLabel + if not label then + label = button:CreateFontString(nil, "OVERLAY", "GameFontNormal") + label:SetPoint("TOPLEFT", button, "TOPLEFT", 2, -2) + label:SetFont("Fonts\\FRIZQT__.TTF", 14, "OUTLINE") + label:SetText("S") + label:SetTextColor(0.1, 1, 0.1) + button.SetGuardLabel = label + end + + local bag, slot = button:GetBagID(), button:GetID() + local itemID = bag and slot and GetBagItemID(bag, slot) + local isSetItem = itemID and savedSetItems[itemID] + label:SetShown(isSetItem == true) + + if isSetItem and merchantOpen then + if not button.SetGuardDimmed then + local icon = GetItemIcon(button) + button.SetGuardWasDesaturated = icon and icon.GetDesaturated and icon:GetDesaturated() or false + button.SetGuardDimmed = true + end + SetButtonDesaturated(button, true) + SetButtonBrightness(button, 0.25) + elseif button.SetGuardDimmed then + SetButtonDesaturated(button, button.SetGuardWasDesaturated == true) + RestoreButtonBrightness(button) + button.SetGuardDimmed = nil + button.SetGuardWasDesaturated = nil + end +end + +function SetGuard:UpdateContainerFrame(frame) + if not frame then + return + end + + if frame.EnumerateValidItems then + for _, button in frame:EnumerateValidItems() do + self:UpdateBagButton(button) + end + elseif frame.Items then + for _, button in ipairs(frame.Items) do + self:UpdateBagButton(button) + end + end +end + +function SetGuard:HookContainerFrame(frame) + if not frame or hookedFrames[frame] or not frame.Update then + return + end + + hooksecurefunc(frame, "Update", function(updatedFrame) + SetGuard:UpdateContainerFrame(updatedFrame) + end) + hookedFrames[frame] = true +end + +function SetGuard:UpdateVisibleBags() + local index = 1 + local frame = _G["ContainerFrame" .. index] + while frame do + self:HookContainerFrame(frame) + self:UpdateContainerFrame(frame) + index = index + 1 + frame = _G["ContainerFrame" .. index] + end + + if ContainerFrameCombinedBags then + self:HookContainerFrame(ContainerFrameCombinedBags) + self:UpdateContainerFrame(ContainerFrameCombinedBags) + end +end + +function SetGuard:QueueUpdate(rebuildSets) + if rebuildSets then + self:RebuildSetSlots() + end + if updateQueued then + return + end + + updateQueued = true + C_Timer.After(0.1, function() + updateQueued = false + SetGuard:UpdateVisibleBags() + end) +end + +function SetGuard:OnEvent(event) + if event == "MERCHANT_SHOW" then + merchantOpen = true + elseif event == "MERCHANT_CLOSED" then + merchantOpen = false + end + + self:QueueUpdate(event == "PLAYER_LOGIN" or event == "EQUIPMENT_SETS_CHANGED") +end + +SetGuard:RegisterEvent("PLAYER_LOGIN") +SetGuard:RegisterEvent("EQUIPMENT_SETS_CHANGED") +SetGuard:RegisterEvent("BAG_UPDATE_DELAYED") +SetGuard:RegisterEvent("MERCHANT_SHOW") +SetGuard:RegisterEvent("MERCHANT_CLOSED") +SetGuard:RegisterEvent("ADDON_LOADED") +SetGuard:SetScript("OnEvent", SetGuard.OnEvent) diff --git a/SetGuard/SetGuard.toc b/SetGuard/SetGuard.toc new file mode 100644 index 0000000..601affe --- /dev/null +++ b/SetGuard/SetGuard.toc @@ -0,0 +1,7 @@ +## Interface: 11508 +## Title: Set Guard +## Notes: Marks bag items saved in equipment sets and dims them at merchants. +## Author: Local +## Version: 1.0 + +SetGuard.lua diff --git a/SuffixQuery/SuffixQuery.lua b/SuffixQuery/SuffixQuery.lua new file mode 100644 index 0000000..1dae583 --- /dev/null +++ b/SuffixQuery/SuffixQuery.lua @@ -0,0 +1,385 @@ +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) diff --git a/SuffixQuery/SuffixQuery.toc b/SuffixQuery/SuffixQuery.toc new file mode 100644 index 0000000..e71f6a8 --- /dev/null +++ b/SuffixQuery/SuffixQuery.toc @@ -0,0 +1,8 @@ +## Interface: 11508 +## Title: Suffix Query +## Notes: Builds auction-house search queries for Classic random item suffixes. +## Author: Local +## Version: 1.0 +## SavedVariables: SuffixQueryDB + +SuffixQuery.lua