базовый encounter journal
@@ -0,0 +1,276 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
C_PlayerGuide.lua
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
Клиентское хранилище данных Путеводителя + UI-facing API.
|
||||
Соответствует контракту из player-guide-requirements.md → "Client API".
|
||||
|
||||
Подключается до PlayerGuide.xml/.lua в .toc.
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
C_PlayerGuide = C_PlayerGuide or {}
|
||||
local L = C_PlayerGuide
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Состояние
|
||||
|
||||
local currentData = nil -- последний валидный payload от сервера
|
||||
local lastError = nil -- строка с описанием последней проблемы валидации
|
||||
|
||||
-- Состояние UI: "data" | "empty" | "loading"
|
||||
local uiState = "loading"
|
||||
|
||||
-- Подписчики на обновления (PlayerGuide.lua вешает сюда свой Refresh)
|
||||
local listeners = {}
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Утилиты
|
||||
|
||||
local function notify()
|
||||
for _, fn in ipairs(listeners) do
|
||||
-- pcall чтобы один сломанный listener не уронил остальные
|
||||
pcall(fn, uiState, currentData)
|
||||
end
|
||||
end
|
||||
|
||||
local function isTable(v) return type(v) == "table" end
|
||||
|
||||
-- Валидация payload'а. Возвращает true либо false + строка ошибки.
|
||||
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
|
||||
-- Уникальность id целей
|
||||
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
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Подписки (для PlayerGuide.lua)
|
||||
|
||||
function L.RegisterListener(fn)
|
||||
table.insert(listeners, fn)
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Публичный API (см. требования → Client API)
|
||||
|
||||
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.GetState()
|
||||
return uiState
|
||||
end
|
||||
|
||||
function L.GetLastError()
|
||||
return lastError
|
||||
end
|
||||
|
||||
function L.HasData()
|
||||
return uiState == "data" and currentData ~= nil
|
||||
end
|
||||
|
||||
function L.GetStageInfo()
|
||||
if not L.HasData() then return nil end
|
||||
return currentData.stageID,
|
||||
currentData.stageName,
|
||||
currentData.stageDescription,
|
||||
currentData.progress or 0
|
||||
end
|
||||
|
||||
-- Доп. метод: список всех этапов (для цепочки сверху)
|
||||
function L.GetStages()
|
||||
if not L.HasData() then return nil end
|
||||
return currentData.stages or {} -- сервер опционально шлёт массив этапов
|
||||
end
|
||||
|
||||
function L.GetNumObjectives()
|
||||
if not L.HasData() or not currentData.objectives then return 0 end
|
||||
return #currentData.objectives
|
||||
end
|
||||
|
||||
function L.GetObjectiveInfo(index)
|
||||
if not L.HasData() or not currentData.objectives then return nil end
|
||||
local o = currentData.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, -- наше расширение поля для UI
|
||||
completed = o.completed and true or false,
|
||||
progress = o.progress or 0,
|
||||
required = o.required or 1,
|
||||
targetID = o.targetID,
|
||||
questID = o.questID,
|
||||
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()
|
||||
if not L.HasData() or not currentData.recommendations then return 0 end
|
||||
return #currentData.recommendations
|
||||
end
|
||||
|
||||
function L.GetRecommendationInfo(index)
|
||||
if not L.HasData() or not currentData.recommendations then return nil end
|
||||
local r = currentData.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()
|
||||
if not L.HasData() or not currentData.rewards then return 0 end
|
||||
return #currentData.rewards
|
||||
end
|
||||
|
||||
function L.GetRewardInfo(index)
|
||||
if not L.HasData() or not currentData.rewards then return nil end
|
||||
local r = currentData.rewards[index]
|
||||
if not r then return nil end
|
||||
return r
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Сетевой слой (AIO)
|
||||
-- MoonWellPlayerGuide.lua перехватывает SetData() из serverToClient и вызывает
|
||||
-- L.SetData(payload). RequestRefresh шлёт обратно в обратную сторону.
|
||||
|
||||
function L.RequestRefresh()
|
||||
L.SetLoading()
|
||||
if AIO and AIO.Handle then
|
||||
-- name берётся из serverToClient на сервере. Тут — лёгкая обёртка.
|
||||
AIO.Handle("MoonWellPlayerGuide", "RequestRefresh")
|
||||
else
|
||||
-- Без AIO (например, в режиме разработки) — отдаём заглушку через
|
||||
-- 0.5 секунды, чтобы UI не висел в Loading навечно.
|
||||
if C_Timer and C_Timer.After then
|
||||
C_Timer.After(0.5, function() L.Clear() end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function L.RequestTrackObjective(objectiveID)
|
||||
if AIO and AIO.Handle then
|
||||
AIO.Handle("MoonWellPlayerGuide", "RequestTrackObjective", objectiveID)
|
||||
end
|
||||
end
|
||||
|
||||
function L.RequestOpenTarget(objectiveID)
|
||||
if AIO and AIO.Handle then
|
||||
AIO.Handle("MoonWellPlayerGuide", "RequestOpenTarget", objectiveID)
|
||||
end
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Статика для разработки (видна, пока сервер не прислал реальный payload).
|
||||
-- Можно убрать перед релизом.
|
||||
|
||||
function L._InjectDevPayload()
|
||||
L.SetData({
|
||||
version = 1,
|
||||
stageID = 5,
|
||||
stageName = "Подготовка к Нордсколу",
|
||||
stageDescription =
|
||||
"Завершите ключевые цепочки заданий в Запределье и докажите готовность к походу на север. "..
|
||||
"По прибытии в Нордскол вам понадобится снаряжение героического уровня.",
|
||||
progress = 62,
|
||||
stages = {
|
||||
{ id = 1, name = "Начало пути", short = "Начало пути", era = "1—58", status = "done" },
|
||||
{ id = 2, name = "Огненные Недра", short = "Огненные Недра", era = "60", status = "done" },
|
||||
{ id = 3, name = "Сквозь Тёмный портал", short = "Тёмный портал", era = "58—60", status = "done" },
|
||||
{ id = 4, name = "Покорение Запределья", short = "Запределье", era = "60—68", status = "done" },
|
||||
{ id = 5, name = "Подготовка к Нордсколу", short = "К Нордсколу", era = "68—70", status = "active" },
|
||||
{ id = 6, name = "Заря Нордскола", short = "Нордскол", era = "70—78", status = "locked" },
|
||||
{ id = 7, name = "Цитадель Ледяной Короны",short = "Ледяная Корона", era = "78—80", status = "locked" },
|
||||
},
|
||||
objectives = {
|
||||
{ id = 1, type = "level", title = "Достигнуть 68 уровня",
|
||||
subtitle = "Текущий уровень: 68. Можно отправляться в Нордскол.",
|
||||
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 = "Награнд • рек. уровень 64—66",
|
||||
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 = "61—64", biome = "fire" },
|
||||
{ id = 2, type = "dungeon", name = "Подземелья Аукенай", loc = "Аукиндон", level = "64—66", biome = "arcane" },
|
||||
{ id = 3, type = "dungeon", name = "Сетеккские залы", loc = "Награнд", level = "67—69", biome = "storm" },
|
||||
{ id = 4, type = "dungeon", name = "Темный Лабиринт", loc = "Аукиндон", level = "68—70", biome = "shadow" },
|
||||
{ id = 5, type = "dungeon", name = "Ботаника", loc = "Долина Призрачной Луны", level = "69—70", biome = "nature" },
|
||||
{ id = 6, type = "dungeon", name = "Расколотые Залы", loc = "Цитадель Адского Пламени", level = "70 H", biome = "fire" },
|
||||
},
|
||||
rewards = {}, -- блок наград временно скрыт (см. SHOW_REWARDS в PlayerGuide.lua)
|
||||
})
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
AIO/MoonWellPlayerGuide.lua
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
AIO-handler (клиентская часть) для namespace "MoonWellPlayerGuide".
|
||||
Сервер отправляет SetData(payload), клиент кладёт его в C_PlayerGuide.
|
||||
Клиент шлёт RequestRefresh обратно при нажатии «Обновить».
|
||||
|
||||
Контракт описан в player-guide-requirements.md → "AIO Contract".
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
local AIO = AIO or require("AIO")
|
||||
if not AIO then return end -- безопасный no-op без AIO
|
||||
|
||||
local handlers = AIO.AddHandlers("MoonWellPlayerGuide", {})
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Server → Client
|
||||
|
||||
-- Полная замена payload
|
||||
function handlers.SetData(player, payload)
|
||||
if C_PlayerGuide and C_PlayerGuide.SetData then
|
||||
local ok, err = C_PlayerGuide.SetData(payload)
|
||||
if not ok and DEFAULT_CHAT_FRAME then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(
|
||||
"|cffff8080[Путеводитель]|r Ошибка payload: "..tostring(err))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Обновление одной цели (для частичных update'ов)
|
||||
function handlers.UpdateObjective(player, objective)
|
||||
if not (C_PlayerGuide and C_PlayerGuide.HasData()) then return end
|
||||
-- Простая стратегия: пересобрать payload локально с заменой цели по id.
|
||||
-- C_PlayerGuide хранит данные internally — даём прямой доступ через _patch:
|
||||
local n = C_PlayerGuide.GetNumObjectives()
|
||||
for i = 1, n do
|
||||
local o = C_PlayerGuide.GetObjectiveInfo(i)
|
||||
if o and o.id == objective.id then
|
||||
-- Грязный, но рабочий способ для MVP — отдать обратно весь массив.
|
||||
-- Для прод-уровня лучше выставить C_PlayerGuide.PatchObjective(o).
|
||||
o.progress = objective.progress or o.progress
|
||||
o.required = objective.required or o.required
|
||||
o.completed = objective.completed
|
||||
if MW_PlayerGuide and MW_PlayerGuide.Refresh then
|
||||
MW_PlayerGuide.Refresh()
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Замена только инфы об этапе (без массива objectives)
|
||||
function handlers.SetStage(player, stagePayload)
|
||||
if not stagePayload then return end
|
||||
-- Объединяем с текущим payload'ом
|
||||
local _, name, desc, progress = C_PlayerGuide.GetStageInfo()
|
||||
C_PlayerGuide.SetData({
|
||||
stageID = stagePayload.stageID,
|
||||
stageName = stagePayload.stageName or name,
|
||||
stageDescription = stagePayload.stageDescription or desc,
|
||||
progress = stagePayload.progress or progress,
|
||||
stages = stagePayload.stages,
|
||||
objectives = stagePayload.objectives or {},
|
||||
recommendations = stagePayload.recommendations or {},
|
||||
rewards = stagePayload.rewards or {},
|
||||
})
|
||||
end
|
||||
|
||||
-- Сообщения от сервера (баблы / chat-фрейм)
|
||||
function handlers.Notify(player, message)
|
||||
if DEFAULT_CHAT_FRAME and message then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffd76a[Путеводитель]|r "..tostring(message))
|
||||
end
|
||||
end
|
||||
|
||||
-- ──────────────────────────────────────────────────────────────────────────
|
||||
-- Client → Server: ничего регистрировать не нужно — это просто AIO.Handle
|
||||
-- внутри C_PlayerGuide.RequestRefresh / RequestTrackObjective / RequestOpenTarget.
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
<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">
|
||||
|
||||
<!-- ============================================================
|
||||
PlayerGuide.xml — вкладка «Путеводитель» в Encounter Journal
|
||||
Канон WotLK: тёмно-синий фон, золотые акценты, Friz Quadrata.
|
||||
Подключается ПОСЛЕ Custom_EncounterJournal.xml в .toc, чтобы
|
||||
PlayerGuideFrame мог анкориться к EncounterJournal как parent.
|
||||
============================================================ -->
|
||||
|
||||
<!-- ───────── Шрифты (Friz Quadrata из клиента 3.3.5a) ───────── -->
|
||||
|
||||
<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>
|
||||
|
||||
<!-- ───────── Backdrop'ы ───────── -->
|
||||
|
||||
<!-- Карточка цели (полупрозрачная пергамент-плашка) -->
|
||||
<Frame name="MW_PG_ObjectiveCardTemplate" virtual="true">
|
||||
<Size><AbsDimension x="0" y="44"/></Size>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background"
|
||||
edgeFile="Interface\Tooltips\UI-Tooltip-Border"
|
||||
tile="true">
|
||||
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<EdgeSize><AbsValue val="12"/></EdgeSize>
|
||||
<Color r="0.22" g="0.15" b="0.07" a="0.65"/>
|
||||
<BorderColor r="0.55" g="0.40" b="0.18" a="1.0"/>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<!-- Иконка типа цели (32×32, золотая рамка применяется отдельной текстурой) -->
|
||||
<Texture parentKey="Icon" file="Interface\Icons\INV_Misc_QuestionMark">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT"><Offset><AbsDimension x="8" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
<Texture parentKey="IconBorder" file="Interface\Buttons\UI-Quickslot2">
|
||||
<Size><AbsDimension x="42" y="42"/></Size>
|
||||
<Anchors><Anchor point="CENTER" relativeKey="$parent.Icon"/></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="OVERLAY">
|
||||
<FontString parentKey="TypeLabel" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Size><AbsDimension x="100" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Icon" relativePoint="TOPRIGHT">
|
||||
<Offset><AbsDimension x="10" y="-1"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Title" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.TypeLabel" relativePoint="TOPRIGHT">
|
||||
<Offset><AbsDimension x="6" y="0"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-90" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString parentKey="Subtitle" inherits="MW_PG_Italic" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.TypeLabel" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-90" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Count" inherits="GameFontNormalSmall" justifyH="RIGHT">
|
||||
<Size><AbsDimension x="80" y="12"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-10" y="-6"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="0.79" g="0.65" b="0.38"/>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
|
||||
<Frames>
|
||||
<StatusBar parentKey="ProgressBar">
|
||||
<Size><AbsDimension x="0" y="6"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeKey="$parent.Icon" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="10" y="-12"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-10" y="-12"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||||
<BarColor r="1.0" g="0.79" b="0.29"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true" file="Interface\Buttons\WHITE8X8">
|
||||
<Color r="0.05" g="0.04" b="0.02" a="0.85"/>
|
||||
</Texture>
|
||||
</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">
|
||||
<Size><AbsDimension x="148" y="106"/></Size>
|
||||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background"
|
||||
edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||||
<BackgroundInsets><AbsInset left="3" right="3" top="3" bottom="3"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="16"/></TileSize>
|
||||
<EdgeSize><AbsValue val="12"/></EdgeSize>
|
||||
<Color r="0.10" g="0.07" b="0.03" a="0.85"/>
|
||||
<BorderColor r="0.55" g="0.40" b="0.18" a="1.0"/>
|
||||
</Backdrop>
|
||||
<Layers>
|
||||
<Layer level="ARTWORK">
|
||||
<!-- Биом-плашка (плейсхолдер: цветной градиент) -->
|
||||
<Texture parentKey="Artwork" file="Interface\Buttons\WHITE8X8">
|
||||
<Size><AbsDimension x="138" y="58"/></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">
|
||||
<FontString parentKey="Level" inherits="GameFontNormalSmall" justifyH="CENTER">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.Artwork" relativePoint="TOPRIGHT">
|
||||
<Offset><AbsDimension x="-4" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString parentKey="Name" inherits="GameFontNormal" justifyH="LEFT">
|
||||
<Size><AbsDimension x="130" y="12"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Artwork" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="2" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString parentKey="Loc" inherits="MW_PG_Italic" justifyH="LEFT">
|
||||
<Size><AbsDimension x="130" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Name" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-2"/></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 parentKey="BadgeBG" file="Interface\Buttons\UI-Quickslot2">
|
||||
<Size><AbsDimension x="60" y="60"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<TexCoords left="0.2" right="0.8" top="0.2" bottom="0.8"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Roman" inherits="GameFontNormalLarge" justifyH="CENTER">
|
||||
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
|
||||
<Color r="1.0" g="0.91" b="0.63"/>
|
||||
</FontString>
|
||||
<FontString 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>
|
||||
|
||||
<!-- ───────── Корневой фрейм ───────── -->
|
||||
<!-- Парент: EncounterJournal.encounter (см. PlayerGuide.lua → Init) -->
|
||||
|
||||
<Frame name="PlayerGuideFrame" hidden="true" parent="EncounterJournal">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="22" y="-58"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-6" y="6"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
|
||||
<Layers>
|
||||
<!-- «Волна» сверху, как в оригинальном EJ — добавляется через
|
||||
родительский фон. Здесь — тонкая золотая внутренняя рамка. -->
|
||||
<Layer level="BORDER">
|
||||
<Texture parentKey="TopGlow" file="Interface\Common\UI-TooltipDivider-Transparent">
|
||||
<Size><AbsDimension x="0" y="16"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-58"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="0" y="-58"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Color r="1.0" g="0.84" b="0.42" a="0.35"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
|
||||
<Frames>
|
||||
|
||||
<!-- 1) Цепочка этапов сверху -->
|
||||
<Frame parentKey="StageChain">
|
||||
<Size><AbsDimension x="0" y="62"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="8" y="-4"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-8" y="-4"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<!-- Бейджи создаются программно в Lua (PlayerGuide_BuildStageChain) -->
|
||||
</Frame>
|
||||
|
||||
<!-- 2) Заголовок текущего этапа -->
|
||||
<Frame parentKey="Header">
|
||||
<Size><AbsDimension x="0" y="78"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.StageChain" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="12" y="-12"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.StageChain" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="-12" y="-12"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Pretitle" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Title" inherits="MW_PG_Title" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Pretitle" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Description" inherits="MW_PG_Body" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Title" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="2" y="-6"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-260" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
|
||||
<FontString parentKey="ProgressLabel" inherits="MW_PG_Caps" justifyH="RIGHT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="ProgressPercent" inherits="MW_PG_Title" justifyH="RIGHT">
|
||||
<Size><AbsDimension x="240" y="28"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.ProgressLabel" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="0" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<StatusBar parentKey="ProgressBar">
|
||||
<Size><AbsDimension x="240" y="10"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.ProgressPercent" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="0" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
|
||||
<BarColor r="1.0" g="0.79" b="0.29"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true" file="Interface\Buttons\WHITE8X8">
|
||||
<Color r="0.05" g="0.04" b="0.02" a="0.85"/>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</StatusBar>
|
||||
|
||||
<Button parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
|
||||
<Size><AbsDimension x="100" y="22"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.ProgressBar" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="0" y="-6"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 3) Секция «Задачи этапа» — заголовок + список карточек -->
|
||||
<Frame parentKey="ObjectivesSection">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeKey="$parent.Header" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="-2" y="-10"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="TOPRIGHT" relativeKey="$parent.Header" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="2" y="-10"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="142"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_OBJECTIVES">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeKey="$parent.Heading" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="8" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<Texture parentKey="Divider" file="Interface\Common\UI-TooltipDivider-Transparent">
|
||||
<Size><AbsDimension x="0" y="2"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeKey="$parent.Heading" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<ScrollFrame parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="-26"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-26" y="4"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<ScrollChild>
|
||||
<Frame parentKey="ScrollChild">
|
||||
<Size><AbsDimension x="600" y="200"/></Size>
|
||||
<!-- Карточки целей вставляются программно через ObjectiveCardTemplate -->
|
||||
</Frame>
|
||||
</ScrollChild>
|
||||
</ScrollFrame>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 4) Секция «Подходящие подземелья» — горизонтальная карусель -->
|
||||
<Frame parentKey="RecommendationsSection">
|
||||
<Size><AbsDimension x="0" y="132"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="6" y="6"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-6" y="6"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString parentKey="Heading" inherits="MW_PG_Section" justifyH="LEFT" text="MW_PLAYER_GUIDE_RECS">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<FontString parentKey="Count" inherits="MW_PG_Caps" justifyH="LEFT">
|
||||
<Anchors>
|
||||
<Anchor point="LEFT" relativeKey="$parent.Heading" relativePoint="RIGHT">
|
||||
<Offset><AbsDimension x="8" y="-2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
</FontString>
|
||||
<Texture parentKey="Divider" file="Interface\Common\UI-TooltipDivider-Transparent">
|
||||
<Size><AbsDimension x="0" y="2"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeKey="$parent.Heading" relativePoint="BOTTOMLEFT">
|
||||
<Offset><AbsDimension x="0" y="-4"/></Offset>
|
||||
</Anchor>
|
||||
<Anchor point="RIGHT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
</Texture>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<ScrollFrame parentKey="ScrollFrame" inherits="UIPanelScrollFrameTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="4" y="-26"/></Offset></Anchor>
|
||||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-26" y="4"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<ScrollChild>
|
||||
<Frame parentKey="ScrollChild">
|
||||
<Size><AbsDimension x="900" y="110"/></Size>
|
||||
</Frame>
|
||||
</ScrollChild>
|
||||
</ScrollFrame>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 5) Пустое состояние -->
|
||||
<Frame 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 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 parentKey="Body" inherits="MW_PG_Body" justifyH="CENTER" text="MW_PLAYER_GUIDE_EMPTY_BODY">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeKey="$parent.Title" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="-10"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Size><AbsDimension x="420" y="60"/></Size>
|
||||
</FontString>
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<Button parentKey="RefreshButton" inherits="UIPanelButtonTemplate2" text="MW_PLAYER_GUIDE_REFRESH">
|
||||
<Size><AbsDimension x="120" y="24"/></Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeKey="$parent.Body" relativePoint="BOTTOM">
|
||||
<Offset><AbsDimension x="0" y="-14"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="MW_PlayerGuide_RefreshButton_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
</Frames>
|
||||
</Frame>
|
||||
|
||||
<!-- 6) Состояние загрузки -->
|
||||
<Frame parentKey="LoadingState" hidden="true">
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
<Size><AbsDimension x="300" y="80"/></Size>
|
||||
<Layers>
|
||||
<Layer level="OVERLAY">
|
||||
<FontString 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 parentKey="Body" inherits="MW_PG_Italic" justifyH="CENTER" text="MW_PLAYER_GUIDE_LOADING_BODY">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeKey="$parent.Title" 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>
|
||||
@@ -0,0 +1,64 @@
|
||||
# PlayerGuide — клиентский пакет (3.3.5a / MoonWellClient)
|
||||
|
||||
Это материалы для интеграции вкладки **«Путеводитель»** в существующий
|
||||
ретропортированный Encounter Journal. Реализация соответствует требованиям
|
||||
из `uploads/player-guide-requirements.md` и опирается на канон-визуал
|
||||
WotLK (Friz Quadrata, тёмно-синий фон EJ, золотые акценты).
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
MoonWellClient/
|
||||
├── EncounterJournal/
|
||||
│ ├── Utils/
|
||||
│ │ └── C_PlayerGuide.lua ← хранилище + API
|
||||
│ ├── Custom_EncounterJournal/
|
||||
│ │ ├── Custom_EncounterJournal.xml ← патч: новая вкладка
|
||||
│ │ ├── Custom_EncounterJournal.lua ← патч: routing вкладки
|
||||
│ │ ├── PlayerGuide.xml ← фреймы и шаблоны (НОВОЕ)
|
||||
│ │ └── PlayerGuide.lua ← рендер и логика (НОВОЕ)
|
||||
│ └── Localization/
|
||||
│ └── ruRU.lua ← патч: MW_PLAYER_GUIDE и др.
|
||||
├── AIO/
|
||||
│ └── MoonWellPlayerGuide.lua ← AIO-handler (клиентская часть)
|
||||
└── MoonWellClient.toc ← патч: подключение файлов
|
||||
```
|
||||
|
||||
## Порядок интеграции
|
||||
|
||||
1. **Скопируйте новые файлы** в проект:
|
||||
* `client-export/PlayerGuide.xml` → `EncounterJournal/Custom_EncounterJournal/PlayerGuide.xml`
|
||||
* `client-export/PlayerGuide.lua` → `EncounterJournal/Custom_EncounterJournal/PlayerGuide.lua`
|
||||
* `client-export/C_PlayerGuide.lua` → `EncounterJournal/Utils/C_PlayerGuide.lua`
|
||||
* `client-export/MoonWellPlayerGuide.lua` → `AIO/MoonWellPlayerGuide.lua`
|
||||
2. **Примените патчи** из `client-export/patches/` к существующим файлам
|
||||
(см. ниже — `Custom_EncounterJournal.xml`, `Custom_EncounterJournal.lua`,
|
||||
`MoonWellClient.toc`, `Localization/ruRU.lua`).
|
||||
3. **Перезагрузите интерфейс** (`/reload`). Если AIO ещё не передал
|
||||
payload — увидите «пустое» состояние с кнопкой «Обновить».
|
||||
|
||||
## Что вошло в MVP (по требованиям)
|
||||
|
||||
| MVP-пункт | Где |
|
||||
| ----------------------------- | ------------------------------------------- |
|
||||
| вкладка `Путеводитель` | `Custom_EncounterJournal.xml` патч |
|
||||
| пустой `PlayerGuideFrame` | `PlayerGuide.xml` |
|
||||
| `C_PlayerGuide.lua` + статика | `C_PlayerGuide.lua` |
|
||||
| заголовок этапа + цели | `PlayerGuide.lua → PlayerGuide_Refresh` |
|
||||
| tab switching / hide-show | `Custom_EncounterJournal.lua → EJ_ContentTab_OnClick` хук |
|
||||
| AIO `SetData` | `MoonWellPlayerGuide.lua → handlers.SetData` |
|
||||
| Обновить (RequestRefresh) | `PlayerGuide.lua → PlayerGuide_RefreshButton_OnClick` |
|
||||
|
||||
## Что отложено (см. «Later UI» в требованиях)
|
||||
|
||||
* Фильтры (все / активные / завершённые)
|
||||
* Pinning отслеживаемой цели
|
||||
* **Награды** — блок временно отключён (`SHOW_REWARDS = false`)
|
||||
* Серверные уведомления (`Notify`)
|
||||
|
||||
## Сноска по визуалу
|
||||
|
||||
Текстуры в XML — это встроенные ресурсы 3.3.5a-клиента. Если у вас
|
||||
есть атлас MoonWell — замените пути в блоке `-- TEXTURE_ATLAS` на свои.
|
||||
Биом-обложки рекомендаций (`Interface\AddOns\MoonWellClient\EncounterJournal\Textures\Biome-*`)
|
||||
надо создать отдельно — это плейсхолдеры (см. таблицу в `PlayerGuide.lua`).
|
||||
@@ -0,0 +1,120 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
patches/Custom_EncounterJournal.lua.patch
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
Патч-сниппеты для существующего Custom_EncounterJournal.lua. Эти куски
|
||||
добавляют routing новой вкладки и hide-show поведение по требованиям:
|
||||
|
||||
«при выборе guide tab:
|
||||
- hide instance cards
|
||||
- hide Encounter Journal detail panel
|
||||
- hide suggested content panel
|
||||
- hide loot journal panel
|
||||
- hide or disable the expansion/tier dropdown unless explicitly needed
|
||||
- show PlayerGuideFrame»
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
-- ─── ПАТЧ #1 ───────────────────────────────────────────────────────────────
|
||||
-- НАЙТИ функцию EJ_ContentTab_Select (или ту, что переключает вкладки в
|
||||
-- Encounter Journal). Обычно она выглядит как switch по id вкладки.
|
||||
-- ДОБАВИТЬ ветку для нашей вкладки. Пример полностью переписанной функции:
|
||||
|
||||
local PLAYERGUIDE_TAB_ID = 1 -- должен совпадать с id="1" в XML патче
|
||||
|
||||
function EJ_ContentTab_Select(id)
|
||||
EncounterJournal.suggestTab:UnlockHighlight()
|
||||
EncounterJournal.dungeonsTab:UnlockHighlight()
|
||||
EncounterJournal.raidsTab:UnlockHighlight()
|
||||
if EncounterJournal.lootJournalTab then
|
||||
EncounterJournal.lootJournalTab:UnlockHighlight()
|
||||
end
|
||||
if EncounterJournal.guideTab then
|
||||
EncounterJournal.guideTab:UnlockHighlight()
|
||||
end
|
||||
|
||||
-- Прячем «обычные» панели EJ. Этот хелпер уже есть в Custom_EncounterJournal:
|
||||
-- EncounterJournal_HideAllPanels()
|
||||
-- Если его нет — выньте тело сюда (Hide instance list, encounter, loot).
|
||||
EncounterJournal_HideAllPanels()
|
||||
|
||||
-- Прячем PlayerGuideFrame — он показывается только на guide-вкладке.
|
||||
if PlayerGuideFrame then PlayerGuideFrame:Hide() end
|
||||
|
||||
if id == PLAYERGUIDE_TAB_ID then
|
||||
-- НАША ВКЛАДКА -----------------------------------------------------
|
||||
EncounterJournal.guideTab:LockHighlight()
|
||||
EncounterJournal.instanceSelect:Hide()
|
||||
EncounterJournal.encounter:Hide()
|
||||
if EncounterJournalSuggestFrame then EncounterJournalSuggestFrame:Hide() end
|
||||
if LootJournal then LootJournal:Hide() end
|
||||
-- Скрыть/задисейблить дропдаун расширений, как просят требования:
|
||||
if EJTierDropDown then EJTierDropDown:Hide() end
|
||||
|
||||
if PlayerGuideFrame then PlayerGuideFrame:Show() end
|
||||
return
|
||||
end
|
||||
|
||||
-- ── Остальные вкладки — оригинальная логика ──
|
||||
if EJTierDropDown then EJTierDropDown:Show() end
|
||||
|
||||
if id == 2 then -- SuggestTab (было 1)
|
||||
EncounterJournal.suggestTab:LockHighlight()
|
||||
EncounterJournal_ListInstances() -- или ваш Suggest-Show
|
||||
elseif id == 3 then -- DungeonTab
|
||||
EncounterJournal.dungeonsTab:LockHighlight()
|
||||
EJ_HideNonInstancePanels()
|
||||
EncounterJournal_ListInstances()
|
||||
elseif id == 4 then -- RaidTab
|
||||
EncounterJournal.raidsTab:LockHighlight()
|
||||
EJ_HideNonInstancePanels()
|
||||
EncounterJournal_ListInstances()
|
||||
elseif id == 5 then -- LootJournalTab
|
||||
if EncounterJournal.lootJournalTab then
|
||||
EncounterJournal.lootJournalTab:LockHighlight()
|
||||
end
|
||||
if LootJournal_Show then LootJournal_Show() end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- ─── ПАТЧ #2 ───────────────────────────────────────────────────────────────
|
||||
-- В EJ_ContentTab_OnClick добавить вызов нашего Select для tab id=1
|
||||
-- (если у вас один централизованный _OnClick — он уже передаёт id и
|
||||
-- ничего дополнительно делать не нужно):
|
||||
|
||||
function EJ_ContentTab_OnClick(self)
|
||||
EJ_ContentTab_Select(self:GetID())
|
||||
PlaySound("igCharacterInfoTab")
|
||||
end
|
||||
|
||||
|
||||
-- ─── ПАТЧ #3 ───────────────────────────────────────────────────────────────
|
||||
-- Защита: при открытии EJ через EncounterJournal_OpenJournal — переключиться
|
||||
-- НА dungeon/raid таб, а не оставлять guide-таб выбранным.
|
||||
-- Найдите вашу EncounterJournal_OpenJournal и убедитесь, что в начале:
|
||||
|
||||
local _orig_EncounterJournal_OpenJournal = EncounterJournal_OpenJournal
|
||||
function EncounterJournal_OpenJournal(instanceID, encounterID, ...)
|
||||
-- При прямом открытии конкретного instance — переключиться на Dungeon/Raid
|
||||
if instanceID then
|
||||
EJ_ContentTab_Select(3) -- Dungeon по умолчанию; ваша логика может выбрать Raid
|
||||
end
|
||||
return _orig_EncounterJournal_OpenJournal(instanceID, encounterID, ...)
|
||||
end
|
||||
|
||||
|
||||
-- ─── ПАТЧ #4 ───────────────────────────────────────────────────────────────
|
||||
-- Init: статика для разработки (можно убрать перед релизом). Если AIO
|
||||
-- ещё не успел прислать payload — покажем dev-payload через 1с после
|
||||
-- первого открытия EJ.
|
||||
|
||||
local _devInjected = false
|
||||
EncounterJournal:HookScript("OnShow", function()
|
||||
if not _devInjected and not C_PlayerGuide.HasData() then
|
||||
_devInjected = true
|
||||
C_Timer.After(1.0, function()
|
||||
if not C_PlayerGuide.HasData() then
|
||||
C_PlayerGuide._InjectDevPayload()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,57 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
patches/Custom_EncounterJournal.xml.patch
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
Это НЕ замена файла — это патч-сниппеты для существующего
|
||||
Custom_EncounterJournal.xml. Вставьте обозначенные блоки.
|
||||
|
||||
В оригинальном Custom_EncounterJournal.xml внутри EncounterJournal.bg или
|
||||
аналогичного контейнера уже есть вкладки SuggestTab / DungeonTab / RaidTab /
|
||||
LootJournalTab. Нужно вставить GuideTab ПЕРЕД ними (id=1) и сдвинуть
|
||||
анкоры остальных.
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
-- ─── ПАТЧ #1 ───────────────────────────────────────────────────────────────
|
||||
-- НАЙТИ блок вкладки SuggestTab (или то, что у вас сейчас называется
|
||||
-- «$parentSuggestTab» / «$parentDungeonTab») и ДОБАВИТЬ перед ним
|
||||
-- следующую кнопку. id=1, чтобы EJ_ContentTab_OnClick знал об этой вкладке.
|
||||
|
||||
<Button name="$parentGuideTab"
|
||||
inherits="EncounterTierTabTemplate"
|
||||
text="MW_PLAYER_GUIDE"
|
||||
parentKey="guideTab"
|
||||
id="1">
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parent" relativePoint="TOPLEFT">
|
||||
<Offset><AbsDimension x="40" y="2"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnClick function="EJ_ContentTab_OnClick"/>
|
||||
</Scripts>
|
||||
</Button>
|
||||
|
||||
|
||||
-- ─── ПАТЧ #2 ───────────────────────────────────────────────────────────────
|
||||
-- ИЗМЕНИТЬ id существующих вкладок (теперь Путеводитель = 1):
|
||||
--
|
||||
-- SuggestTab id="2" (было 1)
|
||||
-- DungeonTab id="3" (было 2)
|
||||
-- RaidTab id="4" (было 3)
|
||||
-- LootJournalTab id="5" (было 4)
|
||||
--
|
||||
-- И СДВИНУТЬ анкор первой следующей вкладки (SuggestTab), чтобы она
|
||||
-- цеплялась за GuideTab:
|
||||
|
||||
<Anchors>
|
||||
<Anchor point="BOTTOMLEFT" relativeTo="$parentGuideTab" relativePoint="BOTTOMRIGHT">
|
||||
<Offset><AbsDimension x="-15" y="0"/></Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
|
||||
|
||||
-- ─── ПАТЧ #3 ───────────────────────────────────────────────────────────────
|
||||
-- В конце <Ui>... </Ui>, ПЕРЕД закрывающим тегом, добавить include
|
||||
-- для нашего PlayerGuide.xml, чтобы PlayerGuideFrame создавался ВНУТРИ
|
||||
-- родителя EncounterJournal:
|
||||
|
||||
<Include file="PlayerGuide.xml"/>
|
||||
@@ -0,0 +1,43 @@
|
||||
--[[----------------------------------------------------------------------------
|
||||
patches/Localization-ruRU.lua.patch
|
||||
─────────────────────────────────────────────────────────────────────────────
|
||||
Строки локализации для вкладки «Путеводитель».
|
||||
Вставьте этот блок в ваш существующий Localization/ruRU.lua (или
|
||||
ruRU.lua в корне), либо подключите как отдельный файл и добавьте в .toc.
|
||||
------------------------------------------------------------------------------]]
|
||||
|
||||
-- Имя вкладки
|
||||
MW_PLAYER_GUIDE = "Путеводитель"
|
||||
|
||||
-- Заголовки секций
|
||||
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_REFRESH = "Обновить"
|
||||
|
||||
-- Пустое состояние
|
||||
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,28 @@
|
||||
# patches/MoonWellClient.toc.patch
|
||||
#
|
||||
# Сниппет для добавления в существующий MoonWellClient.toc. ВАЖНО: порядок
|
||||
# подключения файлов критичен — C_PlayerGuide.lua должен идти ДО PlayerGuide.lua,
|
||||
# а PlayerGuide.xml — ПОСЛЕ Custom_EncounterJournal.xml (PlayerGuideFrame
|
||||
# анкорится к EncounterJournal как parent).
|
||||
#
|
||||
# Вставьте строки ниже в соответствующие места вашего .toc.
|
||||
|
||||
# ─── Локализация ────────────────────────────────────────────────────────────
|
||||
# В блок Localization, рядом с остальными ruRU.lua:
|
||||
Localization\ruRU.lua
|
||||
|
||||
# ─── Хранилище и API ────────────────────────────────────────────────────────
|
||||
# В блок EncounterJournal\Utils\, до подключения чего-либо из
|
||||
# Custom_EncounterJournal\:
|
||||
EncounterJournal\Utils\C_PlayerGuide.lua
|
||||
|
||||
# ─── Сам Encounter Journal (ОБНОВЛЁННЫЙ ПОРЯДОК) ────────────────────────────
|
||||
# Эти строки заменяют ваш существующий блок Custom_EncounterJournal:
|
||||
EncounterJournal\Custom_EncounterJournal\Custom_EncounterJournal.xml
|
||||
EncounterJournal\Custom_EncounterJournal\PlayerGuide.xml
|
||||
EncounterJournal\Custom_EncounterJournal\PlayerGuide.lua
|
||||
EncounterJournal\Custom_EncounterJournal\Custom_EncounterJournal.lua
|
||||
|
||||
# ─── AIO-handler ────────────────────────────────────────────────────────────
|
||||
# Должен подключаться ПОСЛЕ C_PlayerGuide.lua и AIO/AIO_Client.lua:
|
||||
AIO\MoonWellPlayerGuide.lua
|
||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1015 B |
|
After Width: | Height: | Size: 1015 B |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 394 B |
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1,125 @@
|
||||
# PlayerGuide — текстуры
|
||||
|
||||
Все файлы — **PNG с альфа-каналом**, размеры — **степень двойки**
|
||||
(требование BLP-конвертера). Палитра канон-WotLK: золото (`#ffd76a`)
|
||||
на тёмно-коричневом (`#1a1208`).
|
||||
|
||||
## Что есть
|
||||
|
||||
| Файл | Размер | Куда |
|
||||
|---|---|---|
|
||||
| `StageBadge-Active.png` | 64×64 | Бейдж текущего этапа в цепочке. |
|
||||
| `StageBadge-Done.png` | 64×64 | Бейдж завершённого этапа. |
|
||||
| `StageBadge-Locked.png` | 64×64 | Бейдж недоступного этапа. |
|
||||
| `ProgressBar-Fill.png` | 128×16 | Заливка прогресс-бара (`BarTexture`). |
|
||||
| `ProgressBar-BG.png` | 128×16 | Подложка прогресс-бара. |
|
||||
| `Card-Background.png` | 256×256 | Тайл-фон карточек (`bgFile`, tileable). |
|
||||
| `Card-Border.png` | 256×32 | Рамка карточки (`edgeFile`, 8 сегментов 32×32: up/down/left/right/TL/TR/BL/BR). |
|
||||
| `Section-Divider.png` | 256×16 | Декоративный разделитель секций. |
|
||||
| `Corner-Ornament.png` | 64×64 | Угол декоративной рамки фрейма. |
|
||||
| `Top-Wave-Glow.png` | 512×64 | «Волна» свечения сверху (как в оригинальном EJ). |
|
||||
| `Biome-Fire.png` | 256×128 | Обложка рекомендации — огненные локации. |
|
||||
| `Biome-Arcane.png` | 256×128 | Тайная магия. |
|
||||
| `Biome-Storm.png` | 256×128 | Буря/вода. |
|
||||
| `Biome-Shadow.png` | 256×128 | Тьма. |
|
||||
| `Biome-Nature.png` | 256×128 | Природа. |
|
||||
| `Biome-Holy.png` | 256×128 | Свет/нежить. |
|
||||
| `TypeIcon-Level.png` | 64×64 | Иконка цели «уровень». |
|
||||
| `TypeIcon-Quest.png` | 64×64 | «задание». |
|
||||
| `TypeIcon-QuestChain.png` | 64×64 | «цепочка». |
|
||||
| `TypeIcon-Dungeon.png` | 64×64 | «подземелье». |
|
||||
| `TypeIcon-Raid.png` | 64×64 | «рейд». |
|
||||
| `TypeIcon-Boss.png` | 64×64 | «босс». |
|
||||
| `TypeIcon-Achievement.png` | 64×64 | «достижение». |
|
||||
| `TypeIcon-Reputation.png` | 64×64 | «репутация». |
|
||||
| `TypeIcon-Profession.png` | 64×64 | «профессия». |
|
||||
| `TypeIcon-Item.png` | 64×64 | «предмет». |
|
||||
| `TypeIcon-Currency.png` | 64×64 | «валюта». |
|
||||
| `TypeIcon-Custom.png` | 64×64 | прочее. |
|
||||
| `TypeIcon-Done.png` | 64×64 | Цель завершена (зелёная галочка). |
|
||||
| `TypeIcon-Locked.png` | 64×64 | Цель заблокирована (замок). |
|
||||
| `Button-Up.png` | 128×32 | Кнопка «Обновить», нормальное. |
|
||||
| `Button-Down.png` | 128×32 | Кнопка нажата. |
|
||||
| `Button-Highlight.png` | 128×32 | Hover. |
|
||||
|
||||
## Куда положить в проекте
|
||||
|
||||
```
|
||||
MoonWellClient/EncounterJournal/Textures/PlayerGuide/
|
||||
├── StageBadge-Active.blp
|
||||
├── StageBadge-Done.blp
|
||||
├── ...
|
||||
└── Button-Highlight.blp
|
||||
```
|
||||
|
||||
И в XML использовать пути:
|
||||
```
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\StageBadge-Active"
|
||||
```
|
||||
(WoW сам подставит `.blp`.)
|
||||
|
||||
## Конвертация PNG → BLP
|
||||
|
||||
Любой из:
|
||||
- **BLPNG Converter** (Windows, GUI) — самый простой, drag-and-drop;
|
||||
- **BLPLab** — с предпросмотром;
|
||||
- **CLI**: `blpconverter input.png output.blp` (есть пайплайны на GitHub).
|
||||
|
||||
Параметры:
|
||||
- **Compression:** `DXT5` для всего с альфой (всё, кроме `Card-Background` —
|
||||
для него `DXT1` или `Uncompressed`, если хочется лучше);
|
||||
- **Mipmaps:** включить (`Generate Mipmaps`), кроме `ProgressBar-Fill/BG`
|
||||
(тонкие полоски ломаются на мипмапах — там лучше off);
|
||||
- **Sampling:** `Bicubic` или `Lanczos`.
|
||||
|
||||
## Замена биом-обложек
|
||||
|
||||
Сейчас биом-обложки — стилизованные плашки с аркой и светящимся центром.
|
||||
Их можно полностью заменить вашими собственными — главное, оставить размер
|
||||
**256×128** и имена файлов. Если поменяете список биомов, поправьте таблицу
|
||||
`BIOME_COLOR` в `PlayerGuide.lua`.
|
||||
|
||||
## Замена иконок типов целей
|
||||
|
||||
Если у вас есть атлас иконок WoW (например, из `Interface/Icons/`) — карта
|
||||
`TYPE_ICON` в `PlayerGuide.lua` уже использует встроенные текстуры
|
||||
клиента. Текстуры `TypeIcon-*` нужны, только если хочется единого
|
||||
визуального словаря (рамка + глиф).
|
||||
|
||||
## Применение в XML (примеры)
|
||||
|
||||
```xml
|
||||
<!-- Бейдж в цепочке этапов: меняем встроенный UI-Quickslot2 на наш -->
|
||||
<Texture parentKey="BadgeBG"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\StageBadge-Active">
|
||||
<Size><AbsDimension x="44" y="44"/></Size>
|
||||
<Anchors><Anchor point="CENTER"/></Anchors>
|
||||
</Texture>
|
||||
|
||||
<!-- Прогресс-бар: BarTexture указывает на Fill, BG ставится в Layer BACKGROUND -->
|
||||
<StatusBar parentKey="ProgressBar">
|
||||
<BarTexture file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-Fill"/>
|
||||
<Layers>
|
||||
<Layer level="BACKGROUND">
|
||||
<Texture setAllPoints="true"
|
||||
file="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\ProgressBar-BG"/>
|
||||
</Layer>
|
||||
</Layers>
|
||||
</StatusBar>
|
||||
|
||||
<!-- Карточка цели: bgFile = Card-Background, edgeFile = Card-Border -->
|
||||
<Backdrop bgFile="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Card-Background"
|
||||
edgeFile="Interface\AddOns\MoonWellClient\EncounterJournal\Textures\PlayerGuide\Card-Border"
|
||||
tile="true">
|
||||
<BackgroundInsets><AbsInset left="4" right="4" top="4" bottom="4"/></BackgroundInsets>
|
||||
<TileSize><AbsValue val="64"/></TileSize>
|
||||
<EdgeSize><AbsValue val="16"/></EdgeSize>
|
||||
</Backdrop>
|
||||
```
|
||||
|
||||
## Сноска
|
||||
|
||||
Это плейсхолдеры на уровне «лучше, чем встроенные UI-Quickslot2». Если в
|
||||
проекте есть художник — финальные текстуры стоит делать в этом же
|
||||
визуальном словаре, чтобы Lua/XML менять не пришлось (только перерисовать
|
||||
PNG того же размера и сконвертировать).
|
||||
|
After Width: | Height: | Size: 578 B |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,263 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>PlayerGuide — Texture Sheet</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=EB+Garamond:ital,wght@0,400;0,500;1,400&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; min-height: 100%; }
|
||||
body {
|
||||
font-family: 'EB Garamond', Georgia, serif;
|
||||
background:
|
||||
radial-gradient(ellipse at 50% 0%, #1c2c48 0%, transparent 60%),
|
||||
radial-gradient(ellipse at 100% 100%, #0d1422 0%, transparent 70%),
|
||||
linear-gradient(180deg, #0a1018 0%, #050810 100%);
|
||||
color: #d8c8a0;
|
||||
padding: 60px 50px 80px;
|
||||
}
|
||||
h1 {
|
||||
font-family: 'Cinzel', serif; font-weight: 700;
|
||||
font-size: 36px; margin: 0 0 6px;
|
||||
color: #ffd76a;
|
||||
text-shadow: 0 0 14px rgba(197,151,46,0.5), 0 2px 0 #000;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.sub {
|
||||
font-style: italic; color: #a89878;
|
||||
margin: 0 0 36px; font-size: 16px;
|
||||
}
|
||||
.group {
|
||||
margin: 0 0 44px;
|
||||
}
|
||||
.group-title {
|
||||
font-family: 'Cinzel', serif; font-weight: 700;
|
||||
font-size: 13px; letter-spacing: 0.18em; text-transform: uppercase;
|
||||
color: #ffd76a; text-shadow: 0 1px 0 #000;
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
.group-title::before {
|
||||
content: ''; width: 3px; height: 14px;
|
||||
background: #ffd76a; box-shadow: 0 0 6px #ffd76a88;
|
||||
}
|
||||
.group-title::after {
|
||||
content: ''; flex: 1; height: 1px;
|
||||
background: linear-gradient(90deg, rgba(138,106,38,0.5), transparent);
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
.item {
|
||||
background: linear-gradient(180deg, rgba(28,44,72,0.4), rgba(10,16,24,0.4));
|
||||
border: 1px solid rgba(255,215,106,0.18);
|
||||
border-radius: 3px;
|
||||
padding: 12px 12px 10px;
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
}
|
||||
.preview {
|
||||
width: 100%; aspect-ratio: 1 / 1;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background:
|
||||
linear-gradient(45deg, #0d0905 25%, transparent 25%),
|
||||
linear-gradient(-45deg, #0d0905 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, #0d0905 75%),
|
||||
linear-gradient(-45deg, transparent 75%, #0d0905 75%);
|
||||
background-size: 16px 16px;
|
||||
background-position: 0 0, 0 8px, 8px -8px, -8px 0px;
|
||||
background-color: #18120a;
|
||||
border: 1px solid rgba(125,90,42,0.4);
|
||||
margin-bottom: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.preview.wide { aspect-ratio: 2 / 1; }
|
||||
.preview.bar { aspect-ratio: 4 / 1; }
|
||||
.preview img {
|
||||
max-width: 100%; max-height: 100%;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
.name {
|
||||
font-family: 'Cinzel', serif; font-weight: 600;
|
||||
font-size: 11px; color: #ffe9a0;
|
||||
text-shadow: 0 1px 0 #000;
|
||||
letter-spacing: 0.04em; text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.size {
|
||||
font-size: 10px; color: #a89878;
|
||||
font-style: italic; margin-top: 3px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/* Showcase row */
|
||||
.showcase {
|
||||
margin: 24px 0 40px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.showcase-card {
|
||||
background: linear-gradient(180deg, rgba(60,40,18,0.35), rgba(25,15,8,0.55));
|
||||
border: 1px solid #5a4220;
|
||||
box-shadow: inset 0 1px 0 rgba(255,200,100,0.18), 0 4px 12px rgba(0,0,0,0.5);
|
||||
border-radius: 2px;
|
||||
padding: 18px;
|
||||
display: flex; gap: 14px; align-items: stretch;
|
||||
}
|
||||
.showcase-card h3 {
|
||||
margin: 0 0 4px;
|
||||
font-family: 'Cinzel', serif; font-weight: 700;
|
||||
font-size: 14px; color: #ffe9a0; text-shadow: 0 1px 0 #000;
|
||||
}
|
||||
.showcase-card p {
|
||||
margin: 0; font-style: italic; font-size: 12px; color: #a89878;
|
||||
}
|
||||
.progress-demo {
|
||||
margin-top: 10px;
|
||||
height: 14px; width: 100%; position: relative;
|
||||
background: url('Card-Background.png');
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>PlayerGuide — лист текстур</h1>
|
||||
<p class="sub">33 PNG-файла, готовы к конвертации в BLP. Шахматный фон под превью = прозрачность.</p>
|
||||
|
||||
<!-- Demo: показываем как они стыкуются вместе -->
|
||||
<div class="group">
|
||||
<div class="group-title">Демонстрация (как это смотрится в карточке)</div>
|
||||
<div class="showcase">
|
||||
<div class="showcase-card">
|
||||
<img src="TypeIcon-Dungeon.png" width="44" height="44" alt="" />
|
||||
<div style="flex:1;">
|
||||
<h3>Пройти Кузню Крови</h3>
|
||||
<p>Цитадель Адского Пламени • рек. уровень 61—64</p>
|
||||
<div style="margin-top:8px; display:flex; gap:8px; align-items:center;">
|
||||
<div style="flex:1; height:8px; background: url('ProgressBar-BG.png'); position:relative;">
|
||||
<div style="width:35%; height:100%; background: url('ProgressBar-Fill.png');"></div>
|
||||
</div>
|
||||
<span style="font-family:Cinzel,serif; font-size:11px; color:#caa760;">0 / 1</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="showcase-card" style="background: url('Card-Background.png'); border-color: #5a4220;">
|
||||
<img src="StageBadge-Active.png" width="48" height="48" alt="" />
|
||||
<div style="flex:1;">
|
||||
<h3 style="color:#ffd76a;">Подготовка к Нордсколу</h3>
|
||||
<p>Этап V из VII · 62% завершено</p>
|
||||
<div style="margin-top:10px; height:14px; position:relative; background: url('ProgressBar-BG.png');">
|
||||
<div style="width:62%; height:100%; background: url('ProgressBar-Fill.png');"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Группы текстур -->
|
||||
<div class="group">
|
||||
<div class="group-title">Бейджи этапов</div>
|
||||
<div class="grid" id="stage"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Прогресс-бар</div>
|
||||
<div class="grid" id="progress"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Карточка</div>
|
||||
<div class="grid" id="card"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Декор</div>
|
||||
<div class="grid" id="decor"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Обложки рекомендаций (биомы)</div>
|
||||
<div class="grid" id="biome"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Иконки типов целей</div>
|
||||
<div class="grid" id="types"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Иконки статусов</div>
|
||||
<div class="grid" id="status"></div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<div class="group-title">Кнопки</div>
|
||||
<div class="grid" id="buttons"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const groups = {
|
||||
stage: [
|
||||
['StageBadge-Active', '64×64'],
|
||||
['StageBadge-Done', '64×64'],
|
||||
['StageBadge-Locked', '64×64'],
|
||||
],
|
||||
progress: [
|
||||
['ProgressBar-Fill', '128×16', 'bar'],
|
||||
['ProgressBar-BG', '128×16', 'bar'],
|
||||
],
|
||||
card: [
|
||||
['Card-Background', '256×256'],
|
||||
['Card-Border', '256×32', 'bar'],
|
||||
],
|
||||
decor: [
|
||||
['Section-Divider', '256×16', 'bar'],
|
||||
['Corner-Ornament', '64×64'],
|
||||
['Top-Wave-Glow', '512×64', 'bar'],
|
||||
],
|
||||
biome: [
|
||||
['Biome-Fire', '256×128', 'wide'],
|
||||
['Biome-Arcane', '256×128', 'wide'],
|
||||
['Biome-Storm', '256×128', 'wide'],
|
||||
['Biome-Shadow', '256×128', 'wide'],
|
||||
['Biome-Nature', '256×128', 'wide'],
|
||||
['Biome-Holy', '256×128', 'wide'],
|
||||
],
|
||||
types: [
|
||||
['TypeIcon-Level', '64×64'],
|
||||
['TypeIcon-Quest', '64×64'],
|
||||
['TypeIcon-QuestChain', '64×64'],
|
||||
['TypeIcon-Dungeon', '64×64'],
|
||||
['TypeIcon-Raid', '64×64'],
|
||||
['TypeIcon-Boss', '64×64'],
|
||||
['TypeIcon-Achievement', '64×64'],
|
||||
['TypeIcon-Reputation', '64×64'],
|
||||
['TypeIcon-Profession', '64×64'],
|
||||
['TypeIcon-Item', '64×64'],
|
||||
['TypeIcon-Currency', '64×64'],
|
||||
['TypeIcon-Custom', '64×64'],
|
||||
],
|
||||
status: [
|
||||
['TypeIcon-Done', '64×64'],
|
||||
['TypeIcon-Locked', '64×64'],
|
||||
],
|
||||
buttons: [
|
||||
['Button-Up', '128×32', 'bar'],
|
||||
['Button-Down', '128×32', 'bar'],
|
||||
['Button-Highlight', '128×32', 'bar'],
|
||||
],
|
||||
};
|
||||
|
||||
for (const [groupId, items] of Object.entries(groups)) {
|
||||
const el = document.getElementById(groupId);
|
||||
for (const [name, size, shape] of items) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'item';
|
||||
item.innerHTML = `
|
||||
<div class="preview ${shape || ''}">
|
||||
<img src="${name}.png" alt="${name}" />
|
||||
</div>
|
||||
<div class="name">${name}</div>
|
||||
<div class="size">${size}</div>
|
||||
`;
|
||||
el.appendChild(item);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||