Все фиксы аддитивные, обратимые и размещены на пустых/кастомных
ID-бандах, поэтому не конфликтуют со стоковой установкой TrinityCore. Файлы —
в ../../sql/.
Файл: sql/01_quest_earthen_intro_fix.sql
Симптом: свежий персонаж Earthen (союзная раса) завершает первый квест интро «Пробуждение» на Isle of Dorn, после чего ни один NPC ничего не даёт — интро линейное и фазовое, игрок оказывается заперт.
Причина: цепочка
79200 «Who am I?» → 79201 «The Analysis Interface» → 83328 «The Analysis Interface».
Квест 79201 — устаревший дубликат (то же название, что у 83328) без
дающего и без принимающего — его нельзя ни взять, ни сдать. Но 83328 (бригадир
Uzjax) требует завершения 79201 (PrevQuestID = 79201), поэтому цепочка встаёт
намертво.
Фикс: обходим фантом → цепочка идёт 79200 → 83328 напрямую.
Применяется вживую через reload quest_template (без рестарта).
Класс бага: реальный, но без дающего/принимающего квест, назначенный в
PrevQuestID другого квеста, жёстко блокирует цепочку. Внимание: значения-
маркеры PrevQuestID (999999, 99999, …) — это не этот баг: таких ID не
существует, движок пропускает проверку. Блокируют только пререквизиты, которые
существуют, но недостижимы.
Файл: sql/02_quest_gameobject_unblock.sql
+28 gameobject_template и +89 спавнов gameobject (банда guid 8500000+) для
объектов, нужных квестам, но отсутствовавших в базе — разблокирует ~10 квестов.
DisplayID взяты аутентично; координаты сконвертированы из map% в мировые через
UiMapAssignment для билда 68275, Z/ориентация — от ближайшего существующего
спавна на той же карте. Нужен рестарт worldserver.
Файл: sql/03_raid_instance_bindings.sql
Привязывает 5 рейдов — Dragon Soul (967), End Time (938), Hour of Twilight (940), Well of Eternity (939), Throne of the Four Winds (754) — к их скомпилированному скрипту инстанса. Это включает лок-аут, завершение журнала DungeonEncounter и персистентность состояния боссов (трекинг/сброс).
Ограничение: эти скрипты инстансов — заглушки; они не добавляют боевой
ИИ боссов (нет boss_*.cpp). Боссы остаются на дефолтном ИИ. Нужен рестарт.
Файл: sql/04_harandar_graveyards.sql
12 world_safe_locs + 12 связок graveyard_zone + 12 Духов-целителей (банда
guid существ 11000773+) для Harandar (карта 2694), одной из новейших зон,
чтобы смерть/воскрешение там работали. Взято как самодостаточный аддитивный
файл. Нужен рестарт.
Файл: sql/05_world_events_realign.sql
Симптом: к 2026 году годовые праздники начинаются на 2-4 дня раньше, а Ярмарка Новолуния промахивается примерно на две недели.
Причина: TrinityCore хранит праздник как якорную дату плюс фиксированный
occurence в минутах. Стоковая TDB заякорена в 2022-2023, а 525600 минут —
это ровно 365 дней, без високосной четверти, поэтому даже праздники с
фиксированной датой сползают примерно на день каждые четыре года. Хуже того,
всё, что реальный календарь задаёт правилом (Ярмарка Новолуния — первое
воскресенье месяца, Noblegarden следует за Пасхой, Лунный фестиваль — за
лунным новым годом), от этого правила уходит полностью.
Исправление: переякорить каждый праздник на его ближайшее настоящее
наступление, чтобы и последующие годы попадали верно. Файл делает бэкап таблицы
перед изменением. Нужен рестарт — game_event читается при старте.
Важно при проверке: событие активно, когда
MOD(минут с начала, occurence) < length, а не когда «сейчас» попадает между
start_time и end_time. Эти две колонки ограничивают всю повторяющуюся серию,
а не одно наступление.
Файл: sql/06_remove_stray_spawns.sql
Симптом: рейдовые боссы в стартовых зонах, ивентовый реквизит у ворот Штормграда без привязанного ивента и собственные тестовые существа Blizzard, разгуливающие по миру.
Причина: артефакт снифленных данных — клиент увидел существо где-то во время скриптовой сцены, а захват записал его как постоянного жителя.
Почему это важно: босс 90-го уровня у ворот Штормграда — это мгновенная смерть для всех, кто качается мимо. Боты-компаньоны из этого репозитория гибли именно об это.
Исправление: удаляет 30 таких спавнов. Файл сначала делает бэкап каждой удаляемой строки, поэтому откат — один INSERT. Нужен рестарт.
Как они были найдены: такие легаси-записи часто дают 404 во внешних базах, потому что их просто нет в актуальных данных retail. Более надёжный признак — соседи: окружение спавна выдаёт его настоящий дом.
Файл: sql/07_npc_frozen_patrols.sql
Симптом: страж или бродячий моб не двигается вообще. Это не проблема навигации — существо даже не начинает движение.
Причина: creature.MovementType = 2 означает «идти по маршруту», а id
маршрута берётся из creature_addon.PathId. Если у спавна нет строки addon, либо
PathId = 0, либо PathId ссылается на несуществующий waypoint_path, то
WaypointMovementGenerator::DoInitialize() не может загрузить путь и возвращает
false. Генератор движения не инициализируется, и существо стоит всё время, пока
загружен грид, каждый раз записывая в лог couldn't load path for ....
Масштаб: 3 924 из 10 993 waypoint-спавнов — примерно треть всех
патрулирующих NPC в мире. Аномалия именно в строке спавна, а не в шаблоне: у
3 773 из них creature_template.MovementType равен 0 (idle).
Исправление: привести спавн в соответствие с реальностью —
wander_distance > 0 становится случайным движением в этом радиусе, иначе idle.
Ровно так TrinityCore сам чинит другие противоречивые сочетания
MovementType/wander_distance при загрузке. Настоящие маршруты восстановить
нельзя — данных о них в базе никогда не было. Нужен рестарт.
Файл: sql/08_npc_missing_models.sql
Симптом: в логе повторяется Creature (Entry: N) has no model defined ...
can't load. Существа нет в мире — его нельзя увидеть, взять в цель или убить.
Причина: существу нужна хотя бы одна строка в creature_template_model. У
889 заспавненных записей (1 908 спавнов) её не было.
Почему это важно: большинство из них — невидимые служебные NPC: счётчики kill-credit и «зайцы» для целей квестов. Они и должны быть невидимыми, но существовать обязаны: цель квеста, считающая убийства credit-NPC, никогда не выполнится, если этот NPC не может заспавниться.
Исправление: выдать каждой заспавненной записи без модели канонический
невидимый displayId 11686 — ту же модель, что используют собственные невидимые
сталкеры TrinityCore. Затрагиваются только реально заспавненные записи. Нужен
рестарт.
Файл: sql/09_npc_broken_spawns.sql
Четыре дефекта, у каждого свой бэкап:
creature_template — движок их пропускает; шаблон по строке спавна не
восстановить, поэтому они удаляются (261).vehicle_template_accessory; тот механизм не читает таблицу creature, так что
пассажир не пострадает.GetMapHeight() ищет примерно
в 50 ярдах и притягивает существо к земле, поэтому достаточно близкая оценка
сама себя исправляет. Удаляются только те, у кого рядом меньше 3 соседей, чтобы
вычислить высоту (86).Нужен рестарт.
Файл: sql/10_npc_orphan_references.sql
Строки creature_addon, указывающие на несуществующий маршрут (387), строки
addon для уже удалённого спавна (26) и формации, у которых пропал лидер или
участник (у 237 строк пропал лидер, у 355 — участник, а уникальных строк
386, потому что у части пропали оба). Формация с отсутствующим лидером никогда не собирается,
поэтому те, кто должен идти за ним строем, стоят каждый на своей точке спавна.
Запускать после 07 и 09 — 09 удаляет сломанные спавны, превращая их строки addon в сирот, которых затем собирает этот файл. Нужен рестарт.
Файл: sql/hotfixes/01_map_difficulty_unlock.sql
Применяется к базе hotfixes, а не к world.
Симптом: восемь карт полностью пусты в игре, а в логе тысячи раз повторяется
Table \creature` has creature (GUID: N) that is not spawned in any difficulty,
skipped.`
Причина: ObjectMgr::LoadCreatures строит для каждой карты набор допустимых
сложностей из sMapDifficultyStore, а затем оставляет из spawnDifficulties
только те значения, что входят в этот набор. Для этих карт пересечение пустое:
шесть — снятые с эксплуатации сценарии, чьи записи MapDifficulty Blizzard удалила
из клиентских данных, а у двух есть запись со сложностью, которой нет в строках
спавнов. Со строками спавнов всё в порядке — карте просто некуда их спавнить.
Исправление: добавить по одной строке MapDifficulty на карту со
сложностью 0. Хранилища DB2 читаются и из файла, и из базы:
DB2Store::LoadFromDB запускает загрузчик дважды — для VerifiedBuild > 0 и для
<= 0 — поэтому кастомная строка с VerifiedBuild 0 попадает в хранилище
штатно, а не случайно.
Не используйте сложность 1. В Difficulty.db2 нет записи 0, поэтому
GetDefaultMapDifficulty() отсеивает эти строки, и они не могут повлиять на то,
как создаётся и масштабируется инстанс. Настоящая сложность выбираема и
перебила бы родной тюнинг Blizzard для карты.
Возвращает: 1 218 спавнов существ и 1 073 гейм-объекта, включая Darkmaul
Citadel — подземелье Exile’s Reach, где не спавнился ни один NPC. Клиентам
ничего не рассылается: рассылка идёт из отдельной таблицы hotfix_data, которую
этот файл не трогает. Нужен рестарт.
Наводишь на квестодателя в Штормграде, а под ним стоит второй, точно такой же.
Клик перебирает их по очереди; один может быть в бою или уже «застолблён», второй
нет. Движок таблицу creature не чистит — строки накопились от повторных
импортов пересекающихся наборов данных, и один и тот же спавн существует
несколько раз под разными guid.
Измерено: 2 598 групп, удалено 3 403 лишних спавна.
Сложность не в поиске, а в том, что удалять нельзя. Группировка по одним
координатам даёт 7 319 групп и 8 379 спавнов — и почти 5 000 из них здоровые:
разные фазы (игроки на разных этапах сюжета видят в одной точке разных NPC),
разные модель или экипировка (визуально разные существа) и группы, где часть
записей входит в game_event_creature, spawn_group или pool_members, а часть
нет (движок выбирает одну — это вариативность, а не дубль). Фикс требует
совпадения всех тринадцати полей, определяющих то, что видит игрок, и группы со
смешанной принадлежностью пропускает целиком.
Остаётся вожак строя, если он в группе есть, иначе — спавн с наименьшим guid;
связанные строки creature_addon и creature_formations удаляются вместе со
спавном, поэтому осиротевших ссылок не возникает.
Известное ограничение: spawnDifficulties — список через запятую, порядок
элементов в нём не нормализован, а группировка сравнивает его как строку. Два
одинаковых спавна с разным порядком в списке не находятся — 4 пары из 3 403 на
нашей базе. По той же причине после отката часть возвращённых строк перестаёт
группироваться с двойником, поэтому откат проверяйте по числу строк, а не
пересчётом групп дублей. Требует перезапуска.
Лог заполняется строками Could not load VMAP name:City of Threads, id:2669, ...
и повторяет их всё время работы сервера — 1 367 строк за один прогон длиной
2 ч 46 мин на нашем реалме: Приорат Священного Пламени, Город Нитей, Рассветный
Разрушитель, Гнездовье и Долина Вечных Цветов. Внутри задетых плиток у движка нет
ни проверки видимости, ни высоты, ни столкновений вообще.
Строка-переопределение DB2 заменяет запись целиком, а не сливается по полям.
В hotfixes.map лежали 18 строк, пустых во всём кроме ID — Directory
NULL, MapName NULL, WdtFileDataID 0 и, что и ломало всё, ParentMapID = 0 и
CosmeticParentMapID = 0.
Ноль здесь не «нет родителя» — это −1. Ноль — это карта 0, Азерот. Движок считал Азерот родителем этих подземелий. Карта без собственной геометрии для плитки берёт родительскую и сопоставляет её со своим файлом индексов; взятая не у того родителя геометрия описывает другое место, числа записей расходятся, и плитка отвергается целиком.
Измерено на плитке 26_29 Города Нитей: собственный файл индексов карты содержит 2 494 записи, настоящий родитель Каз Алгар — 2 494, точное совпадение, а Азерот — 22. Все задетые плитки выглядят одинаково.
Этих строк не было в hotfix_data, то есть клиентам они не рассылались: вред был
только серверный. После удаления движок читает настоящую запись из Map.db2.
Результат: 1 367 ошибок за прогон до и 0 после. Затрагиваются только строки, пустые во всех пяти полях, поэтому осознанное переопределение случайно не попадёт под удаление. Требует перезапуска.
Применить всё: scripts/apply_fixes.sh (добавь HOTFIXES_DB=hotfixes, чтобы
включить sql/hotfixes/). Каждый файл безопасно запускать дважды — бэкапы
создаются только если их ещё нет, поэтому второй запуск не затрёт исходные
значения — и каждый файл заканчивается блоком отката в комментариях. Всегда делай
бэкап базы world перед применением.
Каждый файл из этого документа проверен так: копия реальной базы откатывалась в сломанное состояние, применялось исправление, проверялось что счётчик дефектов дошёл до нуля, файл применялся второй раз, а затем выполнялся описанный откат — и исходные числа возвращались в точности.