Files
moon-well-ui/vanilla
sindoring c7ff319ccc Fix the pet stat tooltip and the spellbook tab flash
PaperDollStatTooltip upper-cases UnitClass(unit)'s token, and a unit whose
class byte has no ChrClasses row answers two nils (UnitClass, 0x518350):
the server's demons do, so hovering the pet page's stats raised "bad
argument #1 to strupper". Such a unit now gets the stock default line, in
both styles.

LEARNED_SPELL_IN_TAB's glow flashed where 1.12's hidden skill line tabs
stood: it now sits on the book's own tabs, and choosing a tab stops it, as
SpellBookSkillLineTab_OnClick does.

Verified: harness probe (the pet tooltip shows the default strength line,
no error; the tab 2 glow's center matches our tab 2's; choosing tab 2
hides it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:36:06 +04:00
..
2026-10-18 20:28:32 +04:00

MoonWell UI: Forever UI для клиента 1.12.1

Форк mod-forever-ui для MoonWell, то есть для клиента benilla (World of Warcraft 1.12.1). Апстрим написан под 3.3.5a. Здесь его переносят на API 1.12 и поставляют отдельным патчем, без правок исходников клиента.

Что где лежит

  • data/ — апстрим как есть: аддон и экраны входа под 3.3.5, арт, данные сервера. Это эталон для порта и источник арта. Здесь не правим, чтобы git merge upstream/main проходил без конфликтов.
  • vanilla/ — порт под 1.12:
    • Interface/FrameXML/MoonWellUI/ — код, который клиент грузит вместе со стандартным UI: окно выбора стиля и пункт меню;
    • Interface/FrameXML/MoonWellUI/Forever/ — перенесённые файлы Forever UI, в порядке ForeverUI.toc (см. Forever.xml);
    • art-src/ — исходники нашего арта (PNG). Сборка кодирует их в BLP;
    • tools/build.py — сборка источника патча.

Что перенесено

Файл апстрима Как
UIAtlas*.lua, Texts.lua, Texts_enUS.lua, Texts_ruRU.lua как есть: сборка копирует их из data/ и ставит сверху проверку стиля
AtlasUtil.lua целиком: атласы, заливка, nine-slice, панели, вставки, кнопки
Layout.lua позиции и их сохранение (ForeverUIDB через RegisterForSave), /fui reset
PlayerFrame.lua целиком, без транспорта и API угрозы
TargetFrame.lua рамка цели, цель цели, очки серии
TargetFrameAuras.lua баффы и дебаффы цели; в 1.12 у ауры цели нет заклинателя и времени, поэтому все значки одного размера и без таймера
ActionBar.lua главная панель: клиентские кнопки в новой отделке, рамка, стрелки страниц, грифоны/виверны, выезд бонусной панели
MultiBars.lua панели 2–5; левый край считается от ряда, а позиции клиента (UIParent_ManageFramePositions) перебиваются после него
StanceBar.lua, PetBar.lua панели стоек и питомца, без тотемной панели
BottomBar.lua микроменю (кнопки 1.12; «Карта» и «Помощь» убраны, как в camelot), сумки и кольцо ключей, сборка ряда, задержка в подсказке кнопки меню
StatusBars.lua полосы опыта и репутации; потолок уровня 60
CastBar.lua полоса заклинаний на событиях SPELLCAST_* из 1.12; без щита «нельзя прервать» (1.12 этого не сообщает)
Minimap.lua миникарта: кольцо, маска, полоса зоны, отслеживание (значок способности в рамке), почта, масштаб колесом, кольцо день/ночь поверх GameTimeFrame, серверное время в полосе, координаты; константы в таблице C (лимит 32 upvalue в Lua 5.0)
Buffs.lua баффы и дебаффы игрока на GetPlayerBuff/PLAYER_AURAS_CHANGED, сворачивание долгих баффов; без объединения баффов и отмены зачарований оружия (их нет в 1.12)
Tooltips.lua подсказки: nine-slice camelot вместо рамки клиента, заголовок сравнения у ShoppingTooltip1/2 при их показе
Bags.lua окна сумок: рамка, портрет, сетка, поиск, сортировка, раскладка столбцами; GetItemInfo по строке item:, общая сумка по подклассу аукциона; без валют
CharacterFrame.lua лист персонажа 631×484 в вёрстке camelot: рамка, портрет, полоса уровня, модель с вращением мышью, слоты, статы 1.12 группами, сопротивления, колонка боковых вкладок; страницы регистрируются через CharacterSheet.RegisterPage; питомец пока в вёрстке клиента без старого фона
ScrollBar.lua полоса прокрутки camelot, обработчики на this
ReputationTab.lua страница репутации в панелях листа: свои строки по GetFactionInfo (без подзаголовков 1.12), подробности и флажки справа
SkillsTab.lua страница навыков в панелях листа: свои строки по GetSkillLineInfo, ранг и описание выбранного справа
PvPTab.lua страница чести: циферблат ранга camelot на нативных рангах 1.12 (UnitPVPRank, GetPVPRankProgress), статистика пяти периодов справа; без арен и списка полей боя
PetTab.lua страница питомца: строка уровня и семейства, модель питомца с поворотом мышью, значок рациона и полоса опыта слева; преданность и очки обучения в каменной плашке, характеристики, ближний бой, защита и сопротивления справа; без спутников и маунтов (их нет в 1.12)
SpellBook.lua книга заклинаний camelot: разворот на две страницы или одна (кнопка в заголовке), вкладки категорий (общая с иконкой класса, ветки класса, питомец), сетка с заголовком, листание стрелками и колесом, меню «Скрыть пассивные» и «Показывать все ранги»; обычные кнопки вместо secure-контроллера 3.3.5 (в 1.12 CastSpell не защищён), клики как в стоковом SpellButton_OnClick; без групп-выпадашек (в 1.12 нет GetSpellInfo) и без поиска

Compat.lua — наши помощники: HookScript, HookGlobal/HookMethod вместо hooksecurefunc, сила через UnitMana, тексты полос по statusBarText.

Арт. Сборка кладёт в архив только листы, которые нужны перенесённым файлам: она ищет в них имена атласов и пути Interface\ForeverUI\.... Сейчас это 6 листов, около 12 МБ.

Как это работает

Модуль — это один архив, Data/patch-F.MPQ:

  • Interface\FrameXML\FrameXML.toc — стандартный toc игрока плюс строка MoonWellUI\MoonWellUI.xml. Сборка берёт toc из установки, файлов Blizzard в репозитории нет. benilla грузит этот toc из цепочки патчей так же, как 1.12 грузит пропатченный.
  • Interface\FrameXML\MoonWellUI\ — наш код. Он грузится после стандартных файлов и до слоя benilla.
  • Interface\MoonWellUI\ — наш арт (сейчас превью для окна выбора).

Стиль интерфейса хранится в CVar moonwellUIStyle (classic или forever; пусто, пока игрок не выбрал). CVar общий для всего клиента и сохраняется в benilla-config/config.toml.

  • Первый вход в мир. Пока стиль не выбран, после входа в мир открывается окно «Выберите интерфейс»: два превью, радиокнопки, «Готово». Escape его не закрывает, Enter подтверждает.
  • Смена стиля. В меню игры (Esc) под «Макросами» есть пункт «Стиль интерфейса». Он открывает то же окно, уже с «Отменой». Если стиль изменился, UI перезагружается (ReloadUI).
  • Почему не на экране выбора персонажа. В 1.12 у экранов входа (GlueXML) нет GetCVar/SetCVar. Общий для аккаунта выбор там не сохранить без правок клиента.

Сборка

Нужны Python 3.9+, Pillow и mpqx из benilla (cargo build -p benilla-formats --bin mpqx).

python vanilla\tools\build.py --data D:\WoW-1.12.1\Data `
  --mpqx C:\Users\sindo\benilla\target\debug\mpqx.exe

Скрипт пишет build/patch-source/Data/patch-F/. Дальше — обычный деплой MoonWell:

# в C:\Users\sindo\benilla
.\scripts\deploy-moonwell.ps1 -DataPath D:\WoW-1.12.1\Data -IconPath ... -AuthHost ... `
  -PatchSource C:\Users\sindo\moonwell-forever-ui\build\patch-source

-PatchSource принимает один каталог. Если у тебя есть другие свои патчи, положи build/patch-source/Data/patch-F рядом с ними в их Data/.

Порядок патчей. Архивы patch-?.MPQ монтируются по имени, и побеждает последний. Если архив с более поздней буквой (patch-G … patch-Z) тоже содержит Interface\FrameXML\FrameXML.toc, он перекроет наш. Тогда либо дай нашему патчу более позднюю букву (--patch patch-Z), либо добавь нашу строку в тот toc. Русский пакет Data/ruRU монтируется после всех patch-?.MPQ, но FrameXML.toc в нём нет, поэтому наш он не перекрывает.

Проверка без сервера

Харнес benilla грузит стандартный UI с нашим патчем без окна и логина:

# Data с patch-F.MPQ; в харнесе только англ. локаль
WOW_DATA=<Data> target/debug/examples/addon_harness --probe-ui \
  --eval 'return MoonWellUIChoiceFrame:IsVisible()'

Харнес сам шлёт VARIABLES_LOADED, PLAYER_LOGIN и PLAYER_ENTERING_WORLD, поэтому окно выбора уже открыто.

Харнес и capture-режим ничего не сохраняют, и CVar в них всегда пустой. Чтобы проверить стиль Forever, собери тестовый патч с --test-style forever: в нём невыбранный клиент сразу стартует на Forever. Игрокам такой патч не отдаём.

Правила порта

  • API — только 1.12. В обработчиках this, event, arg1…, а не self и .... Lua 5.0: table.getn вместо #, string.find вместо string.match, arg в функциях с ..., math.mod вместо %; не больше 32 upvalue на функцию. Нет hooksecurefunc, secure-шаблонов, SetAtlas из 3.x (у ForeverUI свой AtlasUtil на SetTexCoord).
  • Каждый файл Forever UI начинается с if not MoonWellUI_IsForever() then return end и подключается в Forever/Forever.xml перед Forever.lua. На классическом стиле он ничего не меняет.
  • Файл апстрима, который уже работает на 1.12 (только данные, синтаксис Lua 5.0), не копируем руками, а добавляем в UPSTREAM_AS_IS в build.py: тогда он обновляется вместе с апстримом.
  • Перенесённый файл называется как в апстриме и в первой строке говорит, откуда он и чем отличается.
  • Не переносим то, чего нет в 1.12: транспорт, руны рыцаря смерти, символы, достижения и статистику, LFG, арены, гильдбанк, парикмахерскую, менеджер экипировки, валюты, тотемную панель.
  • Арт берём из data/art/interface/foreverui. Имя атласа или путь к текстуре пишем в перенесённом файле строкой целиком, или хотя бы его начало ("ui-hud-micromenu-" .. набор .. состояние): по этим строкам сборка находит листы. Имя, склеенное из одних переменных, она не увидит.

Синхронизация с апстримом

git fetch upstream
git merge upstream/main   # меняется только data/ и файлы апстрима в корне

Новые правки апстрима в data/addon и data/glue смотрим через git diff и переносим в vanilla/ руками.

Лицензия и арт

Код апстрима — GPL-2 (LICENSE), наш код в vanilla/ — тоже. Арт в data/art взят из современного клиента. Ни код, ни арт не кладём в репозиторий клиента (moonwell-client-core, MIT/Apache, без ассетов): модуль живёт здесь и доезжает до игроков только архивом.