базовый encounter journal

This commit is contained in:
2026-06-13 11:46:39 +04:00
parent d8d94f10b0
commit b0da9ec10b
90 changed files with 4521 additions and 366 deletions
@@ -69,25 +69,26 @@ local MainMenuBarBackpackButton = _G.MainMenuBarBackpackButton;
local HelpMicroButton = _G.HelpMicroButton;
local KeyRingButton = _G.KeyRingButton;
-- Button collections (dynamically set based on server)
local MICRO_BUTTONS
-- Button collections (rebuilt after addons create optional custom buttons).
local function BuildMicroButtonList()
if isAscensionServer then
return {
_G.CharacterMicroButton,
_G.SpellbookMicroButton,
_G.TalentMicroButton,
_G.AchievementMicroButton,
_G.QuestLogMicroButton,
_G.SocialsMicroButton,
_G.LFDMicroButton,
_G.EncounterJournalMicroButton,
_G.PathToAscensionMicroButton,
_G.ChallengesMicroButton,
_G.MainMenuMicroButton,
_G.HelpMicroButton
}
end
if isAscensionServer then
MICRO_BUTTONS = {
_G.CharacterMicroButton,
_G.SpellbookMicroButton,
_G.TalentMicroButton,
_G.AchievementMicroButton,
_G.QuestLogMicroButton,
_G.SocialsMicroButton,
_G.LFDMicroButton,
_G.PathToAscensionMicroButton,
_G.ChallengesMicroButton,
_G.MainMenuMicroButton,
_G.HelpMicroButton
}
else
MICRO_BUTTONS = {
return {
_G.CharacterMicroButton,
_G.SpellbookMicroButton,
_G.TalentMicroButton,
@@ -95,6 +96,7 @@ else
_G.QuestLogMicroButton,
_G.SocialsMicroButton,
_G.LFDMicroButton,
_G.EncounterJournalMicroButton,
_G.CollectionsMicroButton,
_G.PVPMicroButton,
_G.MainMenuMicroButton,
@@ -102,6 +104,7 @@ else
}
end
local MICRO_BUTTONS = BuildMicroButtonList()
local bagslots = {_G.CharacterBag0Slot, _G.CharacterBag1Slot, _G.CharacterBag2Slot, _G.CharacterBag3Slot};
@@ -783,6 +786,8 @@ local function ApplyMicromenuSystem()
return
end
MICRO_BUTTONS = BuildMicroButtonList()
-- Store original states first
StoreOriginalMicroButtonStates()
@@ -911,6 +916,8 @@ local function ApplyMicromenuSystem()
or (_G.PVPParentFrame and _G.PVPParentFrame:IsVisible() and true or false)
or (_G.BattlefieldFrame and _G.BattlefieldFrame:IsVisible() and true or false)
or (_G.HonorFrame and _G.HonorFrame:IsVisible() and true or false)
elseif buttonName == "EncounterJournal" then
return (_G.EncounterJournal and _G.EncounterJournal:IsVisible() and true or false)
end
return pressed
@@ -919,6 +926,152 @@ local function ApplyMicromenuSystem()
-- ============================================================================
-- SECTION 5: SPECIALIZED BUTTON SETUP
-- ============================================================================
local function SetupEncounterJournalButton(button)
local iconTexture = 'Interface\\EncounterJournal\\UI-EJ-PortraitIcon'
local backgroundTexture = 'Interface\\AddOns\\DragonUI\\Textures\\Micromenu\\uimicromenu2x'
local buttonWidth, buttonHeight = button:GetSize()
local dx, dy = -1, 1
local offX, offY = button:GetPushedTextOffset()
if not button.DragonUIEncounterJournalIcon then
button.DragonUIEncounterJournalIcon = button:CreateTexture(nil, 'ARTWORK')
end
local icon = button.DragonUIEncounterJournalIcon
icon:SetTexture(iconTexture)
icon:SetTexCoord(0, 1, 0, 1)
icon:ClearAllPoints()
icon:SetPoint('CENTER', button, 'CENTER', 0, 0)
icon:SetSize(buttonWidth - 8, buttonWidth - 8)
icon:SetAlpha(1)
icon:Show()
local highlightTexture = button:GetHighlightTexture()
if highlightTexture then
highlightTexture:SetTexture(iconTexture)
highlightTexture:SetTexCoord(0, 1, 0, 1)
highlightTexture:ClearAllPoints()
highlightTexture:SetAllPoints(icon)
highlightTexture:SetBlendMode('ADD')
highlightTexture:SetAlpha(0.55)
end
if not button.DragonUIBackground then
local bg = button:CreateTexture(nil, 'BACKGROUND')
bg:SetTexture(backgroundTexture)
bg:SetSize(buttonWidth, buttonHeight + 1)
bg:SetTexCoord(0.0654297, 0.12793, 0.330078, 0.490234)
bg:SetPoint('CENTER', dx, dy)
button.DragonUIBackground = bg
local bgPushed = button:CreateTexture(nil, 'BACKGROUND')
bgPushed:SetTexture(backgroundTexture)
bgPushed:SetSize(buttonWidth, buttonHeight + 1)
bgPushed:SetTexCoord(0.0654297, 0.12793, 0.494141, 0.654297)
bgPushed:SetPoint('CENTER', dx + offX, dy + offY)
bgPushed:Hide()
button.DragonUIBackgroundPushed = bgPushed
else
button.DragonUIBackground:SetTexture(backgroundTexture)
button.DragonUIBackground:SetTexCoord(0.0654297, 0.12793, 0.330078, 0.490234)
button.DragonUIBackground:ClearAllPoints()
button.DragonUIBackground:SetPoint('CENTER', dx, dy)
button.DragonUIBackground:SetSize(buttonWidth, buttonHeight + 1)
if button.DragonUIBackgroundPushed then
button.DragonUIBackgroundPushed:SetTexture(backgroundTexture)
button.DragonUIBackgroundPushed:SetTexCoord(0.0654297, 0.12793, 0.494141, 0.654297)
button.DragonUIBackgroundPushed:ClearAllPoints()
button.DragonUIBackgroundPushed:SetPoint('CENTER', dx + offX, dy + offY)
button.DragonUIBackgroundPushed:SetSize(buttonWidth, buttonHeight + 1)
end
end
button.dragonUIState = button.dragonUIState or {}
button.dragonUIState.pushed = IsSpecialMicroButtonActive(button, "EncounterJournal")
button.dragonUILastState = button.dragonUIState.pushed
button.dragonUITimer = button.dragonUITimer or 0
button.HandleDragonUIState = function()
local state = button.dragonUIState
local hlTex = button:GetHighlightTexture()
if state and state.pushed then
if icon then
icon:ClearAllPoints()
icon:SetPoint('CENTER', button, 'CENTER', offX, offY)
icon:SetAlpha(0.7)
end
if button.DragonUIBackground then
button.DragonUIBackground:Hide()
end
if button.DragonUIBackgroundPushed then
button.DragonUIBackgroundPushed:Show()
end
if hlTex then
hlTex:ClearAllPoints()
hlTex:SetPoint('TOPLEFT', icon, 'TOPLEFT', 0, 0)
hlTex:SetPoint('BOTTOMRIGHT', icon, 'BOTTOMRIGHT', 0, 0)
end
else
if icon then
icon:ClearAllPoints()
icon:SetPoint('CENTER', button, 'CENTER', 0, 0)
icon:SetAlpha(1)
end
if button.DragonUIBackground then
button.DragonUIBackground:Show()
end
if button.DragonUIBackgroundPushed then
button.DragonUIBackgroundPushed:Hide()
end
if hlTex then
hlTex:ClearAllPoints()
hlTex:SetAllPoints(icon)
end
end
end
button:SetScript('OnUpdate', function(self, elapsed)
self.dragonUITimer = (self.dragonUITimer or 0) + elapsed
if self.dragonUITimer >= 0.1 then
self.dragonUITimer = 0
local currentState = IsSpecialMicroButtonActive(self, "EncounterJournal")
if currentState ~= self.dragonUILastState then
self.dragonUILastState = currentState
if self.dragonUIState then
self.dragonUIState.pushed = currentState
end
if self.HandleDragonUIState then
self.HandleDragonUIState()
end
end
end
end)
if not button.DragonUIStateHooks then
button:HookScript('OnMouseDown', function(self)
if self.dragonUIState then
self.dragonUIState.pushed = true
end
if self.HandleDragonUIState then
self.HandleDragonUIState()
end
end)
button:HookScript('OnMouseUp', function(self)
local currentState = IsSpecialMicroButtonActive(self, "EncounterJournal")
if self.dragonUIState then
self.dragonUIState.pushed = currentState
end
if self.HandleDragonUIState then
self.HandleDragonUIState()
end
end)
button.DragonUIStateHooks = true
end
button.HandleDragonUIState()
end
local function SetupPVPButton(button)
-- Mirror the Character button pattern:
-- Instead of fighting WoW's internal NormalTexture alpha management,
@@ -1582,6 +1735,8 @@ local function ApplyMicromenuSystem()
end
local function setupMicroButtons(xOffset)
MICRO_BUTTONS = BuildMicroButtonList()
local buttonxOffset = 0
local useGrayscale = addon.db.profile.micromenu.grayscale_icons
@@ -1705,9 +1860,11 @@ local function ApplyMicromenuSystem()
local isCharacterButton = (buttonName == "Character")
local isPVPButton = (buttonName == "PVP")
local isEncounterJournalButton = (buttonName == "EncounterJournal")
local upCoords = not isCharacterButton and not isPVPButton and GetColoredTextureCoords(name, "Up") or nil
local shouldUseGrayscale = useGrayscale or (not isPVPButton and not upCoords and not isCharacterButton)
local upCoords = not isCharacterButton and not isPVPButton and not isEncounterJournalButton and GetColoredTextureCoords(name, "Up") or nil
local shouldUseGrayscale = (useGrayscale and not isEncounterJournalButton) or
(not isPVPButton and not isEncounterJournalButton and not upCoords and not isCharacterButton)
if shouldUseGrayscale then
-- Grayscale icons
@@ -1741,6 +1898,8 @@ local function ApplyMicromenuSystem()
end
elseif isPVPButton then
SetupPVPButton(button)
elseif isEncounterJournalButton then
SetupEncounterJournalButton(button)
elseif isCharacterButton then
SetupCharacterButton(button)
else
@@ -2245,6 +2404,8 @@ end
return
end
MICRO_BUTTONS = BuildMicroButtonList()
local useGrayscale = addon.db.profile.micromenu.grayscale_icons
local configMode = useGrayscale and "grayscale" or "normal"
local config = addon.db.profile.micromenu[configMode]
@@ -2107,7 +2107,7 @@
</Layer>
</Layers>
<Frames>
<Button name="$parentSuggestTab" inherits="EncounterTierTabTemplate" text="AJ_SUGGESTED_CONTENT_TAB" parentKey="suggestTab" id="1">
<Button name="$parentGuideTab" inherits="EncounterTierTabTemplate" text="MW_PLAYER_GUIDE" parentKey="guideTab" id="5">
<Anchors>
<Anchor point="BOTTOMLEFT" relativePoint="TOPLEFT" x="25" y="-45"/>
</Anchors>
@@ -2115,6 +2115,14 @@
<OnClick function="EJ_ContentTab_OnClick"/>
</Scripts>
</Button>
<Button name="$parentSuggestTab" inherits="EncounterTierTabTemplate" text="AJ_SUGGESTED_CONTENT_TAB" parentKey="suggestTab" id="1">
<Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parentGuideTab" relativePoint="BOTTOMRIGHT" x="35" y="0"/>
</Anchors>
<Scripts>
<OnClick function="EJ_ContentTab_OnClick"/>
</Scripts>
</Button>
<Button name="$parentDungeonTab" inherits="EncounterTierTabTemplate" text="INSTANCES" parentKey="dungeonsTab" id="2">
<Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parentSuggestTab" relativePoint="BOTTOMRIGHT" x="35" y="0"/>
@@ -0,0 +1,484 @@
local SHOW_RECOMMENDATIONS = true
local DENSITY = "regular"
local ACCENT = { r = 1.0, g = 0.84, b = 0.42 }
local ROMAN = { "I","II","III","IV","V","VI","VII","VIII","IX","X" }
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
local BIOME_TEX = {
fire = T .. "Biome-Fire",
arcane = T .. "Biome-Arcane",
storm = T .. "Biome-Storm",
shadow = T .. "Biome-Shadow",
nature = T .. "Biome-Nature",
holy = T .. "Biome-Holy",
}
local BADGE_TEX = {
active = T .. "StageBadge-Active",
done = T .. "StageBadge-Done",
locked = T .. "StageBadge-Locked",
}
local TYPE_ICON = {
level = T .. "TypeIcon-Level",
quest = T .. "TypeIcon-Quest",
quest_chain = T .. "TypeIcon-QuestChain",
dungeon = T .. "TypeIcon-Dungeon",
raid = T .. "TypeIcon-Raid",
boss = T .. "TypeIcon-Boss",
achievement = T .. "TypeIcon-Achievement",
reputation = T .. "TypeIcon-Reputation",
profession = T .. "TypeIcon-Profession",
item = T .. "TypeIcon-Item",
currency = T .. "TypeIcon-Currency",
custom = T .. "TypeIcon-Custom",
done = T .. "TypeIcon-Done",
locked = T .. "TypeIcon-Locked",
}
local function showOrHide(frame, show)
if not frame then return end
if show then frame:Show() else frame:Hide() end
end
local function getScrollBar(scrollFrame)
if not scrollFrame then return nil end
return scrollFrame.ScrollBar or _G[scrollFrame:GetName() .. "ScrollBar"]
end
local function updateScrollBar(scrollFrame, contentHeight, contentWidth)
if not scrollFrame then return end
local viewHeight = scrollFrame:GetHeight() or 0
local viewWidth = scrollFrame:GetWidth() or 0
local scrollBar = getScrollBar(scrollFrame)
if scrollFrame.ScrollChild and viewWidth > 0 then
scrollFrame.ScrollChild:SetWidth(math.max(1, contentWidth or viewWidth))
end
if scrollBar then
if contentHeight and viewHeight > 0 and contentHeight > viewHeight + 2 then
scrollBar:Show()
else
scrollBar:Hide()
end
end
end
local function hideScrollBar(scrollFrame)
local scrollBar = getScrollBar(scrollFrame)
if scrollBar then scrollBar:Hide() end
end
local function getBodyChild()
local body = PlayerGuideFrame and PlayerGuideFrame.BodyScroll
return body and body.ScrollChild
end
-- ─────────────────────────────────── Цепочка этапов ──
local stageBadges = {}
local function buildStageChain(stages)
if not PlayerGuideFrame then return end
local container = PlayerGuideFrame.StageChain
for i = (#stages + 1), #stageBadges do stageBadges[i]:Hide() end
if #stages == 0 then return end
local width = container:GetWidth()
if width == 0 then width = 600 end
local slot = width / #stages
for i, s in ipairs(stages) do
local badge = stageBadges[i]
if not badge then
badge = CreateFrame("Frame", "PlayerGuideStageBadge"..i, container, "MW_PG_StageBadgeTemplate")
stageBadges[i] = badge
end
badge:ClearAllPoints()
badge:SetPoint("LEFT", container, "LEFT", (i - 0.5) * slot - 19, 0)
badge:Show()
badge.Roman:SetText(ROMAN[s.id] or tostring(s.id))
badge.Label:SetText(s.short or s.name or "")
local status = s.status or "locked"
badge.BadgeBG:SetTexture(BADGE_TEX[status] or BADGE_TEX.locked)
if status == "active" then
badge.Roman:SetTextColor(1.0, 0.91, 0.63)
badge.Label:SetTextColor(1.0, 0.84, 0.42)
elseif status == "done" then
badge.Roman:SetText("v")
badge.Roman:SetTextColor(0.79, 0.65, 0.38)
badge.Label:SetTextColor(0.79, 0.65, 0.38)
else
badge.Roman:SetTextColor(0.40, 0.34, 0.22)
badge.Label:SetTextColor(0.40, 0.34, 0.22)
end
end
end
-- ─────────────────────────────────── Карточки целей ──
local objectiveCards = {}
local CARD_HEIGHT = { compact = 54, regular = 70, cozy = 82 }
local CARD_GAP = { compact = 6, regular = 8, cozy = 10 }
local function renderObjectives()
local section = PlayerGuideFrame.ObjectivesSection
local parent = section.ScrollFrame.ScrollChild
local n = C_PlayerGuide.GetNumObjectives()
local width = 0
if PlayerGuideFrame.BodyScroll then
width = PlayerGuideFrame.BodyScroll:GetWidth()
end
if width == 0 then width = section:GetWidth() end
if width == 0 then width = section.ScrollFrame:GetWidth() end
if width == 0 then width = 860 end
-- Body child right inset 8, section right inset 0, scrollframe left/right insets 6/8.
-- The card then keeps 8px equal visual padding on both sides of the objective row.
parent:SetWidth(math.max(1, width - 30))
for i = (n + 1), #objectiveCards do objectiveCards[i]:Hide() end
local h = CARD_HEIGHT[DENSITY] or 44
local gap = CARD_GAP[DENSITY] or 4
local done = 0
for i = 1, n do
local o = C_PlayerGuide.GetObjectiveInfo(i)
if not o then break end
local card = objectiveCards[i]
if not card then
card = CreateFrame("Frame", "PlayerGuideObjective"..i, parent, "MW_PG_ObjectiveCardTemplate")
objectiveCards[i] = card
end
card:ClearAllPoints()
card:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, -(i - 1) * (h + gap))
card:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, -(i - 1) * (h + gap))
card:SetHeight(h)
card.objectiveData = o
card:Show()
if card.SetBackdropColor then
card:SetBackdropColor(0, 0, 0, 0.72)
card:SetBackdropBorderColor(0.64, 0.45, 0.15, 0.95)
end
card.TypeLabel:SetText(_G["MW_PG_TYPE_" .. string.upper(o.type or "custom")] or string.upper(o.type or ""))
card.Title:SetText(o.title or "")
card.Subtitle:SetText(o.subtitle or o.description or "")
card.ProgressBar:SetMinMaxValues(0, o.required or 1)
card.ProgressBar:SetValue(o.progress or 0)
card.Count:SetText(string.format("%d / %d", o.progress or 0, o.required or 1))
if o.completed then
done = done + 1
card.Icon:SetTexture(TYPE_ICON.done)
card.Icon:SetTexCoord(0, 1, 0, 1)
card.Title:SetTextColor(0.62, 0.84, 0.47)
card.TypeLabel:SetTextColor(0.49, 0.65, 0.35)
card.Count:SetTextColor(0.62, 0.84, 0.47)
card.ProgressBar:SetStatusBarColor(0.55, 0.85, 0.40)
card.IconBorder:SetVertexColor(0.40, 0.65, 0.30, 0.7)
if card.SetBackdropBorderColor then
card:SetBackdropBorderColor(0.26, 0.52, 0.18, 0.9)
end
else
card.Icon:SetTexture(TYPE_ICON[o.type] or TYPE_ICON.custom)
card.Icon:SetTexCoord(0, 1, 0, 1)
card.Title:SetTextColor(1.0, 0.91, 0.63)
card.TypeLabel:SetTextColor(0.63, 0.46, 0.19)
card.Count:SetTextColor(0.79, 0.65, 0.38)
card.ProgressBar:SetStatusBarColor(ACCENT.r, ACCENT.g, ACCENT.b)
card.IconBorder:SetVertexColor(ACCENT.r, ACCENT.g, ACCENT.b, 0.55)
end
if o.required == 1 and (o.progress or 0) == 0 then
card.ProgressBar:Hide()
else
card.ProgressBar:Show()
end
end
parent:SetHeight(math.max(1, n * h + math.max(0, n - 1) * gap))
section:SetHeight(48 + parent:GetHeight())
hideScrollBar(section.ScrollFrame)
section.Count:SetText(string.format(" %d / %d", done, n))
end
-- ─────────────────────────────── Карточки рекомендаций ──
local recCards = {}
local function renderRecommendations()
local section = PlayerGuideFrame.RecommendationsSection
if not SHOW_RECOMMENDATIONS then section:Hide(); return end
local parent = section.ScrollFrame.ScrollChild
local n = C_PlayerGuide.GetNumRecommendations()
if n == 0 then section:Hide(); return end
section:Show()
for i = (n + 1), #recCards do recCards[i]:Hide() end
local cardW = 176
local gap = 16
for i = 1, n do
local r = C_PlayerGuide.GetRecommendationInfo(i)
if not r then break end
local card = recCards[i]
if not card then
card = CreateFrame("Button", "PlayerGuideRec"..i, parent, "MW_PG_RecCardTemplate")
recCards[i] = card
end
card:ClearAllPoints()
card:SetPoint("TOPLEFT", parent, "TOPLEFT", (i - 1) * (cardW + gap), 0)
card.recData = r
card:Show()
if card.SetBackdropColor then
card:SetBackdropColor(0, 0, 0, 0.88)
card:SetBackdropBorderColor(0.64, 0.45, 0.15, 0.95)
end
card.Artwork:SetTexture(BIOME_TEX[r.biome] or BIOME_TEX.fire)
card.Name:SetText(r.name or r.title or "")
card.Loc:SetText(r.loc or r.description or "")
card.Level:SetText(r.level or "")
end
parent:SetWidth(math.max(1, n * cardW + math.max(0, n - 1) * gap))
parent:SetHeight(116)
section:SetHeight(162)
hideScrollBar(section.ScrollFrame)
section.Count:SetText(" " .. n)
end
local function updateBodyContent()
local F = PlayerGuideFrame
local child = getBodyChild()
if not F or not child then return end
local width = F.BodyScroll:GetWidth()
if width == 0 then width = 860 end
child:SetWidth(math.max(1, width - 8))
local y = 0
F.ObjectivesSection:ClearAllPoints()
F.ObjectivesSection:SetPoint("TOPLEFT", child, "TOPLEFT", 0, y)
F.ObjectivesSection:SetPoint("TOPRIGHT", child, "TOPRIGHT", 0, y)
y = y - F.ObjectivesSection:GetHeight() - 16
if F.RecommendationsSection:IsShown() then
F.RecommendationsSection:ClearAllPoints()
F.RecommendationsSection:SetPoint("TOPLEFT", child, "TOPLEFT", 0, y)
F.RecommendationsSection:SetPoint("TOPRIGHT", child, "TOPRIGHT", 0, y)
y = y - F.RecommendationsSection:GetHeight()
end
local contentHeight = math.max(1, -y + 8)
child:SetHeight(contentHeight)
updateScrollBar(F.BodyScroll, contentHeight, child:GetWidth())
end
-- ─────────────────────────────────────── Layout ──
local function applyLayout(self, hasChain)
local chain = self.StageChain
local hdr = self.Header
local body = self.BodyScroll
chain:ClearAllPoints()
chain:SetPoint("TOPLEFT", self, "TOPLEFT", 28, -8)
chain:SetPoint("TOPRIGHT", self, "TOPRIGHT", -28, -8)
hdr:ClearAllPoints()
if hasChain then
hdr:SetPoint("TOPLEFT", chain, "BOTTOMLEFT", 0, -6)
hdr:SetPoint("TOPRIGHT", chain, "BOTTOMRIGHT", 0, -6)
else
hdr:SetPoint("TOPLEFT", self, "TOPLEFT", 30, -68)
hdr:SetPoint("TOPRIGHT", self, "TOPRIGHT", -30, -68)
end
body:ClearAllPoints()
body:SetPoint("TOPLEFT", hdr, "BOTTOMLEFT", -4, -8)
body:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -30, 16)
end
-- ─────────────────────────────────────── Show/hide ──
local function showState(state)
local F = PlayerGuideFrame
local hasData = (state == "data")
-- StageChain visibility controlled in refresh() based on #stages
showOrHide(F.Header, hasData)
showOrHide(F.BodyScroll, hasData)
showOrHide(F.ObjectivesSection, hasData)
showOrHide(F.RecommendationsSection, hasData and SHOW_RECOMMENDATIONS)
showOrHide(F.EmptyState, state == "empty")
showOrHide(F.LoadingState, state == "loading")
if not hasData then showOrHide(F.StageChain, false) end
end
-- ─────────────────────────────────────── Render ──
local function refresh()
if not PlayerGuideFrame then return end
local state = C_PlayerGuide.GetState()
showState(state)
if state ~= "data" then return end
local id, name, desc, progress = C_PlayerGuide.GetStageInfo()
local stages = C_PlayerGuide.GetStages() or {}
local F = PlayerGuideFrame
if #stages > 0 then
F.Header.Pretitle:SetText(string.format(
MW_PLAYER_GUIDE_STAGE_OF or "ЭТАП %d ИЗ %d", id or 0, #stages))
else
F.Header.Pretitle:SetText(string.format("ЭТАП %d", id or 0))
end
F.Header.Title:SetText(name or "")
F.Header.Description:SetText(desc or "")
F.Header.ProgressLabel:SetText(MW_PLAYER_GUIDE_STAGE_PROGRESS or "ПРОГРЕСС ЭТАПА")
F.Header.ProgressPercent:SetText((progress or 0) .. "%")
F.Header.ProgressBar:SetMinMaxValues(0, 100)
F.Header.ProgressBar:SetValue(progress or 0)
local hasChain = #stages > 0
showOrHide(F.StageChain, hasChain)
applyLayout(F, hasChain)
buildStageChain(stages)
updateBodyContent()
renderObjectives()
renderRecommendations()
updateBodyContent()
end
-- ─────────────────────────────────── XML callbacks ──
function MW_PlayerGuide_OnLoad(self)
EncounterJournal.playerGuideFrame = self
local bodyChild = self.BodyScroll and self.BodyScroll.ScrollChild
if bodyChild then
self.ObjectivesSection:SetParent(bodyChild)
self.RecommendationsSection:SetParent(bodyChild)
end
applyLayout(self, false)
local objScrollBar = getScrollBar(self.ObjectivesSection.ScrollFrame)
if objScrollBar then objScrollBar:Hide() end
local recScrollBar = getScrollBar(self.RecommendationsSection.ScrollFrame)
if recScrollBar then recScrollBar:Hide() end
hideScrollBar(self.BodyScroll)
C_PlayerGuide.RegisterListener(function() refresh() end)
end
function MW_PlayerGuide_OnShow()
if not C_PlayerGuide.HasData() then
C_PlayerGuide.SetLoading()
C_PlayerGuide.RequestRefresh()
end
refresh()
end
function MW_PlayerGuide_RefreshButton_OnClick()
C_PlayerGuide.SetLoading()
C_PlayerGuide.RequestRefresh()
end
function MW_PlayerGuide_Objective_OnClick(self)
local o = self.objectiveData
if not o then return end
if o.type == "dungeon" or o.type == "raid" then
if o.instanceID and o.instanceID > 0 then
if not EncounterJournal:IsShown() then ShowUIPanel(EncounterJournal) end
EJ_ContentTab_SelectAppropriateInstanceTab(o.instanceID)
EncounterJournal_DisplayInstance(o.instanceID)
end
elseif o.type == "boss" then
if o.encounterID and o.encounterID > 0 then
if not EncounterJournal:IsShown() then ShowUIPanel(EncounterJournal) end
EncounterJournal_DisplayEncounter(o.encounterID)
end
elseif o.type == "achievement" then
if o.achievementID then
if AchievementFrame_LoadUI then AchievementFrame_LoadUI() end
if AchievementFrame_SelectAchievement then
AchievementFrame_SelectAchievement(o.achievementID)
end
end
elseif o.type == "item" then
if o.itemID and o.itemID > 0 then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetItemByID(o.itemID)
GameTooltip:Show()
end
elseif o.type == "quest" or o.type == "quest_chain" then
local qid = o.questID or o.questChainID or o.targetID
if qid and qid > 0 then
pcall(SetItemRef, "quest:" .. qid .. ":-1", "", "LeftButton")
end
else
C_PlayerGuide.RequestOpenTarget(o.id)
end
end
function MW_PlayerGuide_Objective_OnEnter(self)
local o = self.objectiveData
if not o then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine(o.title or "", 1, 0.91, 0.63)
if o.subtitle and o.subtitle ~= "" then
GameTooltip:AddLine(o.subtitle, 0.66, 0.59, 0.47, true)
end
if not o.completed then
GameTooltip:AddLine(
string.format("Прогресс: %d / %d", o.progress or 0, o.required or 1),
0.79, 0.65, 0.38)
end
GameTooltip:Show()
end
function MW_PlayerGuide_Objective_OnLeave() GameTooltip:Hide() end
function MW_PlayerGuide_Rec_OnClick(self)
local r = self.recData
if r and r.instanceID and r.instanceID > 0 then
if not EncounterJournal:IsShown() then ShowUIPanel(EncounterJournal) end
EJ_ContentTab_SelectAppropriateInstanceTab(r.instanceID)
EncounterJournal_DisplayInstance(r.instanceID)
end
end
function MW_PlayerGuide_Rec_OnEnter(self)
local r = self.recData
if not r then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine(r.name or r.title or "", 1, 0.91, 0.63)
if r.loc then GameTooltip:AddLine(r.loc, 0.66, 0.59, 0.47) end
if r.level then GameTooltip:AddLine("Уровень: " .. r.level, 0.79, 0.65, 0.38) end
GameTooltip:Show()
end
function MW_PlayerGuide_Rec_OnLeave() GameTooltip:Hide() end
-- ─────────────────────────────── Экспорт для отладки ──
MW_PlayerGuide = {
Refresh = refresh,
InjectDevData = function() C_PlayerGuide._InjectDevPayload() end,
}
@@ -0,0 +1,486 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.blizzard.com/wow/ui/
..\..\FrameXML\UI.xsd">
<Script file="PlayerGuide.lua"/>
<!-- ───────── Шрифты ───────── -->
<Font name="MW_PG_Title" inherits="GameFontNormalHuge" virtual="true">
<Color r="1.0" g="0.84" b="0.42"/>
<Shadow><Color r="0" g="0" b="0"/><Offset><AbsDimension x="1" y="-1"/></Offset></Shadow>
</Font>
<Font name="MW_PG_Section" inherits="QuestFont_Large" virtual="true">
<Color r="1.0" g="0.84" b="0.42"/>
<Shadow><Color r="0" g="0" b="0"/><Offset><AbsDimension x="1" y="-1"/></Offset></Shadow>
</Font>
<Font name="MW_PG_Body" inherits="GameFontHighlight" virtual="true">
<Color r="0.85" g="0.78" b="0.63"/>
</Font>
<Font name="MW_PG_Caps" inherits="GameFontNormalSmall" virtual="true">
<Color r="0.63" g="0.46" b="0.19"/>
</Font>
<Font name="MW_PG_Italic" inherits="QuestFont" virtual="true">
<Color r="0.66" g="0.59" b="0.47"/>
</Font>
<!-- ───────── Карточка цели ───────── -->
<!-- Высота = 60px (DENSITY "regular"). Позиции: TypeLabel+Title строка 1, Subtitle строка 2, бар внизу. -->
<Frame name="MW_PG_ObjectiveCardTemplate" virtual="true">
<Size><AbsDimension x="0" y="70"/></Size>
<Backdrop bgFile="Interface\Buttons\WHITE8X8"
edgeFile="Interface\Buttons\WHITE8X8"
tile="true">
<BackgroundInsets><AbsInset left="0" right="0" top="0" bottom="0"/></BackgroundInsets>
<EdgeSize><AbsValue val="1"/></EdgeSize>
<TileSize><AbsValue val="64"/></TileSize>
</Backdrop>
<Layers>
<Layer level="BACKGROUND">
<!-- Рамка иконки — фоновый слой, центрируется по LEFT -->
<Texture name="$parentIconBorder" parentKey="IconBorder" file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="50" y="50"/></Size>
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="12" y="0"/></Offset></Anchor>
</Anchors>
<TexCoords left="0.2" right="0.8" top="0.2" bottom="0.8"/>
<Color r="1.0" g="0.84" b="0.42" a="0.55"/>
</Texture>
</Layer>
<Layer level="ARTWORK">
<Texture name="$parentIcon" parentKey="Icon" file="Interface\Icons\INV_Misc_QuestionMark">
<Size><AbsDimension x="34" y="34"/></Size>
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="20" y="0"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
<Layer level="OVERLAY">
<!-- Строка 1: TypeLabel (префикс) + Title (основной текст) на одной строке -->
<FontString name="$parentTypeLabel" parentKey="TypeLabel" inherits="MW_PG_Caps" justifyH="LEFT">
<Size><AbsDimension x="90" y="12"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="72" y="-16"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="$parentTitle" parentKey="Title" inherits="GameFontNormal" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="185" y="-16"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-118" y="-16"/></Offset></Anchor>
</Anchors>
<Color r="1.0" g="0.91" b="0.63"/>
</FontString>
<!-- Строка 2: Subtitle (описание/прогресс) -->
<FontString name="$parentSubtitle" parentKey="Subtitle" inherits="MW_PG_Italic" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="72" y="-36"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-118" y="-36"/></Offset></Anchor>
</Anchors>
</FontString>
<!-- Count: мелко снизу справа -->
<FontString name="$parentCount" parentKey="Count" inherits="GameFontNormalSmall" justifyH="RIGHT">
<Size><AbsDimension x="110" y="10"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-18" y="10"/></Offset></Anchor>
</Anchors>
<Color r="0.63" g="0.46" b="0.19"/>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- Прогресс-бар: тонкая полоска у самого низа карточки -->
<StatusBar name="$parentProgressBar" parentKey="ProgressBar">
<Size><AbsDimension x="0" y="4"/></Size>
<Anchors>
<Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="72" y="8"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-118" y="8"/></Offset></Anchor>
</Anchors>
<BarTexture file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-Fill"/>
<BarColor r="1.0" g="0.79" b="0.29"/>
<Layers>
<Layer level="BACKGROUND">
<Texture setAllPoints="true"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-BG"/>
</Layer>
</Layers>
</StatusBar>
</Frames>
<Scripts>
<OnClick function="MW_PlayerGuide_Objective_OnClick"/>
<OnEnter function="MW_PlayerGuide_Objective_OnEnter"/>
<OnLeave function="MW_PlayerGuide_Objective_OnLeave"/>
</Scripts>
</Frame>
<!-- ───────── Карточка рекомендации ───────── -->
<Button name="MW_PG_RecCardTemplate" virtual="true">
<!-- Height 96: fits in the 100px visible area of RecommendationsSection scrollframe -->
<Size><AbsDimension x="176" y="116"/></Size>
<Backdrop bgFile="Interface\Buttons\WHITE8X8"
edgeFile="Interface\Buttons\WHITE8X8"
tile="true">
<BackgroundInsets><AbsInset left="0" right="0" top="0" bottom="0"/></BackgroundInsets>
<EdgeSize><AbsValue val="1"/></EdgeSize>
<TileSize><AbsValue val="64"/></TileSize>
</Backdrop>
<Layers>
<Layer level="ARTWORK">
<!-- Artwork: 138x56, top offset 5 → bottom at y=61 from card top -->
<Texture name="$parentArtwork" parentKey="Artwork" file="Interface\Buttons\WHITE8X8">
<Size><AbsDimension x="168" y="72"/></Size>
<Anchors>
<Anchor point="TOP"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor>
</Anchors>
<Color r="0.55" g="0.16" b="0.07"/>
</Texture>
</Layer>
<Layer level="OVERLAY">
<!-- Level: top-right corner of artwork (artwork starts at y=-5 → level at TOPRIGHT+(-4,-9)) -->
<FontString name="$parentLevel" parentKey="Level" inherits="GameFontNormalSmall" justifyH="RIGHT">
<Size><AbsDimension x="80" y="12"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-9"/></Offset></Anchor>
</Anchors>
<Color r="1.0" g="0.91" b="0.63"/>
</FontString>
<!-- Name: 4px below artwork (artwork bottom y=61, name at y=65) -->
<FontString name="$parentName" parentKey="Name" inherits="GameFontNormal" justifyH="LEFT">
<Size><AbsDimension x="162" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-80"/></Offset></Anchor>
</Anchors>
<Color r="1.0" g="0.91" b="0.63"/>
</FontString>
<!-- Loc: 3px below name (name bottom y=79, loc at y=82) -->
<FontString name="$parentLoc" parentKey="Loc" inherits="MW_PG_Italic" justifyH="LEFT">
<Size><AbsDimension x="162" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-96"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnClick function="MW_PlayerGuide_Rec_OnClick"/>
<OnEnter function="MW_PlayerGuide_Rec_OnEnter"/>
<OnLeave function="MW_PlayerGuide_Rec_OnLeave"/>
</Scripts>
</Button>
<!-- ───────── Бейдж этапа ───────── -->
<Frame name="MW_PG_StageBadgeTemplate" virtual="true">
<Size><AbsDimension x="38" y="38"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentBadgeBG" parentKey="BadgeBG"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\StageBadge-Locked">
<Size><AbsDimension x="60" y="60"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
</Texture>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentRoman" parentKey="Roman" inherits="GameFontNormalLarge" justifyH="CENTER">
<Anchors><Anchor point="CENTER"/></Anchors>
<Color r="1.0" g="0.91" b="0.63"/>
</FontString>
<FontString name="$parentLabel" parentKey="Label" inherits="MW_PG_Caps" justifyH="CENTER">
<Size><AbsDimension x="100" y="10"/></Size>
<Anchors>
<Anchor point="TOP" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-4"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<!-- ───────── Корневой фрейм ───────── -->
<Frame name="PlayerGuideFrame" hidden="true" parent="EncounterJournal">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="EncounterJournalInset" relativePoint="TOPLEFT">
<Offset><AbsDimension x="5" y="-4"/></Offset>
</Anchor>
<Anchor point="BOTTOMRIGHT" relativeTo="EncounterJournalInset" relativePoint="BOTTOMRIGHT">
<Offset><AbsDimension x="-3" y="0"/></Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture parentKey="BackgroundPage"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Background-Page"
setAllPoints="true">
<TexCoords left="0" right="1" top="0" bottom="1"/>
</Texture>
</Layer>
<Layer level="BORDER">
<Texture parentKey="TabDivider"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
<Size><AbsDimension x="0" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="28" y="-52"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-28" y="-52"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- 1) Цепочка этапов — позиция задаётся Lua в OnLoad -->
<Frame name="PlayerGuideStageChain" parentKey="StageChain">
<Size><AbsDimension x="0" y="62"/></Size>
</Frame>
<!-- 2) Заголовок этапа — позиция задаётся Lua в OnLoad -->
<Frame name="PlayerGuideHeader" parentKey="Header">
<Size><AbsDimension x="0" y="82"/></Size>
<Layers>
<Layer level="OVERLAY">
<FontString name="PlayerGuideHeaderPretitle" parentKey="Pretitle" inherits="MW_PG_Caps" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideHeaderTitle" parentKey="Title" inherits="MW_PG_Title" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-18"/></Offset></Anchor>
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideHeaderDesc" parentKey="Description" inherits="MW_PG_Body" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="-48"/></Offset></Anchor>
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideHeaderProgLabel" parentKey="ProgressLabel" inherits="MW_PG_Caps" justifyH="RIGHT">
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideHeaderProgPct" parentKey="ProgressPercent" inherits="MW_PG_Title" justifyH="RIGHT">
<Size><AbsDimension x="240" y="28"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-14"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<StatusBar name="PlayerGuideHeaderProgBar" parentKey="ProgressBar">
<Size><AbsDimension x="240" y="10"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-52"/></Offset></Anchor>
</Anchors>
<BarTexture file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-Fill"/>
<BarColor r="1.0" g="0.79" b="0.29"/>
<Layers>
<Layer level="BACKGROUND">
<Texture setAllPoints="true"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-BG"/>
</Layer>
</Layers>
</StatusBar>
<Button name="PlayerGuideHeaderRefresh" parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
<Size><AbsDimension x="100" y="22"/></Size>
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="-72"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnLoad>
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
self:SetNormalTexture(T.."Button-Up")
self:SetPushedTexture(T.."Button-Down")
self:SetHighlightTexture(T.."Button-Highlight")
</OnLoad>
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
</Scripts>
</Button>
</Frames>
</Frame>
<!-- 3) Задачи этапа — позиция задаётся Lua в OnLoad -->
<ScrollFrame name="PlayerGuideBodyScroll" parentKey="BodyScroll" inherits="UIPanelScrollFrameTemplate">
<ScrollChild>
<Frame parentKey="ScrollChild">
<Size><AbsDimension x="860" y="1"/></Size>
</Frame>
</ScrollChild>
</ScrollFrame>
<Frame name="PlayerGuideObjSection" parentKey="ObjectivesSection">
<Layers>
<Layer level="OVERLAY">
<Texture name="PlayerGuideObjOrnament" parentKey="Ornament"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Corner-Ornament">
<Size><AbsDimension x="20" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
</Anchors>
</Texture>
<FontString name="PlayerGuideObjHeading" parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_OBJECTIVES">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="26" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideObjCount" parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT" relativeTo="PlayerGuideObjHeading" relativePoint="RIGHT">
<Offset><AbsDimension x="6" y="-2"/></Offset>
</Anchor>
</Anchors>
</FontString>
<Texture name="PlayerGuideObjDivider" parentKey="Divider"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
<Size><AbsDimension x="0" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-24"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-24"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<ScrollFrame name="PlayerGuideObjScroll" parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-48"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-8" y="0"/></Offset></Anchor>
</Anchors>
<ScrollChild>
<Frame parentKey="ScrollChild">
<Size><AbsDimension x="600" y="200"/></Size>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
</Frame>
<!-- 4) Подходящие подземелья — позиция задаётся Lua в OnLoad -->
<Frame name="PlayerGuideRecsSection" parentKey="RecommendationsSection">
<Size><AbsDimension x="0" y="150"/></Size>
<Layers>
<Layer level="OVERLAY">
<Texture name="PlayerGuideRecsOrnament" parentKey="Ornament"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Corner-Ornament">
<Size><AbsDimension x="20" y="20"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor>
</Anchors>
</Texture>
<FontString name="PlayerGuideRecsHeading" parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_RECS">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="26" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PlayerGuideRecsCount" parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT" relativeTo="PlayerGuideRecsHeading" relativePoint="RIGHT">
<Offset><AbsDimension x="6" y="-2"/></Offset>
</Anchor>
</Anchors>
</FontString>
<Texture name="PlayerGuideRecsDivider" parentKey="Divider"
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Section-Divider">
<Size><AbsDimension x="0" y="16"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-24"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-4" y="-24"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<ScrollFrame name="PlayerGuideRecScroll" parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-46"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-36" y="0"/></Offset></Anchor>
</Anchors>
<ScrollChild>
<Frame parentKey="ScrollChild">
<Size><AbsDimension x="900" y="96"/></Size>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
</Frame>
<!-- 5) Пустое состояние -->
<Frame name="PlayerGuideEmptyState" parentKey="EmptyState" hidden="true">
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="20"/></Offset></Anchor></Anchors>
<Size><AbsDimension x="460" y="200"/></Size>
<Layers>
<Layer level="OVERLAY">
<FontString name="PlayerGuideEmptyTitle" parentKey="Title" inherits="MW_PG_Title" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_TITLE">
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor></Anchors>
<Size><AbsDimension x="460" y="28"/></Size>
</FontString>
<FontString name="PlayerGuideEmptyBody" parentKey="Body" inherits="MW_PG_Body" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_BODY">
<Anchors>
<Anchor point="TOP" relativeTo="PlayerGuideEmptyTitle" relativePoint="BOTTOM">
<Offset><AbsDimension x="0" y="-10"/></Offset>
</Anchor>
</Anchors>
<Size><AbsDimension x="420" y="60"/></Size>
</FontString>
</Layer>
</Layers>
<Frames>
<Button name="PlayerGuideEmptyRefresh" parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
<Size><AbsDimension x="120" y="24"/></Size>
<Anchors>
<Anchor point="TOP" relativeTo="PlayerGuideEmptyBody" relativePoint="BOTTOM">
<Offset><AbsDimension x="0" y="-14"/></Offset>
</Anchor>
</Anchors>
<Scripts>
<OnLoad>
local T = "Interface\\AddOns\\MoonWellClient\\EncounterJournal\\Textures\\PlayerGuide\\"
self:SetNormalTexture(T.."Button-Up")
self:SetPushedTexture(T.."Button-Down")
self:SetHighlightTexture(T.."Button-Highlight")
</OnLoad>
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
</Scripts>
</Button>
</Frames>
</Frame>
<!-- 6) Загрузка -->
<Frame name="PlayerGuideLoadingState" parentKey="LoadingState" hidden="true">
<Anchors><Anchor point="CENTER"/></Anchors>
<Size><AbsDimension x="300" y="80"/></Size>
<Layers>
<Layer level="OVERLAY">
<FontString name="PlayerGuideLoadingTitle" parentKey="Title" inherits="MW_PG_Section" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_TITLE">
<Anchors><Anchor point="TOP"/></Anchors>
<Size><AbsDimension x="300" y="20"/></Size>
</FontString>
<FontString name="PlayerGuideLoadingBody" parentKey="Body" inherits="MW_PG_Italic" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_BODY">
<Anchors>
<Anchor point="TOP" relativeTo="PlayerGuideLoadingTitle" relativePoint="BOTTOM">
<Offset><AbsDimension x="0" y="-6"/></Offset>
</Anchor>
</Anchors>
<Size><AbsDimension x="300" y="16"/></Size>
</FontString>
</Layer>
</Layers>
</Frame>
</Frames>
<Scripts>
<OnLoad function="MW_PlayerGuide_OnLoad"/>
<OnShow function="MW_PlayerGuide_OnShow"/>
</Scripts>
</Frame>
</Ui>
@@ -355,6 +355,7 @@ EnsureColorMethods(GREEN_FONT_COLOR)
EnsureColorMethods(DISABLED_FONT_COLOR)
LOOTJOURNAL_SOURCE_TOOLTIP_HEAD = LOOTJOURNAL_SOURCE_TOOLTIP_HEAD or "Способ получения:"
RETURN_TO_DEFAULT = RETURN_TO_DEFAULT or "Настройки по умолчанию"
GUIDEBOOK = GUIDEBOOK or "Путеводитель"
INSTANCES = INSTANCES or "Подземелья"
RAIDS = RAIDS or "Рейды"
LOOT_JOURNAL_ITEM_SETS = LOOT_JOURNAL_ITEM_SETS or "Комплекты"
@@ -49,12 +49,14 @@ local function ConfigureTierTab(tab, text)
end
local function PositionVisibleTierTabs(instanceSelect)
local guidebookTab = instanceSelect and instanceSelect.guidebookTab
local dungeonsTab = instanceSelect and instanceSelect.dungeonsTab
local raidsTab = instanceSelect and instanceSelect.raidsTab
if not dungeonsTab or not raidsTab then
return
end
ConfigureTierTab(guidebookTab, GUIDEBOOK or "Guidebook")
ConfigureTierTab(dungeonsTab, INSTANCES or "Instances")
ConfigureTierTab(raidsTab, RAIDS or "Raids")
@@ -96,10 +98,10 @@ local function SelectVisibleInstanceTab(journal)
end
if journal:IsShown()
and instanceSelect:IsShown()
and (not journal.encounter or not journal.encounter:IsShown())
and selectedTab
and EJ_ContentTab_Select
and instanceSelect:IsShown()
and (not journal.encounter or not journal.encounter:IsShown())
and selectedTab
and EJ_ContentTab_Select
then
EJ_ContentTab_Select(selectedTab)
end
@@ -15,10 +15,12 @@ local function MoonWell_ToggleEncounterJournal()
MoonWell_EncounterJournalApplyDungeonMode()
end
if EncounterJournal_OpenJournal then
EncounterJournal_OpenJournal()
else
ShowUIPanel(EncounterJournal)
ShowUIPanel(EncounterJournal)
if EJPlayerGuide_OpenFrame then
EJPlayerGuide_OpenFrame()
elseif EncounterJournal.instanceSelect and EncounterJournal.instanceSelect.guideTab and EJ_ContentTab_Select then
EJ_ContentTab_Select(EncounterJournal.instanceSelect.guideTab:GetID())
end
end
@@ -28,3 +30,40 @@ SLASH_MOONWELLENCOUNTERJOURNAL3 = "/encounterjournal"
SlashCmdList.MOONWELLENCOUNTERJOURNAL = MoonWell_ToggleEncounterJournal
MoonWell_ToggleEncounterJournal = MoonWell_ToggleEncounterJournal
local function MoonWell_EncounterJournalMicroButton_OnEnter(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine("Путеводитель по приключениям", 1, 1, 1)
GameTooltip:Show()
end
local function MoonWell_CreateEncounterJournalMicroButton()
local button = EncounterJournalMicroButton
if not button then
local iconTexture = "Interface\\EncounterJournal\\UI-EJ-PortraitIcon"
button = CreateFrame("Button", "EncounterJournalMicroButton", MainMenuBarArtFrame or UIParent)
button:SetSize(32, 40)
button:SetPoint("BOTTOMLEFT", LFDMicroButton or MainMenuMicroButton or HelpMicroButton or UIParent, "BOTTOMRIGHT", 0, 0)
button:RegisterForClicks("LeftButtonUp")
button:SetNormalTexture(iconTexture)
button:SetPushedTexture(iconTexture)
button:SetDisabledTexture(iconTexture)
button:SetHighlightTexture(iconTexture)
button:SetPushedTextOffset(1, -1)
local highlight = button:GetHighlightTexture()
if highlight then
highlight:SetBlendMode("ADD")
highlight:SetAlpha(0.55)
end
end
button:SetScript("OnClick", MoonWell_ToggleEncounterJournal)
button:SetScript("OnEnter", MoonWell_EncounterJournalMicroButton_OnEnter)
button:SetScript("OnLeave", GameTooltip_Hide)
button:Show()
return button
end
MoonWell_CreateEncounterJournalMicroButton()
@@ -36,3 +36,40 @@ ENCOUNTER_JOURNAL_SEARCH_RESULTS = "Результаты поиска для \"%
ENCOUNTER_JOURNAL_SHOW_MAP = "Показать\nкарту"
ENCOUNTER_JOURNAL_SHOW_SEARCH_RESULTS = "Показать все результаты: %d"
LOOT_JOURNAL_ITEM_SETS = "Комплекты"
ENCOUNTER_JOURNAL_NAVIGATION_HOME = "Главная"
-- Путеводитель — вкладка
MW_PLAYER_GUIDE = "Путеводитель"
MW_PLAYER_GUIDE_REFRESH = "Обновить"
-- Заголовки секций
MW_PLAYER_GUIDE_OBJECTIVES = "Задачи этапа"
MW_PLAYER_GUIDE_RECS = "Подходящие подземелья"
MW_PLAYER_GUIDE_REWARDS = "Награды за этап"
-- Шапка этапа
MW_PLAYER_GUIDE_STAGE_OF = "ЭТАП %d ИЗ %d"
MW_PLAYER_GUIDE_STAGE_PROGRESS = "ПРОГРЕСС ЭТАПА"
-- Пустое состояние
MW_PLAYER_GUIDE_EMPTY_TITLE = "Путеводитель ещё не открыт"
MW_PLAYER_GUIDE_EMPTY_BODY = "Данные о вашем прогрессе ещё не получены с сервера. " ..
"Это может занять несколько секунд после входа в игру."
-- Загрузка
MW_PLAYER_GUIDE_LOADING_TITLE = "ЗАПРАШИВАЕМ ХРОНИКИ"
MW_PLAYER_GUIDE_LOADING_BODY = "Ожидание данных от Путеводителя"
-- Caps-плашки типов целей
MW_PG_TYPE_LEVEL = "УРОВЕНЬ"
MW_PG_TYPE_QUEST = "ЗАДАНИЕ"
MW_PG_TYPE_QUEST_CHAIN = "ЦЕПОЧКА"
MW_PG_TYPE_DUNGEON = "ПОДЗЕМЕЛЬЕ"
MW_PG_TYPE_RAID = "РЕЙД"
MW_PG_TYPE_BOSS = "БОСС"
MW_PG_TYPE_ACHIEVEMENT = "ДОСТИЖЕНИЕ"
MW_PG_TYPE_REPUTATION = "РЕПУТАЦИЯ"
MW_PG_TYPE_PROFESSION = "ПРОФЕССИЯ"
MW_PG_TYPE_ITEM = "ПРЕДМЕТ"
MW_PG_TYPE_CURRENCY = "ВАЛЮТА"
MW_PG_TYPE_CUSTOM = "ЗАДАЧА"
@@ -0,0 +1,419 @@
C_PlayerGuide = C_PlayerGuide or {}
local L = C_PlayerGuide
local PREFIX = "MWPG"
local SEP = "\t"
local currentData = nil
local lastError = nil
local uiState = "loading" -- "data" | "empty" | "loading"
local listeners = {}
local inbox = {} -- [cmd] = { total, got, chunks }
-- ===== minimal JSON decoder =====
local function jsonDecode(s)
if type(s) ~= "string" then return nil end
local pos = 1
local parseValue, parseString, parseNumber, parseObject, parseArray
local function skipWS()
local _, e = s:find("^%s*", pos)
if e then pos = e + 1 end
end
parseString = function()
pos = pos + 1
local result = {}
while pos <= #s do
local c = s:sub(pos, pos)
if c == '"' then
pos = pos + 1
return table.concat(result)
elseif c == '\\' then
pos = pos + 1
local esc = s:sub(pos, pos)
pos = pos + 1
if esc == '"' then result[#result+1] = '"'
elseif esc == '\\' then result[#result+1] = '\\'
elseif esc == '/' then result[#result+1] = '/'
elseif esc == 'n' then result[#result+1] = '\n'
elseif esc == 'r' then result[#result+1] = '\r'
elseif esc == 't' then result[#result+1] = '\t'
elseif esc == 'u' then
local hex = s:sub(pos, pos + 3)
pos = pos + 4
local code = tonumber(hex, 16) or 0
if code < 0x80 then
result[#result+1] = string.char(code)
elseif code < 0x800 then
result[#result+1] = string.char(
0xC0 + math.floor(code / 64),
0x80 + (code % 64))
else
result[#result+1] = string.char(
0xE0 + math.floor(code / 4096),
0x80 + math.floor((code % 4096) / 64),
0x80 + (code % 64))
end
else
result[#result+1] = esc
end
else
result[#result+1] = c
pos = pos + 1
end
end
return table.concat(result)
end
parseNumber = function()
local numStr = s:match("^-?%d+%.?%d*[eE]?[+-]?%d*", pos)
if not numStr then return 0 end
pos = pos + #numStr
return tonumber(numStr) or 0
end
parseArray = function()
pos = pos + 1
local arr = {}
skipWS()
if s:sub(pos, pos) == ']' then pos = pos + 1; return arr end
while pos <= #s do
arr[#arr+1] = parseValue()
skipWS()
local c = s:sub(pos, pos)
if c == ']' then pos = pos + 1; break
elseif c == ',' then pos = pos + 1 end
end
return arr
end
parseObject = function()
pos = pos + 1
local obj = {}
skipWS()
if s:sub(pos, pos) == '}' then pos = pos + 1; return obj end
while pos <= #s do
skipWS()
local key = parseString()
skipWS()
pos = pos + 1
local val = parseValue()
if key ~= nil then obj[key] = val end
skipWS()
local c = s:sub(pos, pos)
if c == '}' then pos = pos + 1; break
elseif c == ',' then pos = pos + 1 end
end
return obj
end
parseValue = function()
skipWS()
local c = s:sub(pos, pos)
if c == '"' then return parseString()
elseif c == '{' then return parseObject()
elseif c == '[' then return parseArray()
elseif c == 't' then pos = pos + 4; return true
elseif c == 'f' then pos = pos + 5; return false
elseif c == 'n' then pos = pos + 4; return nil
else return parseNumber()
end
end
local ok, result = pcall(parseValue)
if ok then return result end
geterrorhandler()("[PlayerGuide] JSON parse error: " .. tostring(result))
return nil
end
-- ===== internal =====
local function notify()
for _, fn in ipairs(listeners) do
pcall(fn, uiState, currentData)
end
end
local function isTable(v) return type(v) == "table" end
local function validate(data)
if not isTable(data) then return false, "payload is not a table" end
if data.stageID and type(data.stageID) ~= "number" then return false, "stageID is not a number" end
if data.progress and type(data.progress) ~= "number" then return false, "progress is not a number" end
if data.objectives and not isTable(data.objectives) then return false, "objectives is not a table" end
if isTable(data.objectives) then
local seen = {}
for i, o in ipairs(data.objectives) do
if not isTable(o) then return false, "objective #" .. i .. " is not a table" end
if o.id and seen[o.id] then return false, "duplicate objective id: " .. tostring(o.id) end
if o.id then seen[o.id] = true end
end
end
return true
end
-- ===== send =====
local function sendCmd(cmd, arg)
local body = cmd
if arg ~= nil then body = body .. SEP .. tostring(arg) end
local player = UnitName("player")
if player then SendAddonMessage(PREFIX, body, "WHISPER", player) end
end
-- ===== receive dispatch =====
local function onAddonMessage(prefix, message, channel)
if prefix ~= PREFIX or channel ~= "WHISPER" then return end
local cmd, seq, total, body =
message:match("^([^\t]+)\t(%d+)\t(%d+)\t(.*)$")
if not cmd then return end
seq = tonumber(seq) or 0
total = tonumber(total) or 0
if seq == 0 or total == 0 then return end
local box = inbox[cmd]
if not box or box.total ~= total then
box = { total = total, got = 0, chunks = {} }
inbox[cmd] = box
end
if box.chunks[seq] == nil then
box.chunks[seq] = body
box.got = box.got + 1
end
if box.got < total then return end
local full = table.concat(box.chunks)
inbox[cmd] = nil
if cmd == "SetData" then
local data = jsonDecode(full)
L.SetData(data)
elseif cmd == "SetStage" then
local stage = jsonDecode(full)
if isTable(stage) and L.HasData() then
currentData.stageID = stage.stageID or currentData.stageID
currentData.stageName = stage.stageName or currentData.stageName
currentData.stageDescription = stage.stageDescription or currentData.stageDescription
currentData.progress = stage.progress or currentData.progress
if stage.stages then currentData.stages = stage.stages end
if stage.objectives then currentData.objectives = stage.objectives end
if stage.recommendations then currentData.recommendations = stage.recommendations end
notify()
end
elseif cmd == "UpdateObjective" then
local obj = jsonDecode(full)
if isTable(obj) and obj.id and L.HasData() then
local objectives = currentData.objectives or {}
for i, existing in ipairs(objectives) do
if existing.id == obj.id then
objectives[i] = obj
break
end
end
notify()
end
elseif cmd == "Notify" then
if DEFAULT_CHAT_FRAME and full ~= "" then
DEFAULT_CHAT_FRAME:AddMessage("|cffffd76a[Путеводитель]|r " .. full)
end
end
end
local _frame = CreateFrame("Frame")
_frame:RegisterEvent("CHAT_MSG_ADDON")
_frame:RegisterEvent("PLAYER_LOGIN")
_frame:SetScript("OnEvent", function(_, event, ...)
if event == "CHAT_MSG_ADDON" then
onAddonMessage(...)
elseif event == "PLAYER_LOGIN" then
L.RequestRefresh()
end
end)
-- ===== public API =====
function L.RegisterListener(fn)
if type(fn) == "function" then table.insert(listeners, fn) end
end
function L.GetState() return uiState end
function L.GetLastError() return lastError end
function L.HasData() return uiState == "data" and currentData ~= nil end
function L.SetData(data)
local ok, err = validate(data)
if not ok then
lastError = err
uiState = "empty"
notify()
return false, err
end
currentData = data
lastError = nil
uiState = "data"
notify()
return true
end
function L.Clear()
currentData = nil
lastError = nil
uiState = "empty"
notify()
end
function L.SetLoading()
if uiState ~= "data" then
uiState = "loading"
notify()
end
end
function L.GetStageInfo()
local d = currentData
if not d then return nil end
return d.stageID, d.stageName, d.stageDescription, d.progress or 0
end
function L.GetStages()
local d = currentData
if not d then return nil end
return d.stages or {}
end
function L.GetNumObjectives()
local d = currentData
if not d or not d.objectives then return 0 end
return #d.objectives
end
function L.GetObjectiveInfo(index)
local d = currentData
if not d or not d.objectives then return nil end
local o = d.objectives[index]
if not o then return nil end
return {
id = o.id,
type = o.type or "custom",
title = o.title or "",
description = o.description or o.subtitle or "",
subtitle = o.subtitle,
completed = o.completed and true or false,
progress = o.progress or 0,
required = o.required or 1,
targetID = o.targetID,
questID = o.questID,
questChainID = o.questChainID,
instanceID = o.instanceID,
encounterID = o.encounterID,
achievementID = o.achievementID,
factionID = o.factionID,
itemID = o.itemID,
currencyID = o.currencyID,
order = o.order or index,
}
end
function L.GetNumRecommendations()
local d = currentData
if not d or not d.recommendations then return 0 end
return #d.recommendations
end
function L.GetRecommendationInfo(index)
local d = currentData
if not d or not d.recommendations then return nil end
local r = d.recommendations[index]
if not r then return nil end
return {
id = r.id,
type = r.type or "dungeon",
title = r.title or r.name or "",
name = r.name or r.title or "",
description = r.description or r.loc or "",
loc = r.loc,
level = r.level,
biome = r.biome,
instanceID = r.instanceID,
encounterID = r.encounterID,
priority = r.priority or 0,
}
end
function L.GetNumRewards()
local d = currentData
if not d or not d.rewards then return 0 end
return #d.rewards
end
function L.GetRewardInfo(index)
local d = currentData
if not d or not d.rewards then return nil end
return d.rewards[index]
end
function L.RequestRefresh()
L.SetLoading()
sendCmd("RequestRefresh")
end
function L.RequestTrackObjective(objectiveID)
sendCmd("RequestTrackObjective", objectiveID)
end
function L.RequestOpenTarget(objectiveID)
sendCmd("RequestOpenTarget", objectiveID)
end
function L._InjectDevPayload()
L.SetData({
version = 1,
stageID = 5,
stageName = "Подготовка к Нордсколу",
stageDescription =
"Завершите ключевые цепочки заданий в Запределье и докажите готовность к походу на север.",
progress = 62,
stages = {
{ id = 1, name = "Начало пути", short = "Начало пути", status = "done" },
{ id = 2, name = "Огненные Недра", short = "Огн. Недра", status = "done" },
{ id = 3, name = "Тёмный портал", short = "Тёмный портал", status = "done" },
{ id = 4, name = "Покорение Запределья", short = "Запределье", status = "done" },
{ id = 5, name = "Подготовка к Нордсколу",short = "К Нордсколу", status = "active" },
{ id = 6, name = "Заря Нордскола", short = "Нордскол", status = "locked" },
{ id = 7, name = "Цитадель Ледяной Короны",short = "Ледяная Корона",status = "locked" },
},
objectives = {
{ id = 1, type = "level", title = "Достигнуть 68 уровня",
subtitle = "Можно отправляться в Нордскол.",
completed = true, progress = 68, required = 68, order = 1 },
{ id = 2, type = "quest_chain", title = "Цепочка «Поиск виверны» в Награнде",
subtitle = "Кисть Шамана · Награнд",
completed = false, progress = 5, required = 8, order = 2 },
{ id = 3, type = "dungeon", title = "Пройти Кузню Крови",
subtitle = "Цитадель Адского Пламени · 61–64",
completed = false, progress = 0, required = 1, order = 3, instanceID = 256 },
{ id = 4, type = "dungeon", title = "Пройти Подземелья Аукенай",
subtitle = "Аукиндон · 6466",
completed = false, progress = 0, required = 1, order = 4, instanceID = 257 },
{ id = 5, type = "reputation", title = "Дружелюбие с Кенарийской экспедицией",
subtitle = "Нейтрально сейчас · Долина Призрачной Луны",
completed = false, progress = 1200, required = 3000, order = 5, factionID = 942 },
},
recommendations = {
{ id = 1, type = "dungeon", name = "Кузня Крови", loc = "Цитадель Адского Пламени", level = "6164", biome = "fire" },
{ id = 2, type = "dungeon", name = "Подземелья Аукенай", loc = "Аукиндон", level = "6466", biome = "arcane" },
{ id = 3, type = "dungeon", name = "Сетеккские залы", loc = "Награнд", level = "6769", biome = "storm" },
{ id = 4, type = "dungeon", name = "Тёмный Лабиринт", loc = "Аукиндон", level = "6870", biome = "shadow" },
{ id = 5, type = "dungeon", name = "Ботаника", loc = "Долина Призрачной Луны", level = "6970", biome = "nature" },
},
rewards = {},
})
end
@@ -23,6 +23,7 @@ EncounterJournal\EncounterJournalTierSnapshot.lua
EncounterJournal\GlobalStrings.lua
EncounterJournal\Utils\C_EncounterJournal.lua
EncounterJournal\Utils\C_AdventureJournal.lua
EncounterJournal\Utils\C_PlayerGuide.lua
EncounterJournal\EncounterJournalInit.lua
EncounterJournal\EncounterJournalFrameMethods.lua
EncounterJournal\EncounterJournalFonts.xml
@@ -35,6 +36,7 @@ EncounterJournal\SharedXML\HybridScrollFrame.xml
EncounterJournal\SharedXML\NavigationBar.xml
EncounterJournal\Custom_EncounterJournal\Custom_EncounterJournal.lua
EncounterJournal\Custom_EncounterJournal\Custom_EncounterJournal.xml
EncounterJournal\Custom_EncounterJournal\PlayerGuide.xml
EncounterJournal\Custom_EncounterJournal\Custom_LootJournalItems.xml
EncounterJournal\Custom_EncounterJournal\Custom_ItemBrowser.xml
EncounterJournal\EncounterJournalScrollFix.lua