@@ -0,0 +1,769 @@
|
|||||||
|
-- Guda Category Manager
|
||||||
|
-- Handles custom category definitions and rule-based item categorization
|
||||||
|
|
||||||
|
local addon = Guda
|
||||||
|
|
||||||
|
local CategoryManager = {}
|
||||||
|
addon.Modules.CategoryManager = CategoryManager
|
||||||
|
|
||||||
|
-- Rule Types:
|
||||||
|
-- itemType: Match by GetItemInfo type (Armor, Weapon, Consumable, etc.)
|
||||||
|
-- itemSubtype: Match by subtype (Cloth, Potion, Herb, etc.)
|
||||||
|
-- namePattern: Lua pattern match on item name
|
||||||
|
-- quality: Match by quality level (0=Gray, 1=White, 2=Green, 3=Blue, 4=Purple, 5=Orange)
|
||||||
|
-- isBoE: Boolean for Bind on Equip items
|
||||||
|
-- isQuestItem: Boolean for quest items
|
||||||
|
-- texturePattern: Match icon texture path
|
||||||
|
-- itemID: Specific item IDs (table of IDs)
|
||||||
|
|
||||||
|
-- Default category definitions that replicate the existing hardcoded behavior
|
||||||
|
local DEFAULT_CATEGORIES = {
|
||||||
|
order = {
|
||||||
|
"Home", "BoE", "Weapon", "Armor", "Consumable", "Food", "Drink",
|
||||||
|
"Trade Goods", "Reagent", "Recipe", "Quiver", "Container",
|
||||||
|
"Soul Bag", "Miscellaneous", "Quest", "Junk", "Class Items", "Keyring"
|
||||||
|
},
|
||||||
|
definitions = {
|
||||||
|
["BoE"] = {
|
||||||
|
name = "BoE",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Orb_01",
|
||||||
|
rules = {
|
||||||
|
{ type = "isBoE", value = true }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 75,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Weapon"] = {
|
||||||
|
name = "Weapon",
|
||||||
|
icon = "Interface\\Icons\\INV_Sword_04",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Weapon" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 70,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Armor"] = {
|
||||||
|
name = "Armor",
|
||||||
|
icon = "Interface\\Icons\\INV_Chest_Chain",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Armor" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 70,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Consumable"] = {
|
||||||
|
name = "Consumable",
|
||||||
|
icon = "Interface\\Icons\\INV_Potion_54",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Consumable" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 50,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Food"] = {
|
||||||
|
name = "Food",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Food_14",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Consumable" },
|
||||||
|
{ type = "restoreTag", value = "eat" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 55,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Drink"] = {
|
||||||
|
name = "Drink",
|
||||||
|
icon = "Interface\\Icons\\INV_Drink_07",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Consumable" },
|
||||||
|
{ type = "restoreTag", value = "drink" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 55,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Trade Goods"] = {
|
||||||
|
name = "Trade Goods",
|
||||||
|
icon = "Interface\\Icons\\INV_Fabric_Silk_02",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Trade Goods" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 40,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Reagent"] = {
|
||||||
|
name = "Reagent",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Dust_02",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Reagent" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 40,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Recipe"] = {
|
||||||
|
name = "Recipe",
|
||||||
|
icon = "Interface\\Icons\\INV_Scroll_03",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Recipe" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 40,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Quiver"] = {
|
||||||
|
name = "Quiver",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Quiver_03",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Quiver" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 40,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Container"] = {
|
||||||
|
name = "Container",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Bag_07",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Container" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 40,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Soul Bag"] = {
|
||||||
|
name = "Soul Bag",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Bag_EnchantedMageweave",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemSubtype", value = "Soul Bag" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 45,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Miscellaneous"] = {
|
||||||
|
name = "Miscellaneous",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Rune_01",
|
||||||
|
rules = {},
|
||||||
|
matchMode = "any",
|
||||||
|
priority = 0,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
isFallback = true,
|
||||||
|
},
|
||||||
|
["Quest"] = {
|
||||||
|
name = "Quest",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Book_08",
|
||||||
|
rules = {
|
||||||
|
{ type = "isQuestItem", value = true }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 80,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Junk"] = {
|
||||||
|
name = "Junk",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Gear_06",
|
||||||
|
rules = {
|
||||||
|
{ type = "isJunk", value = true }
|
||||||
|
},
|
||||||
|
matchMode = "any",
|
||||||
|
priority = 85,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Class Items"] = {
|
||||||
|
name = "Class Items",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Ammo_Arrow_01",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Projectile" }
|
||||||
|
},
|
||||||
|
matchMode = "any",
|
||||||
|
priority = 90,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Keyring"] = {
|
||||||
|
name = "Keyring",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Key_04",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemType", value = "Key" }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 40,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
},
|
||||||
|
["Home"] = {
|
||||||
|
name = "Home",
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_Rune_01",
|
||||||
|
rules = {},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 0,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
hideControls = true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Deep copy a table
|
||||||
|
local function deepCopy(orig)
|
||||||
|
local copy
|
||||||
|
if type(orig) == "table" then
|
||||||
|
copy = {}
|
||||||
|
for k, v in pairs(orig) do
|
||||||
|
copy[k] = deepCopy(v)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
copy = orig
|
||||||
|
end
|
||||||
|
return copy
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get default categories (returns a deep copy)
|
||||||
|
function CategoryManager:GetDefaultCategories()
|
||||||
|
return deepCopy(DEFAULT_CATEGORIES)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initialize categories from database or defaults
|
||||||
|
function CategoryManager:Initialize()
|
||||||
|
if not Guda_CharDB then return end
|
||||||
|
|
||||||
|
if not Guda_CharDB.categories then
|
||||||
|
Guda_CharDB.categories = self:GetDefaultCategories()
|
||||||
|
addon:Debug("CategoryManager: Initialized with default categories")
|
||||||
|
else
|
||||||
|
-- Ensure all built-in categories exist (migration support)
|
||||||
|
self:MigrateCategories()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Migrate/update categories to ensure all built-ins exist
|
||||||
|
function CategoryManager:MigrateCategories()
|
||||||
|
local cats = Guda_CharDB.categories
|
||||||
|
if not cats then return end
|
||||||
|
|
||||||
|
-- Ensure definitions table exists
|
||||||
|
if not cats.definitions then
|
||||||
|
cats.definitions = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ensure order table exists
|
||||||
|
if not cats.order then
|
||||||
|
cats.order = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add any missing built-in categories
|
||||||
|
for id, def in pairs(DEFAULT_CATEGORIES.definitions) do
|
||||||
|
if not cats.definitions[id] then
|
||||||
|
cats.definitions[id] = deepCopy(def)
|
||||||
|
-- Add to end of order if not present
|
||||||
|
local found = false
|
||||||
|
for _, orderId in ipairs(cats.order) do
|
||||||
|
if orderId == id then
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not found then
|
||||||
|
table.insert(cats.order, id)
|
||||||
|
end
|
||||||
|
addon:Debug("CategoryManager: Added missing built-in category: " .. id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Migrate Food/Drink categories to use restoreTag instead of itemSubtype
|
||||||
|
-- This fixes the issue where subtype "Food & Drink" matched both categories
|
||||||
|
local foodCat = cats.definitions["Food"]
|
||||||
|
if foodCat and foodCat.isBuiltIn then
|
||||||
|
local needsUpdate = false
|
||||||
|
if foodCat.rules then
|
||||||
|
for _, rule in ipairs(foodCat.rules) do
|
||||||
|
if rule.type == "itemSubtype" then
|
||||||
|
needsUpdate = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if needsUpdate then
|
||||||
|
foodCat.rules = {
|
||||||
|
{ type = "itemType", value = "Consumable" },
|
||||||
|
{ type = "restoreTag", value = "eat" }
|
||||||
|
}
|
||||||
|
addon:Debug("CategoryManager: Migrated Food category to use restoreTag")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local drinkCat = cats.definitions["Drink"]
|
||||||
|
if drinkCat and drinkCat.isBuiltIn then
|
||||||
|
local needsUpdate = false
|
||||||
|
if drinkCat.rules then
|
||||||
|
for _, rule in ipairs(drinkCat.rules) do
|
||||||
|
if rule.type == "itemSubtype" then
|
||||||
|
needsUpdate = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if needsUpdate then
|
||||||
|
drinkCat.rules = {
|
||||||
|
{ type = "itemType", value = "Consumable" },
|
||||||
|
{ type = "restoreTag", value = "drink" }
|
||||||
|
}
|
||||||
|
addon:Debug("CategoryManager: Migrated Drink category to use restoreTag")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Migrate BoE priority to be higher than Weapon/Armor (75 > 70)
|
||||||
|
local boeCat = cats.definitions["BoE"]
|
||||||
|
if boeCat and boeCat.isBuiltIn and boeCat.priority and boeCat.priority < 75 then
|
||||||
|
boeCat.priority = 75
|
||||||
|
addon:Debug("CategoryManager: Migrated BoE priority to 75")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Migrate Junk category to use isJunk rule type
|
||||||
|
local junkCat = cats.definitions["Junk"]
|
||||||
|
if junkCat and junkCat.isBuiltIn then
|
||||||
|
local needsUpdate = false
|
||||||
|
-- Check if priority is too low
|
||||||
|
if not junkCat.priority or junkCat.priority < 85 then
|
||||||
|
junkCat.priority = 85
|
||||||
|
needsUpdate = true
|
||||||
|
end
|
||||||
|
-- Migrate from quality=0 rule to isJunk rule
|
||||||
|
if junkCat.rules then
|
||||||
|
for i, rule in ipairs(junkCat.rules) do
|
||||||
|
if rule.type == "quality" and rule.value == 0 then
|
||||||
|
junkCat.rules = { { type = "isJunk", value = true } }
|
||||||
|
needsUpdate = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Check if rules are missing or wrong
|
||||||
|
if not junkCat.rules or table.getn(junkCat.rules) == 0 then
|
||||||
|
junkCat.rules = { { type = "isJunk", value = true } }
|
||||||
|
needsUpdate = true
|
||||||
|
end
|
||||||
|
if needsUpdate then
|
||||||
|
addon:Debug("CategoryManager: Migrated Junk category to use isJunk rule")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get all categories
|
||||||
|
function CategoryManager:GetCategories()
|
||||||
|
if not Guda_CharDB or not Guda_CharDB.categories then
|
||||||
|
return self:GetDefaultCategories()
|
||||||
|
end
|
||||||
|
return Guda_CharDB.categories
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get category order
|
||||||
|
function CategoryManager:GetCategoryOrder()
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
return cats.order or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get category definition by ID
|
||||||
|
function CategoryManager:GetCategory(categoryId)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
if cats.definitions then
|
||||||
|
return cats.definitions[categoryId]
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save categories to database
|
||||||
|
function CategoryManager:SaveCategories(categories)
|
||||||
|
if not Guda_CharDB then return end
|
||||||
|
Guda_CharDB.categories = categories
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add a new custom category
|
||||||
|
function CategoryManager:AddCategory(categoryId, definition)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
|
||||||
|
if cats.definitions[categoryId] then
|
||||||
|
addon:Debug("CategoryManager: Category already exists: " .. categoryId)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
definition.isBuiltIn = false
|
||||||
|
cats.definitions[categoryId] = definition
|
||||||
|
table.insert(cats.order, categoryId)
|
||||||
|
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update an existing category
|
||||||
|
function CategoryManager:UpdateCategory(categoryId, definition)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
|
||||||
|
if not cats.definitions[categoryId] then
|
||||||
|
addon:Debug("CategoryManager: Category not found: " .. categoryId)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Preserve isBuiltIn flag
|
||||||
|
definition.isBuiltIn = cats.definitions[categoryId].isBuiltIn
|
||||||
|
cats.definitions[categoryId] = definition
|
||||||
|
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Delete a category (only custom categories can be deleted)
|
||||||
|
function CategoryManager:DeleteCategory(categoryId)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local def = cats.definitions[categoryId]
|
||||||
|
|
||||||
|
if not def then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if def.isBuiltIn then
|
||||||
|
addon:Debug("CategoryManager: Cannot delete built-in category: " .. categoryId)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
cats.definitions[categoryId] = nil
|
||||||
|
|
||||||
|
-- Remove from order
|
||||||
|
for i, id in ipairs(cats.order) do
|
||||||
|
if id == categoryId then
|
||||||
|
table.remove(cats.order, i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move category up in order
|
||||||
|
function CategoryManager:MoveCategoryUp(categoryId)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
|
||||||
|
for i, id in ipairs(cats.order) do
|
||||||
|
if id == categoryId and i > 1 then
|
||||||
|
cats.order[i] = cats.order[i - 1]
|
||||||
|
cats.order[i - 1] = categoryId
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move category down in order
|
||||||
|
function CategoryManager:MoveCategoryDown(categoryId)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local count = table.getn(cats.order)
|
||||||
|
|
||||||
|
for i, id in ipairs(cats.order) do
|
||||||
|
if id == categoryId and i < count then
|
||||||
|
cats.order[i] = cats.order[i + 1]
|
||||||
|
cats.order[i + 1] = categoryId
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Toggle category enabled state
|
||||||
|
function CategoryManager:ToggleCategory(categoryId)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local def = cats.definitions[categoryId]
|
||||||
|
|
||||||
|
if def then
|
||||||
|
def.enabled = not def.enabled
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reset all categories to defaults
|
||||||
|
function CategoryManager:ResetToDefaults()
|
||||||
|
Guda_CharDB.categories = self:GetDefaultCategories()
|
||||||
|
addon:Debug("CategoryManager: Reset to default categories")
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Rule Evaluation Engine
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
-- Evaluate a single rule against item data
|
||||||
|
function CategoryManager:EvaluateRule(rule, itemData, bagID, slotID, isOtherChar)
|
||||||
|
local ruleType = rule.type
|
||||||
|
local ruleValue = rule.value
|
||||||
|
|
||||||
|
if ruleType == "itemType" then
|
||||||
|
return (itemData.class == ruleValue) or (itemData.type == ruleValue)
|
||||||
|
|
||||||
|
elseif ruleType == "itemSubtype" then
|
||||||
|
local subclass = itemData.subclass or ""
|
||||||
|
-- Check for partial match (e.g., "Food" matches "Food & Drink")
|
||||||
|
if string.find(subclass, ruleValue) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return subclass == ruleValue
|
||||||
|
|
||||||
|
elseif ruleType == "namePattern" then
|
||||||
|
local itemName = itemData.name or ""
|
||||||
|
return string.find(itemName, ruleValue) ~= nil
|
||||||
|
|
||||||
|
elseif ruleType == "quality" then
|
||||||
|
-- For quality 0 (gray/junk), also check tooltip as fallback
|
||||||
|
if ruleValue == 0 then
|
||||||
|
if itemData.quality == 0 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
-- Tooltip fallback for gray detection (current character only)
|
||||||
|
if not isOtherChar and addon.Modules.Utils and addon.Modules.Utils.IsItemGrayTooltip then
|
||||||
|
return addon.Modules.Utils:IsItemGrayTooltip(bagID, slotID, itemData.link)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return itemData.quality == ruleValue
|
||||||
|
|
||||||
|
elseif ruleType == "qualityMin" then
|
||||||
|
-- Minimum quality check (item quality >= ruleValue)
|
||||||
|
return (itemData.quality or 0) >= (ruleValue or 0)
|
||||||
|
|
||||||
|
elseif ruleType == "isBoE" then
|
||||||
|
if isOtherChar then return false end
|
||||||
|
if itemData.class ~= "Weapon" and itemData.class ~= "Armor" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local isBoE = addon.Modules.Utils:IsBindOnEquip(bagID, slotID, itemData.link)
|
||||||
|
return isBoE == ruleValue
|
||||||
|
|
||||||
|
elseif ruleType == "isQuestItem" then
|
||||||
|
-- Use consolidated quest detection from Utils
|
||||||
|
local isQuestItem, _ = addon.Modules.Utils:IsQuestItem(bagID, slotID, itemData, isOtherChar, false)
|
||||||
|
return isQuestItem == ruleValue
|
||||||
|
|
||||||
|
elseif ruleType == "texturePattern" then
|
||||||
|
local texture = itemData.texture or ""
|
||||||
|
return string.find(string.lower(texture), string.lower(ruleValue)) ~= nil
|
||||||
|
|
||||||
|
elseif ruleType == "itemID" then
|
||||||
|
if not itemData.link then return false end
|
||||||
|
local itemID = addon.Modules.Utils:ExtractItemID(itemData.link)
|
||||||
|
if not itemID then return false end
|
||||||
|
|
||||||
|
-- ruleValue can be a single ID or a table of IDs
|
||||||
|
if type(ruleValue) == "table" then
|
||||||
|
for _, id in ipairs(ruleValue) do
|
||||||
|
if itemID == tonumber(id) then return true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
-- Convert string to number if needed
|
||||||
|
return itemID == tonumber(ruleValue)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif ruleType == "isSoulShard" then
|
||||||
|
return addon.Modules.Utils:IsSoulShard(itemData.link) == ruleValue
|
||||||
|
|
||||||
|
elseif ruleType == "isProjectile" then
|
||||||
|
local isProj = (itemData.class == "Projectile" or
|
||||||
|
itemData.subclass == "Arrow" or
|
||||||
|
itemData.subclass == "Bullet")
|
||||||
|
return isProj == ruleValue
|
||||||
|
|
||||||
|
elseif ruleType == "restoreTag" then
|
||||||
|
-- restoreTag is set by tooltip scanning: "eat", "drink", or "restore"
|
||||||
|
local tag = itemData.restoreTag
|
||||||
|
if not tag then return false end
|
||||||
|
return tag == ruleValue
|
||||||
|
|
||||||
|
elseif ruleType == "isJunk" then
|
||||||
|
-- Junk items: gray items (quality 0) OR white equippable items (quality 1 + Weapon/Armor)
|
||||||
|
local quality = itemData.quality
|
||||||
|
local isGray = false
|
||||||
|
local isWhiteEquip = false
|
||||||
|
|
||||||
|
-- Check for gray items (quality 0)
|
||||||
|
if quality == 0 then
|
||||||
|
isGray = true
|
||||||
|
elseif not isOtherChar and addon.Modules.Utils and addon.Modules.Utils.IsItemGrayTooltip then
|
||||||
|
-- Tooltip fallback for gray detection
|
||||||
|
isGray = addon.Modules.Utils:IsItemGrayTooltip(bagID, slotID, itemData.link)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for white equippable items (quality 1 + Weapon/Armor)
|
||||||
|
if quality == 1 then
|
||||||
|
local itemClass = itemData.class or ""
|
||||||
|
addon:Debug("isJunk check: quality=%s, class='%s', name='%s'", tostring(quality), tostring(itemClass), tostring(itemData.name))
|
||||||
|
if itemClass == "Weapon" or itemClass == "Armor" then
|
||||||
|
isWhiteEquip = true
|
||||||
|
addon:Debug("isJunk: WHITE EQUIP DETECTED - %s", tostring(itemData.name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local isJunk = isGray or isWhiteEquip
|
||||||
|
return isJunk == ruleValue
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Evaluate all rules for a category
|
||||||
|
function CategoryManager:EvaluateCategoryRules(categoryDef, itemData, bagID, slotID, isOtherChar)
|
||||||
|
if not categoryDef.enabled then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local rules = categoryDef.rules or {}
|
||||||
|
|
||||||
|
-- No rules = fallback category (matches everything)
|
||||||
|
if table.getn(rules) == 0 then
|
||||||
|
return categoryDef.isFallback == true
|
||||||
|
end
|
||||||
|
|
||||||
|
local matchMode = categoryDef.matchMode or "any"
|
||||||
|
|
||||||
|
if matchMode == "all" then
|
||||||
|
-- All rules must match
|
||||||
|
for _, rule in ipairs(rules) do
|
||||||
|
if not self:EvaluateRule(rule, itemData, bagID, slotID, isOtherChar) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
-- Any rule must match
|
||||||
|
for _, rule in ipairs(rules) do
|
||||||
|
if self:EvaluateRule(rule, itemData, bagID, slotID, isOtherChar) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get sorted categories by priority (highest first)
|
||||||
|
function CategoryManager:GetCategoriesByPriority()
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local sorted = {}
|
||||||
|
|
||||||
|
for id, def in pairs(cats.definitions) do
|
||||||
|
if def.enabled then
|
||||||
|
table.insert(sorted, { id = id, def = def })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(sorted, function(a, b)
|
||||||
|
return (a.def.priority or 0) > (b.def.priority or 0)
|
||||||
|
end)
|
||||||
|
|
||||||
|
return sorted
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Categorize an item using the rule engine
|
||||||
|
-- Returns category ID or "Miscellaneous" as fallback
|
||||||
|
function CategoryManager:CategorizeItem(itemData, bagID, slotID, isOtherChar)
|
||||||
|
local sortedCats = self:GetCategoriesByPriority()
|
||||||
|
|
||||||
|
-- Debug: show white item categorization
|
||||||
|
if itemData.quality == 1 and (itemData.class == "Weapon" or itemData.class == "Armor") then
|
||||||
|
addon:Debug("Categorizing white equip: %s (class=%s, quality=%s)", tostring(itemData.name), tostring(itemData.class), tostring(itemData.quality))
|
||||||
|
for i, entry in ipairs(sortedCats) do
|
||||||
|
addon:Debug(" Cat %d: %s (priority=%s, enabled=%s)", i, entry.id, tostring(entry.def.priority), tostring(entry.def.enabled))
|
||||||
|
if i > 10 then break end -- Limit debug output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, entry in ipairs(sortedCats) do
|
||||||
|
if not entry.def.isFallback then
|
||||||
|
local matches = self:EvaluateCategoryRules(entry.def, itemData, bagID, slotID, isOtherChar)
|
||||||
|
-- Debug: show which category matched for white equip
|
||||||
|
if itemData.quality == 1 and (itemData.class == "Weapon" or itemData.class == "Armor") and matches then
|
||||||
|
addon:Debug(" -> MATCHED: %s", entry.id)
|
||||||
|
end
|
||||||
|
if matches then
|
||||||
|
return entry.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return "Miscellaneous"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build the Guda_CategoryList from current category order (for compatibility)
|
||||||
|
function CategoryManager:BuildCategoryList()
|
||||||
|
local order = self:GetCategoryOrder()
|
||||||
|
local list = {}
|
||||||
|
|
||||||
|
for _, id in ipairs(order) do
|
||||||
|
local def = self:GetCategory(id)
|
||||||
|
if def and def.enabled then
|
||||||
|
table.insert(list, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get available rule types for UI
|
||||||
|
function CategoryManager:GetRuleTypes()
|
||||||
|
return {
|
||||||
|
{ id = "itemType", name = "Item Type", description = "Match by item class (Armor, Weapon, etc.)" },
|
||||||
|
{ id = "itemSubtype", name = "Item Subtype", description = "Match by item subclass (Cloth, Potion, etc.)" },
|
||||||
|
{ id = "namePattern", name = "Name Pattern", description = "Match item name (supports Lua patterns)" },
|
||||||
|
{ id = "quality", name = "Quality", description = "Match by item quality (0=Gray to 5=Legendary)" },
|
||||||
|
{ id = "qualityMin", name = "Quality (min)", description = "Match items with at least this quality" },
|
||||||
|
{ id = "isBoE", name = "Bind on Equip", description = "Match items that bind when equipped" },
|
||||||
|
{ id = "isQuestItem", name = "Quest Item", description = "Match quest items" },
|
||||||
|
{ id = "isJunk", name = "Is Junk", description = "Match junk items (gray + white equippable)" },
|
||||||
|
{ id = "texturePattern", name = "Icon Pattern", description = "Match icon texture path" },
|
||||||
|
{ id = "itemID", name = "Item ID", description = "Match specific item IDs" },
|
||||||
|
{ id = "isSoulShard", name = "Soul Shard", description = "Match soul shards" },
|
||||||
|
{ id = "isProjectile", name = "Projectile", description = "Match arrows and bullets" },
|
||||||
|
{ id = "restoreTag", name = "Restore Type", description = "Match by consumable type (eat, drink, restore)" },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get common item types for UI dropdowns
|
||||||
|
function CategoryManager:GetItemTypes()
|
||||||
|
return {
|
||||||
|
"Armor", "Weapon", "Consumable", "Container", "Trade Goods",
|
||||||
|
"Projectile", "Quiver", "Reagent", "Recipe", "Key", "Miscellaneous", "Quest"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get quality names for UI
|
||||||
|
function CategoryManager:GetQualityNames()
|
||||||
|
return {
|
||||||
|
[0] = "Poor (Gray)",
|
||||||
|
[1] = "Common (White)",
|
||||||
|
[2] = "Uncommon (Green)",
|
||||||
|
[3] = "Rare (Blue)",
|
||||||
|
[4] = "Epic (Purple)",
|
||||||
|
[5] = "Legendary (Orange)",
|
||||||
|
}
|
||||||
|
end
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
-- Guda Constants Module
|
||||||
|
-- Centralized location for all magic numbers and hardcoded values
|
||||||
|
|
||||||
|
local addon = Guda
|
||||||
|
|
||||||
|
-- Extend the existing Constants table from Init.lua
|
||||||
|
local C = addon.Constants
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Bag IDs
|
||||||
|
--=============================================================================
|
||||||
|
C.BAG_BACKPACK = 0
|
||||||
|
C.BAG_FIRST = 1
|
||||||
|
C.BAG_LAST = 4
|
||||||
|
C.BANK_FIRST = 5
|
||||||
|
C.BANK_LAST = 11
|
||||||
|
C.KEYRING_BAG = -2
|
||||||
|
C.BANK_CONTAINER = -1
|
||||||
|
|
||||||
|
-- Bag ID ranges for iteration
|
||||||
|
C.BAG_IDS = {0, 1, 2, 3, 4}
|
||||||
|
C.BANK_BAG_IDS = {5, 6, 7, 8, 9, 10, 11}
|
||||||
|
C.ALL_BAG_IDS = {0, 1, 2, 3, 4, -2} -- Including keyring
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Equipment Slots
|
||||||
|
--=============================================================================
|
||||||
|
C.EQUIPMENT_SLOT_FIRST = 1
|
||||||
|
C.EQUIPMENT_SLOT_LAST = 19
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Item IDs
|
||||||
|
--=============================================================================
|
||||||
|
C.SOUL_SHARD_ID = 6265
|
||||||
|
C.HEARTHSTONE_ID = 6948
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Colors (r, g, b tables for easy unpacking)
|
||||||
|
--=============================================================================
|
||||||
|
C.COLORS = {
|
||||||
|
-- Special item borders
|
||||||
|
KEYRING_CYAN = {r = 0.2, g = 0.8, b = 1.0},
|
||||||
|
QUEST_GOLD = {r = 1.0, g = 0.82, b = 0},
|
||||||
|
|
||||||
|
-- Text colors
|
||||||
|
GRAY_TEXT = {r = 0.5, g = 0.5, b = 0.5},
|
||||||
|
WHITE_TEXT = {r = 1.0, g = 1.0, b = 1.0},
|
||||||
|
GOLD_TITLE = {r = 1.0, g = 0.82, b = 0},
|
||||||
|
CYAN_LABEL = {r = 0, g = 1.0, b = 1.0},
|
||||||
|
|
||||||
|
-- Unusable item tint
|
||||||
|
UNUSABLE_RED = {r = 0.9, g = 0.2, b = 0.2, a = 0.45},
|
||||||
|
|
||||||
|
-- Lock/desaturate
|
||||||
|
LOCKED_GRAY = {r = 0.5, g = 0.5, b = 0.5},
|
||||||
|
}
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- UI Thresholds
|
||||||
|
--=============================================================================
|
||||||
|
C.ICON_SIZE_THRESHOLD = 44 -- Below this, use smaller insets/padding
|
||||||
|
C.ICON_INSET_SMALL = 10 -- Inset for small icons
|
||||||
|
C.ICON_INSET_LARGE = 15 -- Inset for large icons
|
||||||
|
C.ICON_TEXCOORD_CROP = 0.08 -- Texture coordinate crop amount
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Database / Cleanup
|
||||||
|
--=============================================================================
|
||||||
|
C.CLEANUP_OLD_CHARS_DAYS = 90
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Tooltip Scanning
|
||||||
|
--=============================================================================
|
||||||
|
C.QUEST_TOOLTIP_PATTERNS = {
|
||||||
|
"quest starter",
|
||||||
|
"this item begins a quest",
|
||||||
|
"starts a quest",
|
||||||
|
"quest item",
|
||||||
|
"manual",
|
||||||
|
}
|
||||||
|
|
||||||
|
C.BIND_ON_EQUIP_PATTERN = "binds when equipped"
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Specialized Bag Types
|
||||||
|
--=============================================================================
|
||||||
|
C.BAG_TYPES = {
|
||||||
|
SOUL = "soul",
|
||||||
|
HERB = "herb",
|
||||||
|
ENCHANT = "enchant",
|
||||||
|
QUIVER = "quiver",
|
||||||
|
AMMO = "ammo",
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Tooltip patterns for bag type detection
|
||||||
|
C.BAG_TYPE_PATTERNS = {
|
||||||
|
soul = {"soul bag", "soul pouch"},
|
||||||
|
herb = {"herb bag"},
|
||||||
|
enchant = {"enchanting bag"},
|
||||||
|
quiver = {"quiver"},
|
||||||
|
ammo = {"ammo pouch"},
|
||||||
|
}
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Item Categories (for GetItemInfo)
|
||||||
|
--=============================================================================
|
||||||
|
C.ITEM_CATEGORIES = {
|
||||||
|
WEAPON = "Weapon",
|
||||||
|
ARMOR = "Armor",
|
||||||
|
CONSUMABLE = "Consumable",
|
||||||
|
CONTAINER = "Container",
|
||||||
|
TRADE_GOODS = "Trade Goods",
|
||||||
|
PROJECTILE = "Projectile",
|
||||||
|
QUIVER = "Quiver",
|
||||||
|
REAGENT = "Reagent",
|
||||||
|
RECIPE = "Recipe",
|
||||||
|
KEY = "Key",
|
||||||
|
MISCELLANEOUS = "Miscellaneous",
|
||||||
|
QUEST = "Quest",
|
||||||
|
}
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Money Formatting
|
||||||
|
--=============================================================================
|
||||||
|
C.MONEY = {
|
||||||
|
COPPER_PER_SILVER = 100,
|
||||||
|
SILVER_PER_GOLD = 100,
|
||||||
|
COPPER_PER_GOLD = 10000,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Color codes for money display
|
||||||
|
C.MONEY_COLORS = {
|
||||||
|
GOLD = "|cFFFFD700",
|
||||||
|
SILVER = "|cFFC7C7CF",
|
||||||
|
COPPER = "|cFFEDA55F",
|
||||||
|
WHITE = "|cFFFFFFFF",
|
||||||
|
}
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Frame Constants
|
||||||
|
--=============================================================================
|
||||||
|
C.FRAME = {
|
||||||
|
TITLE_HEIGHT = 40,
|
||||||
|
SEARCH_BAR_HEIGHT = 30,
|
||||||
|
FOOTER_HEIGHT = 45,
|
||||||
|
FOOTER_HEIGHT_HIDDEN = 10,
|
||||||
|
MIN_WIDTH = 200,
|
||||||
|
MIN_HEIGHT = 150,
|
||||||
|
MAX_WIDTH = 1250,
|
||||||
|
MAX_HEIGHT = 1000,
|
||||||
|
}
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Bank Slots
|
||||||
|
--=============================================================================
|
||||||
|
C.BANK_MAIN_SLOTS = 24 -- Number of slots in main bank container
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Tooltip Hook Settings
|
||||||
|
--=============================================================================
|
||||||
|
C.TOOLTIP = {
|
||||||
|
DEBOUNCE_TIME = 0.2, -- Seconds to wait before clearing cache
|
||||||
|
MAX_MONEY_FRAMES = 8, -- Maximum number of money frames to search
|
||||||
|
}
|
||||||
|
|
||||||
|
addon:Debug("Constants module loaded")
|
||||||
@@ -122,6 +122,11 @@ function DB:Initialize()
|
|||||||
Guda_CharDB.settings.markUnusableItems = true
|
Guda_CharDB.settings.markUnusableItems = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Initialize CategoryManager for custom categories
|
||||||
|
if addon.Modules.CategoryManager then
|
||||||
|
addon.Modules.CategoryManager:Initialize()
|
||||||
|
end
|
||||||
|
|
||||||
-- Initialize this character's data
|
-- Initialize this character's data
|
||||||
if not Guda_DB.characters[fullName] then
|
if not Guda_DB.characters[fullName] then
|
||||||
local localizedClass, englishClass = UnitClass("player")
|
local localizedClass, englishClass = UnitClass("player")
|
||||||
|
|||||||
+200
-240
@@ -4,261 +4,221 @@ local addon = Guda
|
|||||||
local Tooltip = {}
|
local Tooltip = {}
|
||||||
addon.Modules.Tooltip = Tooltip
|
addon.Modules.Tooltip = Tooltip
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Item Counting Helper Functions (extracted for clarity and reuse)
|
||||||
|
--=============================================================================
|
||||||
|
|
||||||
-- Helper function to get item ID from link (Lua 5.0 compatible)
|
-- Helper function to get item ID from link (Lua 5.0 compatible)
|
||||||
local function GetItemIDFromLink(link)
|
local function GetItemIDFromLink(link)
|
||||||
if not link then return nil end
|
if not link then return nil end
|
||||||
if type(link) == "number" then return link end
|
if type(link) == "number" then return link end
|
||||||
|
local _, _, itemID = string.find(link, "item:(%d+)")
|
||||||
-- Try to find itemID in a standard link or a raw item:ID string
|
return itemID and tonumber(itemID) or nil
|
||||||
local _, _, itemID = string.find(link, "item:(%d+)")
|
|
||||||
return itemID and tonumber(itemID) or nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Count items in saved bag/bank data structure
|
||||||
|
-- Used for both bags and bank counting from saved character data
|
||||||
|
local function CountFromSavedContainers(containersData, itemID)
|
||||||
|
local count = 0
|
||||||
|
if not containersData or type(containersData) ~= "table" then
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
for bagID, bagData in pairs(containersData) do
|
||||||
|
if bagData and type(bagData) == "table" and bagData.slots and type(bagData.slots) == "table" then
|
||||||
|
for slotID, itemData in pairs(bagData.slots) do
|
||||||
|
if itemData and type(itemData) == "table" and itemData.link then
|
||||||
|
local slotItemID = GetItemIDFromLink(itemData.link)
|
||||||
|
if slotItemID == itemID then
|
||||||
|
count = count + (itemData.count or 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Count items in saved mailbox data structure
|
||||||
|
local function CountFromSavedMailbox(mailboxData, itemID)
|
||||||
|
local count = 0
|
||||||
|
if not mailboxData or type(mailboxData) ~= "table" then
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, mail in ipairs(mailboxData) do
|
||||||
|
local itemsToCheck = mail.items or (mail.item and {mail.item}) or {}
|
||||||
|
for _, item in ipairs(itemsToCheck) do
|
||||||
|
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
||||||
|
if slotItemID == itemID then
|
||||||
|
count = count + (item.count or 1)
|
||||||
|
elseif not slotItemID and item.name then
|
||||||
|
-- Fallback to name matching if link is missing
|
||||||
|
local targetName = GetItemInfo(itemID)
|
||||||
|
if targetName == item.name then
|
||||||
|
count = count + (item.count or 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Count items in saved equipped data structure
|
||||||
|
local function CountFromSavedEquipped(equippedData, itemID)
|
||||||
|
local count = 0
|
||||||
|
if not equippedData or type(equippedData) ~= "table" then
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
for slotName, itemData in pairs(equippedData) do
|
||||||
|
if itemData and type(itemData) == "table" and itemData.link then
|
||||||
|
local slotItemID = GetItemIDFromLink(itemData.link)
|
||||||
|
if slotItemID == itemID then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Count items in live container (bags or bank)
|
||||||
|
local function CountFromLiveContainer(bagIDs, itemID)
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for _, bagID in ipairs(bagIDs) do
|
||||||
|
local numSlots = GetContainerNumSlots(bagID)
|
||||||
|
if numSlots and numSlots > 0 then
|
||||||
|
for slot = 1, numSlots do
|
||||||
|
local link = GetContainerItemLink(bagID, slot)
|
||||||
|
if link then
|
||||||
|
local slotItemID = GetItemIDFromLink(link)
|
||||||
|
if slotItemID == itemID then
|
||||||
|
local _, itemCount = GetContainerItemInfo(bagID, slot)
|
||||||
|
count = count + (itemCount or 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Count items in live mailbox
|
||||||
|
local function CountFromLiveMailbox(itemID)
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
if not (addon.Modules.MailboxScanner and addon.Modules.MailboxScanner:IsMailboxOpen()) then
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
local numInboxItems = GetInboxNumItems()
|
||||||
|
for i = 1, numInboxItems do
|
||||||
|
local _, _, _, _, _, _, _, hasItem = GetInboxHeaderInfo(i)
|
||||||
|
if hasItem then
|
||||||
|
local numAttachments = GetInboxNumAttachments and GetInboxNumAttachments(i) or 1
|
||||||
|
if numAttachments == 0 and hasItem then
|
||||||
|
numAttachments = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
for j = 1, numAttachments do
|
||||||
|
local name, _, itemCount = GetInboxItem(i, j)
|
||||||
|
if name then
|
||||||
|
local itemLink = addon.Modules.Utils:GetInboxItemLink(i, j)
|
||||||
|
if itemLink then
|
||||||
|
local slotItemID = GetItemIDFromLink(itemLink)
|
||||||
|
if slotItemID == itemID then
|
||||||
|
count = count + (itemCount or 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Count items in live equipment slots
|
||||||
|
local function CountFromLiveEquipped(itemID)
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for slotID = 1, 19 do
|
||||||
|
local link = GetInventoryItemLink("player", slotID)
|
||||||
|
if link then
|
||||||
|
local slotItemID = GetItemIDFromLink(link)
|
||||||
|
if slotItemID == itemID then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- Main Counting Functions
|
||||||
|
--=============================================================================
|
||||||
|
|
||||||
|
-- Count items for current character using live game data
|
||||||
local function CountCurrentCharacterItems(itemID)
|
local function CountCurrentCharacterItems(itemID)
|
||||||
local bagCount = 0
|
local bagCount = 0
|
||||||
local bankCount = 0
|
local bankCount = 0
|
||||||
local mailCount = 0
|
local mailCount = 0
|
||||||
local equippedCount = 0
|
local equippedCount = 0
|
||||||
|
|
||||||
-- Count current character's bags in real-time
|
-- Count bags in real-time
|
||||||
local bagsToCount = {0, 1, 2, 3, 4, -2}
|
bagCount = CountFromLiveContainer({0, 1, 2, 3, 4, -2}, itemID)
|
||||||
for _, bagID in ipairs(bagsToCount) do
|
|
||||||
local numSlots = GetContainerNumSlots(bagID)
|
|
||||||
for slot = 1, numSlots do
|
|
||||||
local link = GetContainerItemLink(bagID, slot)
|
|
||||||
if link then
|
|
||||||
local slotItemID = GetItemIDFromLink(link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
local _, count = GetContainerItemInfo(bagID, slot)
|
|
||||||
bagCount = bagCount + (count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Count current character's bank in real-time if bank is open
|
-- Count bank: live if open, otherwise from saved data
|
||||||
local bankFrame = getglobal("BankFrame")
|
local bankFrame = getglobal("BankFrame")
|
||||||
if bankFrame and bankFrame:IsVisible() then
|
if bankFrame and bankFrame:IsVisible() then
|
||||||
-- Main bank slots (-1)
|
-- Main bank + bank bags
|
||||||
local numMainSlots = GetContainerNumSlots(-1) or 24
|
bankCount = CountFromLiveContainer({-1, 5, 6, 7, 8, 9, 10, 11}, itemID)
|
||||||
for slot = 1, numMainSlots do
|
else
|
||||||
local link = GetContainerItemLink(-1, slot)
|
-- Use saved data
|
||||||
if link then
|
local playerName = addon.Modules.DB:GetPlayerFullName()
|
||||||
local slotItemID = GetItemIDFromLink(link)
|
local charData = Guda_DB and Guda_DB.characters and Guda_DB.characters[playerName]
|
||||||
if slotItemID == itemID then
|
if charData then
|
||||||
local _, count = GetContainerItemInfo(-1, slot)
|
bankCount = CountFromSavedContainers(charData.bank, itemID)
|
||||||
bankCount = bankCount + (count or 1)
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Bank bags (5-11)
|
-- Count mailbox: live if open, otherwise from saved data
|
||||||
for bagID = 5, 11 do
|
if addon.Modules.MailboxScanner and addon.Modules.MailboxScanner:IsMailboxOpen() then
|
||||||
local numSlots = GetContainerNumSlots(bagID)
|
mailCount = CountFromLiveMailbox(itemID)
|
||||||
if numSlots and numSlots > 0 then
|
else
|
||||||
for slot = 1, numSlots do
|
local playerName = addon.Modules.DB:GetPlayerFullName()
|
||||||
local link = GetContainerItemLink(bagID, slot)
|
local charData = Guda_DB and Guda_DB.characters and Guda_DB.characters[playerName]
|
||||||
if link then
|
if charData then
|
||||||
local slotItemID = GetItemIDFromLink(link)
|
mailCount = CountFromSavedMailbox(charData.mailbox, itemID)
|
||||||
if slotItemID == itemID then
|
end
|
||||||
local _, count = GetContainerItemInfo(bagID, slot)
|
end
|
||||||
bankCount = bankCount + (count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Bank not open - use saved data for bank counts
|
|
||||||
local playerName = addon.Modules.DB:GetPlayerFullName()
|
|
||||||
local charData = Guda_DB and Guda_DB.characters and Guda_DB.characters[playerName]
|
|
||||||
if charData and charData.bank and type(charData.bank) == "table" then
|
|
||||||
for bagID, bagData in pairs(charData.bank) do
|
|
||||||
if bagData and type(bagData) == "table" and bagData.slots and type(bagData.slots) == "table" then
|
|
||||||
for slotID, itemData in pairs(bagData.slots) do
|
|
||||||
if itemData and type(itemData) == "table" and itemData.link then
|
|
||||||
local slotItemID = GetItemIDFromLink(itemData.link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
bankCount = bankCount + (itemData.count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Count current character's mailbox in real-time if mailbox is open
|
-- Count equipped items in real-time
|
||||||
if addon.Modules.MailboxScanner and addon.Modules.MailboxScanner:IsMailboxOpen() then
|
equippedCount = CountFromLiveEquipped(itemID)
|
||||||
local numInboxItems = GetInboxNumItems()
|
|
||||||
for i = 1, numInboxItems do
|
|
||||||
local _, _, _, _, _, _, _, hasItem = GetInboxHeaderInfo(i)
|
|
||||||
if hasItem then
|
|
||||||
-- Turtle WoW supports up to 12 attachments per mail.
|
|
||||||
-- We use GetInboxNumAttachments if available to avoid over-scanning.
|
|
||||||
local numAttachments = 0
|
|
||||||
if GetInboxNumAttachments then
|
|
||||||
numAttachments = GetInboxNumAttachments(i) or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Fallback: if we don't have the count but header says there's an item, assume at least 1.
|
return bagCount, bankCount, equippedCount, mailCount
|
||||||
if numAttachments == 0 and hasItem then
|
|
||||||
numAttachments = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
for j = 1, numAttachments do -- Turtle WoW supports up to 12 attachments
|
|
||||||
local name, _, count = GetInboxItem(i, j)
|
|
||||||
if name then
|
|
||||||
local itemLink = addon.Modules.Utils:GetInboxItemLink(i, j)
|
|
||||||
if itemLink then
|
|
||||||
local slotItemID = GetItemIDFromLink(itemLink)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
mailCount = mailCount + (count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Mailbox not open - use saved data
|
|
||||||
local playerName = addon.Modules.DB:GetPlayerFullName()
|
|
||||||
local charData = Guda_DB and Guda_DB.characters and Guda_DB.characters[playerName]
|
|
||||||
if charData and charData.mailbox and type(charData.mailbox) == "table" then
|
|
||||||
for _, mail in ipairs(charData.mailbox) do
|
|
||||||
if mail.items then
|
|
||||||
for _, item in ipairs(mail.items) do
|
|
||||||
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
mailCount = mailCount + (item.count or 1)
|
|
||||||
elseif not slotItemID and item.name then
|
|
||||||
-- Fallback to name matching if link is missing
|
|
||||||
local targetName = GetItemInfo(itemID)
|
|
||||||
if targetName == item.name then
|
|
||||||
mailCount = mailCount + (item.count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif mail.item then -- Fallback for single item data structure
|
|
||||||
local item = mail.item
|
|
||||||
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
mailCount = mailCount + (item.count or 1)
|
|
||||||
elseif not slotItemID and item.name then
|
|
||||||
-- Fallback to name matching if link is missing
|
|
||||||
local targetName = GetItemInfo(itemID)
|
|
||||||
if targetName == item.name then
|
|
||||||
mailCount = mailCount + (item.count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Count equipped items in real-time
|
|
||||||
for slotID = 1, 19 do -- All equipment slots
|
|
||||||
local link = GetInventoryItemLink("player", slotID)
|
|
||||||
if link then
|
|
||||||
local slotItemID = GetItemIDFromLink(link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
equippedCount = equippedCount + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return bagCount, bankCount, equippedCount, mailCount
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Count items for a specific character with real-time data for current character
|
-- Count items for a specific character (current or other)
|
||||||
local function CountItemsForCharacter(itemID, characterData, isCurrentChar)
|
local function CountItemsForCharacter(itemID, characterData, isCurrentChar)
|
||||||
-- For current character, use real-time counting to avoid database sync issues
|
-- For current character, use real-time counting
|
||||||
if isCurrentChar then
|
if isCurrentChar then
|
||||||
return CountCurrentCharacterItems(itemID)
|
return CountCurrentCharacterItems(itemID)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For other characters, use saved data
|
-- For other characters, use saved data
|
||||||
local bagCount = 0
|
local bagCount = CountFromSavedContainers(characterData.bags, itemID)
|
||||||
local bankCount = 0
|
local bankCount = CountFromSavedContainers(characterData.bank, itemID)
|
||||||
local mailCount = 0
|
local mailCount = CountFromSavedMailbox(characterData.mailbox, itemID)
|
||||||
local equippedCount = 0
|
local equippedCount = CountFromSavedEquipped(characterData.equipped, itemID)
|
||||||
|
|
||||||
-- Count bags from saved data
|
return bagCount, bankCount, equippedCount, mailCount
|
||||||
if characterData.bags and type(characterData.bags) == "table" then
|
|
||||||
for bagID, bagData in pairs(characterData.bags) do
|
|
||||||
if bagData and type(bagData) == "table" and bagData.slots and type(bagData.slots) == "table" then
|
|
||||||
for slotID, itemData in pairs(bagData.slots) do
|
|
||||||
if itemData and type(itemData) == "table" and itemData.link then
|
|
||||||
local slotItemID = GetItemIDFromLink(itemData.link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
bagCount = bagCount + (itemData.count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Count bank from saved data
|
|
||||||
if characterData.bank and type(characterData.bank) == "table" then
|
|
||||||
for bagID, bagData in pairs(characterData.bank) do
|
|
||||||
if bagData and type(bagData) == "table" and bagData.slots and type(bagData.slots) == "table" then
|
|
||||||
for slotID, itemData in pairs(bagData.slots) do
|
|
||||||
if itemData and type(itemData) == "table" and itemData.link then
|
|
||||||
local slotItemID = GetItemIDFromLink(itemData.link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
bankCount = bankCount + (itemData.count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Count mailbox from saved data
|
|
||||||
if characterData.mailbox and type(characterData.mailbox) == "table" then
|
|
||||||
for _, mail in ipairs(characterData.mailbox) do
|
|
||||||
if mail.items then
|
|
||||||
for _, item in ipairs(mail.items) do
|
|
||||||
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
mailCount = mailCount + (item.count or 1)
|
|
||||||
elseif not slotItemID and item.name then
|
|
||||||
-- Fallback to name matching if link is missing
|
|
||||||
local targetName = GetItemInfo(itemID)
|
|
||||||
if targetName == item.name then
|
|
||||||
mailCount = mailCount + (item.count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif mail.item then -- Fallback for single item data structure
|
|
||||||
local item = mail.item
|
|
||||||
local slotItemID = item.link and GetItemIDFromLink(item.link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
mailCount = mailCount + (item.count or 1)
|
|
||||||
elseif not slotItemID and item.name then
|
|
||||||
-- Fallback to name matching if link is missing
|
|
||||||
local targetName = GetItemInfo(itemID)
|
|
||||||
if targetName == item.name then
|
|
||||||
mailCount = mailCount + (item.count or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Count equipped items from saved data
|
|
||||||
if characterData.equipped and type(characterData.equipped) == "table" then
|
|
||||||
for slotName, itemData in pairs(characterData.equipped) do
|
|
||||||
if itemData and type(itemData) == "table" and itemData.link then
|
|
||||||
local slotItemID = GetItemIDFromLink(itemData.link)
|
|
||||||
if slotItemID == itemID then
|
|
||||||
equippedCount = equippedCount + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return bagCount, bankCount, equippedCount, mailCount
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+241
-210
@@ -5,6 +5,64 @@ local addon = Guda
|
|||||||
local Utils = {}
|
local Utils = {}
|
||||||
addon.Modules.Utils = Utils
|
addon.Modules.Utils = Utils
|
||||||
|
|
||||||
|
--=============================================================================
|
||||||
|
-- SafeCall: Nil-safe module method invocation
|
||||||
|
-- Replaces verbose nil-checks like:
|
||||||
|
-- if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.Method then
|
||||||
|
-- return addon.Modules.Utils:Method(arg1, arg2)
|
||||||
|
-- end
|
||||||
|
-- With:
|
||||||
|
-- return Utils:SafeCall("Utils", "Method", arg1, arg2)
|
||||||
|
--=============================================================================
|
||||||
|
|
||||||
|
-- Call a method on a module safely, returns nil if module/method doesn't exist
|
||||||
|
-- Parameters:
|
||||||
|
-- moduleName: Name of the module in addon.Modules (e.g., "Utils", "DB", "BagFrame")
|
||||||
|
-- methodName: Name of the method to call (e.g., "GetQualityColor", "GetSetting")
|
||||||
|
-- ...: Arguments to pass to the method
|
||||||
|
-- Returns: The return value(s) of the method, or nil if not callable
|
||||||
|
function Utils:SafeCall(moduleName, methodName, ...)
|
||||||
|
if not addon or not addon.Modules then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local module = addon.Modules[moduleName]
|
||||||
|
if not module then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local method = module[methodName]
|
||||||
|
if not method or type(method) ~= "function" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Call method with module as self (for : style calls)
|
||||||
|
return method(module, unpack(arg))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if a module method exists without calling it
|
||||||
|
function Utils:HasMethod(moduleName, methodName)
|
||||||
|
if not addon or not addon.Modules then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local module = addon.Modules[moduleName]
|
||||||
|
if not module then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local method = module[methodName]
|
||||||
|
return method ~= nil and type(method) == "function"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get a module reference safely
|
||||||
|
function Utils:GetModule(moduleName)
|
||||||
|
if not addon or not addon.Modules then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return addon.Modules[moduleName]
|
||||||
|
end
|
||||||
|
|
||||||
-- Format money (copper to gold/silver/copper string) - WoW 1.12.1 version
|
-- Format money (copper to gold/silver/copper string) - WoW 1.12.1 version
|
||||||
function Utils:FormatMoney(copper, showZero, useColors)
|
function Utils:FormatMoney(copper, showZero, useColors)
|
||||||
if not copper or copper == 0 then
|
if not copper or copper == 0 then
|
||||||
@@ -190,49 +248,123 @@ local function GetScanTooltip()
|
|||||||
return scanTooltip
|
return scanTooltip
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if an item is a quest item by scanning its tooltip
|
-- Check if an item is a quest item by scanning its tooltip (internal helper)
|
||||||
function Utils:IsQuestItemTooltip(bagID, slotID)
|
-- Returns: isQuestItem, isQuestStarter
|
||||||
if not bagID or not slotID then return false end
|
local function ScanTooltipForQuest(tooltip, tooltipName)
|
||||||
|
local isQuestItem = false
|
||||||
|
local isQuestStarter = false
|
||||||
|
|
||||||
local tooltip = GetScanTooltip()
|
|
||||||
tooltip:ClearLines()
|
|
||||||
tooltip:SetBagItem(bagID, slotID)
|
|
||||||
|
|
||||||
-- Check tooltip lines for explicit quest-related phrases (case-insensitive)
|
|
||||||
for i = 1, tooltip:NumLines() do
|
for i = 1, tooltip:NumLines() do
|
||||||
local line = getglobal("GudaBagScanTooltipTextLeft" .. i)
|
local line = getglobal(tooltipName .. "TextLeft" .. i)
|
||||||
if line then
|
if line then
|
||||||
local text = line:GetText()
|
local text = line:GetText()
|
||||||
if text then
|
if text then
|
||||||
local tl = string.lower(text)
|
local tl = string.lower(text)
|
||||||
-- Only match explicit quest markers to avoid misclassifying consumables/recipes
|
-- Check for quest starter patterns first
|
||||||
-- Include "manual" which some quest items use (same as ItemButton detection)
|
|
||||||
if string.find(tl, "quest starter") or
|
if string.find(tl, "quest starter") or
|
||||||
string.find(tl, "this item begins a quest") or
|
string.find(tl, "this item begins a quest") or
|
||||||
string.find(tl, "starts a quest") or
|
string.find(tl, "starts a quest") then
|
||||||
string.find(tl, "quest item") or
|
isQuestItem = true
|
||||||
string.find(tl, "manual") then
|
isQuestStarter = true
|
||||||
|
break
|
||||||
-- Double check category to avoid misidentifying equipment with "Use:"
|
-- Check for regular quest item patterns
|
||||||
local link = GetContainerItemLink(bagID, slotID)
|
elseif string.find(tl, "quest item") or
|
||||||
if link and self.ExtractItemID and self.GetItemInfoSafe then
|
string.find(tl, "manual") then
|
||||||
local itemID = self:ExtractItemID(link)
|
isQuestItem = true
|
||||||
if itemID then
|
-- Don't break, might still find a quest starter pattern
|
||||||
local _, _, _, _, itemCategory, itemType = self:GetItemInfoSafe(itemID)
|
|
||||||
-- If it's Weapon or Armor, and NOT categorized as Quest, then it's not a Quest Item
|
|
||||||
if (itemCategory == "Weapon" or itemCategory == "Armor" or itemType == "Weapon" or itemType == "Armor") and
|
|
||||||
(itemCategory ~= "Quest" and itemType ~= "Quest") then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
|
||||||
|
return isQuestItem, isQuestStarter
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Consolidated quest item detection function
|
||||||
|
-- Handles tooltip scanning, category checks, equipment filtering, and QuestItemsDB lookup
|
||||||
|
-- Parameters:
|
||||||
|
-- bagID, slotID: Required for tooltip scanning (can be nil for other char items)
|
||||||
|
-- itemData: Optional item data table with link, class/category, type fields
|
||||||
|
-- isOtherChar: Boolean, true if checking an item from another character's saved data
|
||||||
|
-- isBank: Boolean, true if the item is in the bank
|
||||||
|
-- Returns: isQuestItem (boolean), isQuestStarter (boolean)
|
||||||
|
function Utils:IsQuestItem(bagID, slotID, itemData, isOtherChar, isBank)
|
||||||
|
bagID = tonumber(bagID)
|
||||||
|
slotID = tonumber(slotID)
|
||||||
|
|
||||||
|
local isQuestItem = false
|
||||||
|
local isQuestStarter = false
|
||||||
|
|
||||||
|
-- Get item link and category info
|
||||||
|
local itemLink = itemData and itemData.link
|
||||||
|
local itemCategory = itemData and (itemData.class or itemData.category) or ""
|
||||||
|
local itemType = itemData and itemData.type or ""
|
||||||
|
local itemID
|
||||||
|
|
||||||
|
-- For live items, query the link directly
|
||||||
|
if not isOtherChar and bagID and slotID then
|
||||||
|
itemLink = GetContainerItemLink(bagID, slotID)
|
||||||
|
end
|
||||||
|
|
||||||
|
if itemLink then
|
||||||
|
itemID = self:ExtractItemID(itemLink)
|
||||||
|
if itemID then
|
||||||
|
local _, _, _, _, cat, typ = self:GetItemInfoSafe(itemID)
|
||||||
|
itemCategory = cat or itemCategory
|
||||||
|
itemType = typ or itemType
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if item is equipment (should not be classified as quest unless explicitly Quest category)
|
||||||
|
local isEquipment = (itemCategory == "Weapon" or itemCategory == "Armor" or
|
||||||
|
itemType == "Weapon" or itemType == "Armor")
|
||||||
|
local isQuestCategory = (itemCategory == "Quest" or itemType == "Quest")
|
||||||
|
|
||||||
|
-- Priority 1: If explicitly categorized as Quest, it's a quest item
|
||||||
|
if isQuestCategory then
|
||||||
|
return true, false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Priority 2: Tooltip scanning for current character items
|
||||||
|
if not isOtherChar and bagID and slotID then
|
||||||
|
local tooltip = GetScanTooltip()
|
||||||
|
tooltip:ClearLines()
|
||||||
|
|
||||||
|
-- Handle bank items differently
|
||||||
|
if isBank and bagID == -1 then
|
||||||
|
if tooltip.SetInventoryItem then
|
||||||
|
tooltip:SetInventoryItem("player", 39 + slotID)
|
||||||
|
else
|
||||||
|
tooltip:SetBagItem(bagID, slotID)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
tooltip:SetBagItem(bagID, slotID)
|
||||||
|
end
|
||||||
|
|
||||||
|
isQuestItem, isQuestStarter = ScanTooltipForQuest(tooltip, "GudaBagScanTooltip")
|
||||||
|
|
||||||
|
-- Filter out equipment that has quest-like text but isn't categorized as Quest
|
||||||
|
if isQuestItem and isEquipment and not isQuestCategory then
|
||||||
|
isQuestItem = false
|
||||||
|
isQuestStarter = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Priority 3: Check QuestItemsDB for known faction-specific quest items
|
||||||
|
if not isQuestItem and itemID and addon.IsQuestItemByID then
|
||||||
|
local playerFaction = UnitFactionGroup("player")
|
||||||
|
if addon:IsQuestItemByID(itemID, playerFaction) then
|
||||||
|
isQuestItem = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return isQuestItem, isQuestStarter
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Legacy compatibility wrapper - keep the old function name working
|
||||||
|
function Utils:IsQuestItemTooltip(bagID, slotID)
|
||||||
|
local isQuest, _ = self:IsQuestItem(bagID, slotID, nil, false, false)
|
||||||
|
return isQuest
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if an item has a gray title in its tooltip or link
|
-- Check if an item has a gray title in its tooltip or link
|
||||||
@@ -296,129 +428,8 @@ function Utils:TruncateText(text, maxLen)
|
|||||||
return string.sub(text, 1, maxLen - 3) .. "..."
|
return string.sub(text, 1, maxLen - 3) .. "..."
|
||||||
end
|
end
|
||||||
|
|
||||||
function Utils:IsAmmoQuiverBag(bagID)
|
|
||||||
-- Skip backpack, bank, and keyring
|
|
||||||
if bagID == 0 or bagID == -1 or bagID == -2 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get the bag item
|
|
||||||
local invSlot = ContainerIDToInventoryID(bagID)
|
|
||||||
if not invSlot then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local link = GetInventoryItemLink("player", invSlot)
|
|
||||||
if not link then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Use tooltip scanning to get item class (more reliable in 1.12.1)
|
|
||||||
local tooltip = GetScanTooltip()
|
|
||||||
tooltip:ClearLines()
|
|
||||||
tooltip:SetInventoryItem("player", invSlot)
|
|
||||||
|
|
||||||
-- Scan tooltip lines for "Quiver" or "Ammo Pouch"
|
|
||||||
for i = 1, tooltip:NumLines() do
|
|
||||||
local line = getglobal("GudaBagScanTooltipTextLeft" .. i)
|
|
||||||
if line then
|
|
||||||
local text = line:GetText()
|
|
||||||
if text then
|
|
||||||
-- Check if the line contains "Quiver" or "Ammo Pouch"
|
|
||||||
if string.find(text, "Quiver") or string.find(text, "Ammo Pouch") then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if a bag is Herb Bag
|
|
||||||
function Utils:IsHerbBag(bagID)
|
|
||||||
-- Skip backpack, bank, and keyring
|
|
||||||
if bagID == 0 or bagID == -1 or bagID == -2 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local invSlot = ContainerIDToInventoryID(bagID)
|
|
||||||
if not invSlot then return false end
|
|
||||||
|
|
||||||
local link = GetInventoryItemLink("player", invSlot)
|
|
||||||
if not link then return false end
|
|
||||||
|
|
||||||
-- Prefer specialized type detection
|
|
||||||
local bagType = self:GetSpecializedBagType(bagID)
|
|
||||||
if bagType == "herb" then return true end
|
|
||||||
|
|
||||||
-- Fallback: tooltip scan for "Herb Bag"
|
|
||||||
local tooltip = GetScanTooltip()
|
|
||||||
tooltip:ClearLines()
|
|
||||||
tooltip:SetInventoryItem("player", invSlot)
|
|
||||||
|
|
||||||
for i = 1, tooltip:NumLines() do
|
|
||||||
local line = getglobal("GudaBagScanTooltipTextLeft" .. i)
|
|
||||||
if line then
|
|
||||||
local text = line:GetText()
|
|
||||||
if text and string.find(string.lower(text), "herb bag") then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if a bag is Soul Bag
|
|
||||||
function Utils:IsSoulBag(bagID)
|
|
||||||
-- Skip backpack, bank, and keyring
|
|
||||||
if bagID == 0 or bagID == -1 or bagID == -2 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get the bag item
|
|
||||||
local invSlot = ContainerIDToInventoryID(bagID)
|
|
||||||
if not invSlot then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local link = GetInventoryItemLink("player", invSlot)
|
|
||||||
if not link then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- First try GetSpecializedBagType
|
|
||||||
local bagType = self:GetSpecializedBagType(bagID)
|
|
||||||
if bagType == "soul" then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Fallback: Use tooltip scanning (more reliable in 1.12.1)
|
|
||||||
local tooltip = GetScanTooltip()
|
|
||||||
tooltip:ClearLines()
|
|
||||||
tooltip:SetInventoryItem("player", invSlot)
|
|
||||||
|
|
||||||
-- Scan tooltip lines for "Soul Bag" or "Soul Pouch"
|
|
||||||
for i = 1, tooltip:NumLines() do
|
|
||||||
local line = getglobal("GudaBagScanTooltipTextLeft" .. i)
|
|
||||||
if line then
|
|
||||||
local text = line:GetText()
|
|
||||||
if text then
|
|
||||||
-- Check if the line contains "Soul Bag" or "Soul Pouch" or just "Soul" in bag name
|
|
||||||
local textLower = string.lower(text)
|
|
||||||
if string.find(textLower, "soul bag") or string.find(textLower, "soul pouch") or
|
|
||||||
(string.find(textLower, "soul") and (string.find(textLower, "bag") or string.find(textLower, "pouch"))) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Returns: "soul", "herb", "enchant", "quiver", "ammo", or nil
|
-- Returns: "soul", "herb", "enchant", "quiver", "ammo", or nil
|
||||||
|
-- This is the consolidated bag type detection function with tooltip fallback
|
||||||
function Utils:GetSpecializedBagType(bagID)
|
function Utils:GetSpecializedBagType(bagID)
|
||||||
-- Skip backpack, bank, and keyring
|
-- Skip backpack, bank, and keyring
|
||||||
if bagID == 0 or bagID == -1 or bagID == -2 then
|
if bagID == 0 or bagID == -1 or bagID == -2 then
|
||||||
@@ -437,44 +448,94 @@ function Utils:GetSpecializedBagType(bagID)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local itemID = self:ExtractItemID(link)
|
local itemID = self:ExtractItemID(link)
|
||||||
if not itemID then
|
|
||||||
return nil
|
-- Try GetItemInfo first (more reliable when available)
|
||||||
|
if itemID then
|
||||||
|
local _, _, _, _, _, itemType = self:GetItemInfoSafe(itemID)
|
||||||
|
if itemType then
|
||||||
|
local typeLower = string.lower(itemType)
|
||||||
|
|
||||||
|
if string.find(typeLower, "soul bag") or string.find(typeLower, "soul pouch") then
|
||||||
|
return "soul"
|
||||||
|
end
|
||||||
|
if string.find(typeLower, "herb bag") then
|
||||||
|
return "herb"
|
||||||
|
end
|
||||||
|
if string.find(typeLower, "enchanting bag") then
|
||||||
|
return "enchant"
|
||||||
|
end
|
||||||
|
if string.find(typeLower, "quiver") then
|
||||||
|
return "quiver"
|
||||||
|
end
|
||||||
|
if string.find(typeLower, "ammo pouch") then
|
||||||
|
return "ammo"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local itemName, _, itemRarity, itemLevel, itemCategory, itemType, itemStackCount, itemSubType = self:GetItemInfoSafe(itemID)
|
-- Fallback: tooltip scanning for all bag types
|
||||||
if itemType then
|
local tooltip = GetScanTooltip()
|
||||||
-- Check for exact subtype matches
|
tooltip:ClearLines()
|
||||||
local typeLower = string.lower(itemType)
|
tooltip:SetInventoryItem("player", invSlot)
|
||||||
|
|
||||||
-- Soul Bag / Soul Pouch
|
for i = 1, tooltip:NumLines() do
|
||||||
if string.find(typeLower, "soul bag") or string.find(typeLower, "soul pouch") then
|
local line = getglobal("GudaBagScanTooltipTextLeft" .. i)
|
||||||
return "soul"
|
if line then
|
||||||
end
|
local text = line:GetText()
|
||||||
|
if text then
|
||||||
|
local textLower = string.lower(text)
|
||||||
|
|
||||||
-- Herb Bag
|
-- Soul Bag / Soul Pouch
|
||||||
if string.find(typeLower, "herb bag") then
|
if string.find(textLower, "soul bag") or string.find(textLower, "soul pouch") or
|
||||||
return "herb"
|
(string.find(textLower, "soul") and (string.find(textLower, "bag") or string.find(textLower, "pouch"))) then
|
||||||
end
|
return "soul"
|
||||||
|
end
|
||||||
|
|
||||||
-- Enchanting Bag
|
-- Herb Bag
|
||||||
if string.find(typeLower, "enchanting bag") then
|
if string.find(textLower, "herb bag") then
|
||||||
return "enchant"
|
return "herb"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Quiver
|
-- Enchanting Bag
|
||||||
if string.find(typeLower, "quiver") then
|
if string.find(textLower, "enchanting bag") then
|
||||||
return "quiver"
|
return "enchant"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Ammo Pouch
|
-- Quiver
|
||||||
if string.find(typeLower, "ammo pouch") then
|
if string.find(textLower, "quiver") then
|
||||||
return "ammo"
|
return "quiver"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ammo Pouch
|
||||||
|
if string.find(textLower, "ammo pouch") then
|
||||||
|
return "ammo"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Simple helper to check if a bag is of a specific type
|
||||||
|
function Utils:IsBagType(bagID, bagType)
|
||||||
|
return self:GetSpecializedBagType(bagID) == bagType
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convenience wrappers for common bag type checks
|
||||||
|
function Utils:IsAmmoQuiverBag(bagID)
|
||||||
|
local bagType = self:GetSpecializedBagType(bagID)
|
||||||
|
return bagType == "quiver" or bagType == "ammo"
|
||||||
|
end
|
||||||
|
|
||||||
|
function Utils:IsHerbBag(bagID)
|
||||||
|
return self:IsBagType(bagID, "herb")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Utils:IsSoulBag(bagID)
|
||||||
|
return self:IsBagType(bagID, "soul")
|
||||||
|
end
|
||||||
|
|
||||||
-- Get container priority for sorting (higher = more important)
|
-- Get container priority for sorting (higher = more important)
|
||||||
function Utils:GetContainerPriority(bagID)
|
function Utils:GetContainerPriority(bagID)
|
||||||
local bagType = self:GetSpecializedBagType(bagID)
|
local bagType = self:GetSpecializedBagType(bagID)
|
||||||
@@ -727,37 +788,7 @@ function Utils:IsEnchantingItem(itemLink)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if a bag is Enchanting Bag (parallel to IsHerbBag)
|
-- Check if a bag is Enchanting Bag
|
||||||
function Utils:IsEnchantBag(bagID)
|
function Utils:IsEnchantBag(bagID)
|
||||||
-- Skip backpack, bank, and keyring
|
return self:IsBagType(bagID, "enchant")
|
||||||
if bagID == 0 or bagID == -1 or bagID == -2 then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local invSlot = ContainerIDToInventoryID(bagID)
|
|
||||||
if not invSlot then return false end
|
|
||||||
|
|
||||||
local link = GetInventoryItemLink("player", invSlot)
|
|
||||||
if not link then return false end
|
|
||||||
|
|
||||||
-- Prefer specialized type detection
|
|
||||||
local bagType = self:GetSpecializedBagType(bagID)
|
|
||||||
if bagType == "enchant" then return true end
|
|
||||||
|
|
||||||
-- Fallback: tooltip scan for "Enchanting Bag"
|
|
||||||
local tooltip = GetScanTooltip()
|
|
||||||
tooltip:ClearLines()
|
|
||||||
tooltip:SetInventoryItem("player", invSlot)
|
|
||||||
|
|
||||||
for i = 1, tooltip:NumLines() do
|
|
||||||
local line = getglobal("GudaBagScanTooltipTextLeft" .. i)
|
|
||||||
if line then
|
|
||||||
local text = line:GetText()
|
|
||||||
if text and string.find(string.lower(text), "enchanting bag") then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
@@ -2,17 +2,19 @@
|
|||||||
## Title: Guda
|
## Title: Guda
|
||||||
## Notes: All-in-one bag and bank addon for World of Warcraft 1.12.1 (Turtle WoW)
|
## Notes: All-in-one bag and bank addon for World of Warcraft 1.12.1 (Turtle WoW)
|
||||||
## Author: Vati
|
## Author: Vati
|
||||||
## Version: 1.6.1
|
## Version: 1.6.2
|
||||||
## SavedVariables: Guda_DB
|
## SavedVariables: Guda_DB
|
||||||
## SavedVariablesPerCharacter: Guda_CharDB
|
## SavedVariablesPerCharacter: Guda_CharDB
|
||||||
|
|
||||||
Localization.lua
|
Localization.lua
|
||||||
|
|
||||||
Core\Init.lua
|
Core\Init.lua
|
||||||
|
Core\Constants.lua
|
||||||
Core\Database.lua
|
Core\Database.lua
|
||||||
DB\QuestItems.lua
|
DB\QuestItems.lua
|
||||||
Core\Events.lua
|
Core\Events.lua
|
||||||
Core\Utils.lua
|
Core\Utils.lua
|
||||||
|
Core\CategoryManager.lua
|
||||||
Core\Tooltip.lua
|
Core\Tooltip.lua
|
||||||
|
|
||||||
Data\BagScanner.lua
|
Data\BagScanner.lua
|
||||||
|
|||||||
+19
-5
@@ -590,10 +590,17 @@ local function AddSortKeys(items)
|
|||||||
item.isMount = isMount
|
item.isMount = isMount
|
||||||
|
|
||||||
-- Class and slot ordering
|
-- Class and slot ordering
|
||||||
if itemRarity == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data.link) then
|
-- Check for items that should be treated as junk:
|
||||||
|
-- 1. Gray items (quality 0)
|
||||||
|
-- 2. Items with gray tooltip
|
||||||
|
-- 3. White equippable items (quality 1 Weapon/Armor) - vendor trash
|
||||||
|
local isGrayItem = itemRarity == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data.link)
|
||||||
|
local isWhiteEquip = (itemRarity == 1) and (itemCategory == "Weapon" or itemCategory == "Armor")
|
||||||
|
|
||||||
|
if isGrayItem or isWhiteEquip then
|
||||||
item.sortedClass = CATEGORY_ORDER["Junk"] or 99
|
item.sortedClass = CATEGORY_ORDER["Junk"] or 99
|
||||||
item.equipSlotOrder = 999
|
item.equipSlotOrder = 999
|
||||||
item.isEquippable = false -- Treat gray gear as junk, not gear
|
item.isEquippable = false -- Treat junk gear as junk, not gear
|
||||||
elseif isEquippable then
|
elseif isEquippable then
|
||||||
item.sortedClass = 1 -- All equippable gear gets priority class
|
item.sortedClass = 1 -- All equippable gear gets priority class
|
||||||
item.equipSlotOrder = EQUIP_SLOT_ORDER[itemSubType] or 999
|
item.equipSlotOrder = EQUIP_SLOT_ORDER[itemSubType] or 999
|
||||||
@@ -1042,12 +1049,19 @@ local function BuildGreyTailPositions(bagIDs, greyCount)
|
|||||||
return tailSlots
|
return tailSlots
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Split a list of collected items into non-greys and greys (quality 0)
|
-- Split a list of collected items into non-junk and junk items
|
||||||
|
-- Junk includes: gray items (quality 0), gray tooltip items, white equippable items (quality 1 Weapon/Armor)
|
||||||
local function SplitGreyItems(items)
|
local function SplitGreyItems(items)
|
||||||
local nonGreys, greys = {}, {}
|
local nonGreys, greys = {}, {}
|
||||||
for _, item in ipairs(items) do
|
for _, item in ipairs(items) do
|
||||||
-- Use same logic as AddSortKeys for determining Junk/Grey status (stability)
|
-- Use same logic as AddSortKeys for determining Junk status (stability)
|
||||||
if tonumber(item.quality or 0) == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data.link) then
|
local quality = tonumber(item.quality or 0)
|
||||||
|
local isGray = quality == 0 or IsItemGrayTooltip(item.bagID, item.slot, item.data.link)
|
||||||
|
-- White equippable items (Weapon/Armor) are also treated as junk
|
||||||
|
local itemClass = item.class or ""
|
||||||
|
local isWhiteEquip = (quality == 1) and (itemClass == "Weapon" or itemClass == "Armor")
|
||||||
|
|
||||||
|
if isGray or isWhiteEquip then
|
||||||
table.insert(greys, item)
|
table.insert(greys, item)
|
||||||
else
|
else
|
||||||
table.insert(nonGreys, item)
|
table.insert(nonGreys, item)
|
||||||
|
|||||||
+154
-1
@@ -385,6 +385,9 @@ function BagFrame:Update()
|
|||||||
-- Update money
|
-- Update money
|
||||||
self:UpdateMoney()
|
self:UpdateMoney()
|
||||||
|
|
||||||
|
-- Update hearthstone
|
||||||
|
self:UpdateHearthstone()
|
||||||
|
|
||||||
-- Update bag slots info
|
-- Update bag slots info
|
||||||
self:UpdateBagSlotsInfo(bagData, isOtherChar)
|
self:UpdateBagSlotsInfo(bagData, isOtherChar)
|
||||||
|
|
||||||
@@ -505,8 +508,15 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY)
|
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY)
|
||||||
header:SetWidth(blockWidth)
|
header:SetWidth(blockWidth)
|
||||||
|
|
||||||
|
-- Get display name from category definition
|
||||||
local displayName = catName
|
local displayName = catName
|
||||||
header.fullName = catName
|
if addon.Modules.CategoryManager then
|
||||||
|
local catDef = addon.Modules.CategoryManager:GetCategory(catName)
|
||||||
|
if catDef and catDef.name then
|
||||||
|
displayName = catDef.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
header.fullName = displayName
|
||||||
header.isShortened = false
|
header.isShortened = false
|
||||||
if string.len(displayName) > 8 and numItems < 2 then
|
if string.len(displayName) > 8 and numItems < 2 then
|
||||||
displayName = string.sub(displayName, 1, 6) .. "..."
|
displayName = string.sub(displayName, 1, 6) .. "..."
|
||||||
@@ -571,6 +581,10 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- Also check if Empty section should show
|
||||||
|
if totalFreeSlots > 0 then
|
||||||
|
hasAnyBottom = true
|
||||||
|
end
|
||||||
|
|
||||||
if hasAnyBottom then
|
if hasAnyBottom then
|
||||||
y = y - 10
|
y = y - 10
|
||||||
@@ -935,6 +949,141 @@ function BagFrame:CreateMoneyFrame()
|
|||||||
addon:Debug("MoneyFrame created via CreateMoneyFrame")
|
addon:Debug("MoneyFrame created via CreateMoneyFrame")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Hearthstone item ID
|
||||||
|
local HEARTHSTONE_ID = 6948
|
||||||
|
|
||||||
|
-- Create hearthstone display frame
|
||||||
|
function BagFrame:CreateHearthstoneFrame()
|
||||||
|
local frameName = "Guda_BagFrame_HearthstoneFrame"
|
||||||
|
if getglobal(frameName) then return end
|
||||||
|
|
||||||
|
local frame = CreateFrame("Button", frameName, Guda_BagFrame)
|
||||||
|
frame:SetWidth(20)
|
||||||
|
frame:SetHeight(20)
|
||||||
|
frame:SetPoint("BOTTOMRIGHT", Guda_BagFrame, "BOTTOMRIGHT", -150, 16)
|
||||||
|
frame:SetFrameStrata("HIGH")
|
||||||
|
frame:SetFrameLevel(10)
|
||||||
|
|
||||||
|
-- Icon texture
|
||||||
|
local icon = frame:CreateTexture(frameName .. "_Icon", "ARTWORK")
|
||||||
|
icon:SetAllPoints(frame)
|
||||||
|
icon:SetTexture("Interface\\Icons\\INV_Misc_Rune_01")
|
||||||
|
|
||||||
|
-- Cooldown frame (use Model type with CooldownFrameTemplate in vanilla)
|
||||||
|
local cooldown = CreateFrame("Model", frameName .. "_Cooldown", frame, "CooldownFrameTemplate")
|
||||||
|
cooldown:SetAllPoints(frame)
|
||||||
|
cooldown:EnableMouse(false)
|
||||||
|
frame.cooldown = cooldown
|
||||||
|
|
||||||
|
-- Enable mouse and clicks
|
||||||
|
frame:EnableMouse(true)
|
||||||
|
frame:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
BagFrame:ShowHearthstoneTooltip(this)
|
||||||
|
end)
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
GameTooltip:Hide()
|
||||||
|
end)
|
||||||
|
frame:SetScript("OnClick", function()
|
||||||
|
BagFrame:UseHearthstone()
|
||||||
|
end)
|
||||||
|
frame:SetScript("OnMouseUp", function()
|
||||||
|
if arg1 == "LeftButton" or arg1 == "RightButton" then
|
||||||
|
BagFrame:UseHearthstone()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
addon:Debug("HearthstoneFrame created")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find hearthstone in bags
|
||||||
|
function BagFrame:FindHearthstone()
|
||||||
|
for bag = 0, 4 do
|
||||||
|
local numSlots = GetContainerNumSlots(bag)
|
||||||
|
for slot = 1, numSlots do
|
||||||
|
local link = GetContainerItemLink(bag, slot)
|
||||||
|
if link then
|
||||||
|
local itemId = addon.Modules.Utils:ExtractItemID(link)
|
||||||
|
if itemId and itemId == HEARTHSTONE_ID then
|
||||||
|
return bag, slot, link
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update hearthstone display
|
||||||
|
function BagFrame:UpdateHearthstone()
|
||||||
|
local hideFooter = addon.Modules.DB:GetSetting("hideFooter")
|
||||||
|
local frame = getglobal("Guda_BagFrame_HearthstoneFrame")
|
||||||
|
|
||||||
|
if hideFooter then
|
||||||
|
if frame then frame:Hide() end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not frame then
|
||||||
|
self:CreateHearthstoneFrame()
|
||||||
|
frame = getglobal("Guda_BagFrame_HearthstoneFrame")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not frame then return end
|
||||||
|
|
||||||
|
local bag, slot, link = self:FindHearthstone()
|
||||||
|
|
||||||
|
if bag then
|
||||||
|
frame:SetAlpha(1)
|
||||||
|
frame:Show()
|
||||||
|
|
||||||
|
-- Update cooldown
|
||||||
|
local start, duration, enable = GetContainerItemCooldown(bag, slot)
|
||||||
|
if frame.cooldown and start and duration and duration > 0 then
|
||||||
|
CooldownFrame_SetTimer(frame.cooldown, start, duration, enable)
|
||||||
|
elseif frame.cooldown then
|
||||||
|
frame.cooldown:Hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Store location for use
|
||||||
|
frame.bag = bag
|
||||||
|
frame.slot = slot
|
||||||
|
frame.link = link
|
||||||
|
else
|
||||||
|
frame:SetAlpha(0.3)
|
||||||
|
frame:Show()
|
||||||
|
frame.bag = nil
|
||||||
|
frame.slot = nil
|
||||||
|
frame.link = nil
|
||||||
|
if frame.cooldown then
|
||||||
|
frame.cooldown:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Show hearthstone tooltip
|
||||||
|
function BagFrame:ShowHearthstoneTooltip(frame)
|
||||||
|
if frame.bag and frame.slot then
|
||||||
|
GameTooltip:SetOwner(frame, "ANCHOR_TOP")
|
||||||
|
GameTooltip:SetBagItem(frame.bag, frame.slot)
|
||||||
|
GameTooltip:Show()
|
||||||
|
else
|
||||||
|
GameTooltip:SetOwner(frame, "ANCHOR_TOP")
|
||||||
|
GameTooltip:ClearLines()
|
||||||
|
GameTooltip:AddLine("Hearthstone", 1, 1, 1)
|
||||||
|
GameTooltip:AddLine("Not in bags", 1, 0, 0)
|
||||||
|
GameTooltip:Show()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Use hearthstone
|
||||||
|
function BagFrame:UseHearthstone()
|
||||||
|
local frame = getglobal("Guda_BagFrame_HearthstoneFrame")
|
||||||
|
if frame and frame.bag and frame.slot then
|
||||||
|
UseContainerItem(frame.bag, frame.slot)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Setup tooltip on toolbar empty space
|
-- Setup tooltip on toolbar empty space
|
||||||
function BagFrame:SetupToolbarTooltip()
|
function BagFrame:SetupToolbarTooltip()
|
||||||
local toolbar = getglobal("Guda_BagFrame_Toolbar")
|
local toolbar = getglobal("Guda_BagFrame_Toolbar")
|
||||||
@@ -1889,17 +2038,21 @@ function BagFrame:UpdateFooterVisibility()
|
|||||||
local hideFooter = addon.Modules.DB:GetSetting("hideFooter")
|
local hideFooter = addon.Modules.DB:GetSetting("hideFooter")
|
||||||
local toolbar = getglobal("Guda_BagFrame_Toolbar")
|
local toolbar = getglobal("Guda_BagFrame_Toolbar")
|
||||||
local moneyFrame = getglobal("Guda_BagFrame_MoneyFrame")
|
local moneyFrame = getglobal("Guda_BagFrame_MoneyFrame")
|
||||||
|
local hearthstoneFrame = getglobal("Guda_BagFrame_HearthstoneFrame")
|
||||||
|
|
||||||
if hideFooter then
|
if hideFooter then
|
||||||
if toolbar then toolbar:Hide() end
|
if toolbar then toolbar:Hide() end
|
||||||
if moneyFrame then moneyFrame:Hide() end
|
if moneyFrame then moneyFrame:Hide() end
|
||||||
|
if hearthstoneFrame then hearthstoneFrame:Hide() end
|
||||||
else
|
else
|
||||||
if toolbar then toolbar:Show() end
|
if toolbar then toolbar:Show() end
|
||||||
if moneyFrame then moneyFrame:Show() end
|
if moneyFrame then moneyFrame:Show() end
|
||||||
|
if hearthstoneFrame then hearthstoneFrame:Show() end
|
||||||
|
|
||||||
-- Trigger layout updates to ensure they are correctly positioned
|
-- Trigger layout updates to ensure they are correctly positioned
|
||||||
self:UpdateBaglineLayout()
|
self:UpdateBaglineLayout()
|
||||||
self:UpdateMoney()
|
self:UpdateMoney()
|
||||||
|
self:UpdateHearthstone()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+62
-32
@@ -5,10 +5,18 @@ local FrameHelpers = {}
|
|||||||
addon.Modules.FrameHelpers = FrameHelpers
|
addon.Modules.FrameHelpers = FrameHelpers
|
||||||
|
|
||||||
-- Standard category list used by both Bag and Bank frames
|
-- Standard category list used by both Bag and Bank frames
|
||||||
|
-- Now dynamically built from CategoryManager if available
|
||||||
Guda_CategoryList = {
|
Guda_CategoryList = {
|
||||||
"BoE", "Weapon", "Armor", "Consumable", "Food", "Drink", "Trade Goods", "Reagent", "Recipe", "Quiver", "Container", "Soul Bag", "Miscellaneous", "Quest", "Junk", "Class Items", "Keyring"
|
"BoE", "Weapon", "Armor", "Consumable", "Food", "Drink", "Trade Goods", "Reagent", "Recipe", "Quiver", "Container", "Soul Bag", "Miscellaneous", "Quest", "Junk", "Class Items", "Keyring"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Rebuild category list from CategoryManager
|
||||||
|
function Guda_RefreshCategoryList()
|
||||||
|
if addon.Modules.CategoryManager then
|
||||||
|
Guda_CategoryList = addon.Modules.CategoryManager:BuildCategoryList()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Categorize a single item into categories and specialItems tables
|
-- Categorize a single item into categories and specialItems tables
|
||||||
-- Returns nothing, modifies tables in place
|
-- Returns nothing, modifies tables in place
|
||||||
function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems, isOtherChar)
|
function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems, isOtherChar)
|
||||||
@@ -25,8 +33,20 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Priority 1: Special items (Hearthstone, Mounts, Tools)
|
-- Priority 1: Special items (Hearthstone, Mounts, Tools)
|
||||||
|
-- These are handled separately and go into specialItems table, not categories
|
||||||
if string.find(itemName, "Hearthstone") then
|
if string.find(itemName, "Hearthstone") then
|
||||||
table.insert(specialItems.Hearthstone, {bagID = bagID, slotID = slotID, itemData = itemData})
|
-- Only show in Home section if Home category is enabled
|
||||||
|
local showHome = true
|
||||||
|
if addon.Modules.CategoryManager then
|
||||||
|
local homeCat = addon.Modules.CategoryManager:GetCategory("Home")
|
||||||
|
if homeCat then
|
||||||
|
showHome = homeCat.enabled
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if showHome then
|
||||||
|
table.insert(specialItems.Hearthstone, {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||||
|
end
|
||||||
|
-- Always return - if Home is disabled, Hearthstone is hidden completely
|
||||||
return
|
return
|
||||||
elseif addon.Modules.SortEngine and addon.Modules.SortEngine.IsMount and addon.Modules.SortEngine.IsMount(itemData.texture) then
|
elseif addon.Modules.SortEngine and addon.Modules.SortEngine.IsMount and addon.Modules.SortEngine.IsMount(itemData.texture) then
|
||||||
table.insert(specialItems.Mount, {bagID = bagID, slotID = slotID, itemData = itemData})
|
table.insert(specialItems.Mount, {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||||
@@ -44,6 +64,15 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Use CategoryManager rule engine if available, otherwise fall back to legacy logic
|
||||||
|
if addon.Modules.CategoryManager then
|
||||||
|
cat = addon.Modules.CategoryManager:CategorizeItem(itemData, bagID, slotID, isOtherChar)
|
||||||
|
if not categories[cat] then cat = "Miscellaneous" end
|
||||||
|
table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Legacy categorization logic (fallback if CategoryManager not available)
|
||||||
-- Priority 2: Class Items (Soul Shards, Arrows, Bullets)
|
-- Priority 2: Class Items (Soul Shards, Arrows, Bullets)
|
||||||
if addon.Modules.Utils:IsSoulShard(itemData.link) or
|
if addon.Modules.Utils:IsSoulShard(itemData.link) or
|
||||||
itemData.class == "Projectile" or
|
itemData.class == "Projectile" or
|
||||||
@@ -53,33 +82,8 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Priority 3: Quest Items
|
-- Priority 3: Quest Items (use consolidated detection)
|
||||||
-- Check multiple sources: tooltip scan, itemClass, itemType, and QuestItemsDB
|
local isQuestItem, _ = addon.Modules.Utils:IsQuestItem(bagID, slotID, itemData, isOtherChar, false)
|
||||||
local isQuestItem = false
|
|
||||||
local itemCategory = itemData.class or itemData.category or ""
|
|
||||||
local itemType = itemData.type or ""
|
|
||||||
|
|
||||||
-- If it's a Weapon or Armor, it shouldn't be a QuestItem unless it's specifically categorized as Quest
|
|
||||||
local isEquipment = (itemCategory == "Weapon" or itemCategory == "Armor" or itemType == "Weapon" or itemType == "Armor")
|
|
||||||
local isQuestCategory = (itemCategory == "Quest" or itemType == "Quest")
|
|
||||||
|
|
||||||
if isQuestCategory then
|
|
||||||
isQuestItem = true
|
|
||||||
elseif not isOtherChar then
|
|
||||||
isQuestItem = addon.Modules.Utils:IsQuestItemTooltip(bagID, slotID)
|
|
||||||
-- If tooltip said it is quest, but it is equipment and not quest category, reject it
|
|
||||||
if isQuestItem and isEquipment and not isQuestCategory then
|
|
||||||
isQuestItem = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Also check the QuestItemsDB for known faction-specific quest items
|
|
||||||
if not isQuestItem and itemData.link then
|
|
||||||
local itemID = addon.Modules.Utils:ExtractItemID(itemData.link)
|
|
||||||
if itemID and addon.IsQuestItemByID then
|
|
||||||
local playerFaction = UnitFactionGroup("player")
|
|
||||||
isQuestItem = addon:IsQuestItemByID(itemID, playerFaction)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if isQuestItem then
|
if isQuestItem then
|
||||||
table.insert(categories["Quest"], {bagID = bagID, slotID = slotID, itemData = itemData})
|
table.insert(categories["Quest"], {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||||
return
|
return
|
||||||
@@ -106,7 +110,7 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Priority 6: BoE Equipment (Armor/Weapons that bind when equipped)
|
-- Priority 6: BoE Equipment
|
||||||
if (itemData.class == "Weapon" or itemData.class == "Armor") and not isOtherChar then
|
if (itemData.class == "Weapon" or itemData.class == "Armor") and not isOtherChar then
|
||||||
local isBoE = addon.Modules.Utils:IsBindOnEquip(bagID, slotID, itemData.link)
|
local isBoE = addon.Modules.Utils:IsBindOnEquip(bagID, slotID, itemData.link)
|
||||||
if isBoE then
|
if isBoE then
|
||||||
@@ -117,7 +121,7 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Priority 7: Equipment for other characters (can't scan tooltip)
|
-- Priority 7: Equipment for other characters
|
||||||
if (itemData.class == "Weapon" or itemData.class == "Armor") and isOtherChar then
|
if (itemData.class == "Weapon" or itemData.class == "Armor") and isOtherChar then
|
||||||
table.insert(categories[itemData.class], {bagID = bagID, slotID = slotID, itemData = itemData})
|
table.insert(categories[itemData.class], {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||||
return
|
return
|
||||||
@@ -131,10 +135,36 @@ end
|
|||||||
|
|
||||||
-- Initialize empty category tables
|
-- Initialize empty category tables
|
||||||
function Guda_InitCategories()
|
function Guda_InitCategories()
|
||||||
|
-- Refresh category list from CategoryManager (enabled categories only for display)
|
||||||
|
Guda_RefreshCategoryList()
|
||||||
|
|
||||||
|
-- Create tables for ALL categories (not just enabled) so items can be categorized
|
||||||
|
-- Items in disabled categories simply won't be displayed
|
||||||
local categories = {}
|
local categories = {}
|
||||||
for _, cat in ipairs(Guda_CategoryList) do
|
|
||||||
categories[cat] = {}
|
if addon.Modules.CategoryManager then
|
||||||
|
-- Get full category order (all categories, not just enabled)
|
||||||
|
local allCategories = addon.Modules.CategoryManager:GetCategoryOrder()
|
||||||
|
for _, cat in ipairs(allCategories) do
|
||||||
|
categories[cat] = {}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Fallback: use the display list
|
||||||
|
for _, cat in ipairs(Guda_CategoryList) do
|
||||||
|
categories[cat] = {}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Always ensure Miscellaneous exists as fallback
|
||||||
|
if not categories["Miscellaneous"] then
|
||||||
|
categories["Miscellaneous"] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Always ensure Keyring exists (handled specially in BagFrame)
|
||||||
|
if not categories["Keyring"] then
|
||||||
|
categories["Keyring"] = {}
|
||||||
|
end
|
||||||
|
|
||||||
local specialItems = {
|
local specialItems = {
|
||||||
Hearthstone = {},
|
Hearthstone = {},
|
||||||
Mount = {},
|
Mount = {},
|
||||||
|
|||||||
+345
-225
@@ -8,90 +8,13 @@ local nextButtonID = 1
|
|||||||
local scanTooltip = CreateFrame("GameTooltip", "Guda_QuestScanTooltip", nil, "GameTooltipTemplate")
|
local scanTooltip = CreateFrame("GameTooltip", "Guda_QuestScanTooltip", nil, "GameTooltipTemplate")
|
||||||
scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
scanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||||
|
|
||||||
-- Check if an item is a quest item by scanning its tooltip
|
-- Helper function to check if an item is a quest item
|
||||||
-- Check if an item is a quest item by scanning its tooltip and determine type
|
-- Delegates to consolidated Utils:IsQuestItem() function
|
||||||
local function IsQuestItem(bagID, slotID, isBank)
|
local function IsQuestItem(bagID, slotID, isBank)
|
||||||
bagID = tonumber(bagID)
|
if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsQuestItem then
|
||||||
slotID = tonumber(slotID)
|
return addon.Modules.Utils:IsQuestItem(bagID, slotID, nil, false, isBank)
|
||||||
if not bagID or not slotID then return false end
|
|
||||||
|
|
||||||
scanTooltip:ClearLines()
|
|
||||||
if isBank and bagID == -1 then
|
|
||||||
if scanTooltip.SetInventoryItem then
|
|
||||||
scanTooltip:SetInventoryItem("player", 39 + slotID)
|
|
||||||
else
|
|
||||||
scanTooltip:SetBagItem(bagID, slotID)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
scanTooltip:SetBagItem(bagID, slotID)
|
|
||||||
end
|
end
|
||||||
|
return false, false
|
||||||
local isQuestItem = false
|
|
||||||
local isQuestStarter = false
|
|
||||||
|
|
||||||
-- Check all tooltip lines for quest-related text
|
|
||||||
for i = 1, scanTooltip:NumLines() do
|
|
||||||
local line = getglobal("Guda_QuestScanTooltipTextLeft" .. i)
|
|
||||||
if line then
|
|
||||||
local text = line:GetText()
|
|
||||||
if text then
|
|
||||||
-- Check for quest starter patterns
|
|
||||||
if string.find(text, "Quest Starter") or
|
|
||||||
string.find(text, "This Item Begins a Quest") or
|
|
||||||
string.find(text, "Use: Starts a Quest") then
|
|
||||||
isQuestItem = true
|
|
||||||
isQuestStarter = true
|
|
||||||
break
|
|
||||||
-- Check for regular quest item patterns
|
|
||||||
elseif string.find(text, "Quest Item") or
|
|
||||||
string.find(text, "Manual") then
|
|
||||||
isQuestItem = true
|
|
||||||
-- Don't break, might still find a quest starter pattern
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Also check item category/type via GetItemInfo for "Quest"
|
|
||||||
-- Turtle WoW GetItemInfo returns: name, link, rarity, level, itemCategory, itemType, stack, subType, texture, equipLoc, sellPrice
|
|
||||||
local link = GetContainerItemLink(bagID, slotID)
|
|
||||||
local itemID
|
|
||||||
local itemCategory, itemType
|
|
||||||
|
|
||||||
if link and addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.ExtractItemID and addon.Modules.Utils.GetItemInfoSafe then
|
|
||||||
itemID = addon.Modules.Utils:ExtractItemID(link)
|
|
||||||
if itemID then
|
|
||||||
_, _, _, _, itemCategory, itemType = addon.Modules.Utils:GetItemInfoSafe(itemID)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If it's a Weapon or Armor, it shouldn't be a QuestItem unless it's specifically categorized as Quest
|
|
||||||
-- This avoids "Use:" equipment showing up in the quest bar
|
|
||||||
if itemCategory == "Weapon" or itemCategory == "Armor" or itemType == "Weapon" or itemType == "Armor" then
|
|
||||||
if itemCategory ~= "Quest" and itemType ~= "Quest" then
|
|
||||||
isQuestItem = false
|
|
||||||
isQuestStarter = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not isQuestItem then
|
|
||||||
if itemCategory == "Quest" or itemType == "Quest" then
|
|
||||||
isQuestItem = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check the QuestItemsDB for known faction-specific quest items
|
|
||||||
if not isQuestItem then
|
|
||||||
if itemID and addon.IsQuestItemByID then
|
|
||||||
local playerFaction = UnitFactionGroup("player")
|
|
||||||
local isDBQuestItem = addon:IsQuestItemByID(itemID, playerFaction)
|
|
||||||
if isDBQuestItem then
|
|
||||||
isQuestItem = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return isQuestItem, isQuestStarter
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--=====================================================
|
--=====================================================
|
||||||
@@ -466,50 +389,31 @@ function Guda_ItemButton_UpdateCooldown(self)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Set item data
|
--=====================================================
|
||||||
function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly)
|
-- Helper functions for SetItem (extracted for clarity)
|
||||||
-- Proactively convert to number to avoid comparisons with strings in downstream functions
|
--=====================================================
|
||||||
bagID = tonumber(bagID)
|
|
||||||
slotID = tonumber(slotID)
|
|
||||||
|
|
||||||
-- Proactively clear any previous cooldown overlay state before reassigning this pooled button
|
-- Reset all visual state on a button (for reuse from pool)
|
||||||
do
|
local function ResetButtonVisualState(self)
|
||||||
local cd = getglobal(self:GetName().."Cooldown") or self.cooldown
|
|
||||||
if cd then
|
|
||||||
if CooldownFrame_SetTimer then
|
|
||||||
CooldownFrame_SetTimer(cd, 0, 0, 0)
|
|
||||||
elseif CooldownFrame_Set then
|
|
||||||
CooldownFrame_Set(cd, 0, 0, 0)
|
|
||||||
end
|
|
||||||
if cd.Hide then cd:Hide() end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Reset all visual state before reassigning pooled button
|
|
||||||
if self.questBorder then self.questBorder:Hide() end
|
if self.questBorder then self.questBorder:Hide() end
|
||||||
if self.questIcon then self.questIcon:Hide() end
|
if self.questIcon then self.questIcon:Hide() end
|
||||||
if self.qualityBorder then self.qualityBorder:Hide() end
|
if self.qualityBorder then self.qualityBorder:Hide() end
|
||||||
if self.unusableOverlay then self.unusableOverlay:Hide() end
|
if self.unusableOverlay then self.unusableOverlay:Hide() end
|
||||||
|
|
||||||
self.bagID = bagID
|
-- Clear cooldown overlay
|
||||||
self.slotID = slotID
|
local cd = getglobal(self:GetName().."Cooldown") or self.cooldown
|
||||||
-- Also set the Blizzard slot ID for compatibility with ContainerFrameItemButtonTemplate behavior
|
if cd then
|
||||||
-- ALWAYS set ID to something (0 if nil) to avoid leaking old IDs when button is reused
|
if CooldownFrame_SetTimer then
|
||||||
if self.SetID then
|
CooldownFrame_SetTimer(cd, 0, 0, 0)
|
||||||
self:SetID(slotID or 0)
|
elseif CooldownFrame_Set then
|
||||||
|
CooldownFrame_Set(cd, 0, 0, 0)
|
||||||
|
end
|
||||||
|
if cd.Hide then cd:Hide() end
|
||||||
end
|
end
|
||||||
-- Explicitly set bag index to avoid Blizzard's ContainerFrameItemButton_OnEnter logic
|
end
|
||||||
-- from picking up this button as part of a real bag.
|
|
||||||
self.bagIndex = bagID or -100 -- Use an invalid bag index for non-bag buttons
|
|
||||||
self.itemData = itemData
|
|
||||||
self.isBank = isBank or false
|
|
||||||
self.otherChar = otherCharName
|
|
||||||
self.isReadOnly = isReadOnly or false -- Track if this is read-only mode
|
|
||||||
self.isMail = false -- Clear mailbox flag by default
|
|
||||||
self.mailIndex = nil
|
|
||||||
self.mailItemIndex = nil
|
|
||||||
|
|
||||||
-- Re-register for drag/drop every time (crucial for button reuse in Classic/Vanilla)
|
-- Configure drag/drop registration based on read-only state
|
||||||
|
local function SetupDragDrop(self)
|
||||||
if not self.isReadOnly and not self.otherChar then
|
if not self.isReadOnly and not self.otherChar then
|
||||||
if self.RegisterForDrag then
|
if self.RegisterForDrag then
|
||||||
self:RegisterForDrag("LeftButton")
|
self:RegisterForDrag("LeftButton")
|
||||||
@@ -529,137 +433,364 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
|||||||
self:EnableMouse(true) -- Still enable mouse for tooltips
|
self:EnableMouse(true) -- Still enable mouse for tooltips
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Default to true if not specified (for non-filtered displays)
|
-- Get display texture and count for an item slot
|
||||||
|
-- Returns: texture, count, hasItem (boolean)
|
||||||
|
local function GetItemDisplayInfo(bagID, slotID, itemData, isReadOnly)
|
||||||
|
local displayTexture, displayCount
|
||||||
|
local hasItem = false
|
||||||
|
|
||||||
|
if not isReadOnly then
|
||||||
|
-- LIVE MODE: Query game state directly
|
||||||
|
local liveTexture, liveCount = GetContainerItemInfo(bagID, slotID)
|
||||||
|
if liveTexture then
|
||||||
|
displayTexture = liveTexture
|
||||||
|
displayCount = liveCount
|
||||||
|
hasItem = true
|
||||||
|
elseif bagID == -2 then
|
||||||
|
-- Fallback for keyring in 1.12.1
|
||||||
|
local link = GetContainerItemLink(bagID, slotID)
|
||||||
|
if link then
|
||||||
|
hasItem = true
|
||||||
|
local _, _, _, _, _, _, _, _, itemTexture = GetItemInfo(link)
|
||||||
|
displayTexture = itemTexture
|
||||||
|
displayCount = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- READ-ONLY MODE: Use cached itemData
|
||||||
|
if itemData and itemData.texture then
|
||||||
|
displayTexture = itemData.texture
|
||||||
|
displayCount = itemData.count
|
||||||
|
hasItem = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return displayTexture, displayCount, hasItem
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update the tracking checkmark on an item button
|
||||||
|
local function UpdateTrackingCheckmark(self, Utils)
|
||||||
|
local check = getglobal(self:GetName().."_Check")
|
||||||
|
if not check then return end
|
||||||
|
|
||||||
|
local isTracked = false
|
||||||
|
if self.hasItem and self.itemData and self.itemData.link then
|
||||||
|
local itemID = Utils and Utils.ExtractItemID and Utils:ExtractItemID(self.itemData.link)
|
||||||
|
if itemID then
|
||||||
|
local trackedItems = Utils and Utils.SafeCall and Utils:SafeCall("DB", "GetSetting", "trackedItems") or {}
|
||||||
|
if trackedItems[itemID] then
|
||||||
|
isTracked = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if isTracked then
|
||||||
|
check:Show()
|
||||||
|
else
|
||||||
|
check:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Resize empty slot background to match icon size
|
||||||
|
local function UpdateEmptySlotBackground(self, emptySlotBg, iconSize)
|
||||||
|
if not emptySlotBg then return end
|
||||||
|
|
||||||
|
emptySlotBg:ClearAllPoints()
|
||||||
|
-- Use smaller padding for small icons
|
||||||
|
local bgPadding = iconSize < 44 and 1 or 2
|
||||||
|
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", -bgPadding, bgPadding)
|
||||||
|
emptySlotBg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", bgPadding, -bgPadding)
|
||||||
|
emptySlotBg:SetTexCoord(0.1, 0.9, 0.1, 0.9)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Resize texture elements to match button size
|
||||||
|
local function ResizeTextureElements(self, iconSize)
|
||||||
|
-- Pushed texture
|
||||||
|
local pushedTexture = getglobal(self:GetName().."PushedTexture")
|
||||||
|
if not pushedTexture and self.GetPushedTexture then
|
||||||
|
pushedTexture = self:GetPushedTexture()
|
||||||
|
end
|
||||||
|
if pushedTexture then
|
||||||
|
pushedTexture:ClearAllPoints()
|
||||||
|
pushedTexture:SetPoint("CENTER", self, "CENTER", 0, 0)
|
||||||
|
pushedTexture:SetWidth(iconSize)
|
||||||
|
pushedTexture:SetHeight(iconSize)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Highlight texture
|
||||||
|
local highlightTexture = getglobal(self:GetName().."HighlightTexture")
|
||||||
|
if not highlightTexture and self.GetHighlightTexture then
|
||||||
|
highlightTexture = self:GetHighlightTexture()
|
||||||
|
end
|
||||||
|
if highlightTexture then
|
||||||
|
highlightTexture:ClearAllPoints()
|
||||||
|
highlightTexture:SetPoint("CENTER", self, "CENTER", 0, 0)
|
||||||
|
highlightTexture:SetWidth(iconSize)
|
||||||
|
highlightTexture:SetHeight(iconSize)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Checked texture
|
||||||
|
local checkedTexture = getglobal(self:GetName().."CheckedTexture")
|
||||||
|
if not checkedTexture and self.GetCheckedTexture then
|
||||||
|
checkedTexture = self:GetCheckedTexture()
|
||||||
|
end
|
||||||
|
if checkedTexture then
|
||||||
|
checkedTexture:ClearAllPoints()
|
||||||
|
checkedTexture:SetPoint("CENTER", self, "CENTER", 0, 0)
|
||||||
|
checkedTexture:SetWidth(iconSize)
|
||||||
|
checkedTexture:SetHeight(iconSize)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Position icon and borders based on icon size
|
||||||
|
local function PositionIconAndBorders(self, iconSize)
|
||||||
|
local iconTexture = getglobal(self:GetName().."IconTexture")
|
||||||
|
if not iconTexture then
|
||||||
|
iconTexture = getglobal(self:GetName().."Icon") or self.icon or self.Icon
|
||||||
|
end
|
||||||
|
|
||||||
|
if not iconTexture or not self.hasItem then return end
|
||||||
|
|
||||||
|
-- Calculate icon inset based on size
|
||||||
|
local iconInset = iconSize < 44 and 10 or 15
|
||||||
|
local iconDisplaySize = iconSize - iconInset
|
||||||
|
|
||||||
|
iconTexture:ClearAllPoints()
|
||||||
|
iconTexture:SetPoint("CENTER", self, "CENTER", -0.5, 0.5)
|
||||||
|
iconTexture:SetWidth(iconDisplaySize)
|
||||||
|
iconTexture:SetHeight(iconDisplaySize)
|
||||||
|
iconTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||||
|
iconTexture:Show()
|
||||||
|
|
||||||
|
-- Position quality border around the icon
|
||||||
|
if self.qualityBorder then
|
||||||
|
self.qualityBorder:ClearAllPoints()
|
||||||
|
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
||||||
|
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Position quest border around the icon
|
||||||
|
if self.questBorder then
|
||||||
|
self.questBorder:ClearAllPoints()
|
||||||
|
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
||||||
|
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Position quest icon in top-right corner
|
||||||
|
if self.questIcon then
|
||||||
|
local questIconSize = math.max(12, math.min(20, iconSize * 0.35))
|
||||||
|
self.questIcon:SetWidth(questIconSize)
|
||||||
|
self.questIcon:SetHeight(questIconSize)
|
||||||
|
self.questIcon:ClearAllPoints()
|
||||||
|
self.questIcon:SetPoint("TOPRIGHT", self, "TOPRIGHT", 1, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update quality border display
|
||||||
|
local function UpdateQualityBorder(self, itemQuality, itemLink, bagID, Utils)
|
||||||
|
if not self.qualityBorder then return end
|
||||||
|
|
||||||
|
if bagID == -2 then
|
||||||
|
-- Special border for keyring items (cyan/blue)
|
||||||
|
self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 1)
|
||||||
|
self.qualityBorder:Show()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not itemQuality then
|
||||||
|
self.qualityBorder:Hide()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check settings
|
||||||
|
local showEquipmentBorder, showOtherBorder
|
||||||
|
if Utils and Utils.SafeCall then
|
||||||
|
showEquipmentBorder = Utils:SafeCall("DB", "GetSetting", "showQualityBorderEquipment")
|
||||||
|
showOtherBorder = Utils:SafeCall("DB", "GetSetting", "showQualityBorderOther")
|
||||||
|
end
|
||||||
|
|
||||||
|
if showEquipmentBorder == nil then showEquipmentBorder = true end
|
||||||
|
if showOtherBorder == nil then showOtherBorder = true end
|
||||||
|
|
||||||
|
-- Check if item is equipment
|
||||||
|
local isEquipment = false
|
||||||
|
if itemLink and Utils and Utils.IsEquipment then
|
||||||
|
isEquipment = Utils:IsEquipment(itemLink)
|
||||||
|
end
|
||||||
|
|
||||||
|
local shouldShowBorder = (isEquipment and showEquipmentBorder) or (not isEquipment and showOtherBorder)
|
||||||
|
|
||||||
|
if shouldShowBorder then
|
||||||
|
local r, g, b = 1, 1, 1
|
||||||
|
if Utils and Utils.GetQualityColor then
|
||||||
|
r, g, b = Utils:GetQualityColor(itemQuality)
|
||||||
|
end
|
||||||
|
self.qualityBorder:SetBackdropBorderColor(r, g, b, 1)
|
||||||
|
self.qualityBorder:Show()
|
||||||
|
else
|
||||||
|
self.qualityBorder:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clear item button for empty slot
|
||||||
|
local function ClearItemButton(self, emptySlotBg, countText, bagID)
|
||||||
|
self.hasItem = false
|
||||||
|
|
||||||
|
if SetItemButtonTexture then SetItemButtonTexture(self, nil) end
|
||||||
|
if SetItemButtonCount then SetItemButtonCount(self, 0) end
|
||||||
|
if SetItemButtonDesaturated then SetItemButtonDesaturated(self, false) end
|
||||||
|
|
||||||
|
-- Clear cooldown overlay
|
||||||
|
local cooldown = getglobal(self:GetName().."Cooldown") or self.cooldown
|
||||||
|
if cooldown and cooldown.Hide then cooldown:Hide() end
|
||||||
|
|
||||||
|
-- Clear icon texture
|
||||||
|
local iconTexture = getglobal(self:GetName().."IconTexture")
|
||||||
|
if not iconTexture then
|
||||||
|
iconTexture = getglobal(self:GetName().."Icon") or self.icon or self.Icon
|
||||||
|
end
|
||||||
|
if iconTexture then
|
||||||
|
iconTexture:SetTexture(nil)
|
||||||
|
iconTexture:Hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clear unusable tint
|
||||||
|
if SetItemButtonTextureVertexColor then
|
||||||
|
SetItemButtonTextureVertexColor(self, 1.0, 1.0, 1.0)
|
||||||
|
end
|
||||||
|
if self.unusableOverlay and self.unusableOverlay.Hide then
|
||||||
|
self.unusableOverlay:Hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hide normal texture
|
||||||
|
self:SetNormalTexture("")
|
||||||
|
local normalBorder = getglobal(self:GetName().."NormalTexture")
|
||||||
|
if normalBorder then normalBorder:SetTexture("") end
|
||||||
|
|
||||||
|
-- Show/hide empty slot background
|
||||||
|
if emptySlotBg then
|
||||||
|
emptySlotBg:Show()
|
||||||
|
emptySlotBg:SetAlpha(0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
if countText then countText:Hide() end
|
||||||
|
|
||||||
|
-- Handle quality border for empty keyring slots
|
||||||
|
if self.qualityBorder then
|
||||||
|
if bagID == -2 then
|
||||||
|
self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 0.5)
|
||||||
|
self.qualityBorder:Show()
|
||||||
|
else
|
||||||
|
self.qualityBorder:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hide quest elements
|
||||||
|
if self.questBorder then self.questBorder:Hide() end
|
||||||
|
if self.questIcon then self.questIcon:Hide() end
|
||||||
|
end
|
||||||
|
|
||||||
|
--=====================================================
|
||||||
|
-- Main SetItem function (orchestrates helper functions)
|
||||||
|
--=====================================================
|
||||||
|
|
||||||
|
-- Set item data
|
||||||
|
function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly)
|
||||||
|
-- Proactively convert to number to avoid comparisons with strings in downstream functions
|
||||||
|
bagID = tonumber(bagID)
|
||||||
|
slotID = tonumber(slotID)
|
||||||
|
|
||||||
|
-- Reset all visual state before reassigning pooled button
|
||||||
|
ResetButtonVisualState(self)
|
||||||
|
|
||||||
|
-- Set button properties
|
||||||
|
self.bagID = bagID
|
||||||
|
self.slotID = slotID
|
||||||
|
if self.SetID then
|
||||||
|
self:SetID(slotID or 0)
|
||||||
|
end
|
||||||
|
self.bagIndex = bagID or -100
|
||||||
|
self.itemData = itemData
|
||||||
|
self.isBank = isBank or false
|
||||||
|
self.otherChar = otherCharName
|
||||||
|
self.isReadOnly = isReadOnly or false
|
||||||
|
self.isMail = false
|
||||||
|
self.mailIndex = nil
|
||||||
|
self.mailItemIndex = nil
|
||||||
|
self.mailData = nil
|
||||||
|
|
||||||
|
-- Configure drag/drop
|
||||||
|
SetupDragDrop(self)
|
||||||
|
|
||||||
|
-- Default to true if not specified
|
||||||
if matchesFilter == nil then
|
if matchesFilter == nil then
|
||||||
matchesFilter = true
|
matchesFilter = true
|
||||||
end
|
end
|
||||||
|
|
||||||
self.mailData = nil -- Clear mail metadata by default
|
-- Get UI elements
|
||||||
|
|
||||||
-- Use Blizzard's default count fontstring (ContainerFrameItemButtonTemplate creates $parentCount)
|
|
||||||
local countText = getglobal(self:GetName().."Count")
|
local countText = getglobal(self:GetName().."Count")
|
||||||
local emptySlotBg = getglobal(self:GetName().."_EmptySlotBg")
|
local emptySlotBg = getglobal(self:GetName().."_EmptySlotBg")
|
||||||
|
local Utils = addon and addon.Modules and addon.Modules.Utils
|
||||||
|
|
||||||
-- Apply icon size setting (nil-safe)
|
-- Get icon size setting
|
||||||
local iconSize = 37
|
local iconSize = 37
|
||||||
if addon and addon.Modules and addon.Modules.DB and addon.Modules.DB.GetSetting then
|
if Utils and Utils.SafeCall then
|
||||||
iconSize = addon.Modules.DB:GetSetting("iconSize") or iconSize
|
iconSize = Utils:SafeCall("DB", "GetSetting", "iconSize") or iconSize
|
||||||
elseif Guda and Guda.Modules and Guda.Modules.DB and Guda.Modules.DB.GetSetting then
|
|
||||||
iconSize = Guda.Modules.DB:GetSetting("iconSize") or iconSize
|
|
||||||
end
|
end
|
||||||
if addon and addon.Constants and addon.Constants.BUTTON_SIZE then
|
if addon and addon.Constants then
|
||||||
iconSize = iconSize or addon.Constants.BUTTON_SIZE
|
iconSize = iconSize or addon.Constants.BUTTON_SIZE
|
||||||
end
|
end
|
||||||
self:SetWidth(iconSize)
|
self:SetWidth(iconSize)
|
||||||
self:SetHeight(iconSize)
|
self:SetHeight(iconSize)
|
||||||
|
|
||||||
-- In live mode (readOnly=false), query real-time game state instead of cached DB
|
-- Get item display info (texture, count, hasItem)
|
||||||
-- In read-only mode (readOnly=true), use cached itemData from DB
|
local displayTexture, displayCount, hasItem = GetItemDisplayInfo(bagID, slotID, itemData, self.isReadOnly)
|
||||||
local displayTexture, displayCount
|
self.hasItem = hasItem
|
||||||
|
|
||||||
if not self.isReadOnly then
|
-- Apply display based on whether slot has item
|
||||||
-- LIVE MODE: Always query game state directly, never use cached itemData
|
|
||||||
local liveTexture, liveCount = GetContainerItemInfo(bagID, slotID)
|
|
||||||
if liveTexture then
|
|
||||||
displayTexture = liveTexture
|
|
||||||
displayCount = liveCount
|
|
||||||
self.hasItem = true
|
|
||||||
elseif bagID == -2 then
|
|
||||||
-- Fallback for keyring in 1.12.1
|
|
||||||
local link = GetContainerItemLink(bagID, slotID)
|
|
||||||
if link then
|
|
||||||
self.hasItem = true
|
|
||||||
-- We might not have the texture from GetContainerItemInfo, try to get it from GetItemInfo
|
|
||||||
local _, _, _, _, _, _, _, _, itemTexture = GetItemInfo(link)
|
|
||||||
displayTexture = itemTexture
|
|
||||||
displayCount = 1 -- Keyring items are usually unique anyway
|
|
||||||
else
|
|
||||||
self.hasItem = false
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- No item in this slot (even if itemData has cached data)
|
|
||||||
self.hasItem = false
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- READ-ONLY MODE: Use cached itemData from DB (can't query other characters)
|
|
||||||
if itemData and itemData.texture then
|
|
||||||
displayTexture = itemData.texture
|
|
||||||
displayCount = itemData.count
|
|
||||||
self.hasItem = true
|
|
||||||
else
|
|
||||||
self.hasItem = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Apply the determined texture and count
|
|
||||||
if self.hasItem then
|
if self.hasItem then
|
||||||
|
-- Set texture
|
||||||
if SetItemButtonTexture then
|
if SetItemButtonTexture then
|
||||||
SetItemButtonTexture(self, displayTexture)
|
SetItemButtonTexture(self, displayTexture)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Explicitly set and show icon texture as SetItemButtonTexture can be unreliable for custom paths in 1.12
|
|
||||||
local iconTexture = getglobal(self:GetName().."IconTexture") or getglobal(self:GetName().."Icon") or self.icon or self.Icon
|
local iconTexture = getglobal(self:GetName().."IconTexture") or getglobal(self:GetName().."Icon") or self.icon or self.Icon
|
||||||
if iconTexture and displayTexture then
|
if iconTexture and displayTexture then
|
||||||
iconTexture:SetTexture(displayTexture)
|
iconTexture:SetTexture(displayTexture)
|
||||||
iconTexture:Show()
|
iconTexture:Show()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Set count
|
||||||
if SetItemButtonCount then SetItemButtonCount(self, displayCount or 1) end
|
if SetItemButtonCount then SetItemButtonCount(self, displayCount or 1) end
|
||||||
if emptySlotBg then emptySlotBg:Hide() end
|
if emptySlotBg then emptySlotBg:Hide() end
|
||||||
|
|
||||||
-- Update cooldown overlay for live items
|
-- Update cooldown overlay for live items
|
||||||
if not self.isReadOnly and not self.otherChar and Guda_ItemButton_UpdateCooldown then
|
if not self.isReadOnly and not self.otherChar and Guda_ItemButton_UpdateCooldown then
|
||||||
Guda_ItemButton_UpdateCooldown(self)
|
Guda_ItemButton_UpdateCooldown(self)
|
||||||
else
|
else
|
||||||
-- Ensure cooldown is hidden for read-only/other character views
|
|
||||||
local cd = getglobal(self:GetName().."Cooldown") or self.cooldown
|
local cd = getglobal(self:GetName().."Cooldown") or self.cooldown
|
||||||
if cd and cd.Hide then cd:Hide() end
|
if cd and cd.Hide then cd:Hide() end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update unusable red overlay tint
|
-- Update unusable red overlay tint
|
||||||
if Guda_ItemButton_UpdateUsableTint then
|
if Guda_ItemButton_UpdateUsableTint then
|
||||||
Guda_ItemButton_UpdateUsableTint(self)
|
Guda_ItemButton_UpdateUsableTint(self)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Fully clear all item button state for empty slots
|
-- Clear empty slot
|
||||||
if SetItemButtonTexture then SetItemButtonTexture(self, nil) end
|
ClearItemButton(self, emptySlotBg, countText, bagID)
|
||||||
if SetItemButtonCount then SetItemButtonCount(self, 0) end
|
|
||||||
if SetItemButtonDesaturated then SetItemButtonDesaturated(self, false) end
|
|
||||||
|
|
||||||
-- Ensure cooldown overlay is hidden for empty slots
|
|
||||||
local cooldown = getglobal(self:GetName().."Cooldown") or self.cooldown
|
|
||||||
if cooldown and cooldown.Hide then cooldown:Hide() end
|
|
||||||
|
|
||||||
-- Also clear the icon texture directly
|
|
||||||
local iconTexture = getglobal(self:GetName().."IconTexture")
|
|
||||||
if not iconTexture then
|
|
||||||
iconTexture = getglobal(self:GetName().."Icon") or self.icon or self.Icon
|
|
||||||
end
|
|
||||||
if iconTexture then
|
|
||||||
iconTexture:SetTexture(nil)
|
|
||||||
iconTexture:Hide()
|
|
||||||
end
|
|
||||||
|
|
||||||
if emptySlotBg then emptySlotBg:Show() end
|
|
||||||
|
|
||||||
-- Ensure any unusable tint is cleared on empty
|
|
||||||
if SetItemButtonTextureVertexColor then
|
|
||||||
SetItemButtonTextureVertexColor(self, 1.0, 1.0, 1.0)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clear unusable overlay for empty slots ✅ FIX: Clear red overlay when item is sold
|
|
||||||
if self.unusableOverlay and self.unusableOverlay.Hide then
|
|
||||||
self.unusableOverlay:Hide()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update tracking checkmark
|
-- Update tracking checkmark
|
||||||
|
UpdateTrackingCheckmark(self, Utils)
|
||||||
local check = getglobal(self:GetName().."_Check")
|
local check = getglobal(self:GetName().."_Check")
|
||||||
if check then
|
if check then
|
||||||
local isTracked = false
|
local isTracked = false
|
||||||
if self.hasItem then
|
if self.hasItem and self.itemData and self.itemData.link then
|
||||||
local itemID = addon.Modules.Utils:ExtractItemID(self.itemData and self.itemData.link)
|
local itemID = Utils and Utils.ExtractItemID and Utils:ExtractItemID(self.itemData.link)
|
||||||
if itemID then
|
if itemID then
|
||||||
local trackedItems = addon.Modules.DB:GetSetting("trackedItems") or {}
|
local trackedItems = Utils and Utils.SafeCall and Utils:SafeCall("DB", "GetSetting", "trackedItems") or {}
|
||||||
if trackedItems[itemID] then
|
if trackedItems[itemID] then
|
||||||
isTracked = true
|
isTracked = true
|
||||||
end
|
end
|
||||||
@@ -735,10 +866,8 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
|||||||
if countText and countText.GetFont then
|
if countText and countText.GetFont then
|
||||||
local font, _, flags = countText:GetFont()
|
local font, _, flags = countText:GetFont()
|
||||||
local fontSize = 12
|
local fontSize = 12
|
||||||
if addon and addon.Modules and addon.Modules.DB and addon.Modules.DB.GetSetting then
|
if Utils and Utils.SafeCall then
|
||||||
fontSize = addon.Modules.DB:GetSetting("iconFontSize") or fontSize
|
fontSize = Utils:SafeCall("DB", "GetSetting", "iconFontSize") or fontSize
|
||||||
elseif Guda and Guda.Modules and Guda.Modules.DB and Guda.Modules.DB.GetSetting then
|
|
||||||
fontSize = Guda.Modules.DB:GetSetting("iconFontSize") or fontSize
|
|
||||||
end
|
end
|
||||||
countText:SetFont(font, fontSize, flags)
|
countText:SetFont(font, fontSize, flags)
|
||||||
|
|
||||||
@@ -814,14 +943,11 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
|||||||
self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 1)
|
self.qualityBorder:SetBackdropBorderColor(0.2, 0.8, 1.0, 1)
|
||||||
self.qualityBorder:Show()
|
self.qualityBorder:Show()
|
||||||
elseif itemQuality then
|
elseif itemQuality then
|
||||||
-- Check settings to determine if we should show borders (nil-safe)
|
-- Check settings to determine if we should show borders
|
||||||
local showEquipmentBorder, showOtherBorder
|
local showEquipmentBorder, showOtherBorder
|
||||||
if addon and addon.Modules and addon.Modules.DB and addon.Modules.DB.GetSetting then
|
if Utils and Utils.SafeCall then
|
||||||
showEquipmentBorder = addon.Modules.DB:GetSetting("showQualityBorderEquipment")
|
showEquipmentBorder = Utils:SafeCall("DB", "GetSetting", "showQualityBorderEquipment")
|
||||||
showOtherBorder = addon.Modules.DB:GetSetting("showQualityBorderOther")
|
showOtherBorder = Utils:SafeCall("DB", "GetSetting", "showQualityBorderOther")
|
||||||
elseif Guda and Guda.Modules and Guda.Modules.DB and Guda.Modules.DB.GetSetting then
|
|
||||||
showEquipmentBorder = Guda.Modules.DB:GetSetting("showQualityBorderEquipment")
|
|
||||||
showOtherBorder = Guda.Modules.DB:GetSetting("showQualityBorderOther")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Default to true if settings not found
|
-- Default to true if settings not found
|
||||||
@@ -832,14 +958,10 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
|||||||
showOtherBorder = true
|
showOtherBorder = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if item is equipment (nil-safe)
|
-- Check if item is equipment
|
||||||
local isEquipment = false
|
local isEquipment = false
|
||||||
if itemLink then
|
if itemLink and Utils and Utils.IsEquipment then
|
||||||
if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.IsEquipment then
|
isEquipment = Utils:IsEquipment(itemLink)
|
||||||
isEquipment = addon.Modules.Utils:IsEquipment(itemLink)
|
|
||||||
elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.IsEquipment then
|
|
||||||
isEquipment = Guda.Modules.Utils:IsEquipment(itemLink)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Determine if we should show the border based on item type and settings
|
-- Determine if we should show the border based on item type and settings
|
||||||
@@ -848,10 +970,8 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
|||||||
if shouldShowBorder then
|
if shouldShowBorder then
|
||||||
-- Show colored border for all items (Poor, Common, Uncommon, Rare, Epic, etc.)
|
-- Show colored border for all items (Poor, Common, Uncommon, Rare, Epic, etc.)
|
||||||
local r, g, b = 1, 1, 1
|
local r, g, b = 1, 1, 1
|
||||||
if addon and addon.Modules and addon.Modules.Utils and addon.Modules.Utils.GetQualityColor then
|
if Utils and Utils.GetQualityColor then
|
||||||
r, g, b = addon.Modules.Utils:GetQualityColor(itemQuality)
|
r, g, b = Utils:GetQualityColor(itemQuality)
|
||||||
elseif Guda and Guda.Modules and Guda.Modules.Utils and Guda.Modules.Utils.GetQualityColor then
|
|
||||||
r, g, b = Guda.Modules.Utils:GetQualityColor(itemQuality)
|
|
||||||
end
|
end
|
||||||
self.qualityBorder:SetBackdropBorderColor(r, g, b, 1)
|
self.qualityBorder:SetBackdropBorderColor(r, g, b, 1)
|
||||||
self.qualityBorder:Show()
|
self.qualityBorder:Show()
|
||||||
|
|||||||
@@ -52,19 +52,23 @@ function Guda_SettingsPopup_SelectTab(tabName)
|
|||||||
-- Hide all tab content frames
|
-- Hide all tab content frames
|
||||||
local generalTab = getglobal("Guda_SettingsPopup_GeneralTab")
|
local generalTab = getglobal("Guda_SettingsPopup_GeneralTab")
|
||||||
local iconsTab = getglobal("Guda_SettingsPopup_IconsTab")
|
local iconsTab = getglobal("Guda_SettingsPopup_IconsTab")
|
||||||
|
local categoriesTab = getglobal("Guda_SettingsPopup_CategoriesTab")
|
||||||
local guideTab = getglobal("Guda_SettingsPopup_GuideTab")
|
local guideTab = getglobal("Guda_SettingsPopup_GuideTab")
|
||||||
|
|
||||||
if generalTab then generalTab:Hide() end
|
if generalTab then generalTab:Hide() end
|
||||||
if iconsTab then iconsTab:Hide() end
|
if iconsTab then iconsTab:Hide() end
|
||||||
|
if categoriesTab then categoriesTab:Hide() end
|
||||||
if guideTab then guideTab:Hide() end
|
if guideTab then guideTab:Hide() end
|
||||||
|
|
||||||
-- Reset all tab button backgrounds to inactive (0.1 alpha)
|
-- Reset all tab button backgrounds to inactive (0.1 alpha)
|
||||||
local generalBg = getglobal("Guda_SettingsPopup_GeneralTabButton_Bg")
|
local generalBg = getglobal("Guda_SettingsPopup_GeneralTabButton_Bg")
|
||||||
local iconsBg = getglobal("Guda_SettingsPopup_IconsTabButton_Bg")
|
local iconsBg = getglobal("Guda_SettingsPopup_IconsTabButton_Bg")
|
||||||
|
local categoriesBg = getglobal("Guda_SettingsPopup_CategoriesTabButton_Bg")
|
||||||
local guideBg = getglobal("Guda_SettingsPopup_GuideTabButton_Bg")
|
local guideBg = getglobal("Guda_SettingsPopup_GuideTabButton_Bg")
|
||||||
|
|
||||||
if generalBg then generalBg:SetTexture(1, 1, 1, 0.1) end
|
if generalBg then generalBg:SetTexture(1, 1, 1, 0.1) end
|
||||||
if iconsBg then iconsBg:SetTexture(1, 1, 1, 0.1) end
|
if iconsBg then iconsBg:SetTexture(1, 1, 1, 0.1) end
|
||||||
|
if categoriesBg then categoriesBg:SetTexture(1, 1, 1, 0.1) end
|
||||||
if guideBg then guideBg:SetTexture(1, 1, 1, 0.1) end
|
if guideBg then guideBg:SetTexture(1, 1, 1, 0.1) end
|
||||||
|
|
||||||
-- Show selected tab and highlight its button
|
-- Show selected tab and highlight its button
|
||||||
@@ -74,6 +78,10 @@ function Guda_SettingsPopup_SelectTab(tabName)
|
|||||||
elseif tabName == "icons" then
|
elseif tabName == "icons" then
|
||||||
if iconsTab then iconsTab:Show() end
|
if iconsTab then iconsTab:Show() end
|
||||||
if iconsBg then iconsBg:SetTexture(1, 1, 1, 0.3) end
|
if iconsBg then iconsBg:SetTexture(1, 1, 1, 0.3) end
|
||||||
|
elseif tabName == "categories" then
|
||||||
|
if categoriesTab then categoriesTab:Show() end
|
||||||
|
if categoriesBg then categoriesBg:SetTexture(1, 1, 1, 0.3) end
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
elseif tabName == "guide" then
|
elseif tabName == "guide" then
|
||||||
if guideTab then guideTab:Show() end
|
if guideTab then guideTab:Show() end
|
||||||
if guideBg then guideBg:SetTexture(1, 1, 1, 0.3) end
|
if guideBg then guideBg:SetTexture(1, 1, 1, 0.3) end
|
||||||
@@ -1160,6 +1168,798 @@ function Guda_SettingsPopup_ReverseStackSortCheckbox_OnClick(self)
|
|||||||
-- No immediate UI update needed
|
-- No immediate UI update needed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Categories Tab Functions
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
-- Number of visible rows in the category list
|
||||||
|
local CATEGORY_ROW_HEIGHT = 22
|
||||||
|
local CATEGORY_VISIBLE_ROWS = 14
|
||||||
|
local categoryRowFrames = {}
|
||||||
|
|
||||||
|
-- Create or get a category row frame
|
||||||
|
local function GetCategoryRowFrame(index)
|
||||||
|
if categoryRowFrames[index] then
|
||||||
|
return categoryRowFrames[index]
|
||||||
|
end
|
||||||
|
|
||||||
|
local container = getglobal("Guda_SettingsPopup_CategoryListContainer")
|
||||||
|
if not container then return nil end
|
||||||
|
|
||||||
|
local rowName = "Guda_SettingsPopup_CategoryRow" .. index
|
||||||
|
local row = CreateFrame("Frame", rowName, container)
|
||||||
|
row:SetHeight(CATEGORY_ROW_HEIGHT)
|
||||||
|
row:SetWidth(420)
|
||||||
|
row:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -((index - 1) * CATEGORY_ROW_HEIGHT))
|
||||||
|
|
||||||
|
-- Background highlight
|
||||||
|
local bg = row:CreateTexture(nil, "BACKGROUND")
|
||||||
|
bg:SetAllPoints(row)
|
||||||
|
bg:SetTexture(1, 1, 1, 0)
|
||||||
|
row.bg = bg
|
||||||
|
|
||||||
|
-- Enable checkbox
|
||||||
|
local checkbox = CreateFrame("CheckButton", rowName .. "_Checkbox", row, "UICheckButtonTemplate")
|
||||||
|
checkbox:SetWidth(20)
|
||||||
|
checkbox:SetHeight(20)
|
||||||
|
checkbox:SetPoint("LEFT", row, "LEFT", 0, 0)
|
||||||
|
checkbox:SetScript("OnClick", function()
|
||||||
|
local catId = this:GetParent().categoryId
|
||||||
|
if catId and Guda.Modules.CategoryManager then
|
||||||
|
Guda.Modules.CategoryManager:ToggleCategory(catId)
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
Guda_SettingsPopup_RefreshBagFrames()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
row.checkbox = checkbox
|
||||||
|
|
||||||
|
-- Category name
|
||||||
|
local nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
nameText:SetPoint("LEFT", checkbox, "RIGHT", 5, 0)
|
||||||
|
nameText:SetWidth(160)
|
||||||
|
nameText:SetJustifyH("LEFT")
|
||||||
|
row.nameText = nameText
|
||||||
|
|
||||||
|
-- Edit button
|
||||||
|
local editBtn = CreateFrame("Button", rowName .. "_EditBtn", row, "UIPanelButtonTemplate")
|
||||||
|
editBtn:SetWidth(40)
|
||||||
|
editBtn:SetHeight(18)
|
||||||
|
editBtn:SetPoint("LEFT", nameText, "RIGHT", 5, 0)
|
||||||
|
editBtn:SetText("Edit")
|
||||||
|
editBtn:SetScript("OnClick", function()
|
||||||
|
local catId = this:GetParent().categoryId
|
||||||
|
if catId then
|
||||||
|
Guda_CategoryEditor_Open(catId)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
row.editBtn = editBtn
|
||||||
|
|
||||||
|
-- Move Up button
|
||||||
|
local upBtn = CreateFrame("Button", rowName .. "_UpBtn", row)
|
||||||
|
upBtn:SetWidth(20)
|
||||||
|
upBtn:SetHeight(20)
|
||||||
|
upBtn:SetPoint("LEFT", editBtn, "RIGHT", 5, 0)
|
||||||
|
upBtn:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Up")
|
||||||
|
upBtn:SetPushedTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Down")
|
||||||
|
upBtn:SetHighlightTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Highlight")
|
||||||
|
upBtn:SetScript("OnClick", function()
|
||||||
|
local catId = this:GetParent().categoryId
|
||||||
|
if catId and Guda.Modules.CategoryManager then
|
||||||
|
Guda.Modules.CategoryManager:MoveCategoryUp(catId)
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
Guda_SettingsPopup_RefreshBagFrames()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
row.upBtn = upBtn
|
||||||
|
|
||||||
|
-- Move Down button
|
||||||
|
local downBtn = CreateFrame("Button", rowName .. "_DownBtn", row)
|
||||||
|
downBtn:SetWidth(20)
|
||||||
|
downBtn:SetHeight(20)
|
||||||
|
downBtn:SetPoint("LEFT", upBtn, "RIGHT", 2, 0)
|
||||||
|
downBtn:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Up")
|
||||||
|
downBtn:SetPushedTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Down")
|
||||||
|
downBtn:SetHighlightTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Highlight")
|
||||||
|
downBtn:SetScript("OnClick", function()
|
||||||
|
local catId = this:GetParent().categoryId
|
||||||
|
if catId and Guda.Modules.CategoryManager then
|
||||||
|
Guda.Modules.CategoryManager:MoveCategoryDown(catId)
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
Guda_SettingsPopup_RefreshBagFrames()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
row.downBtn = downBtn
|
||||||
|
|
||||||
|
-- Delete button (only for custom categories)
|
||||||
|
local deleteBtn = CreateFrame("Button", rowName .. "_DeleteBtn", row, "UIPanelCloseButton")
|
||||||
|
deleteBtn:SetWidth(20)
|
||||||
|
deleteBtn:SetHeight(20)
|
||||||
|
deleteBtn:SetPoint("LEFT", downBtn, "RIGHT", 5, 0)
|
||||||
|
deleteBtn:SetScript("OnClick", function()
|
||||||
|
local catId = this:GetParent().categoryId
|
||||||
|
if catId and Guda.Modules.CategoryManager then
|
||||||
|
local def = Guda.Modules.CategoryManager:GetCategory(catId)
|
||||||
|
if def and not def.isBuiltIn then
|
||||||
|
Guda.Modules.CategoryManager:DeleteCategory(catId)
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
Guda_SettingsPopup_RefreshBagFrames()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
row.deleteBtn = deleteBtn
|
||||||
|
|
||||||
|
-- Built-in indicator
|
||||||
|
local builtInText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||||
|
builtInText:SetPoint("LEFT", deleteBtn, "RIGHT", 5, 0)
|
||||||
|
builtInText:SetText("(Built-in)")
|
||||||
|
builtInText:SetTextColor(0.5, 0.5, 0.5)
|
||||||
|
row.builtInText = builtInText
|
||||||
|
|
||||||
|
-- Hover highlight
|
||||||
|
row:EnableMouse(true)
|
||||||
|
row:SetScript("OnEnter", function()
|
||||||
|
this.bg:SetTexture(1, 1, 1, 0.1)
|
||||||
|
end)
|
||||||
|
row:SetScript("OnLeave", function()
|
||||||
|
this.bg:SetTexture(1, 1, 1, 0)
|
||||||
|
end)
|
||||||
|
|
||||||
|
categoryRowFrames[index] = row
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update the category list display
|
||||||
|
function Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
if not Guda.Modules.CategoryManager then return end
|
||||||
|
|
||||||
|
local scrollFrame = getglobal("Guda_SettingsPopup_CategoriesScrollFrame")
|
||||||
|
if not scrollFrame then return end
|
||||||
|
|
||||||
|
local categoryOrder = Guda.Modules.CategoryManager:GetCategoryOrder()
|
||||||
|
local totalCategories = table.getn(categoryOrder)
|
||||||
|
|
||||||
|
-- Update scroll frame
|
||||||
|
FauxScrollFrame_Update(scrollFrame, totalCategories, CATEGORY_VISIBLE_ROWS, CATEGORY_ROW_HEIGHT)
|
||||||
|
|
||||||
|
local offset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||||
|
|
||||||
|
for i = 1, CATEGORY_VISIBLE_ROWS do
|
||||||
|
local row = GetCategoryRowFrame(i)
|
||||||
|
if row then
|
||||||
|
local dataIndex = i + offset
|
||||||
|
if dataIndex <= totalCategories then
|
||||||
|
local categoryId = categoryOrder[dataIndex]
|
||||||
|
local categoryDef = Guda.Modules.CategoryManager:GetCategory(categoryId)
|
||||||
|
|
||||||
|
if categoryDef then
|
||||||
|
row.categoryId = categoryId
|
||||||
|
row.nameText:SetText(categoryDef.name or categoryId)
|
||||||
|
row.checkbox:SetChecked(categoryDef.enabled and 1 or 0)
|
||||||
|
|
||||||
|
-- Show/hide delete button based on whether it's built-in
|
||||||
|
if categoryDef.isBuiltIn then
|
||||||
|
row.deleteBtn:Hide()
|
||||||
|
row.builtInText:Show()
|
||||||
|
else
|
||||||
|
row.deleteBtn:Show()
|
||||||
|
row.builtInText:Hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hide all controls for hideControls categories (only checkbox visible)
|
||||||
|
if categoryDef.hideControls then
|
||||||
|
row.editBtn:Hide()
|
||||||
|
row.upBtn:Hide()
|
||||||
|
row.downBtn:Hide()
|
||||||
|
row.deleteBtn:Hide()
|
||||||
|
row.builtInText:Hide()
|
||||||
|
else
|
||||||
|
row.editBtn:Show()
|
||||||
|
row.upBtn:Show()
|
||||||
|
row.downBtn:Show()
|
||||||
|
|
||||||
|
-- Enable/disable move buttons based on position
|
||||||
|
if dataIndex == 1 then
|
||||||
|
row.upBtn:Disable()
|
||||||
|
else
|
||||||
|
-- Check if category above has hideControls (can't move above it)
|
||||||
|
local aboveCatId = categoryOrder[dataIndex - 1]
|
||||||
|
local aboveCatDef = Guda.Modules.CategoryManager:GetCategory(aboveCatId)
|
||||||
|
if aboveCatDef and aboveCatDef.hideControls then
|
||||||
|
row.upBtn:Disable()
|
||||||
|
else
|
||||||
|
row.upBtn:Enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if dataIndex == totalCategories then
|
||||||
|
row.downBtn:Disable()
|
||||||
|
else
|
||||||
|
row.downBtn:Enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set text color based on enabled state
|
||||||
|
if categoryDef.enabled then
|
||||||
|
row.nameText:SetTextColor(1, 1, 1)
|
||||||
|
else
|
||||||
|
row.nameText:SetTextColor(0.5, 0.5, 0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
row:Show()
|
||||||
|
else
|
||||||
|
row:Hide()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
row:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set button texts
|
||||||
|
local addBtn = getglobal("Guda_SettingsPopup_AddCategoryButton")
|
||||||
|
if addBtn then
|
||||||
|
addBtn:SetText("+ Add Category")
|
||||||
|
end
|
||||||
|
|
||||||
|
local resetBtn = getglobal("Guda_SettingsPopup_ResetCategoriesButton")
|
||||||
|
if resetBtn then
|
||||||
|
resetBtn:SetText("Reset Defaults")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add new custom category
|
||||||
|
function Guda_SettingsPopup_AddCategory_OnClick()
|
||||||
|
if not Guda.Modules.CategoryManager then return end
|
||||||
|
|
||||||
|
-- Generate unique ID
|
||||||
|
local baseId = "Custom"
|
||||||
|
local counter = 1
|
||||||
|
local newId = baseId .. counter
|
||||||
|
|
||||||
|
local cats = Guda.Modules.CategoryManager:GetCategories()
|
||||||
|
while cats.definitions[newId] do
|
||||||
|
counter = counter + 1
|
||||||
|
newId = baseId .. counter
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create new category definition
|
||||||
|
local newDef = {
|
||||||
|
name = "Custom " .. counter,
|
||||||
|
icon = "Interface\\Icons\\INV_Misc_QuestionMark",
|
||||||
|
rules = {},
|
||||||
|
matchMode = "any",
|
||||||
|
priority = 80,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Add to database
|
||||||
|
if Guda.Modules.CategoryManager:AddCategory(newId, newDef) then
|
||||||
|
-- Update display
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
-- Open editor for the new category
|
||||||
|
Guda_CategoryEditor_Open(newId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reset categories to defaults
|
||||||
|
function Guda_SettingsPopup_ResetCategories_OnClick()
|
||||||
|
if Guda.Modules.CategoryManager then
|
||||||
|
Guda.Modules.CategoryManager:ResetToDefaults()
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
Guda_SettingsPopup_RefreshBagFrames()
|
||||||
|
Guda:Print("Categories reset to defaults.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Refresh bag and bank frames after category changes
|
||||||
|
function Guda_SettingsPopup_RefreshBagFrames()
|
||||||
|
-- Refresh category list
|
||||||
|
Guda_RefreshCategoryList()
|
||||||
|
|
||||||
|
-- Update bag frame if visible
|
||||||
|
local bagFrame = getglobal("Guda_BagFrame")
|
||||||
|
if bagFrame and bagFrame:IsShown() then
|
||||||
|
Guda.Modules.BagFrame:Update()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update bank frame if visible
|
||||||
|
local bankFrame = getglobal("Guda_BankFrame")
|
||||||
|
if bankFrame and bankFrame:IsShown() then
|
||||||
|
Guda.Modules.BankFrame:Update()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Category Editor Functions
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
local editorCategoryId = nil
|
||||||
|
local editorMatchMode = "any"
|
||||||
|
local editorRules = {}
|
||||||
|
local editorRuleFrames = {}
|
||||||
|
local RULE_ROW_HEIGHT = 28
|
||||||
|
local MAX_RULES = 6
|
||||||
|
|
||||||
|
-- Rule type options for dropdown
|
||||||
|
local RULE_TYPE_OPTIONS = {
|
||||||
|
{ id = "itemType", name = "Item Type" },
|
||||||
|
{ id = "itemSubtype", name = "Item Subtype" },
|
||||||
|
{ id = "namePattern", name = "Name Contains" },
|
||||||
|
{ id = "itemID", name = "Item ID" },
|
||||||
|
{ id = "quality", name = "Quality (exact)" },
|
||||||
|
{ id = "qualityMin", name = "Quality (min)" },
|
||||||
|
{ id = "isBoE", name = "Bind on Equip" },
|
||||||
|
{ id = "isQuestItem", name = "Quest Item" },
|
||||||
|
{ id = "isJunk", name = "Is Junk" },
|
||||||
|
{ id = "restoreTag", name = "Restore Type" },
|
||||||
|
{ id = "isSoulShard", name = "Soul Shard" },
|
||||||
|
{ id = "isProjectile", name = "Projectile" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Value options for specific rule types
|
||||||
|
local RULE_VALUE_OPTIONS = {
|
||||||
|
itemType = { "Armor", "Weapon", "Consumable", "Container", "Trade Goods", "Projectile", "Quiver", "Reagent", "Recipe", "Key", "Miscellaneous", "Quest" },
|
||||||
|
quality = { "0 - Poor", "1 - Common", "2 - Uncommon", "3 - Rare", "4 - Epic", "5 - Legendary" },
|
||||||
|
qualityMin = { "0 - Poor", "1 - Common", "2 - Uncommon", "3 - Rare", "4 - Epic", "5 - Legendary" },
|
||||||
|
isBoE = { "true", "false" },
|
||||||
|
isQuestItem = { "true", "false" },
|
||||||
|
isJunk = { "true", "false" },
|
||||||
|
isSoulShard = { "true", "false" },
|
||||||
|
isProjectile = { "true", "false" },
|
||||||
|
restoreTag = { "eat", "drink", "restore" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- OnLoad for Category Editor
|
||||||
|
function Guda_CategoryEditor_OnLoad(self)
|
||||||
|
Guda:ApplyBackdrop(self, "DEFAULT_FRAME")
|
||||||
|
|
||||||
|
-- Set button texts
|
||||||
|
local addBtn = getglobal("Guda_CategoryEditor_AddRuleButton")
|
||||||
|
if addBtn then addBtn:SetText("+ Add Rule") end
|
||||||
|
|
||||||
|
local saveBtn = getglobal("Guda_CategoryEditor_SaveButton")
|
||||||
|
if saveBtn then saveBtn:SetText("Save") end
|
||||||
|
|
||||||
|
local cancelBtn = getglobal("Guda_CategoryEditor_CancelButton")
|
||||||
|
if cancelBtn then cancelBtn:SetText("Cancel") end
|
||||||
|
|
||||||
|
-- Create radio button labels
|
||||||
|
local anyRadio = getglobal("Guda_CategoryEditor_MatchAny")
|
||||||
|
if anyRadio then
|
||||||
|
local label = anyRadio:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||||
|
label:SetPoint("LEFT", anyRadio, "RIGHT", 2, 0)
|
||||||
|
label:SetText("Any rule")
|
||||||
|
end
|
||||||
|
|
||||||
|
local allRadio = getglobal("Guda_CategoryEditor_MatchAll")
|
||||||
|
if allRadio then
|
||||||
|
local label = allRadio:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||||
|
label:SetPoint("LEFT", allRadio, "RIGHT", 2, 0)
|
||||||
|
label:SetText("All rules")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- OnShow for Category Editor
|
||||||
|
function Guda_CategoryEditor_OnShow(self)
|
||||||
|
Guda_CategoryEditor_UpdateRulesDisplay()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Open Category Editor for a specific category
|
||||||
|
function Guda_CategoryEditor_Open(categoryId)
|
||||||
|
if not Guda.Modules.CategoryManager then return end
|
||||||
|
|
||||||
|
local categoryDef = Guda.Modules.CategoryManager:GetCategory(categoryId)
|
||||||
|
if not categoryDef then return end
|
||||||
|
|
||||||
|
editorCategoryId = categoryId
|
||||||
|
editorMatchMode = categoryDef.matchMode or "any"
|
||||||
|
|
||||||
|
-- Copy rules
|
||||||
|
editorRules = {}
|
||||||
|
if categoryDef.rules then
|
||||||
|
for i, rule in ipairs(categoryDef.rules) do
|
||||||
|
table.insert(editorRules, { type = rule.type, value = rule.value })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set title
|
||||||
|
local title = getglobal("Guda_CategoryEditor_Title")
|
||||||
|
if title then
|
||||||
|
if categoryDef.isBuiltIn then
|
||||||
|
title:SetText("Edit Category (Built-in)")
|
||||||
|
else
|
||||||
|
title:SetText("Edit Category")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set name
|
||||||
|
local nameBox = getglobal("Guda_CategoryEditor_NameEditBox")
|
||||||
|
if nameBox then
|
||||||
|
nameBox:SetText(categoryDef.name or categoryId)
|
||||||
|
-- Disable name editing for built-in categories
|
||||||
|
if categoryDef.isBuiltIn then
|
||||||
|
nameBox:EnableMouse(false)
|
||||||
|
nameBox:EnableKeyboard(false)
|
||||||
|
nameBox:SetTextColor(0.5, 0.5, 0.5)
|
||||||
|
else
|
||||||
|
nameBox:EnableMouse(true)
|
||||||
|
nameBox:EnableKeyboard(true)
|
||||||
|
nameBox:SetTextColor(1, 1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set match mode
|
||||||
|
Guda_CategoryEditor_SetMatchMode(editorMatchMode)
|
||||||
|
|
||||||
|
-- Show editor
|
||||||
|
local editor = getglobal("Guda_CategoryEditor")
|
||||||
|
if editor then
|
||||||
|
editor:Show()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set match mode (radio buttons)
|
||||||
|
function Guda_CategoryEditor_SetMatchMode(mode)
|
||||||
|
editorMatchMode = mode
|
||||||
|
|
||||||
|
local anyRadio = getglobal("Guda_CategoryEditor_MatchAny")
|
||||||
|
local allRadio = getglobal("Guda_CategoryEditor_MatchAll")
|
||||||
|
|
||||||
|
if anyRadio then anyRadio:SetChecked(mode == "any" and 1 or 0) end
|
||||||
|
if allRadio then allRadio:SetChecked(mode == "all" and 1 or 0) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get or create a rule row frame
|
||||||
|
local function GetRuleRowFrame(index)
|
||||||
|
if editorRuleFrames[index] then
|
||||||
|
return editorRuleFrames[index]
|
||||||
|
end
|
||||||
|
|
||||||
|
local container = getglobal("Guda_CategoryEditor_RulesContainer")
|
||||||
|
if not container then return nil end
|
||||||
|
|
||||||
|
local rowName = "Guda_CategoryEditor_RuleRow" .. index
|
||||||
|
local row = CreateFrame("Frame", rowName, container)
|
||||||
|
row:SetHeight(RULE_ROW_HEIGHT)
|
||||||
|
row:SetWidth(360)
|
||||||
|
row:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -((index - 1) * RULE_ROW_HEIGHT))
|
||||||
|
|
||||||
|
-- Rule type dropdown button
|
||||||
|
local typeBtn = CreateFrame("Button", rowName .. "_TypeBtn", row, "UIPanelButtonTemplate")
|
||||||
|
typeBtn:SetWidth(120)
|
||||||
|
typeBtn:SetHeight(22)
|
||||||
|
typeBtn:SetPoint("LEFT", row, "LEFT", 0, 0)
|
||||||
|
typeBtn:SetText("Select Type")
|
||||||
|
typeBtn.ruleIndex = index
|
||||||
|
typeBtn:SetScript("OnClick", function()
|
||||||
|
Guda_CategoryEditor_ShowTypeDropdown(this, this.ruleIndex)
|
||||||
|
end)
|
||||||
|
row.typeBtn = typeBtn
|
||||||
|
|
||||||
|
-- Value input (editbox for text, button for dropdowns)
|
||||||
|
local valueBox = CreateFrame("EditBox", rowName .. "_ValueBox", row, "InputBoxTemplate")
|
||||||
|
valueBox:SetWidth(140)
|
||||||
|
valueBox:SetHeight(22)
|
||||||
|
valueBox:SetPoint("LEFT", typeBtn, "RIGHT", 5, 0)
|
||||||
|
valueBox:SetAutoFocus(false)
|
||||||
|
valueBox.ruleIndex = index
|
||||||
|
valueBox:SetScript("OnTextChanged", function()
|
||||||
|
local idx = this.ruleIndex
|
||||||
|
if editorRules[idx] then
|
||||||
|
editorRules[idx].value = this:GetText()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
valueBox:SetScript("OnEscapePressed", function() this:ClearFocus() end)
|
||||||
|
valueBox:SetScript("OnEnterPressed", function() this:ClearFocus() end)
|
||||||
|
row.valueBox = valueBox
|
||||||
|
|
||||||
|
-- Value dropdown button (for predefined values)
|
||||||
|
local valueBtn = CreateFrame("Button", rowName .. "_ValueBtn", row, "UIPanelButtonTemplate")
|
||||||
|
valueBtn:SetWidth(140)
|
||||||
|
valueBtn:SetHeight(22)
|
||||||
|
valueBtn:SetPoint("LEFT", typeBtn, "RIGHT", 5, 0)
|
||||||
|
valueBtn:SetText("Select Value")
|
||||||
|
valueBtn.ruleIndex = index
|
||||||
|
valueBtn:SetScript("OnClick", function()
|
||||||
|
Guda_CategoryEditor_ShowValueDropdown(this, this.ruleIndex)
|
||||||
|
end)
|
||||||
|
valueBtn:Hide()
|
||||||
|
row.valueBtn = valueBtn
|
||||||
|
|
||||||
|
-- Delete button
|
||||||
|
local deleteBtn = CreateFrame("Button", rowName .. "_DeleteBtn", row, "UIPanelCloseButton")
|
||||||
|
deleteBtn:SetWidth(22)
|
||||||
|
deleteBtn:SetHeight(22)
|
||||||
|
deleteBtn:SetPoint("LEFT", valueBox, "RIGHT", 5, 0)
|
||||||
|
deleteBtn.ruleIndex = index
|
||||||
|
deleteBtn:SetScript("OnClick", function()
|
||||||
|
Guda_CategoryEditor_RemoveRule(this.ruleIndex)
|
||||||
|
end)
|
||||||
|
row.deleteBtn = deleteBtn
|
||||||
|
|
||||||
|
editorRuleFrames[index] = row
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update rules display
|
||||||
|
function Guda_CategoryEditor_UpdateRulesDisplay()
|
||||||
|
local numRules = table.getn(editorRules)
|
||||||
|
|
||||||
|
for i = 1, MAX_RULES do
|
||||||
|
local row = GetRuleRowFrame(i)
|
||||||
|
if row then
|
||||||
|
if i <= numRules then
|
||||||
|
local rule = editorRules[i]
|
||||||
|
row.ruleIndex = i
|
||||||
|
row.typeBtn.ruleIndex = i
|
||||||
|
row.valueBox.ruleIndex = i
|
||||||
|
row.valueBtn.ruleIndex = i
|
||||||
|
row.deleteBtn.ruleIndex = i
|
||||||
|
|
||||||
|
-- Set type button text
|
||||||
|
local typeName = "Select Type"
|
||||||
|
for _, opt in ipairs(RULE_TYPE_OPTIONS) do
|
||||||
|
if opt.id == rule.type then
|
||||||
|
typeName = opt.name
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
row.typeBtn:SetText(typeName)
|
||||||
|
|
||||||
|
-- Show appropriate value input
|
||||||
|
if RULE_VALUE_OPTIONS[rule.type] then
|
||||||
|
-- Use dropdown for predefined values
|
||||||
|
row.valueBox:Hide()
|
||||||
|
row.valueBtn:Show()
|
||||||
|
local displayValue = tostring(rule.value or "Select")
|
||||||
|
-- Format quality display
|
||||||
|
if (rule.type == "quality" or rule.type == "qualityMin") and type(rule.value) == "number" then
|
||||||
|
local qualNames = { [0]="Poor", [1]="Common", [2]="Uncommon", [3]="Rare", [4]="Epic", [5]="Legendary" }
|
||||||
|
displayValue = rule.value .. " - " .. (qualNames[rule.value] or "")
|
||||||
|
end
|
||||||
|
row.valueBtn:SetText(displayValue)
|
||||||
|
else
|
||||||
|
-- Use editbox for text input
|
||||||
|
row.valueBtn:Hide()
|
||||||
|
row.valueBox:Show()
|
||||||
|
row.valueBox:SetText(tostring(rule.value or ""))
|
||||||
|
end
|
||||||
|
|
||||||
|
row:Show()
|
||||||
|
else
|
||||||
|
row:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Enable/disable Add Rule button
|
||||||
|
local addBtn = getglobal("Guda_CategoryEditor_AddRuleButton")
|
||||||
|
if addBtn then
|
||||||
|
if numRules >= MAX_RULES then
|
||||||
|
addBtn:Disable()
|
||||||
|
else
|
||||||
|
addBtn:Enable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add a new rule
|
||||||
|
function Guda_CategoryEditor_AddRule()
|
||||||
|
if table.getn(editorRules) >= MAX_RULES then return end
|
||||||
|
|
||||||
|
table.insert(editorRules, { type = "itemType", value = "Consumable" })
|
||||||
|
Guda_CategoryEditor_UpdateRulesDisplay()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove a rule
|
||||||
|
function Guda_CategoryEditor_RemoveRule(index)
|
||||||
|
if index > 0 and index <= table.getn(editorRules) then
|
||||||
|
table.remove(editorRules, index)
|
||||||
|
Guda_CategoryEditor_UpdateRulesDisplay()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper to set rule type (called from dropdown)
|
||||||
|
function Guda_CategoryEditor_SetRuleType(ruleIndex, typeId)
|
||||||
|
if not editorRules[ruleIndex] then return end
|
||||||
|
|
||||||
|
editorRules[ruleIndex].type = typeId
|
||||||
|
-- Reset value when type changes
|
||||||
|
if RULE_VALUE_OPTIONS[typeId] then
|
||||||
|
editorRules[ruleIndex].value = RULE_VALUE_OPTIONS[typeId][1]
|
||||||
|
-- Convert to proper type
|
||||||
|
if typeId == "quality" or typeId == "qualityMin" then
|
||||||
|
editorRules[ruleIndex].value = 0
|
||||||
|
elseif typeId == "isBoE" or typeId == "isQuestItem" or typeId == "isSoulShard" or typeId == "isProjectile" then
|
||||||
|
editorRules[ruleIndex].value = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
editorRules[ruleIndex].value = ""
|
||||||
|
end
|
||||||
|
Guda_CategoryEditor_UpdateRulesDisplay()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper to set rule value (called from dropdown)
|
||||||
|
function Guda_CategoryEditor_SetRuleValue(ruleIndex, val, ruleType)
|
||||||
|
if not editorRules[ruleIndex] then return end
|
||||||
|
|
||||||
|
if ruleType == "quality" or ruleType == "qualityMin" then
|
||||||
|
local num = tonumber(string.sub(val, 1, 1))
|
||||||
|
editorRules[ruleIndex].value = num or 0
|
||||||
|
elseif ruleType == "isBoE" or ruleType == "isQuestItem" or ruleType == "isSoulShard" or ruleType == "isProjectile" then
|
||||||
|
editorRules[ruleIndex].value = (val == "true")
|
||||||
|
else
|
||||||
|
editorRules[ruleIndex].value = val
|
||||||
|
end
|
||||||
|
Guda_CategoryEditor_UpdateRulesDisplay()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Show type dropdown menu
|
||||||
|
function Guda_CategoryEditor_ShowTypeDropdown(button, ruleIndex)
|
||||||
|
local menu = {}
|
||||||
|
for i = 1, table.getn(RULE_TYPE_OPTIONS) do
|
||||||
|
local opt = RULE_TYPE_OPTIONS[i]
|
||||||
|
table.insert(menu, {
|
||||||
|
text = opt.name,
|
||||||
|
ruleIndex = ruleIndex,
|
||||||
|
typeId = opt.id,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
Guda_ShowSimpleDropdown(button, menu, "type")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Show value dropdown menu
|
||||||
|
function Guda_CategoryEditor_ShowValueDropdown(button, ruleIndex)
|
||||||
|
local rule = editorRules[ruleIndex]
|
||||||
|
if not rule then return end
|
||||||
|
|
||||||
|
local options = RULE_VALUE_OPTIONS[rule.type]
|
||||||
|
if not options then return end
|
||||||
|
|
||||||
|
local menu = {}
|
||||||
|
for i = 1, table.getn(options) do
|
||||||
|
local val = options[i]
|
||||||
|
table.insert(menu, {
|
||||||
|
text = val,
|
||||||
|
ruleIndex = ruleIndex,
|
||||||
|
ruleType = rule.type,
|
||||||
|
value = val,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
Guda_ShowSimpleDropdown(button, menu, "value")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simple dropdown menu helper
|
||||||
|
local dropdownFrame = nil
|
||||||
|
function Guda_ShowSimpleDropdown(anchor, menuItems, menuType)
|
||||||
|
if not dropdownFrame then
|
||||||
|
dropdownFrame = CreateFrame("Frame", "Guda_SimpleDropdown", UIParent)
|
||||||
|
dropdownFrame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||||
|
dropdownFrame:SetWidth(150)
|
||||||
|
dropdownFrame:SetBackdrop({
|
||||||
|
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
||||||
|
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
|
||||||
|
tile = true, tileSize = 16, edgeSize = 16,
|
||||||
|
insets = { left = 4, right = 4, top = 4, bottom = 4 }
|
||||||
|
})
|
||||||
|
dropdownFrame:SetBackdropColor(0, 0, 0, 1)
|
||||||
|
dropdownFrame:EnableMouse(true)
|
||||||
|
dropdownFrame:Hide()
|
||||||
|
|
||||||
|
dropdownFrame:SetScript("OnLeave", function()
|
||||||
|
-- Hide after a short delay if mouse leaves
|
||||||
|
this.hideTimer = 0.5
|
||||||
|
end)
|
||||||
|
dropdownFrame:SetScript("OnUpdate", function()
|
||||||
|
if this.hideTimer then
|
||||||
|
this.hideTimer = this.hideTimer - arg1
|
||||||
|
if this.hideTimer <= 0 then
|
||||||
|
this.hideTimer = nil
|
||||||
|
-- Check if mouse is over any child
|
||||||
|
if not MouseIsOver(this) then
|
||||||
|
this:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clear old buttons
|
||||||
|
local children = { dropdownFrame:GetChildren() }
|
||||||
|
for _, child in ipairs(children) do
|
||||||
|
child:Hide()
|
||||||
|
child:SetParent(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create menu buttons
|
||||||
|
local btnHeight = 20
|
||||||
|
local totalHeight = 10
|
||||||
|
for i, item in ipairs(menuItems) do
|
||||||
|
local btn = CreateFrame("Button", nil, dropdownFrame)
|
||||||
|
btn:SetWidth(140)
|
||||||
|
btn:SetHeight(btnHeight)
|
||||||
|
btn:SetPoint("TOPLEFT", dropdownFrame, "TOPLEFT", 5, -(5 + (i-1) * btnHeight))
|
||||||
|
|
||||||
|
local text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||||
|
text:SetPoint("LEFT", btn, "LEFT", 5, 0)
|
||||||
|
text:SetText(item.text)
|
||||||
|
btn.text = text
|
||||||
|
|
||||||
|
local highlight = btn:CreateTexture(nil, "HIGHLIGHT")
|
||||||
|
highlight:SetAllPoints(btn)
|
||||||
|
highlight:SetTexture(1, 1, 1, 0.2)
|
||||||
|
|
||||||
|
-- Store data on button for vanilla Lua closure compatibility
|
||||||
|
btn.menuType = menuType
|
||||||
|
btn.ruleIndex = item.ruleIndex
|
||||||
|
btn.typeId = item.typeId
|
||||||
|
btn.ruleType = item.ruleType
|
||||||
|
btn.value = item.value
|
||||||
|
|
||||||
|
btn:SetScript("OnClick", function()
|
||||||
|
dropdownFrame:Hide()
|
||||||
|
if this.menuType == "type" then
|
||||||
|
Guda_CategoryEditor_SetRuleType(this.ruleIndex, this.typeId)
|
||||||
|
elseif this.menuType == "value" then
|
||||||
|
Guda_CategoryEditor_SetRuleValue(this.ruleIndex, this.value, this.ruleType)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
btn:SetScript("OnEnter", function()
|
||||||
|
dropdownFrame.hideTimer = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
totalHeight = totalHeight + btnHeight
|
||||||
|
end
|
||||||
|
totalHeight = totalHeight + 5
|
||||||
|
|
||||||
|
dropdownFrame:SetHeight(totalHeight)
|
||||||
|
dropdownFrame:ClearAllPoints()
|
||||||
|
dropdownFrame:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, 0)
|
||||||
|
dropdownFrame:Show()
|
||||||
|
dropdownFrame.hideTimer = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save category changes
|
||||||
|
function Guda_CategoryEditor_Save()
|
||||||
|
if not editorCategoryId or not Guda.Modules.CategoryManager then return end
|
||||||
|
|
||||||
|
local categoryDef = Guda.Modules.CategoryManager:GetCategory(editorCategoryId)
|
||||||
|
if not categoryDef then return end
|
||||||
|
|
||||||
|
-- Get name (only for custom categories)
|
||||||
|
local nameBox = getglobal("Guda_CategoryEditor_NameEditBox")
|
||||||
|
if nameBox and not categoryDef.isBuiltIn then
|
||||||
|
categoryDef.name = nameBox:GetText()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set match mode
|
||||||
|
categoryDef.matchMode = editorMatchMode
|
||||||
|
|
||||||
|
-- Set rules
|
||||||
|
categoryDef.rules = {}
|
||||||
|
for _, rule in ipairs(editorRules) do
|
||||||
|
if rule.type and rule.type ~= "" then
|
||||||
|
table.insert(categoryDef.rules, { type = rule.type, value = rule.value })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save to database
|
||||||
|
Guda.Modules.CategoryManager:UpdateCategory(editorCategoryId, categoryDef)
|
||||||
|
|
||||||
|
-- Refresh displays
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
Guda_SettingsPopup_RefreshBagFrames()
|
||||||
|
|
||||||
|
-- Close editor
|
||||||
|
local editor = getglobal("Guda_CategoryEditor")
|
||||||
|
if editor then editor:Hide() end
|
||||||
|
|
||||||
|
Guda:Print("Category '" .. (categoryDef.name or editorCategoryId) .. "' saved.")
|
||||||
|
end
|
||||||
|
|
||||||
-- Initialize
|
-- Initialize
|
||||||
function SettingsPopup:Initialize()
|
function SettingsPopup:Initialize()
|
||||||
Guda:Debug("Settings popup initialized")
|
Guda:Debug("Settings popup initialized")
|
||||||
|
|||||||
+379
-5
@@ -62,7 +62,7 @@
|
|||||||
<!-- Tab Buttons (GudaPlates style) -->
|
<!-- Tab Buttons (GudaPlates style) -->
|
||||||
<Button name="Guda_SettingsPopup_GeneralTabButton" enableMouse="true">
|
<Button name="Guda_SettingsPopup_GeneralTabButton" enableMouse="true">
|
||||||
<Size>
|
<Size>
|
||||||
<AbsDimension x="150" y="28"/>
|
<AbsDimension x="113" y="28"/>
|
||||||
</Size>
|
</Size>
|
||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="TOPLEFT" relativeTo="$parent" relativePoint="TOPLEFT">
|
<Anchor point="TOPLEFT" relativeTo="$parent" relativePoint="TOPLEFT">
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
|
|
||||||
<Button name="Guda_SettingsPopup_IconsTabButton" enableMouse="true">
|
<Button name="Guda_SettingsPopup_IconsTabButton" enableMouse="true">
|
||||||
<Size>
|
<Size>
|
||||||
<AbsDimension x="150" y="28"/>
|
<AbsDimension x="113" y="28"/>
|
||||||
</Size>
|
</Size>
|
||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="LEFT" relativeTo="Guda_SettingsPopup_GeneralTabButton" relativePoint="RIGHT">
|
<Anchor point="LEFT" relativeTo="Guda_SettingsPopup_GeneralTabButton" relativePoint="RIGHT">
|
||||||
@@ -124,9 +124,9 @@
|
|||||||
</Scripts>
|
</Scripts>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button name="Guda_SettingsPopup_GuideTabButton" enableMouse="true">
|
<Button name="Guda_SettingsPopup_CategoriesTabButton" enableMouse="true">
|
||||||
<Size>
|
<Size>
|
||||||
<AbsDimension x="150" y="28"/>
|
<AbsDimension x="113" y="28"/>
|
||||||
</Size>
|
</Size>
|
||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="LEFT" relativeTo="Guda_SettingsPopup_IconsTabButton" relativePoint="RIGHT">
|
<Anchor point="LEFT" relativeTo="Guda_SettingsPopup_IconsTabButton" relativePoint="RIGHT">
|
||||||
@@ -142,7 +142,39 @@
|
|||||||
</Texture>
|
</Texture>
|
||||||
</Layer>
|
</Layer>
|
||||||
<Layer level="OVERLAY">
|
<Layer level="OVERLAY">
|
||||||
<FontString name="$parent_Text" inherits="GameFontNormal" text="Quick Guide">
|
<FontString name="$parent_Text" inherits="GameFontNormal" text="Categories">
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="CENTER"/>
|
||||||
|
</Anchors>
|
||||||
|
</FontString>
|
||||||
|
</Layer>
|
||||||
|
</Layers>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_SettingsPopup_SelectTab("categories")
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button name="Guda_SettingsPopup_GuideTabButton" enableMouse="true">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="113" y="28"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="LEFT" relativeTo="Guda_SettingsPopup_CategoriesTabButton" relativePoint="RIGHT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="2" y="0"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Layers>
|
||||||
|
<Layer level="BACKGROUND">
|
||||||
|
<Texture name="$parent_Bg" setAllPoints="true">
|
||||||
|
<Color r="1" g="1" b="1" a="0.1"/>
|
||||||
|
</Texture>
|
||||||
|
</Layer>
|
||||||
|
<Layer level="OVERLAY">
|
||||||
|
<FontString name="$parent_Text" inherits="GameFontNormal" text="Guide">
|
||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="CENTER"/>
|
<Anchor point="CENTER"/>
|
||||||
</Anchors>
|
</Anchors>
|
||||||
@@ -600,6 +632,106 @@
|
|||||||
</Frames>
|
</Frames>
|
||||||
</Frame>
|
</Frame>
|
||||||
|
|
||||||
|
<!-- Categories Tab Content -->
|
||||||
|
<Frame name="Guda_SettingsPopup_CategoriesTab" hidden="true">
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT" relativeTo="$parent" relativePoint="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="15" y="-90"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
<Anchor point="BOTTOMRIGHT" relativeTo="$parent" relativePoint="BOTTOMRIGHT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="-15" y="15"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Layers>
|
||||||
|
<Layer level="ARTWORK">
|
||||||
|
<FontString name="$parent_Header" inherits="GameFontNormal" text="Manage item categories and their display order:">
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="5" y="-5"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
</FontString>
|
||||||
|
</Layer>
|
||||||
|
</Layers>
|
||||||
|
<Frames>
|
||||||
|
<!-- Scroll Frame for Category List -->
|
||||||
|
<ScrollFrame name="Guda_SettingsPopup_CategoriesScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="440" y="320"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="5" y="-25"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnVerticalScroll>
|
||||||
|
FauxScrollFrame_OnVerticalScroll(18, Guda_SettingsPopup_CategoriesTab_Update)
|
||||||
|
</OnVerticalScroll>
|
||||||
|
<OnShow>
|
||||||
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
</OnShow>
|
||||||
|
</Scripts>
|
||||||
|
</ScrollFrame>
|
||||||
|
|
||||||
|
<!-- Category Row Container -->
|
||||||
|
<Frame name="Guda_SettingsPopup_CategoryListContainer">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="420" y="320"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT" relativeTo="Guda_SettingsPopup_CategoriesScrollFrame" relativePoint="TOPLEFT"/>
|
||||||
|
</Anchors>
|
||||||
|
</Frame>
|
||||||
|
|
||||||
|
<!-- Add Category Button -->
|
||||||
|
<Button name="Guda_SettingsPopup_AddCategoryButton" inherits="UIPanelButtonTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="120" y="25"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="BOTTOMLEFT" relativeTo="$parent" relativePoint="BOTTOMLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="5" y="10"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_SettingsPopup_AddCategory_OnClick()
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Reset to Defaults Button -->
|
||||||
|
<Button name="Guda_SettingsPopup_ResetCategoriesButton" inherits="UIPanelButtonTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="120" y="25"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="LEFT" relativeTo="Guda_SettingsPopup_AddCategoryButton" relativePoint="RIGHT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="10" y="0"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_SettingsPopup_ResetCategories_OnClick()
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</Button>
|
||||||
|
</Frames>
|
||||||
|
</Frame>
|
||||||
|
|
||||||
<!-- Guide Tab Content -->
|
<!-- Guide Tab Content -->
|
||||||
<Frame name="Guda_SettingsPopup_GuideTab" hidden="true">
|
<Frame name="Guda_SettingsPopup_GuideTab" hidden="true">
|
||||||
<Anchors>
|
<Anchors>
|
||||||
@@ -651,4 +783,246 @@
|
|||||||
</Scripts>
|
</Scripts>
|
||||||
</Frame>
|
</Frame>
|
||||||
|
|
||||||
|
<!-- Category Editor Popup -->
|
||||||
|
<Frame name="Guda_CategoryEditor" toplevel="true" movable="true" enableMouse="true" hidden="true" parent="UIParent" frameStrata="DIALOG">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="400" y="380"/>
|
||||||
|
</Size>
|
||||||
|
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="CENTER" relativePoint="CENTER" relativeTo="UIParent">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="0" y="50"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
|
||||||
|
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
||||||
|
<BackgroundInsets>
|
||||||
|
<AbsInset left="11" right="12" top="12" bottom="11"/>
|
||||||
|
</BackgroundInsets>
|
||||||
|
<TileSize>
|
||||||
|
<AbsValue val="32"/>
|
||||||
|
</TileSize>
|
||||||
|
<EdgeSize>
|
||||||
|
<AbsValue val="32"/>
|
||||||
|
</EdgeSize>
|
||||||
|
</Backdrop>
|
||||||
|
|
||||||
|
<Layers>
|
||||||
|
<Layer level="ARTWORK">
|
||||||
|
<!-- Title -->
|
||||||
|
<FontString name="$parent_Title" inherits="GameFontNormalLarge" text="Edit Category">
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOP">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="0" y="-15"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Color r="1" g="0.82" b="0"/>
|
||||||
|
</FontString>
|
||||||
|
|
||||||
|
<!-- Name Label -->
|
||||||
|
<FontString name="$parent_NameLabel" inherits="GameFontNormal" text="Name:">
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="20" y="-45"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
</FontString>
|
||||||
|
|
||||||
|
<!-- Match Mode Label -->
|
||||||
|
<FontString name="$parent_MatchModeLabel" inherits="GameFontNormal" text="Match Mode:">
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="20" y="-80"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
</FontString>
|
||||||
|
|
||||||
|
<!-- Rules Label -->
|
||||||
|
<FontString name="$parent_RulesLabel" inherits="GameFontNormal" text="Rules:">
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="20" y="-115"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
</FontString>
|
||||||
|
</Layer>
|
||||||
|
</Layers>
|
||||||
|
|
||||||
|
<Frames>
|
||||||
|
<!-- Close Button -->
|
||||||
|
<Button name="$parent_CloseButton" inherits="UIPanelCloseButton">
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPRIGHT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="-5" y="-5"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_CategoryEditor:Hide()
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Name EditBox -->
|
||||||
|
<EditBox name="Guda_CategoryEditor_NameEditBox" autoFocus="false" inherits="InputBoxTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="200" y="20"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="80" y="-42"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnEscapePressed>
|
||||||
|
this:ClearFocus()
|
||||||
|
</OnEscapePressed>
|
||||||
|
<OnEnterPressed>
|
||||||
|
this:ClearFocus()
|
||||||
|
</OnEnterPressed>
|
||||||
|
</Scripts>
|
||||||
|
</EditBox>
|
||||||
|
|
||||||
|
<!-- Match Mode: Any Radio -->
|
||||||
|
<CheckButton name="Guda_CategoryEditor_MatchAny" inherits="UIRadioButtonTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="20" y="20"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="100" y="-77"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_CategoryEditor_SetMatchMode("any")
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</CheckButton>
|
||||||
|
|
||||||
|
<!-- Match Mode: All Radio -->
|
||||||
|
<CheckButton name="Guda_CategoryEditor_MatchAll" inherits="UIRadioButtonTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="20" y="20"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="LEFT" relativeTo="Guda_CategoryEditor_MatchAny" relativePoint="RIGHT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="60" y="0"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_CategoryEditor_SetMatchMode("all")
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</CheckButton>
|
||||||
|
|
||||||
|
<!-- Rules Container -->
|
||||||
|
<Frame name="Guda_CategoryEditor_RulesContainer">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="360" y="180"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPLEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="20" y="-130"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
</Frame>
|
||||||
|
|
||||||
|
<!-- Add Rule Button -->
|
||||||
|
<Button name="Guda_CategoryEditor_AddRuleButton" inherits="UIPanelButtonTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="100" y="22"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="TOPRIGHT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="-20" y="-112"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_CategoryEditor_AddRule()
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Save Button -->
|
||||||
|
<Button name="Guda_CategoryEditor_SaveButton" inherits="UIPanelButtonTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="100" y="25"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="BOTTOMRIGHT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="-20" y="15"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_CategoryEditor_Save()
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Cancel Button -->
|
||||||
|
<Button name="Guda_CategoryEditor_CancelButton" inherits="UIPanelButtonTemplate">
|
||||||
|
<Size>
|
||||||
|
<AbsDimension x="100" y="25"/>
|
||||||
|
</Size>
|
||||||
|
<Anchors>
|
||||||
|
<Anchor point="RIGHT" relativeTo="Guda_CategoryEditor_SaveButton" relativePoint="LEFT">
|
||||||
|
<Offset>
|
||||||
|
<AbsDimension x="-10" y="0"/>
|
||||||
|
</Offset>
|
||||||
|
</Anchor>
|
||||||
|
</Anchors>
|
||||||
|
<Scripts>
|
||||||
|
<OnClick>
|
||||||
|
Guda_CategoryEditor:Hide()
|
||||||
|
</OnClick>
|
||||||
|
</Scripts>
|
||||||
|
</Button>
|
||||||
|
</Frames>
|
||||||
|
|
||||||
|
<Scripts>
|
||||||
|
<OnLoad>
|
||||||
|
Guda_CategoryEditor_OnLoad(this)
|
||||||
|
</OnLoad>
|
||||||
|
<OnShow>
|
||||||
|
Guda_CategoryEditor_OnShow(this)
|
||||||
|
</OnShow>
|
||||||
|
<OnMouseDown>
|
||||||
|
if (arg1 == "LeftButton") then
|
||||||
|
this:StartMoving()
|
||||||
|
end
|
||||||
|
</OnMouseDown>
|
||||||
|
<OnMouseUp>
|
||||||
|
this:StopMovingOrSizing()
|
||||||
|
</OnMouseUp>
|
||||||
|
</Scripts>
|
||||||
|
</Frame>
|
||||||
|
|
||||||
</Ui>
|
</Ui>
|
||||||
|
|||||||
Reference in New Issue
Block a user