базовый encounter journal
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user