Files
moonwell-client/client-export/PlayerGuide.lua

403 lines
15 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--[[----------------------------------------------------------------------------
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,
}