--[[---------------------------------------------------------------------------- PlayerGuide.lua ───────────────────────────────────────────────────────────────────────────── UI-логика вкладки «Путеводитель». Парный файл PlayerGuide.xml. Зависимости: * C_PlayerGuide (Utils/C_PlayerGuide.lua) — данные и API * MW_PLAYER_GUIDE* (Localization/ruRU.lua) — строки * EncounterJournal (Custom_EncounterJournal) — родительский фрейм ------------------------------------------------------------------------------]] -- ────────────────────────────────────────────────────────────────────────── -- Конфигурация фичи local SHOW_REWARDS = false -- блок наград временно отключён local SHOW_RECOMMENDATIONS = true local DENSITY = "compact" -- compact | regular | cozy local ACCENT = { r = 1.0, g = 0.84, b = 0.42 } -- золото local ROMAN = { "I","II","III","IV","V","VI","VII","VIII","IX","X" } -- Биом → цвет «обложки» рекомендации (rgb 0..1). -- В будущем заменить на текстуры Interface\AddOns\MoonWellClient\EncounterJournal\Textures\Biome-*. local BIOME_COLOR = { fire = { 0.83, 0.29, 0.10 }, arcane = { 0.48, 0.31, 0.83 }, storm = { 0.24, 0.56, 0.77 }, shadow = { 0.35, 0.16, 0.54 }, nature = { 0.31, 0.63, 0.29 }, holy = { 0.83, 0.69, 0.24 }, } -- Тип цели → глиф (заменить на иконки из BLP, когда будет атлас). local TYPE_GLYPH = { level = "⚔", quest = "!", quest_chain = "!", dungeon = "⛨", raid = "☠", boss = "☠", achievement = "★", reputation = "◈", profession = "⚒", item = "◆", currency = "✦", custom = "∗", } -- Тип цели → плейсхолдер-иконка из встроенных текстур 3.3.5a. local TYPE_ICON = { level = "Interface\\Icons\\Ability_Warrior_Challange", quest = "Interface\\Icons\\INV_Misc_Note_01", quest_chain = "Interface\\Icons\\INV_Misc_Note_05", dungeon = "Interface\\Icons\\INV_Misc_Key_06", raid = "Interface\\Icons\\INV_Misc_Head_Dragon_01", boss = "Interface\\Icons\\Ability_Warrior_DecisiveStrike", achievement = "Interface\\Icons\\Achievement_GuildPerk_HonorableMention", reputation = "Interface\\Icons\\INV_Scroll_03", profession = "Interface\\Icons\\Trade_Engineering", item = "Interface\\Icons\\INV_Misc_Gem_Diamond_02", currency = "Interface\\Icons\\INV_Misc_Coin_17", custom = "Interface\\Icons\\INV_Misc_QuestionMark", } -- ────────────────────────────────────────────────────────────────────────── -- Хелперы local function pct(cur, req) if not req or req <= 0 then return 0 end return math.floor((cur / req) * 100 + 0.5) end local function setRGB(fs, c) fs:SetTextColor(c.r or c[1], c.g or c[2], c.b or c[3]) end local function showOrHide(frame, show) if not frame then return end if show then frame:Show() else frame:Hide() end 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" if status == "active" then badge.Roman:SetTextColor(1.0, 0.91, 0.63) badge.Label:SetTextColor(1.0, 0.84, 0.42) badge.BadgeBG:SetVertexColor(1.0, 0.84, 0.42, 1) elseif status == "done" then badge.Roman:SetTextColor(0.79, 0.65, 0.38) badge.Label:SetTextColor(0.79, 0.65, 0.38) badge.BadgeBG:SetVertexColor(0.55, 0.40, 0.18, 1) -- Можно заменить римскую цифру на «✓» для завершённых: badge.Roman:SetText("✓") else badge.Roman:SetTextColor(0.40, 0.34, 0.22) badge.Label:SetTextColor(0.40, 0.34, 0.22) badge.BadgeBG:SetVertexColor(0.20, 0.15, 0.08, 1) end end end -- ────────────────────────────────────────────────────────────────────────── -- Карточки целей local objectiveCards = {} local CARD_HEIGHT = { compact = 44, regular = 56, cozy = 72 } local CARD_GAP = { compact = 4, regular = 6, cozy = 8 } local function renderObjectives() local section = PlayerGuideFrame.ObjectivesSection local parent = section.ScrollFrame.ScrollChild local n = C_PlayerGuide.GetNumObjectives() -- Скрыть лишние 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() -- Иконка типа card.Icon:SetTexture(TYPE_ICON[o.type] or TYPE_ICON.custom) card.Icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) -- crop borders Blizz-стиль -- Тип-плашка caps card.TypeLabel:SetText(_G["MW_PG_TYPE_"..string.upper(o.type)] or string.upper(o.type)) -- Заголовок и подзаголовок 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.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) else 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 end parent:SetHeight(math.max(1, n * (h + gap))) 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 = 148 local gap = 8 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() local biome = BIOME_COLOR[r.biome] or BIOME_COLOR.fire card.Artwork:SetVertexColor(biome[1], biome[2], biome[3], 1) card.Name:SetText(r.name or "") card.Loc:SetText(r.loc or "") card.Level:SetText(r.level or "") end parent:SetWidth(math.max(1, n * (cardW + gap))) section.Count:SetText("· "..n) end -- ────────────────────────────────────────────────────────────────────────── -- Главный Refresh local function showState(state) local F = PlayerGuideFrame local hasData = (state == "data") showOrHide(F.StageChain, hasData) showOrHide(F.Header, hasData) showOrHide(F.ObjectivesSection, hasData) showOrHide(F.RecommendationsSection, hasData and SHOW_RECOMMENDATIONS) showOrHide(F.EmptyState, state == "empty") showOrHide(F.LoadingState, state == "loading") end 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 {} PlayerGuideFrame.Header.Pretitle:SetText( string.format(MW_PLAYER_GUIDE_STAGE_OF or "Этап %d из %d", id or 0, #stages)) PlayerGuideFrame.Header.Title:SetText(name or "") PlayerGuideFrame.Header.Description:SetText(desc or "") PlayerGuideFrame.Header.ProgressLabel:SetText(MW_PLAYER_GUIDE_STAGE_PROGRESS or "ПРОГРЕСС ЭТАПА") PlayerGuideFrame.Header.ProgressPercent:SetText((progress or 0) .. "%") PlayerGuideFrame.Header.ProgressBar:SetMinMaxValues(0, 100) PlayerGuideFrame.Header.ProgressBar:SetValue(progress or 0) buildStageChain(stages) renderObjectives() renderRecommendations() end -- ────────────────────────────────────────────────────────────────────────── -- XML script handlers function MW_PlayerGuide_OnLoad(self) -- Подписываемся на изменения C_PlayerGuide C_PlayerGuide.RegisterListener(function() refresh() end) end function MW_PlayerGuide_OnShow(self) -- Если данных ещё нет — попросим сервер if not C_PlayerGuide.HasData() then C_PlayerGuide.SetLoading() C_PlayerGuide.RequestRefresh() end refresh() end function MW_PlayerGuide_RefreshButton_OnClick(self) 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 EncounterJournal_OpenJournal then EncounterJournal_OpenJournal(o.instanceID) end elseif o.type == "boss" then if o.encounterID and EncounterJournal_OpenJournal then EncounterJournal_OpenJournal(o.instanceID, o.encounterID) end elseif o.type == "achievement" and o.achievementID then if AchievementFrame_LoadUI then AchievementFrame_LoadUI() end if AchievementFrame_SelectAchievement then AchievementFrame_SelectAchievement(o.achievementID) end elseif o.type == "item" and o.itemID then -- Запрос подсказки предмета if GameTooltip then GameTooltip:SetOwner(self, "ANCHOR_RIGHT") GameTooltip:SetItemByID(o.itemID) GameTooltip:Show() 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, o.required), 0.79, 0.65, 0.38) end GameTooltip:Show() end function MW_PlayerGuide_Objective_OnLeave(self) GameTooltip:Hide() end function MW_PlayerGuide_Rec_OnClick(self) local r = self.recData if r and r.instanceID and EncounterJournal_OpenJournal then EncounterJournal_OpenJournal(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 "", 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(self) GameTooltip:Hide() end -- ────────────────────────────────────────────────────────────────────────── -- Публичные хелперы для Custom_EncounterJournal.lua (routing вкладки) function PlayerGuide_Show() if PlayerGuideFrame then PlayerGuideFrame:Show() end end function PlayerGuide_Hide() if PlayerGuideFrame then PlayerGuideFrame:Hide() end end -- Глобальный экспорт для отладки MW_PlayerGuide = { Refresh = refresh, Show = PlayerGuide_Show, Hide = PlayerGuide_Hide, InjectDevData = function() C_PlayerGuide._InjectDevPayload() end, }