mirror of
https://codeberg.org/Duvelcorp/MetaHunt.git
synced 2026-09-27 18:16:04 +00:00
V1.3.0
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
MTH_CONST = MTH_CONST or {}
|
||||
MTH_CONST.version = MTH_CONST.version or "1.2.0"
|
||||
MTH_CONST.version = MTH_CONST.version or "1.3.0"
|
||||
|
||||
MTH_CONST.WEAPON_TYPES = {
|
||||
BOWS = "Bows",
|
||||
|
||||
@@ -15,6 +15,7 @@ local MTH_FEED_TrackerFrame = nil
|
||||
local MTH_FEED_ActiveCoreAttemptId = nil
|
||||
local MTH_FEED_BootstrapFrame = nil
|
||||
local MTH_FEED_NameToItemCache = {}
|
||||
local MTH_FEED_InvalidateDietMapCache
|
||||
|
||||
local MTH_FEED_HARDCODED_LEVEL_RULES = {
|
||||
{ minPetLevel = 1, minFoodLevel = 1 },
|
||||
@@ -28,7 +29,10 @@ local MTH_FEED_HARDCODED_LEVEL_RULES = {
|
||||
MTH_FEED_TRACE = false
|
||||
|
||||
local function MTH_FEED_Trace(message)
|
||||
return
|
||||
if not MTH_FEED_TRACE then return end
|
||||
if type(DEFAULT_CHAT_FRAME) == "table" and DEFAULT_CHAT_FRAME.AddMessage then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[MTH-FeedTrack]|r " .. tostring(message))
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_FEED_Now()
|
||||
@@ -636,6 +640,7 @@ end
|
||||
|
||||
function MTH_FEED_ReinstallCoreTracking()
|
||||
local ok = MTH_FEED_InstallCoreHooks()
|
||||
MTH_FEED_EnsureTrackerFrame()
|
||||
return ok and true or false
|
||||
end
|
||||
|
||||
@@ -852,7 +857,7 @@ end
|
||||
|
||||
local MTH_FEED_DietMapCache = nil
|
||||
|
||||
local function MTH_FEED_InvalidateDietMapCache()
|
||||
MTH_FEED_InvalidateDietMapCache = function()
|
||||
MTH_FEED_DietMapCache = nil
|
||||
end
|
||||
|
||||
@@ -1457,6 +1462,30 @@ function MTH_FEED_GetSessionFeedCount(petId)
|
||||
return tonumber(MTH_FEED_Runtime.sessionFeedsByPetId[petKey]) or 0
|
||||
end
|
||||
|
||||
function MTH_FEED_DebugSessionFeeds()
|
||||
local pet = MTH_FEED_GetCurrentPetContext()
|
||||
local petId = pet and pet.petId or "(none)"
|
||||
local count = MTH_FEED_GetSessionFeedCount(petId)
|
||||
local hookOk = MTH_FEED_IsCoreHookInstalled()
|
||||
local frameOk = MTH_FEED_TrackerFrame ~= nil
|
||||
local activeId = tostring(MTH_FEED_ActiveCoreAttemptId or "nil")
|
||||
if type(DEFAULT_CHAT_FRAME) == "table" and DEFAULT_CHAT_FRAME.AddMessage then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[MTH-FeedTrack]|r petId=" .. tostring(petId)
|
||||
.. " feeds=" .. tostring(count)
|
||||
.. " hookOk=" .. tostring(hookOk)
|
||||
.. " frameOk=" .. tostring(frameOk)
|
||||
.. " activeAttempt=" .. activeId)
|
||||
local anyFeeds = false
|
||||
for k, v in pairs(MTH_FEED_Runtime.sessionFeedsByPetId) do
|
||||
anyFeeds = true
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[MTH-FeedTrack]|r session petId='" .. tostring(k) .. "' count=" .. tostring(v))
|
||||
end
|
||||
if not anyFeeds then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff88aaff[MTH-FeedTrack]|r (no session feeds recorded)")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MTH_FEED_RecordUnknownFoodCandidate(payload)
|
||||
local store = MTH_FEED_EnsureStore()
|
||||
local itemId = payload and tonumber(payload.itemId) or nil
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
MTH = MTH or {
|
||||
version = "1.2.0",
|
||||
version = "1.3.0",
|
||||
name = "MetaHunt",
|
||||
modules = {},
|
||||
config = {},
|
||||
|
||||
+1
-4
@@ -373,9 +373,6 @@ eventFrame:SetScript("OnEvent", function()
|
||||
end
|
||||
MTH_DF_InstallHooks()
|
||||
MTH_DebugFrame:Initialize()
|
||||
if MTH and MTH.Print then
|
||||
local mode = MTH_DF_IsGlobalCaptureEnabled() and "legacy-global" or "safe-local"
|
||||
MTH:Print("=== MetaHunt Debug Frame Initialized (" .. mode .. ") ===", "debug")
|
||||
end
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
-- hunterbook-model-viewer.lua
|
||||
-- Embeds a small 3D creature skin preview at the bottom of the Beast Lore /
|
||||
-- Abilities inspector. Clicking it opens a larger draggable popup with
|
||||
-- < / > navigation through all skin variants of the selected family.
|
||||
--
|
||||
-- Uses MTH_DS_BeastSkins[familyName] generated by .tools/generators/gen_beast_skins.py
|
||||
-- Skin rendering: PlayerModel:SetUnit(CreatureDisplayInfo.ID)
|
||||
|
||||
local function mv_log(msg)
|
||||
if MTH_DebugFrame and type(MTH_DebugFrame.AddInfo) == "function" then
|
||||
MTH_DebugFrame:AddInfo("[MV] " .. tostring(msg))
|
||||
end
|
||||
end
|
||||
|
||||
local function mv_err(msg)
|
||||
if MTH_DebugFrame and type(MTH_DebugFrame.AddError) == "function" then
|
||||
MTH_DebugFrame:AddError("[MV] " .. tostring(msg))
|
||||
end
|
||||
end
|
||||
|
||||
local MTH_MV = {
|
||||
inited = false,
|
||||
family = nil,
|
||||
skins = {},
|
||||
index = 1,
|
||||
facing = 0.6,
|
||||
rotating = false,
|
||||
rotateLastX = 0,
|
||||
keepPopup = false,
|
||||
skinBeastRows = {},
|
||||
skinListEmpty = nil,
|
||||
-- small preview (parented to book frame, anchored below MTH_BOOK_DetailBackdrop)
|
||||
previewFrame = nil,
|
||||
previewModel = nil,
|
||||
-- popup
|
||||
popup = nil,
|
||||
popupModel = nil,
|
||||
popupTitle = nil,
|
||||
popupSkinLabel = nil,
|
||||
}
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Internal
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local function MTH_MV_ApplyModel(model, skinId)
|
||||
if not model or not skinId then
|
||||
mv_err("ApplyModel: nil model or skinId model="..tostring(model).." id="..tostring(skinId))
|
||||
return
|
||||
end
|
||||
mv_log("ApplyModel id="..tostring(skinId))
|
||||
model:SetUnit(skinId)
|
||||
model:SetPosition(0, 0, 0)
|
||||
model:SetModelScale(1)
|
||||
local useFacing = (MTH_MV.popupModel ~= nil and model == MTH_MV.popupModel)
|
||||
model:SetFacing(useFacing and MTH_MV.facing or 0.6)
|
||||
end
|
||||
|
||||
local MTH_MV_BuildSkinBeastList -- forward declaration
|
||||
|
||||
local function MTH_MV_DisplayCurrent()
|
||||
local skin = MTH_MV.skins[MTH_MV.index]
|
||||
if not skin then return end
|
||||
local n = table.getn(MTH_MV.skins)
|
||||
|
||||
MTH_MV_ApplyModel(MTH_MV.previewModel, skin.id)
|
||||
MTH_MV_ApplyModel(MTH_MV.popupModel, skin.id)
|
||||
|
||||
local indexStr = tostring(MTH_MV.index) .. " / " .. tostring(n)
|
||||
if MTH_MV.popupSkinLabel then
|
||||
MTH_MV.popupSkinLabel:SetText(indexStr .. " · " .. tostring(skin.tex or ""))
|
||||
end
|
||||
MTH_MV_BuildSkinBeastList(skin.id)
|
||||
end
|
||||
|
||||
MTH_MV_BuildSkinBeastList = function(skinId)
|
||||
for i = 1, table.getn(MTH_MV.skinBeastRows) do
|
||||
MTH_MV.skinBeastRows[i]:Hide()
|
||||
end
|
||||
if MTH_MV.skinListEmpty then MTH_MV.skinListEmpty:Hide() end
|
||||
if not skinId or not MTH_DS_Beasts then return end
|
||||
|
||||
local matches = {}
|
||||
for npcId, beast in pairs(MTH_DS_Beasts) do
|
||||
if type(beast) == "table" and beast.skinId == skinId then
|
||||
local nm = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(npcId, beast.name)) or beast.name
|
||||
table.insert(matches, { id = npcId, name = nm or "?", lvl = tostring(beast.lvl or "?") })
|
||||
end
|
||||
end
|
||||
table.sort(matches, function(a, b) return (a.name or "") < (b.name or "") end)
|
||||
|
||||
local maxRows = table.getn(MTH_MV.skinBeastRows)
|
||||
local shown = 0
|
||||
for i = 1, table.getn(matches) do
|
||||
if shown >= maxRows then break end
|
||||
shown = shown + 1
|
||||
local row = MTH_MV.skinBeastRows[shown]
|
||||
row._npcId = matches[i].id
|
||||
row.nameText:SetText(matches[i].name)
|
||||
row.lvlText:SetText(matches[i].lvl)
|
||||
row:Show()
|
||||
end
|
||||
if shown == 0 and MTH_MV.skinListEmpty then
|
||||
MTH_MV.skinListEmpty:Show()
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_MV_Init()
|
||||
if MTH_MV.inited then mv_log("Init: already done") return end
|
||||
mv_log("Init: starting")
|
||||
local backdrop = getglobal("MTH_BOOK_DetailBackdrop")
|
||||
if not backdrop then
|
||||
mv_err("Init: MTH_BOOK_DetailBackdrop not found")
|
||||
return
|
||||
end
|
||||
mv_log("Init: backdrop found, creating previewFrame")
|
||||
MTH_MV.inited = true
|
||||
|
||||
-- ----------------------------------------------------------------
|
||||
-- Model preview container (sibling of inspector, full column width)
|
||||
-- Hardcoded offset from book frame TOPLEFT: x=608 (right column),
|
||||
-- y = -(166 inspector_top + 178 inspector_h + 6 gap) = -350
|
||||
-- ----------------------------------------------------------------
|
||||
local bookFrame = backdrop:GetParent()
|
||||
MTH_MV.previewFrame = CreateFrame("Frame", "MTH_BOOK_SkinPreview", bookFrame)
|
||||
MTH_MV.previewFrame:SetWidth(136)
|
||||
MTH_MV.previewFrame:SetHeight(136)
|
||||
MTH_MV.previewFrame:SetPoint("TOPLEFT", bookFrame, "TOPLEFT", 608, -384)
|
||||
MTH_MV.previewFrame:SetBackdrop({
|
||||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 12,
|
||||
insets = {left = 3, right = 3, top = 3, bottom = 3},
|
||||
})
|
||||
MTH_MV.previewFrame:SetBackdropColor(0, 0, 0, 0.7)
|
||||
MTH_MV.previewFrame:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.8)
|
||||
MTH_MV.previewFrame:Hide()
|
||||
|
||||
mv_log("Init: previewFrame created, creating PlayerModel")
|
||||
-- PlayerModel fills the inner area
|
||||
MTH_MV.previewModel = CreateFrame("PlayerModel", nil, MTH_MV.previewFrame)
|
||||
MTH_MV.previewModel:SetPoint("TOPLEFT", MTH_MV.previewFrame, "TOPLEFT", 3, -3)
|
||||
MTH_MV.previewModel:SetPoint("BOTTOMRIGHT", MTH_MV.previewFrame, "BOTTOMRIGHT", -3, 3)
|
||||
|
||||
-- Invisible click overlay → opens popup
|
||||
local previewBtn = CreateFrame("Button", nil, MTH_MV.previewFrame)
|
||||
previewBtn:SetAllPoints(MTH_MV.previewFrame)
|
||||
previewBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
|
||||
previewBtn:SetScript("OnClick", function()
|
||||
MTH_BOOK_ModelViewer_OpenPopup()
|
||||
end)
|
||||
previewBtn:SetScript("OnEnter", function()
|
||||
GameTooltip:SetOwner(MTH_MV.previewFrame, "ANCHOR_LEFT")
|
||||
GameTooltip:AddLine("Click to open skin viewer", 1, 1, 1)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
previewBtn:SetScript("OnLeave", function()
|
||||
GameTooltip:Hide()
|
||||
end)
|
||||
|
||||
mv_log("Init: previewModel created, creating popup")
|
||||
-- ----------------------------------------------------------------
|
||||
-- Popup frame (wider: left side = model viewer, right = beast list)
|
||||
-- ----------------------------------------------------------------
|
||||
local popup = CreateFrame("Frame", "MTH_BOOK_ModelPopup", UIParent)
|
||||
popup:SetWidth(490)
|
||||
popup:SetHeight(320)
|
||||
popup:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||||
popup:SetFrameStrata("TOOLTIP")
|
||||
popup:SetToplevel(true)
|
||||
popup:SetMovable(true)
|
||||
popup:EnableMouse(true)
|
||||
popup:RegisterForDrag("LeftButton")
|
||||
popup:SetScript("OnDragStart", function() this:StartMoving() end)
|
||||
popup:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||||
popup:SetScript("OnHide", function() MTH_MV.rotating = false end)
|
||||
popup:Hide()
|
||||
popup:SetBackdrop({
|
||||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
|
||||
tile = true, tileSize = 32, edgeSize = 32,
|
||||
insets = {left = 11, right = 12, top = 12, bottom = 11},
|
||||
})
|
||||
popup:SetBackdropColor(0.05, 0.05, 0.08, 0.95)
|
||||
|
||||
-- Large model background (left side, x=20)
|
||||
local modelBg = CreateFrame("Frame", nil, popup)
|
||||
modelBg:SetWidth(224)
|
||||
modelBg:SetHeight(226)
|
||||
modelBg:SetPoint("TOPLEFT", popup, "TOPLEFT", 20, -42)
|
||||
modelBg:SetBackdrop({
|
||||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 12,
|
||||
insets = {left = 3, right = 3, top = 3, bottom = 3},
|
||||
})
|
||||
modelBg:SetBackdropColor(0, 0, 0, 0.85)
|
||||
modelBg:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.9)
|
||||
|
||||
MTH_MV.popupModel = CreateFrame("PlayerModel", "MTH_BOOK_ModelPopupModel", modelBg)
|
||||
MTH_MV.popupModel:SetPoint("TOPLEFT", modelBg, "TOPLEFT", 3, -3)
|
||||
MTH_MV.popupModel:SetPoint("BOTTOMRIGHT", modelBg, "BOTTOMRIGHT", -3, 3)
|
||||
|
||||
-- Mouse rotation: drag on the black model area rotates; outside drags the popup
|
||||
MTH_MV.popupModel:EnableMouse(true)
|
||||
MTH_MV.popupModel:SetScript("OnMouseDown", function()
|
||||
MTH_MV.rotating = true
|
||||
MTH_MV.rotateLastX = GetCursorPosition()
|
||||
end)
|
||||
MTH_MV.popupModel:SetScript("OnMouseUp", function()
|
||||
MTH_MV.rotating = false
|
||||
end)
|
||||
MTH_MV.popupModel:SetScript("OnUpdate", function()
|
||||
if not MTH_MV.rotating then return end
|
||||
local cx = GetCursorPosition()
|
||||
local dx = cx - MTH_MV.rotateLastX
|
||||
MTH_MV.rotateLastX = cx
|
||||
if dx ~= 0 then
|
||||
MTH_MV.facing = MTH_MV.facing - dx * 0.03
|
||||
MTH_MV.popupModel:SetFacing(MTH_MV.facing)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Title (family name) anchored above the model
|
||||
MTH_MV.popupTitle = popup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
MTH_MV.popupTitle:SetPoint("TOP", modelBg, "TOP", 0, 18)
|
||||
MTH_MV.popupTitle:SetTextColor(1, 0.82, 0)
|
||||
MTH_MV.popupTitle:SetText("Skin Viewer")
|
||||
|
||||
-- Close [X] button
|
||||
local closeBtn = CreateFrame("Button", nil, popup)
|
||||
closeBtn:SetWidth(22)
|
||||
closeBtn:SetHeight(22)
|
||||
closeBtn:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -10, -10)
|
||||
closeBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
|
||||
local closeLbl = closeBtn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
closeLbl:SetAllPoints(closeBtn)
|
||||
closeLbl:SetText("X")
|
||||
closeLbl:SetTextColor(0.9, 0.9, 0.9)
|
||||
closeBtn:SetScript("OnClick", function() this:GetParent():Hide() end)
|
||||
|
||||
-- "Unique Appearance" badge removed (inline texture syntax unsupported in this client)
|
||||
|
||||
-- Nav: ◀ label ▶ (kept within the left 264px area)
|
||||
local prevBtn = CreateFrame("Button", nil, popup, "UIPanelButtonTemplate")
|
||||
prevBtn:SetWidth(28)
|
||||
prevBtn:SetHeight(20)
|
||||
prevBtn:SetPoint("BOTTOMLEFT", popup, "BOTTOMLEFT", 14, 14)
|
||||
prevBtn:SetText("<")
|
||||
prevBtn:SetScript("OnClick", function()
|
||||
local n = table.getn(MTH_MV.skins)
|
||||
if n < 1 then return end
|
||||
MTH_MV.index = MTH_MV.index - 1
|
||||
if MTH_MV.index < 1 then MTH_MV.index = n end
|
||||
MTH_MV_DisplayCurrent()
|
||||
end)
|
||||
|
||||
local nextBtn = CreateFrame("Button", nil, popup, "UIPanelButtonTemplate")
|
||||
nextBtn:SetWidth(28)
|
||||
nextBtn:SetHeight(20)
|
||||
nextBtn:SetPoint("BOTTOMLEFT", popup, "BOTTOMLEFT", 222, 14)
|
||||
nextBtn:SetText(">")
|
||||
nextBtn:SetScript("OnClick", function()
|
||||
local n = table.getn(MTH_MV.skins)
|
||||
if n < 1 then return end
|
||||
MTH_MV.index = MTH_MV.index + 1
|
||||
if MTH_MV.index > n then MTH_MV.index = 1 end
|
||||
MTH_MV_DisplayCurrent()
|
||||
end)
|
||||
|
||||
MTH_MV.popupSkinLabel = popup:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||
MTH_MV.popupSkinLabel:SetPoint("BOTTOMLEFT", prevBtn, "BOTTOMRIGHT", 4, 0)
|
||||
MTH_MV.popupSkinLabel:SetPoint("BOTTOMRIGHT", nextBtn, "BOTTOMLEFT", -4, 0)
|
||||
MTH_MV.popupSkinLabel:SetJustifyH("CENTER")
|
||||
MTH_MV.popupSkinLabel:SetTextColor(0.9, 0.9, 0.9)
|
||||
|
||||
-- ---------------------------------------------------------------
|
||||
-- Right panel: vertical divider + beast list
|
||||
-- ---------------------------------------------------------------
|
||||
local divider = popup:CreateTexture(nil, "ARTWORK")
|
||||
divider:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
divider:SetVertexColor(0.35, 0.35, 0.35, 0.8)
|
||||
divider:SetPoint("TOPLEFT", popup, "TOPLEFT", 270, -36)
|
||||
divider:SetPoint("BOTTOMLEFT", popup, "BOTTOMLEFT", 270, 12)
|
||||
divider:SetWidth(1)
|
||||
|
||||
local listHeader = popup:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||
listHeader:SetPoint("TOPLEFT", popup, "TOPLEFT", 278, -18)
|
||||
listHeader:SetTextColor(1, 0.82, 0)
|
||||
listHeader:SetText("Beasts with this skin")
|
||||
|
||||
local listSep = popup:CreateTexture(nil, "ARTWORK")
|
||||
listSep:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
listSep:SetVertexColor(0.35, 0.35, 0.35, 0.6)
|
||||
listSep:SetPoint("TOPLEFT", popup, "TOPLEFT", 278, -30)
|
||||
listSep:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -10, -30)
|
||||
listSep:SetHeight(1)
|
||||
|
||||
MTH_MV.skinListEmpty = popup:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||
MTH_MV.skinListEmpty:SetPoint("TOPLEFT", popup, "TOPLEFT", 278, -38)
|
||||
MTH_MV.skinListEmpty:SetTextColor(0.5, 0.5, 0.5)
|
||||
MTH_MV.skinListEmpty:SetText("\226\128\148")
|
||||
MTH_MV.skinListEmpty:Hide()
|
||||
|
||||
-- Beast rows (14 max)
|
||||
local ROW_X = 278
|
||||
local ROW_START = -36
|
||||
local ROW_H = 17
|
||||
local MAX_ROWS = 14
|
||||
for i = 1, MAX_ROWS do
|
||||
local row = CreateFrame("Button", nil, popup)
|
||||
row:SetPoint("TOPLEFT", popup, "TOPLEFT", ROW_X, ROW_START - (i - 1) * ROW_H)
|
||||
row:SetWidth(200)
|
||||
row:SetHeight(ROW_H - 1)
|
||||
row._npcId = nil
|
||||
|
||||
local hl = row:CreateTexture(nil, "HIGHLIGHT")
|
||||
hl:SetAllPoints(row)
|
||||
hl:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
||||
hl:SetBlendMode("ADD")
|
||||
hl:SetAlpha(0.35)
|
||||
|
||||
row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||
row.nameText:SetPoint("LEFT", row, "LEFT", 0, 0)
|
||||
row.nameText:SetWidth(158)
|
||||
row.nameText:SetJustifyH("LEFT")
|
||||
|
||||
row.lvlText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||
row.lvlText:SetPoint("LEFT", row, "LEFT", 160, 0)
|
||||
row.lvlText:SetWidth(38)
|
||||
row.lvlText:SetJustifyH("LEFT")
|
||||
row.lvlText:SetTextColor(0.7, 0.7, 0.7)
|
||||
|
||||
row:SetScript("OnClick", function()
|
||||
if this._npcId and type(MTH_BOOK_JumpToBeastById) == "function" then
|
||||
MTH_MV.keepPopup = true
|
||||
MTH_BOOK_JumpToBeastById(this._npcId)
|
||||
MTH_MV.keepPopup = false
|
||||
end
|
||||
end)
|
||||
row:Hide()
|
||||
MTH_MV.skinBeastRows[i] = row
|
||||
end
|
||||
|
||||
MTH_MV.popup = popup
|
||||
mv_log("Init: complete")
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Public API
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
function MTH_BOOK_ModelViewer_SetFamily(family, skinId)
|
||||
mv_log("SetFamily: "..tostring(family).." skinId="..tostring(skinId))
|
||||
MTH_MV_Init()
|
||||
if not MTH_MV.inited then mv_err("SetFamily: init failed") return end
|
||||
local skins = MTH_DS_BeastSkins and type(MTH_DS_BeastSkins) == "table" and MTH_DS_BeastSkins[family]
|
||||
if not skins or table.getn(skins) == 0 then
|
||||
mv_log("SetFamily: no skins for "..tostring(family).." DS type="..type(MTH_DS_BeastSkins or false))
|
||||
MTH_BOOK_ModelViewer_Clear()
|
||||
return
|
||||
end
|
||||
|
||||
-- Filter to only skins used by at least one tameable beast
|
||||
local usedSkinIds = {}
|
||||
if MTH_DS_Beasts then
|
||||
for _, beast in pairs(MTH_DS_Beasts) do
|
||||
if type(beast) == "table" and beast.skinId then
|
||||
usedSkinIds[beast.skinId] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
local filteredSkins = {}
|
||||
for i = 1, table.getn(skins) do
|
||||
if usedSkinIds[skins[i].id] then
|
||||
table.insert(filteredSkins, skins[i])
|
||||
end
|
||||
end
|
||||
if table.getn(filteredSkins) == 0 then
|
||||
MTH_BOOK_ModelViewer_Clear()
|
||||
return
|
||||
end
|
||||
mv_log("SetFamily: "..table.getn(filteredSkins).." tameable skins (filtered from "..table.getn(skins)..")")
|
||||
|
||||
MTH_MV.family = family
|
||||
MTH_MV.skins = filteredSkins
|
||||
MTH_MV.facing = 0.6
|
||||
|
||||
-- If we have a specific skinId, jump to the matching skin entry.
|
||||
MTH_MV.index = 1
|
||||
if skinId then
|
||||
for i = 1, table.getn(filteredSkins) do
|
||||
if filteredSkins[i].id == skinId then
|
||||
MTH_MV.index = i
|
||||
mv_log("SetFamily: skinId "..skinId.." matched skin index "..i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if MTH_MV.popupTitle then
|
||||
MTH_MV.popupTitle:SetText(tostring(family) .. " — Skins")
|
||||
end
|
||||
|
||||
MTH_MV_DisplayCurrent()
|
||||
|
||||
if MTH_MV.previewFrame then MTH_MV.previewFrame:Show() end
|
||||
MTH_MV_DisplayCurrent()
|
||||
end
|
||||
|
||||
function MTH_BOOK_ModelViewer_Clear()
|
||||
mv_log("Clear")
|
||||
if MTH_MV.previewFrame then MTH_MV.previewFrame:Hide() end
|
||||
if not MTH_MV.keepPopup then
|
||||
if MTH_MV.popup and MTH_MV.popup:IsShown() then MTH_MV.popup:Hide() end
|
||||
end
|
||||
MTH_MV.family = nil
|
||||
MTH_MV.skins = {}
|
||||
MTH_MV.index = 1
|
||||
end
|
||||
|
||||
function MTH_BOOK_ModelViewer_OpenPopup()
|
||||
mv_log("OpenPopup skins="..table.getn(MTH_MV.skins))
|
||||
MTH_MV_Init()
|
||||
if not MTH_MV.popup then mv_err("OpenPopup: no popup frame") return end
|
||||
if table.getn(MTH_MV.skins) == 0 then mv_log("OpenPopup: no skins") return end
|
||||
MTH_MV.popup:Show()
|
||||
MTH_MV.popup:Raise()
|
||||
MTH_MV_DisplayCurrent()
|
||||
end
|
||||
@@ -2,17 +2,18 @@ if type(MTH_HUNTERBOOK_TABS) ~= "table" then MTH_HUNTERBOOK_TABS = {} end
|
||||
|
||||
MTH_HUNTERBOOK_TABS.pets = {
|
||||
headerLabel = "Beasts",
|
||||
columnLabels = { "ID", "Lvl", "Family", "Name", "Abilities", "Zone", "R", "E", "U" },
|
||||
columnLabels = { "ID", "Lvl", "Fam", "Name", "Abilities", "Zone", "R", "E", "U", "AS" },
|
||||
columnLayout = {
|
||||
{ x = 8, width = 28, align = "LEFT" },
|
||||
{ x = 38, width = 30, align = "LEFT" },
|
||||
{ x = 68, width = 74, align = "LEFT" },
|
||||
{ x = 142, width = 130, align = "LEFT" },
|
||||
{ x = 272, width = 180, align = "LEFT" },
|
||||
{ x = 452, width = 68, align = "LEFT" },
|
||||
{ x = 520, width = 10, align = "CENTER" },
|
||||
{ x = 532, width = 10, align = "CENTER" },
|
||||
{ x = 544, width = 10, align = "CENTER" },
|
||||
{ x = 68, width = 18, align = "LEFT" },
|
||||
{ x = 88, width = 130, align = "LEFT" },
|
||||
{ x = 220, width = 148, align = "LEFT" },
|
||||
{ x = 370, width = 80, align = "LEFT" },
|
||||
{ x = 452, width = 14, align = "CENTER" },
|
||||
{ x = 468, width = 14, align = "CENTER" },
|
||||
{ x = 484, width = 14, align = "CENTER" },
|
||||
{ x = 500, width = 54, align = "LEFT" },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ MTH_HUNTERBOOK_TABS.families = {
|
||||
}
|
||||
|
||||
local function MTH_BOOKTAB_FamiliesTrace(message)
|
||||
return
|
||||
end
|
||||
|
||||
local function MTH_BOOKTAB_FamiliesSafeLower(value)
|
||||
@@ -83,6 +82,7 @@ function MTH_BOOKTAB_BuildFamiliesRows()
|
||||
|
||||
for familyName, familyRow in pairs(families) do
|
||||
if type(familyRow) == "table" then
|
||||
MTH_BOOKTAB_FamiliesTrace("BUILD " .. tostring(familyName) .. " icon=" .. tostring(familyRow.icon))
|
||||
local abilities = {}
|
||||
local seen = {}
|
||||
|
||||
@@ -191,6 +191,7 @@ function MTH_BOOKTAB_BuildFamiliesRows()
|
||||
|
||||
table.insert(results, {
|
||||
family = tostring(familyName),
|
||||
icon = familyRow.icon or nil,
|
||||
named = tonumber(familyRow.named) or 0,
|
||||
coords = tonumber(familyRow.coords) or 0,
|
||||
abilities = abilities,
|
||||
@@ -289,9 +290,15 @@ function MTH_BOOKTAB_EnsureFamiliesUI()
|
||||
row:SetPoint("TOPRIGHT", ui.rows[i - 1], "BOTTOMRIGHT", 0, -1)
|
||||
end
|
||||
|
||||
row.familyIcon = row:CreateTexture(nil, "ARTWORK")
|
||||
row.familyIcon:SetPoint("LEFT", row, "LEFT", 4, 0)
|
||||
row.familyIcon:SetWidth(16)
|
||||
row.familyIcon:SetHeight(16)
|
||||
row.familyIcon:Hide()
|
||||
|
||||
row.family = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
row.family:SetPoint("LEFT", row, "LEFT", 4, 0)
|
||||
row.family:SetWidth(106)
|
||||
row.family:SetPoint("LEFT", row, "LEFT", 24, 0)
|
||||
row.family:SetWidth(88)
|
||||
row.family:SetJustifyH("LEFT")
|
||||
|
||||
row.named = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
@@ -393,6 +400,14 @@ function MTH_BOOKTAB_RenderFamiliesList()
|
||||
if row then
|
||||
rowFrame:Show()
|
||||
rowFrame.family:SetText(tostring(row.family or "-"))
|
||||
local familyIconPath = row.icon and MTH_BOOK_ResolveIconPath and MTH_BOOK_ResolveIconPath(row.icon) or nil
|
||||
MTH_BOOKTAB_FamiliesTrace("RENDER " .. tostring(row.family) .. " icon=" .. tostring(row.icon) .. " path=" .. tostring(familyIconPath))
|
||||
if familyIconPath then
|
||||
rowFrame.familyIcon:SetTexture(familyIconPath)
|
||||
rowFrame.familyIcon:Show()
|
||||
else
|
||||
rowFrame.familyIcon:Hide()
|
||||
end
|
||||
rowFrame.named:SetText(tostring(row.named or 0))
|
||||
rowFrame.coords:SetText(tostring(row.coords or 0))
|
||||
rowFrame.diet:SetText(tostring(row.dietText or "-"))
|
||||
|
||||
+64
-47
@@ -150,7 +150,7 @@ local function MTH_HB_RequireOpenDeps()
|
||||
return true
|
||||
end
|
||||
|
||||
local MTH_BOOK_MAX_COLS = 9
|
||||
local MTH_BOOK_MAX_COLS = 10
|
||||
local MTH_BOOK_MAX_ROWS = 20
|
||||
|
||||
local MTH_BOOK_PetFamilyOptions = {
|
||||
@@ -222,7 +222,8 @@ local MTH_BOOK_CONTENT_LAYOUT_DEFAULT = {
|
||||
detailX = 608,
|
||||
detailY = -166,
|
||||
detailW = 136,
|
||||
detailH = 324,
|
||||
detailH = 208,
|
||||
detailTextH = 192, -- inspector text only; model viewer sits below in its own frame
|
||||
}
|
||||
|
||||
local MTH_BOOK_CONTENT_LAYOUT_ITEMS = {
|
||||
@@ -328,7 +329,7 @@ local function MTH_BOOK_ApplyContentLayoutForMode()
|
||||
detailText:ClearAllPoints()
|
||||
detailText:SetPoint("TOPLEFT", detailParent, "TOPLEFT", 6, -6)
|
||||
detailText:SetWidth(layout.detailW - 12)
|
||||
detailText:SetHeight(layout.detailH - 12)
|
||||
detailText:SetHeight(layout.detailTextH or (layout.detailH - 12))
|
||||
end
|
||||
|
||||
local sliderHeight = layout.listH - 32
|
||||
@@ -1281,6 +1282,7 @@ local function MTH_BOOK_GetSortKey(entry, col)
|
||||
if col == 7 then return traits.rare and 1 or 0 end
|
||||
if col == 8 then return traits.elite and 1 or 0 end
|
||||
if col == 9 then return traits.unique and 1 or 0 end
|
||||
if col == 10 then return tonumber(beast.attackSpeed) or 0 end
|
||||
end
|
||||
|
||||
if MTH_BOOK_STATE.mode == "abilities" then
|
||||
@@ -2994,9 +2996,14 @@ end
|
||||
function MTH_BOOK_UpdateDetail()
|
||||
local detail = getglobal("MTH_BOOK_DetailBackdropDetailText")
|
||||
if not detail then return end
|
||||
-- Hide model skin preview when not in a beast-list mode
|
||||
if MTH_BOOK_STATE.mode ~= "pets" and MTH_BOOK_STATE.mode ~= "abilities" then
|
||||
if MTH_BOOK_ModelViewer_Clear then MTH_BOOK_ModelViewer_Clear() end
|
||||
end
|
||||
local petTop = MTH_BOOK_STATE.petUI and MTH_BOOK_STATE.petUI.detailTop
|
||||
local petBottom = MTH_BOOK_STATE.petUI and MTH_BOOK_STATE.petUI.detailBottom
|
||||
if not MTH_BOOK_STATE.selectedEntry then
|
||||
if MTH_BOOK_ModelViewer_Clear then MTH_BOOK_ModelViewer_Clear() end
|
||||
if MTH_BOOK_STATE.mode == "petabilities" then
|
||||
if petTop and petBottom then
|
||||
MTH_BOOK_SetDetailText(petTop, "|cFFFFD100Baseline|r\n\nSelect a spell baseline from the left list.")
|
||||
@@ -3090,26 +3097,7 @@ function MTH_BOOK_UpdateDetail()
|
||||
MTH_BOOK_AddDetailKV(lines, "Rare", beast.rare and "Yes" or "No")
|
||||
MTH_BOOK_AddDetailKV(lines, "Abilities", beast.abilities or "None")
|
||||
|
||||
local fam = MTH_DS_Families and beast.family and MTH_DS_Families[beast.family]
|
||||
if fam and fam.abilities and table.getn(fam.abilities) > 0 then
|
||||
MTH_BOOK_AddDetailSection(lines, "Family Pool")
|
||||
table.insert(lines, " " .. table.concat(fam.abilities, ", "))
|
||||
end
|
||||
|
||||
if beast.coords and table.getn(beast.coords) > 0 then
|
||||
MTH_BOOK_AddDetailSection(lines, "Locations")
|
||||
for i = 1, table.getn(beast.coords) do
|
||||
local c = beast.coords[i]
|
||||
if c then
|
||||
local x = tonumber(c[1] or 0) or 0
|
||||
local y = tonumber(c[2] or 0) or 0
|
||||
local zoneId = c[3]
|
||||
table.insert(lines, string.format(" - %s (%.1f, %.1f)", MTH_BOOK_GetZoneName(zoneId), x, y))
|
||||
end
|
||||
if i >= 8 then break end
|
||||
end
|
||||
end
|
||||
|
||||
if MTH_BOOK_ModelViewer_SetFamily then MTH_BOOK_ModelViewer_SetFamily(beast.family, beast.skinId) end
|
||||
MTH_BOOK_SetDetailText(detail, table.concat(lines, "\n"))
|
||||
MTH_BOOK_UpdateOpenMapButton()
|
||||
return
|
||||
@@ -3145,20 +3133,7 @@ function MTH_BOOK_UpdateDetail()
|
||||
end
|
||||
MTH_BOOK_AddDetailKV(lines, "Rare", beast.rare and "Yes" or "No")
|
||||
|
||||
if beast.coords and table.getn(beast.coords) > 0 then
|
||||
MTH_BOOK_AddDetailSection(lines, "Locations")
|
||||
for i = 1, table.getn(beast.coords) do
|
||||
local c = beast.coords[i]
|
||||
if c then
|
||||
local x = tonumber(c[1] or 0) or 0
|
||||
local y = tonumber(c[2] or 0) or 0
|
||||
local zoneId = c[3]
|
||||
table.insert(lines, string.format(" - %s (%.1f, %.1f)", MTH_BOOK_GetZoneName(zoneId), x, y))
|
||||
end
|
||||
if i >= 8 then break end
|
||||
end
|
||||
end
|
||||
|
||||
if MTH_BOOK_ModelViewer_SetFamily then MTH_BOOK_ModelViewer_SetFamily(beast.family, beast.skinId) end
|
||||
MTH_BOOK_SetDetailText(detail, table.concat(lines, "\n"))
|
||||
MTH_BOOK_UpdateOpenMapButton()
|
||||
return
|
||||
@@ -3467,7 +3442,7 @@ local function MTH_BOOK_GetColumnLabels()
|
||||
return tabDef.columnLabels
|
||||
end
|
||||
if MTH_BOOK_STATE.mode == "pets" then
|
||||
return { "ID", "Lvl", "Family", "Name", "Abilities", "Zone", "R", "E", "U" }
|
||||
return { "ID", "Lvl", "Fam", "Name", "Abilities", "Zone", "R", "E", "U", "AS" }
|
||||
elseif MTH_BOOK_STATE.mode == "petabilities" then
|
||||
return { "Ability Name", "Ranks", "Families" }
|
||||
elseif MTH_BOOK_STATE.mode == "stable" then
|
||||
@@ -3493,13 +3468,14 @@ local function MTH_BOOK_GetColumnLayout()
|
||||
return {
|
||||
{ x = 8, width = 28, align = "LEFT" },
|
||||
{ x = 38, width = 30, align = "LEFT" },
|
||||
{ x = 68, width = 74, align = "LEFT" },
|
||||
{ x = 142, width = 130, align = "LEFT" },
|
||||
{ x = 272, width = 180, align = "LEFT" },
|
||||
{ x = 452, width = 68, align = "LEFT" },
|
||||
{ x = 520, width = 10, align = "CENTER" },
|
||||
{ x = 532, width = 10, align = "CENTER" },
|
||||
{ x = 544, width = 10, align = "CENTER" },
|
||||
{ x = 68, width = 18, align = "LEFT" },
|
||||
{ x = 88, width = 130, align = "LEFT" },
|
||||
{ x = 220, width = 148, align = "LEFT" },
|
||||
{ x = 370, width = 80, align = "LEFT" },
|
||||
{ x = 452, width = 14, align = "CENTER" },
|
||||
{ x = 468, width = 14, align = "CENTER" },
|
||||
{ x = 484, width = 14, align = "CENTER" },
|
||||
{ x = 500, width = 54, align = "LEFT" },
|
||||
}
|
||||
elseif MTH_BOOK_STATE.mode == "petabilities" then
|
||||
return {
|
||||
@@ -3660,6 +3636,19 @@ local function MTH_BOOK_ApplyColumnLayout(parent)
|
||||
btn.cols[itemNameColumn]:SetPoint("LEFT", btn, "LEFT", itemNameLayout.x + 12, 0)
|
||||
btn.cols[itemNameColumn]:SetWidth(itemNameLayout.width - 16)
|
||||
end
|
||||
elseif MTH_BOOK_STATE.mode == "families" and layout[1] then
|
||||
local familyCol = layout[1]
|
||||
btn.itemIcon:ClearAllPoints()
|
||||
btn.itemIcon:SetPoint("LEFT", btn, "LEFT", familyCol.x, 0)
|
||||
if btn.cols and btn.cols[1] then
|
||||
btn.cols[1]:ClearAllPoints()
|
||||
btn.cols[1]:SetPoint("LEFT", btn, "LEFT", familyCol.x + 18, 0)
|
||||
btn.cols[1]:SetWidth(familyCol.width - 18)
|
||||
end
|
||||
elseif MTH_BOOK_STATE.mode == "pets" and layout[3] then
|
||||
local famCol = layout[3]
|
||||
btn.itemIcon:ClearAllPoints()
|
||||
btn.itemIcon:SetPoint("LEFT", btn, "LEFT", famCol.x, 0)
|
||||
else
|
||||
btn.itemIcon:Hide()
|
||||
end
|
||||
@@ -3671,7 +3660,7 @@ end
|
||||
local function MTH_BOOK_GetRowValues(entry)
|
||||
if MTH_BOOK_STATE.mode == "pets" then
|
||||
local beast = MTH_DS_Beasts and MTH_DS_Beasts[entry]
|
||||
if not beast then return { "", "", "", "", "", "", "", "", "" } end
|
||||
if not beast then return { "", "", "", "", "", "", "", "", "", "" } end
|
||||
local beastDisplayName = (MTH and MTH.GetLocalizedBeastName and MTH:GetLocalizedBeastName(entry, beast.name)) or beast.name
|
||||
local traits = MTH_BOOK_ParseBeastTraits(beast)
|
||||
local abilities = MTH_BOOK_GetBeastAbilitiesSummary(beast)
|
||||
@@ -3679,13 +3668,14 @@ local function MTH_BOOK_GetRowValues(entry)
|
||||
return {
|
||||
"|cFF33CCFF" .. tostring(entry) .. "|r",
|
||||
tostring(beast.lvl or "?"),
|
||||
tostring(beast.family or "?"),
|
||||
"", -- family icon rendered separately
|
||||
tostring(beastDisplayName or "Unknown"),
|
||||
abilities,
|
||||
zone,
|
||||
(traits.rare and "R" or ""),
|
||||
(traits.elite and "E" or ""),
|
||||
(traits.unique and "U" or ""),
|
||||
tostring(beast.attackSpeed or ""),
|
||||
}
|
||||
end
|
||||
|
||||
@@ -4090,6 +4080,31 @@ MTH_BOOK_UpdateResults = function()
|
||||
btn.itemIcon:SetTexture(nil)
|
||||
btn.itemIcon:Hide()
|
||||
end
|
||||
elseif MTH_BOOK_STATE.mode == "families" and type(entry) == "table" and entry.icon then
|
||||
local iconPath = MTH_BOOK_ResolveIconPath(entry.icon)
|
||||
if iconPath then
|
||||
btn.itemIcon:SetTexture(iconPath)
|
||||
btn.itemIcon:Show()
|
||||
else
|
||||
btn.itemIcon:SetTexture(nil)
|
||||
btn.itemIcon:Hide()
|
||||
end
|
||||
elseif MTH_BOOK_STATE.mode == "pets" then
|
||||
local beast = MTH_DS_Beasts and MTH_DS_Beasts[entry]
|
||||
local famData = beast and beast.family and MTH_DS_Families and MTH_DS_Families[beast.family]
|
||||
if famData and famData.icon and famData.icon ~= "" then
|
||||
local iconPath = MTH_BOOK_ResolveIconPath(famData.icon)
|
||||
if iconPath then
|
||||
btn.itemIcon:SetTexture(iconPath)
|
||||
btn.itemIcon:Show()
|
||||
else
|
||||
btn.itemIcon:SetTexture(nil)
|
||||
btn.itemIcon:Hide()
|
||||
end
|
||||
else
|
||||
btn.itemIcon:SetTexture(nil)
|
||||
btn.itemIcon:Hide()
|
||||
end
|
||||
else
|
||||
btn.itemIcon:SetTexture(nil)
|
||||
btn.itemIcon:Hide()
|
||||
@@ -4614,6 +4629,8 @@ function MTH_BOOK_JumpToBeastById(beastId)
|
||||
MTH_BOOK_STATE.mode = "pets"
|
||||
local widgets = MTH_BOOK_GetFilterWidgets()
|
||||
MTH_BOOK_ResetStateForMode("pets")
|
||||
MTH_BOOK_STATE.petHideNoAbilities = false
|
||||
MTH_BOOK_STATE.petHideUnknown = false
|
||||
MTH_BOOK_STATE.search = tostring(id)
|
||||
MTH_BOOK_STATE.forcedBeastId = id
|
||||
MTH_BOOK_ApplyStateToWidgets(widgets)
|
||||
|
||||
+3
-3
@@ -74,7 +74,7 @@
|
||||
</Frame>
|
||||
|
||||
<Frame name="$parentDetailBackdrop">
|
||||
<Size><AbsDimension x="136" y="324"/></Size>
|
||||
<Size><AbsDimension x="136" y="155"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-166"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -85,7 +85,7 @@
|
||||
</Backdrop>
|
||||
<Frames>
|
||||
<EditBox name="$parentDetailText" autoFocus="false" multiLine="true" text="Select an entry to view details.">
|
||||
<Size><AbsDimension x="120" y="306"/></Size>
|
||||
<Size><AbsDimension x="120" y="139"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-8"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -96,7 +96,7 @@
|
||||
<Button name="$parentOpenMapButton" inherits="UIPanelButtonTemplate" text="Open Map" hidden="true">
|
||||
<Size><AbsDimension x="136" y="22"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-496"/></Offset></Anchor>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="608" y="-524"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
------------------------------------------------------
|
||||
-- MetaHunt Profile Management Options Panel
|
||||
------------------------------------------------------
|
||||
|
||||
local MAX_PROFILE_ROWS = 15
|
||||
local MAX_CHAR_ROWS = 10
|
||||
local ROW_H = 26
|
||||
|
||||
-- Row pools — anonymous frames created once against scroll content, reused each refresh
|
||||
local gProfileRows = {} -- [i] = { frame, nameText, infoText, loadBtn, delBtn }
|
||||
local gCharRows = {} -- [i] = { frame, nameText, infoText, copyBtn }
|
||||
|
||||
-- Static element refs created once
|
||||
local gActiveLabel = nil
|
||||
local gSetupDone = false
|
||||
|
||||
-- Pending confirmations — globals so StaticPopup OnAccept can read them
|
||||
MTH_PROFILE_PENDING_LOAD = nil
|
||||
MTH_PROFILE_PENDING_DELETE = nil
|
||||
MTH_PROFILE_PENDING_SAVE = nil
|
||||
|
||||
-- ── Static popup dialogs ───────────────────────────────────────────────────
|
||||
|
||||
StaticPopupDialogs["MTH_PROFILE_LOAD_CONFIRM"] = {
|
||||
text = "Load profile \"%s\"?\n\nCurrent settings will be replaced.\nThe UI will reload to apply all changes.",
|
||||
button1 = "Load & Reload",
|
||||
button2 = "Cancel",
|
||||
OnAccept = function()
|
||||
if MTH_PROFILE_PENDING_LOAD then
|
||||
local ok, err = MTH_Profile_Load(MTH_PROFILE_PENDING_LOAD)
|
||||
MTH_PROFILE_PENDING_LOAD = nil
|
||||
if ok then
|
||||
ReloadUI()
|
||||
elseif MTH and MTH.Print then
|
||||
MTH:Print("Profile load failed: " .. tostring(err), "error")
|
||||
end
|
||||
end
|
||||
end,
|
||||
timeout = 0, whileDead = 0, hideOnEscape = 1,
|
||||
}
|
||||
|
||||
StaticPopupDialogs["MTH_PROFILE_DELETE_CONFIRM"] = {
|
||||
text = "Delete profile \"%s\"?\n\nThis cannot be undone.",
|
||||
button1 = "Delete",
|
||||
button2 = "Cancel",
|
||||
OnAccept = function()
|
||||
if MTH_PROFILE_PENDING_DELETE then
|
||||
MTH_Profile_Delete(MTH_PROFILE_PENDING_DELETE)
|
||||
MTH_PROFILE_PENDING_DELETE = nil
|
||||
MTH_SetupProfilesOptions()
|
||||
end
|
||||
end,
|
||||
timeout = 0, whileDead = 0, hideOnEscape = 1,
|
||||
}
|
||||
|
||||
StaticPopupDialogs["MTH_PROFILE_SAVE_CONFIRM"] = {
|
||||
text = "Profile \"%s\" already exists.\nOverwrite with current settings?",
|
||||
button1 = "Overwrite",
|
||||
button2 = "Cancel",
|
||||
OnAccept = function()
|
||||
if MTH_PROFILE_PENDING_SAVE then
|
||||
MTH_Profile_Save(MTH_PROFILE_PENDING_SAVE)
|
||||
MTH_PROFILE_PENDING_SAVE = nil
|
||||
MTH_SetupProfilesOptions()
|
||||
end
|
||||
end,
|
||||
timeout = 0, whileDead = 0, hideOnEscape = 1,
|
||||
}
|
||||
|
||||
-- ── internal helpers ───────────────────────────────────────────────────────
|
||||
|
||||
local function PROF_TrimString(s)
|
||||
if type(s) ~= "string" then return "" end
|
||||
return string.gsub(s, "^%s*(.-)%s*$", "%1")
|
||||
end
|
||||
|
||||
local function PROF_DoSave(rawName)
|
||||
local name = PROF_TrimString(rawName)
|
||||
if string.len(name) == 0 then
|
||||
if MTH and MTH.Print then MTH:Print("Enter a profile name first.", "error") end
|
||||
return
|
||||
end
|
||||
if string.len(name) > 50 then
|
||||
if MTH and MTH.Print then MTH:Print("Profile name too long (50 chars max).", "error") end
|
||||
return
|
||||
end
|
||||
local existing = type(MTH_SavedVariables) == "table"
|
||||
and type(MTH_SavedVariables.profiles) == "table"
|
||||
and MTH_SavedVariables.profiles[name] ~= nil
|
||||
if existing then
|
||||
MTH_PROFILE_PENDING_SAVE = name
|
||||
StaticPopup_Show("MTH_PROFILE_SAVE_CONFIRM", name)
|
||||
else
|
||||
MTH_Profile_Save(name)
|
||||
if MTH and MTH.Print then MTH:Print("Profile saved: " .. name) end
|
||||
MTH_SetupProfilesOptions()
|
||||
end
|
||||
end
|
||||
|
||||
-- ── scroll frames + row pool creation (idempotent) ────────────────────────
|
||||
|
||||
local function PROF_EnsureScrollFrames(container)
|
||||
if not MTH_GetFrame("MetaHuntOptionsProfilesScroll") then
|
||||
local sf = CreateFrame("ScrollFrame", "MetaHuntOptionsProfilesScroll", container, "UIPanelScrollFrameTemplate")
|
||||
local sc = CreateFrame("Frame", "MetaHuntOptionsProfilesContent", sf)
|
||||
sc:SetWidth(510)
|
||||
sc:SetHeight(MAX_PROFILE_ROWS * ROW_H + 4)
|
||||
sf:SetScrollChild(sc)
|
||||
end
|
||||
if not MTH_GetFrame("MetaHuntOptionsCharScroll") then
|
||||
local csf = CreateFrame("ScrollFrame", "MetaHuntOptionsCharScroll", container, "UIPanelScrollFrameTemplate")
|
||||
local csc = CreateFrame("Frame", "MetaHuntOptionsCharContent", csf)
|
||||
csc:SetWidth(510)
|
||||
csc:SetHeight(MAX_CHAR_ROWS * ROW_H + 4)
|
||||
csf:SetScrollChild(csc)
|
||||
end
|
||||
end
|
||||
|
||||
local function PROF_EnsureProfileRows()
|
||||
local sc = MTH_GetFrame("MetaHuntOptionsProfilesContent")
|
||||
if not sc then return end
|
||||
for i = 1, MAX_PROFILE_ROWS do
|
||||
if not gProfileRows[i] then
|
||||
local row = CreateFrame("Frame", nil, sc)
|
||||
row:SetHeight(ROW_H)
|
||||
row:SetWidth(510)
|
||||
row:SetPoint("TOPLEFT", sc, "TOPLEFT", 0, -(i - 1) * ROW_H)
|
||||
|
||||
local nameText = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
nameText:SetPoint("LEFT", row, "LEFT", 4, 2)
|
||||
nameText:SetWidth(210)
|
||||
nameText:SetJustifyH("LEFT")
|
||||
|
||||
local infoText = row:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
infoText:SetPoint("LEFT", row, "LEFT", 218, 2)
|
||||
infoText:SetWidth(155)
|
||||
infoText:SetJustifyH("LEFT")
|
||||
infoText:SetTextColor(0.55, 0.55, 0.55)
|
||||
|
||||
local loadBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
|
||||
loadBtn:SetWidth(58)
|
||||
loadBtn:SetHeight(20)
|
||||
loadBtn:SetPoint("RIGHT", row, "RIGHT", -62, 0)
|
||||
loadBtn:SetText("Load")
|
||||
|
||||
local delBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
|
||||
delBtn:SetWidth(58)
|
||||
delBtn:SetHeight(20)
|
||||
delBtn:SetPoint("RIGHT", row, "RIGHT", 0, 0)
|
||||
delBtn:SetText("Delete")
|
||||
|
||||
gProfileRows[i] = { frame = row, nameText = nameText, infoText = infoText, loadBtn = loadBtn, delBtn = delBtn }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function PROF_EnsureCharRows()
|
||||
local csc = MTH_GetFrame("MetaHuntOptionsCharContent")
|
||||
if not csc then return end
|
||||
for i = 1, MAX_CHAR_ROWS do
|
||||
if not gCharRows[i] then
|
||||
local row = CreateFrame("Frame", nil, csc)
|
||||
row:SetHeight(ROW_H)
|
||||
row:SetWidth(510)
|
||||
row:SetPoint("TOPLEFT", csc, "TOPLEFT", 0, -(i - 1) * ROW_H)
|
||||
|
||||
local nameText = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
nameText:SetPoint("LEFT", row, "LEFT", 4, 2)
|
||||
nameText:SetWidth(250)
|
||||
nameText:SetJustifyH("LEFT")
|
||||
nameText:SetTextColor(0.70, 0.85, 1.00)
|
||||
|
||||
local infoText = row:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
infoText:SetPoint("LEFT", row, "LEFT", 258, 2)
|
||||
infoText:SetWidth(120)
|
||||
infoText:SetJustifyH("LEFT")
|
||||
infoText:SetTextColor(0.55, 0.55, 0.55)
|
||||
|
||||
local copyBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
|
||||
copyBtn:SetWidth(80)
|
||||
copyBtn:SetHeight(20)
|
||||
copyBtn:SetPoint("RIGHT", row, "RIGHT", 0, 0)
|
||||
copyBtn:SetText("Import")
|
||||
|
||||
gCharRows[i] = { frame = row, nameText = nameText, infoText = infoText, copyBtn = copyBtn }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ── dynamic refresh functions ──────────────────────────────────────────────
|
||||
|
||||
local function PROF_RefreshActive()
|
||||
if not gActiveLabel then return end
|
||||
local active = type(MTH_Profile_GetActive) == "function" and MTH_Profile_GetActive()
|
||||
if active then
|
||||
gActiveLabel:SetText("Active: |cffffd200" .. active .. "|r")
|
||||
gActiveLabel:SetTextColor(0.90, 0.90, 0.90)
|
||||
else
|
||||
gActiveLabel:SetText("Active: |cff888888Custom (unsaved)|r")
|
||||
gActiveLabel:SetTextColor(0.90, 0.90, 0.90)
|
||||
end
|
||||
gActiveLabel:Show()
|
||||
end
|
||||
|
||||
local function PROF_RefreshProfileList()
|
||||
PROF_EnsureProfileRows()
|
||||
local profiles = type(MTH_Profile_GetList) == "function" and MTH_Profile_GetList() or {}
|
||||
local active = type(MTH_Profile_GetActive) == "function" and MTH_Profile_GetActive()
|
||||
local n = table.getn(profiles)
|
||||
|
||||
local sc = MTH_GetFrame("MetaHuntOptionsProfilesContent")
|
||||
if sc then
|
||||
sc:SetHeight(math.max(n * ROW_H + 4, ROW_H))
|
||||
end
|
||||
|
||||
for i = 1, MAX_PROFILE_ROWS do
|
||||
local row = gProfileRows[i]
|
||||
if not row then break end
|
||||
local name = profiles[i]
|
||||
if name then
|
||||
-- Name (with active indicator)
|
||||
if name == active then
|
||||
row.nameText:SetText(name .. " |cff00cc44[active]|r")
|
||||
else
|
||||
row.nameText:SetText(name)
|
||||
end
|
||||
-- Info: date saved by who
|
||||
local p = type(MTH_SavedVariables) == "table"
|
||||
and type(MTH_SavedVariables.profiles) == "table"
|
||||
and MTH_SavedVariables.profiles[name]
|
||||
local info = ""
|
||||
if type(p) == "table" then
|
||||
if p.savedAt and p.savedAt ~= "" then
|
||||
info = p.savedAt
|
||||
end
|
||||
end
|
||||
row.infoText:SetText(info)
|
||||
-- Wire buttons (SetScript replaces previous handler each refresh)
|
||||
local rowName = name
|
||||
row.loadBtn:SetScript("OnClick", function()
|
||||
MTH_PROFILE_PENDING_LOAD = rowName
|
||||
StaticPopup_Show("MTH_PROFILE_LOAD_CONFIRM", rowName)
|
||||
end)
|
||||
row.delBtn:SetScript("OnClick", function()
|
||||
MTH_PROFILE_PENDING_DELETE = rowName
|
||||
StaticPopup_Show("MTH_PROFILE_DELETE_CONFIRM", rowName)
|
||||
end)
|
||||
row.frame:Show()
|
||||
row.loadBtn:Show()
|
||||
row.delBtn:Show()
|
||||
else
|
||||
row.frame:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function PROF_RefreshCharList()
|
||||
PROF_EnsureCharRows()
|
||||
local chars = type(MTH_Profile_GetCharList) == "function" and MTH_Profile_GetCharList() or {}
|
||||
local n = table.getn(chars)
|
||||
|
||||
local csc = MTH_GetFrame("MetaHuntOptionsCharContent")
|
||||
if csc then
|
||||
csc:SetHeight(math.max(n * ROW_H + 4, ROW_H))
|
||||
end
|
||||
|
||||
for i = 1, MAX_CHAR_ROWS do
|
||||
local row = gCharRows[i]
|
||||
if not row then break end
|
||||
local charKey = chars[i]
|
||||
if charKey then
|
||||
local snap = type(MTH_SavedVariables) == "table"
|
||||
and type(MTH_SavedVariables.charSnapshots) == "table"
|
||||
and MTH_SavedVariables.charSnapshots[charKey]
|
||||
local info = (type(snap) == "table" and snap.savedAt) and snap.savedAt or ""
|
||||
row.nameText:SetText(charKey)
|
||||
row.infoText:SetText(info)
|
||||
local ck = charKey
|
||||
row.copyBtn:SetScript("OnClick", function()
|
||||
local ok, pName = MTH_Profile_CopyFromChar(ck)
|
||||
if ok then
|
||||
MTH_SetupProfilesOptions()
|
||||
if MTH and MTH.Print then
|
||||
MTH:Print('Imported "' .. ck .. '" as profile "' .. tostring(pName) .. '".')
|
||||
end
|
||||
end
|
||||
end)
|
||||
row.frame:Show()
|
||||
row.copyBtn:Show()
|
||||
else
|
||||
row.frame:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ── main setup (static elements created once, dynamic lists refreshed every call) ──
|
||||
|
||||
function MTH_SetupProfilesOptions()
|
||||
local container = MTH_GetFrame("MetaHuntOptionsProfiles")
|
||||
if not container then return end
|
||||
|
||||
if not gSetupDone then
|
||||
-- Title
|
||||
local title = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
title:SetPoint("TOPLEFT", container, "TOPLEFT", 6, -6)
|
||||
title:SetText("Profiles")
|
||||
title:SetTextColor(1.00, 0.82, 0.00)
|
||||
|
||||
-- Active profile indicator
|
||||
gActiveLabel = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
gActiveLabel:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
|
||||
gActiveLabel:SetWidth(540)
|
||||
gActiveLabel:SetJustifyH("LEFT")
|
||||
|
||||
-- ── Save As section ───────────────────────────────────────────────
|
||||
local saveHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
saveHdr:SetPoint("TOPLEFT", gActiveLabel, "BOTTOMLEFT", 0, -10)
|
||||
saveHdr:SetText("Save current settings as:")
|
||||
saveHdr:SetTextColor(0.90, 0.90, 0.90)
|
||||
|
||||
local eb = CreateFrame("EditBox", "MTH_ProfileNameInput", container, "InputBoxTemplate")
|
||||
eb:SetWidth(240)
|
||||
eb:SetHeight(20)
|
||||
eb:SetMaxLetters(50)
|
||||
eb:SetAutoFocus(false)
|
||||
eb:SetPoint("TOPLEFT", saveHdr, "BOTTOMLEFT", 2, -6)
|
||||
eb:SetScript("OnEnterPressed", function()
|
||||
PROF_DoSave(this:GetText())
|
||||
this:SetText("")
|
||||
this:ClearFocus()
|
||||
end)
|
||||
eb:SetScript("OnEscapePressed", function()
|
||||
this:SetText("")
|
||||
this:ClearFocus()
|
||||
end)
|
||||
|
||||
local saveBtn = CreateFrame("Button", "MTH_ProfileSaveBtn", container, "UIPanelButtonTemplate")
|
||||
saveBtn:SetWidth(70)
|
||||
saveBtn:SetHeight(20)
|
||||
saveBtn:SetPoint("LEFT", eb, "RIGHT", 6, 0)
|
||||
saveBtn:SetText("Save")
|
||||
saveBtn:SetScript("OnClick", function()
|
||||
local box = MTH_GetFrame("MTH_ProfileNameInput")
|
||||
if box then
|
||||
PROF_DoSave(box:GetText())
|
||||
box:SetText("")
|
||||
box:ClearFocus()
|
||||
end
|
||||
end)
|
||||
|
||||
-- ── Saved Profiles section ────────────────────────────────────────
|
||||
local profHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
profHdr:SetPoint("TOPLEFT", eb, "BOTTOMLEFT", -2, -12)
|
||||
profHdr:SetText("Saved Profiles")
|
||||
profHdr:SetTextColor(1.00, 0.82, 0.00)
|
||||
|
||||
local rule1 = container:CreateTexture(nil, "ARTWORK")
|
||||
rule1:SetPoint("TOPLEFT", profHdr, "BOTTOMLEFT", 0, -3)
|
||||
rule1:SetWidth(540)
|
||||
rule1:SetHeight(1)
|
||||
rule1:SetTexture(0.30, 0.30, 0.30, 0.80)
|
||||
|
||||
PROF_EnsureScrollFrames(container)
|
||||
|
||||
local sf = MTH_GetFrame("MetaHuntOptionsProfilesScroll")
|
||||
sf:SetPoint("TOPLEFT", rule1, "BOTTOMLEFT", 0, -4)
|
||||
sf:SetWidth(530)
|
||||
sf:SetHeight(170)
|
||||
sf:Show()
|
||||
|
||||
-- ── Copy from Character section ───────────────────────────────────
|
||||
local charHdr = container:CreateFontString(nil, "ARTWORK", "GameFontNormal")
|
||||
charHdr:SetPoint("TOPLEFT", sf, "BOTTOMLEFT", 0, -10)
|
||||
charHdr:SetText("Copy from Character")
|
||||
charHdr:SetTextColor(1.00, 0.82, 0.00)
|
||||
|
||||
local charNote = container:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
charNote:SetPoint("TOPLEFT", charHdr, "BOTTOMLEFT", 0, -2)
|
||||
charNote:SetWidth(540)
|
||||
charNote:SetJustifyH("LEFT")
|
||||
charNote:SetText("Snapshots are saved automatically on logout. Click Import to create a profile from one.")
|
||||
charNote:SetTextColor(0.60, 0.60, 0.60)
|
||||
|
||||
local rule2 = container:CreateTexture(nil, "ARTWORK")
|
||||
rule2:SetPoint("TOPLEFT", charNote, "BOTTOMLEFT", 0, -3)
|
||||
rule2:SetWidth(540)
|
||||
rule2:SetHeight(1)
|
||||
rule2:SetTexture(0.30, 0.30, 0.30, 0.80)
|
||||
|
||||
local csf = MTH_GetFrame("MetaHuntOptionsCharScroll")
|
||||
csf:SetPoint("TOPLEFT", rule2, "BOTTOMLEFT", 0, -4)
|
||||
csf:SetWidth(530)
|
||||
csf:SetHeight(140)
|
||||
csf:Show()
|
||||
|
||||
gSetupDone = true
|
||||
end
|
||||
|
||||
-- Refresh dynamic content every time the panel is opened
|
||||
PROF_RefreshActive()
|
||||
PROF_RefreshProfileList()
|
||||
PROF_RefreshCharList()
|
||||
end
|
||||
@@ -117,6 +117,11 @@ function MTH_SelectOptionsTab(tabKey)
|
||||
MTH_SetupGeneralOptions()
|
||||
MTH_OPTIONS_SETUP["General"] = true
|
||||
end
|
||||
elseif tabKey == "Profiles" then
|
||||
if type(MTH_SetupProfilesOptions) == "function" then
|
||||
MTH_SetupProfilesOptions()
|
||||
MTH_OPTIONS_SETUP["Profiles"] = true
|
||||
end
|
||||
elseif tabKey == "Messages" then
|
||||
if not MTH_OPTIONS_SETUP["Messages"] then
|
||||
if type(MTH_SetupMessagesOptions) == "function" then
|
||||
|
||||
@@ -27,7 +27,8 @@ MTH_OPTIONS_CONST = MTH_OPTIONS_CONST or {
|
||||
}
|
||||
|
||||
MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
|
||||
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
|
||||
{ key = "General", label = "General", frame = "MetaHuntOptionsGeneral" },
|
||||
{ key = "Profiles", label = "Profiles", frame = "MetaHuntOptionsProfiles" },
|
||||
{ key = "Messages", label = "Messages", frame = "MetaHuntOptionsMessages" },
|
||||
{ key = "Pet", label = "ZPet", frame = "MetaHuntOptionsPet" },
|
||||
{ key = "Track", label = "ZTrack", frame = "MetaHuntOptionsTrack" },
|
||||
@@ -52,7 +53,8 @@ MTH_OPTIONS_TABS = MTH_OPTIONS_TABS or {
|
||||
}
|
||||
|
||||
MTH_OPTIONS_TREE = MTH_OPTIONS_TREE or {
|
||||
{ label = "General", key = "General" },
|
||||
{ label = "General", key = "General" },
|
||||
{ label = "Profiles", key = "Profiles" },
|
||||
{ label = "Messages", key = "Messages" },
|
||||
{ label = "Auto Buy", key = "AutoBuy" },
|
||||
{ label = "Auto Quest", key = "AutoQuest" },
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
|
||||
<!-- Content Areas -->
|
||||
<Frame name="$parentGeneral" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentProfiles" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentMessages" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentPet" hidden="true" enableMouse="true"/>
|
||||
<Frame name="$parentTrack" hidden="true" enableMouse="true"/>
|
||||
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
------------------------------------------------------
|
||||
-- MetaHunt Profile System
|
||||
-- Captures:
|
||||
-- MTH_SavedVariables.modules (account-wide option values, e.g. feedomatic)
|
||||
-- MTH_CharSavedVariables.modules (per-char option values: ICU, Chronometer,
|
||||
-- AutoBuy, AutoQuest, SmartAmmo, tooltips...)
|
||||
-- MTH_CharSavedVariables.moduleStates (enabled/disabled per char)
|
||||
-- ZHunterMod_Saved / MTH_CharSavedVariables.modules.zhunter (zButtons layout)
|
||||
-- Auto-snapshots current character on PLAYER_LOGOUT into
|
||||
-- MTH_SavedVariables.charSnapshots["CharName-RealmName"]
|
||||
------------------------------------------------------
|
||||
|
||||
local function MTH_Profile_DeepCopy(orig)
|
||||
if type(orig) ~= "table" then return orig end
|
||||
local copy = {}
|
||||
for k, v in orig do
|
||||
copy[MTH_Profile_DeepCopy(k)] = MTH_Profile_DeepCopy(v)
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
local function MTH_Profile_EnsureStore()
|
||||
if type(MTH_SavedVariables) ~= "table" then
|
||||
MTH_SavedVariables = {}
|
||||
end
|
||||
if type(MTH_SavedVariables.profiles) ~= "table" then
|
||||
MTH_SavedVariables.profiles = {}
|
||||
end
|
||||
if type(MTH_SavedVariables.charSnapshots) ~= "table" then
|
||||
MTH_SavedVariables.charSnapshots = {}
|
||||
end
|
||||
end
|
||||
|
||||
local function MTH_Profile_GetCharKey()
|
||||
local name = (type(UnitName) == "function") and UnitName("player") or "Unknown"
|
||||
local realm = (type(GetRealmName) == "function") and GetRealmName() or "Unknown"
|
||||
name = (name and name ~= "") and name or "Unknown"
|
||||
realm = (realm and realm ~= "") and realm or "Unknown"
|
||||
return name .. "-" .. realm
|
||||
end
|
||||
|
||||
local function MTH_Profile_BuildSnapshot()
|
||||
local snap = {}
|
||||
snap.savedBy = MTH_Profile_GetCharKey()
|
||||
snap.savedAt = (type(date) == "function") and date("%Y-%m-%d %H:%M") or ""
|
||||
|
||||
-- Account-wide module option values (e.g. feedomatic)
|
||||
snap.config = (type(MTH_SavedVariables) == "table"
|
||||
and type(MTH_SavedVariables.modules) == "table")
|
||||
and MTH_Profile_DeepCopy(MTH_SavedVariables.modules)
|
||||
or {}
|
||||
|
||||
-- Per-character module option values (ICU, Chronometer, AutoBuy, SmartAmmo, etc.)
|
||||
snap.charConfig = (type(MTH_CharSavedVariables) == "table"
|
||||
and type(MTH_CharSavedVariables.modules) == "table")
|
||||
and MTH_Profile_DeepCopy(MTH_CharSavedVariables.modules)
|
||||
or {}
|
||||
|
||||
-- Per-character module enabled/disabled states
|
||||
snap.moduleStates = (type(MTH_CharSavedVariables) == "table"
|
||||
and type(MTH_CharSavedVariables.moduleStates) == "table")
|
||||
and MTH_Profile_DeepCopy(MTH_CharSavedVariables.moduleStates)
|
||||
or {}
|
||||
|
||||
-- zButtons config — kept as dedicated key for clarity
|
||||
-- (also lives in charConfig.zhunter but we keep it explicit)
|
||||
local zh = nil
|
||||
if type(MTH_CharSavedVariables) == "table"
|
||||
and type(MTH_CharSavedVariables.modules) == "table"
|
||||
and type(MTH_CharSavedVariables.modules.zhunter) == "table"
|
||||
then
|
||||
zh = MTH_CharSavedVariables.modules.zhunter
|
||||
elseif type(ZHunterMod_Saved) == "table" then
|
||||
zh = ZHunterMod_Saved
|
||||
end
|
||||
snap.zhunter = zh and MTH_Profile_DeepCopy(zh) or {}
|
||||
|
||||
return snap
|
||||
end
|
||||
|
||||
-- Save current settings as a named profile
|
||||
function MTH_Profile_Save(name)
|
||||
if type(name) ~= "string" or string.len(name) == 0 then
|
||||
return false, "invalid name"
|
||||
end
|
||||
MTH_Profile_EnsureStore()
|
||||
MTH_SavedVariables.profiles[name] = MTH_Profile_BuildSnapshot()
|
||||
return true
|
||||
end
|
||||
|
||||
-- Delete a named profile
|
||||
function MTH_Profile_Delete(name)
|
||||
MTH_Profile_EnsureStore()
|
||||
if MTH_SavedVariables.profiles[name] == nil then return false end
|
||||
MTH_SavedVariables.profiles[name] = nil
|
||||
if type(MTH_CharSavedVariables) == "table"
|
||||
and MTH_CharSavedVariables.activeProfile == name
|
||||
then
|
||||
MTH_CharSavedVariables.activeProfile = nil
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Apply a profile to SavedVariables; a /reload is required for changes to take full effect
|
||||
function MTH_Profile_Load(name)
|
||||
MTH_Profile_EnsureStore()
|
||||
local p = MTH_SavedVariables.profiles[name]
|
||||
if type(p) ~= "table" then return false, "profile not found" end
|
||||
|
||||
if type(MTH_CharSavedVariables) ~= "table" then MTH_CharSavedVariables = {} end
|
||||
if type(MTH_CharSavedVariables.modules) ~= "table" then MTH_CharSavedVariables.modules = {} end
|
||||
|
||||
-- Apply account-wide module option values (e.g. feedomatic)
|
||||
if type(p.config) == "table" then
|
||||
if type(MTH_SavedVariables.modules) ~= "table" then
|
||||
MTH_SavedVariables.modules = {}
|
||||
end
|
||||
for modName, modData in p.config do
|
||||
MTH_SavedVariables.modules[modName] = MTH_Profile_DeepCopy(modData)
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply per-character module option values (ICU, Chronometer, AutoBuy, SmartAmmo, etc.)
|
||||
if type(p.charConfig) == "table" then
|
||||
for modName, modData in p.charConfig do
|
||||
MTH_CharSavedVariables.modules[modName] = MTH_Profile_DeepCopy(modData)
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply module enabled/disabled
|
||||
if type(p.moduleStates) == "table" then
|
||||
if type(MTH_CharSavedVariables.moduleStates) ~= "table" then
|
||||
MTH_CharSavedVariables.moduleStates = {}
|
||||
end
|
||||
for k, v in p.moduleStates do
|
||||
MTH_CharSavedVariables.moduleStates[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply zButtons (also written into charConfig.zhunter above, but keep explicit)
|
||||
if type(p.zhunter) == "table" then
|
||||
ZHunterMod_Saved = MTH_Profile_DeepCopy(p.zhunter)
|
||||
MTH_CharSavedVariables.modules.zhunter = ZHunterMod_Saved
|
||||
end
|
||||
|
||||
MTH_CharSavedVariables.activeProfile = name
|
||||
return true
|
||||
end
|
||||
|
||||
-- Create a named profile from another character's auto-snapshot
|
||||
-- profileName defaults to charKey if not provided
|
||||
function MTH_Profile_CopyFromChar(charKey, profileName)
|
||||
MTH_Profile_EnsureStore()
|
||||
local snap = MTH_SavedVariables.charSnapshots[charKey]
|
||||
if type(snap) ~= "table" then
|
||||
return false, "no snapshot for " .. tostring(charKey)
|
||||
end
|
||||
local name = (type(profileName) == "string" and string.len(profileName) > 0)
|
||||
and profileName or charKey
|
||||
MTH_SavedVariables.profiles[name] = MTH_Profile_DeepCopy(snap)
|
||||
return true, name
|
||||
end
|
||||
|
||||
-- Return sorted list of profile names
|
||||
function MTH_Profile_GetList()
|
||||
MTH_Profile_EnsureStore()
|
||||
local list = {}
|
||||
for name, _ in MTH_SavedVariables.profiles do
|
||||
table.insert(list, name)
|
||||
end
|
||||
table.sort(list)
|
||||
return list
|
||||
end
|
||||
|
||||
-- Return sorted list of character snapshot keys
|
||||
function MTH_Profile_GetCharList()
|
||||
MTH_Profile_EnsureStore()
|
||||
local list = {}
|
||||
for charKey, _ in MTH_SavedVariables.charSnapshots do
|
||||
table.insert(list, charKey)
|
||||
end
|
||||
table.sort(list)
|
||||
return list
|
||||
end
|
||||
|
||||
-- Return the active profile name for the current character, or nil
|
||||
function MTH_Profile_GetActive()
|
||||
if type(MTH_CharSavedVariables) == "table" then
|
||||
return MTH_CharSavedVariables.activeProfile
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Auto-snapshot on logout so other characters can import this config
|
||||
local MTH_ProfileEventFrame = CreateFrame("Frame", "MTH_ProfileEventFrame")
|
||||
MTH_ProfileEventFrame:RegisterEvent("PLAYER_LOGOUT")
|
||||
MTH_ProfileEventFrame:SetScript("OnEvent", function()
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
MTH_Profile_EnsureStore()
|
||||
MTH_SavedVariables.charSnapshots[MTH_Profile_GetCharKey()] = MTH_Profile_BuildSnapshot()
|
||||
end
|
||||
end)
|
||||
Reference in New Issue
Block a user