@@ -0,0 +1,753 @@
# mod-taxi-builder
GM-инструмент для создания собственных точек полёта и маршрутов
прямо в игре на сервере MoonWell (AzerothCore 3.3.5a). Не требует
ручного редактирования DBC: все данные складываются в три таблицы
`acore_world` , а внешний CLI [moonwell-taxi ](../../tools/moonwell-taxi/ )
собирает из них `TaxiNodes.dbc` , `TaxiPath.dbc` и `TaxiPathNode.dbc`
для серверной `dbc/` -папки и клиентского патча.
Все команды требуют права `SEC_ADMINISTRATOR` (стандартный GM-уровень
3+).
---
## Содержание
- [Установка ](#установка )
- [Полный сценарий: новый маршрут с нуля ](#полный-сценарий-новый-маршрут-с-нуля )
- [Команды ](#команды )
- [`.taxi node` ](#taxi-node )
- [`.taxi path` ](#taxi-path )
- [`.taxi record` ](#taxi-record )
- [`.taxi markers` ](#taxi-markers )
- [`.taxi export dbc` ](#taxi-export-dbc )
- [Лимит TaxiMask и распределение id ](#лимит-taximask-и-распределение-id )
- [Правила валидации ](#правила-валидации )
- [Прореживание точек: `moonwell-taxi thin` ](#прореживание-точек-moonwell-taxi-thin )
- [Структура БД ](#структура-бд )
- [Связь с CLI moonwell-taxi ](#связь-с -cli-moonwell-taxi )
- [Известные ограничения ](#известные-ограничения )
- [FAQ / типичные ошибки ](#faq--типичные-ошибки )
---
## Установка
1. Клонируйте репозиторий ядра и убедитесь, что папка
`modules/mod-taxi-builder/` присутствует.
2. Примените миграцию схемы. Самый простой способ — через
[import-custom-sql.sh ](../../import-custom-sql.sh ):
```bash
docker compose up -d ac-database
bash import-custom-sql.sh
` ``
В ` acore_world` появятся три таблицы: ` custom_taxi_nodes`,
` custom_taxi_paths`, ` custom_taxi_path_nodes`. Скрипт идемпотентен —
повторный запуск пропустит уже созданные таблицы.
3. Соберите ядро со включенными модулями (значение по умолчанию):
` ``bash
mkdir -p build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/azeroth-server \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DSCRIPTS=static -DMODULES=static
make -j$(nproc) && make install
` ``
4. Перезапустите worldserver. На старте появится строка о регистрации
` taxi_builder_commandscript`.
5. (Один раз) поставьте ` PyMySQL` для CLI:
` ``bash
sudo apt install -y python3-pymysql
` ``
---
## Полный сценарий: новый маршрут с нуля
Допустим, GM хочет добавить кастомный маршрут «Златоземье → Штормград»
(вернее, «новая точка → существующий узел Штормграда ` id=2`»).
` ``
# 1. Встаём ровно туда, где должна быть новая точка полёта.
.taxi node create "Златоземье"
# -> [taxi-builder] node created: id=441 name="Златоземье" map=0 ...
# 2. Создаём маршрут от новой точки к Штормграду (vanilla node id=2),
# стоимость — 10 медяков.
.taxi path create 441 2 10
# -> [taxi-builder] path created: id=1979 441 (Златоземье) -> 2 (Stormwind) ...
# 3. Запускаем запись маршрута.
.taxi record start 1979
# -> [taxi-builder] recording started for path 1979 (existing points: 0)
# 4. Идём/летим по будущему маршруту. На каждой ключевой точке:
.taxi record point
# (или с комментарием — пригодится для последующего отчёта)
.taxi record point "взлёт над таверной"
.taxi record point
.taxi record point
# ...записываем 15-25 точек...
# Если оступились — откат последней:
.taxi record undo
# 5. Завершаем запись. Точки сохраняются.
.taxi record finish
# -> path 1979 now has 18 waypoints.
# 6. Проверяем маршрут на типовые проблемы.
.taxi path validate 1979
# -> Validation: 0 errors, 2 warnings — OK
# (Опционально) Расставить визуальные маркеры на всех точках, обойти их пешком:
.taxi markers spawn 1979
# ... осмотр ...
.taxi markers clear 1979
# 7. Из консоли (или из чата) запускаем экспорт DBC.
moonwell-taxi export-dbc
# 8. Кладём серверные DBC в папку сервера и перезапускаем worldserver.
# 9. Кладём клиентские DBC в патч-MPQ и перезапускаем клиент (или удаляем Cache).
# 10. На месте мастера полётов в Златоземье спавним NPC с npcflag=flight master
# и фракцией Альянса/Орды (для MVP — стандартные команды AzerothCore .npc add).
# 11. Подходим к нему, выбираем путь, летим.
` ``
После шага 9 точка появится у мастеров полётов и игроки смогут летать
по записанному маршруту.
---
## Команды
### ` .taxi node`
Управление точками полёта (записи в ` custom_taxi_nodes`).
#### ` .taxi node create "Name"`
Создаёт новую такситочку из текущей позиции GM. Работает только из
игры (нужны координаты).
- ` id` подбирается автоматически из безопасного диапазона (` >= 441`,
избегая конфликтов с vanilla TaxiNodes ~313).
- ` mount_alliance` и ` mount_horde` определяются автоматически: модуль
смотрит, какие mount-creature чаще всего используются у vanilla точек
на текущей карте, и подставляет их. Если карта неизвестна — fallback
` 541/541` (как в примере ТЗ).
- Имя сохраняется как одна строка и при экспорте в DBC дублируется во
все 16 локалей (отображается на любом клиенте).
` ``
.taxi node create "Златоземье"
[taxi-builder] node created: id=441 name="Златоземье" map=0
xyz=(-9445.77, 79.40, 57.38) mounts=A:1572/H:295
` ``
#### ` .taxi node list [filter]`
Печатает список точек: сначала custom (` custom_taxi_nodes`), затем
vanilla (` sTaxiNodesStore`). Без аргумента дамп ограничен 50 строками
(в чате не должно быть спама). С ` filter` — подстрочный поиск без
учёта регистра. Доступна и из консоли.
` ``
.taxi node list Storm
[vanilla] 2 Stormwind, Elwynn @ map 0 (-9446.5, 64.4, 56.0)
[vanilla] 38 Stonewatch Tower, Redridge @ map 0 (-9241.3, -2280.7, 65.0)
Shown 2 (cap 50). Custom: 1, Vanilla: 365.
` ``
#### ` .taxi node free`
Печатает все свободные TaxiNode id в диапазоне ` 1..448`, сгруппированные
в непрерывные подряд-идущие отрезки. Полезно перед ` .taxi node create`
посмотреть, сколько слотов осталось, и до создания убедиться, что
auto-allocator не упрётся. Подробности — в разделе
[Лимит TaxiMask и распределение id](#лимит-taximask-и-распределение-id).
` ``text
.taxi node free
Free TaxiNode ids in 1..448: 83 total (7 above-vanilla 441..448, 76 in vanilla gaps).
338..339 (2)
...
441..448 (8)
` ``
#### ` .taxi node goto <nodeId>`
Телепортирует GM к такситочке (custom или vanilla — что найдётся
первым). Удобно для быстрой проверки, что точка стоит, где надо.
` ``text
.taxi node goto 441
[taxi-builder] teleported to custom node 441 (Златоземье)
` ``
---
### ` .taxi path`
Управление маршрутами (записи в ` custom_taxi_paths`).
#### ` .taxi path create <fromNodeId> <toNodeId> <cost>`
Создаёт направленный маршрут между двумя taxi-нодами. Любой конец
может быть как custom-нодом, так и vanilla — модуль ищет по обоим
источникам и валит, если ID нигде нет. Если уже есть включённый путь
с тем же ` from→to` — отказ (нельзя случайно создать дубль).
- ` id` подбирается автоматически из диапазона ` >= 1979`.
- ` cost` хранится в медяках.
- ` is_enabled` ставится в ` 1`.
` ``
.taxi path create 441 2 10
[taxi-builder] path created: id=1979 441 (Златоземье) -> 2 (Stormwind) cost=10 copper
Now record waypoints with: .taxi record start 1979 -> .taxi record point ... -> .taxi record finish
` ``
#### ` .taxi path reverse <pathId>`
Создаёт обратный путь из существующего:
1. Делает новый ` TaxiPath` ` to → from` с тем же ` cost`.
2. Копирует все ` TaxiPathNode` исходного пути в обратном порядке,
пересчитывая ` node_index` от ` 0`.
3. Печатает предупреждение из ТЗ: ручную проверку посадки никто не
отменял.
` ``
.taxi path reverse 1979
[taxi-builder] reversed path 1979 (441->2) into new path 1980 (2->441) with 18 waypoints.
WARNING: reverse paths are auto-generated. Check landing waypoints by hand
(` .taxi path goto 1980 <lastIndex>`)...
` ``
#### ` .taxi path list`
Все custom-маршруты с резолвом имён узлов и счётчиком точек.
` ``
.taxi path list
1979 441 (Златоземье) -> 2 (Stormwind, Elwynn) cost=10 pts=18 enabled
1980 2 (Stormwind, Elwynn) -> 441 (Златоземье) cost=10 pts=18 enabled
Total: 2 custom paths.
` ``
#### ` .taxi path report <pathId>`
Подробный отчёт по маршруту в формате секции 5.3 ТЗ.
` ``
.taxi path report 1979
PathID: 1979 (enabled)
From: 441 Златоземье [custom]
To: 2 Stormwind, Elwynn [vanilla]
Cost: 10 copper
Points: 18
Total distance: 1842.3 yd
Min segment: 41.7 yd Max segment: 95.4 yd
Last point distance to ToTaxiNode: 4.8 yd
` ``
#### ` .taxi path validate <pathId>`
Применяет правила секции 4 ТЗ. См. подробности в разделе
[Правила валидации](#правила-валидации).
` ``
.taxi path validate 1979
[warn] segment 7->8 is 38.2 yd (< 40 yd, may be filtered by IsNodeIncludedInShortenedPath)
[warn] sharp turn at NodeIndex 12: 89 deg (> 75). Consider adding intermediate points.
Validation: 0 errors, 2 warnings — OK
` ``
При наличии errors статус — ` FAIL`. Маршрут с errors не пропустит
` .taxi path test`.
#### ` .taxi path goto <pathId> <nodeIndex>`
Телепорт к конкретной точке маршрута. Удобно для шага «обход маркеров».
` ``
.taxi path goto 1979 7
[taxi-builder] teleported to path 1979, NodeIndex 7 map=0 (-9095.00, 230.00, 180.00)
` ``
#### ` .taxi path test <pathId>`
«Сухой запуск»: запускает валидацию (отказ при errors), затем
телепортирует GM на первую точку маршрута, развернув лицом ко второй.
Реальный полёт по таксиспрайнам в этой версии возможен только после
прогона ` moonwell-taxi export-dbc` и перезапуска worldserver+клиента.
` ``
.taxi path test 1979
[warn] segment 7->8 is 38.2 yd ...
[taxi-builder] dropped at first waypoint of path 1979 (18 total).
Step through with ` .taxi path goto 1979 <index>`.
Real taxi flight needs ` moonwell-taxi export-dbc` + worldserver/client restart.
` ``
---
### ` .taxi record`
Запись точек маршрута. Запись — *сессия в памяти*, привязанная к GM-у:
рестарт worldserver очистит сессию, но уже записанные waypoint-ы в БД
останутся, и сессию можно продолжить через ` .taxi record start <pathId>`.
#### ` .taxi record start <pathId>`
Начинает запись для существующего custom-пути. Запоминает baseline —
число точек, которые были у пути до старта (для корректного ` cancel`).
` ``
.taxi record start 1979
[taxi-builder] recording started for path 1979 (existing points: 0).
Use ` .taxi record point ["comment"]` and ` .taxi record finish` when done.
` ``
#### ` .taxi record point [comment]`
Записывает следующую точку с координатами и map_id GM-а . ` node_index`
автоматически = ` MAX(node_index) + 1` (или 0 для первой).
Опциональный комментарий пишется в ` comment` колонку (до 255 символов).
` ``
.taxi record point
[taxi-builder] path=1979 point #0 map=0 (-9445.77, 79.40, 57.38)
.taxi record point "разворот над озером"
[taxi-builder] path=1979 point #1 map=0 (-9395.00, 80.00, 70.00) // разворот над озером
` ``
#### ` .taxi record undo`
Удаляет последнюю записанную в этой сессии точку. Не даёт уйти ниже
baseline (точки, существовавшие до ` start`, защищены).
` ``
.taxi record undo
[taxi-builder] removed point #17 from path 1979.
` ``
#### ` .taxi record finish`
Закрывает сессию. Точки остаются в БД. Печатает счётчики.
` ``
.taxi record finish
[taxi-builder] recording finished. Path 1979 now has 18 waypoints (added 18 this session).
Run ` .taxi path report 1979` or ` .taxi path validate 1979` to review.
` ``
#### ` .taxi record cancel`
Откатывает текущую сессию: удаляет ровно те точки, которые добавил
этот ` start ... point ...`. Существовавшие до сессии waypoint-ы не
трогаются — это полезно при правке уже готового маршрута.
` ``
.taxi record cancel
[taxi-builder] recording cancelled for path 1979; rolled back to 5 waypoints.
` ``
---
### ` .taxi markers`
Временные визуальные маркеры на waypoint-ах. По умолчанию используется
GO ` 178559` (Banner Stand of Argent Crusade) — он мелкий, виден всем
и есть в стоковой ` gameobject_template`. При желании смените
` MARKER_GO_ENTRY` в [src/cs_taxi_builder.cpp](src/cs_taxi_builder.cpp).
#### ` .taxi markers spawn <pathId>`
Спавнит маркеры на всех точках маршрута, лежащих на текущей карте
GM-а. Точки на других континентах пропускаются (грид всё равно не
загружен — было бы бесполезно).
Маркеры держатся 24 часа или до явного ` clear`. Повторный ` spawn`
сначала чистит предыдущие, чтобы не плодить дубли.
` ``
.taxi markers spawn 1979
[taxi-builder] spawned 18 markers for path 1979 on map 0 (0 skipped on other maps).
Auto-despawn in 86400 s. Use ` .taxi markers clear 1979` to remove now.
` ``
#### ` .taxi markers clear <pathId>`
Удаляет все маркеры данного пути.
` ``
.taxi markers clear 1979
[taxi-builder] cleared 18 markers for path 1979.
` ``
---
### ` .taxi export dbc`
Внутри игры команда не делает экспорт сама — она лишь напоминает
запустить внешний CLI. Это сделано специально: генерация DBC, sha256
и копирование файлов — задача внешнего инструмента, чтобы не нагружать
runtime worldserver-а .
` ``
.taxi export dbc
[taxi-builder] export dbc stub: run ` moonwell-taxi export-dbc` from CLI
` ``
См. раздел [Связь с CLI moonwell-taxi](#связь-с -cli-moonwell-taxi).
---
## Лимит TaxiMask и распределение id
` TaxiNode.id` ограничен сверху значением **448**. Это hardcoded в
` src/server/shared/DataStores/DBCStructure.h:2284`:
` ``cpp
static constexpr std::size_t TaxiMaskSize = 14;
typedef std::array<uint32, TaxiMaskSize> TaxiMask;
` ``
14 × 32 = 448 бит. По одному биту на каждую такситочку: «доступна
ли в taxi-сети» и «открыл ли её игрок». Сервер в
` ObjectMgr::GetNearestTaxiNode` пропускает любой узел с
` (id - 1) / 32 >= 14`, то есть ` id > 448`.
Симптом нарушения: GM создал точку ` id = 441`, custom NPC появился,
но при выборе destination сервер берёт ближайший **vanilla** узел
вместо нашего, строит другую трассу — игрок «крутится по дороге»
и в конце сваливается. Видимых ошибок в логе нет.
Поднять потолок без патча клиента нельзя: пакет ` SMSG_SHOWTAXINODES`
шлёт ровно 14 uint32, клиент 3.3.5a читает фиксированный размер.
### Что внутри 1..448
В реальной vanilla-DBC проекта используется до ` id = 440`, и часть
номеров в этом диапазоне пустует. Вместе с диапазоном выше vanilla
(` 441..448`) даёт ~84 свободных слота. Команда ` .taxi node free`
показывает текущую картину:
` ``text
.taxi node free
Free TaxiNode ids in 1..448: 84 total (8 above-vanilla 441..448, 76 in vanilla gaps).
338..339 (2)
343..347 (5)
349..351 (3)
354..356 (3)
360..374 (15)
378
385..391 (7)
396..403 (8)
406..437 (32)
441..448 (8)
` ``
` .taxi node create` использует двухзонный авто-выбор:
1. Сначала ищет первый свободный id в ` 441..448` (выше vanilla,
всегда безопасно).
2. Если эта зона исчерпана — берёт первый свободный id в дырах
` 1..440`.
«Свободный» означает: id не загружен в ` sTaxiNodesStore`
(merged DBC на сервере) **и** не присутствует в ` custom_taxi_nodes`
(может быть ещё не задеплоен в DBC).
### Что делать при исчерпании всех 84 слотов
- Освободить слот: удалить лишнюю строку из ` custom_taxi_nodes`
(плюс зависимости в ` custom_taxi_paths`).
- Перейти на бинарный патч ` Wow.exe` под расширенный ` TaxiMaskSize`
(Cataclysm-эра приватки делают так — увеличивают до 32 uint32 =
1024 ноды). Это вне scope модуля: нужен патч exe + изменение
` TaxiMaskSize` в ядре + раскатка патча игрокам.
- Использовать «фейковые» полёты через скриптового NPC и ` MoveSplinePath`,
без записи в TaxiMask. Не показывается на карте как обычная точка
полёта, но id неограничены. Тоже отдельный модуль.
---
## Правила валидации
` .taxi path validate <pathId>` применяет следующие правила
(пороги — константы в [src/cs_taxi_builder.cpp](src/cs_taxi_builder.cpp),
секция «Thresholds per spec section 4»):
| # | Правило | Уровень |
| --- | --- | --- |
| 4.1 | ` from`/` to`-нод существует (custom или vanilla) | error |
| 4.1 | у пути есть хотя бы одна waypoint | error |
| 4.1 | ` node_index` идёт подряд от 0 | error |
| 4.2 | минимум 3 точки | error |
| 4.2 | рекомендация: 12+ точек | warning |
| 4.3 | сегмент ` > 200 yd` (XY) | error |
| 4.3 | сегмент ` > 120 yd` (XY) | warning |
| 4.3 | сегмент ` < 40 yd` (XY) | warning (` IsNodeIncludedInShortenedPath` режет) |
| 4.3 | сегмент пересекает границу карты | error (полёт между континентами невозможен) |
| 4.4 | первая точка ` > 20 yd` от ` FromTaxiNode` | warning |
| 4.4 | последняя точка ` > 20 yd` от ` ToTaxiNode` | warning |
| 4.4 | первая/последняя точка на другой карте, чем нод | error |
| 4.5 | модуль абсолютной разницы Z последней точки и ` ToTaxiNode > 30 yd` | warning (риск падения) |
| 4.6 | поворот ` > 75°` в плоскости XY между сегментами | warning |
` .taxi path test` отказывается работать, если есть хотя бы один error.
Warnings — индикативны, не блокируют.
---
## Прореживание точек: ` moonwell-taxi thin`
Самая частая поломка свежезаписанного маршрута: в полёте таксИ
«крутится по дороге» и сбрасывает игрока на кладбище. Причина —
большинство сегментов короче 40 yd. AzerothCore при построении
spline-а вызывает ` IsNodeIncludedInShortenedPath` (` SKIP_SPLINE_POINT_DISTANCE_SQ = 40 * 40`)
и выкидывает waypoint-ы, которые ближе ~40 yd друг к другу. Если у
вас 68 точек, среднее расстояние 12 yd — после фильтрации остаётся
3-5 точек, по которым spline крутится как угодно.
Симптом виден прямо в ` .taxi path validate <pathId>`:
` ``
[warn] segment 19->20 is 12.1 yd (< 40 yd, may be filtered by IsNodeIncludedInShortenedPath)
[warn] segment 20->21 is 12.7 yd (< 40 yd, ...)
...
Validation: 0 errors, 67 warnings — OK
` ``
Чинить руками удалением точек по одной — больно. Внешний CLI имеет
подкоманду ` thin`, которая делает это безопасно: пишет бэкап,
удаляет старые waypoint-ы и записывает прореженные с пересчитанным
` node_index` от 0.
` ``bash
# 1. посмотреть, что получится, без записи в БД
python3 tools/moonwell-taxi/moonwell_taxi.py thin --path 1979 --min-yd 50 --dry-run
# 2. реально применить
python3 tools/moonwell-taxi/moonwell_taxi.py thin --path 1979 --min-yd 50
` ``
Параметры:
- ` --path <id>` — обязательный, какой ` custom_taxi_paths.id` чистить.
- ` --min-yd <float>` — минимальное XY-расстояние между соседними
оставленными точками. Дефолт ` 50`. Для длинных прямых смело
ставьте ` 60-80`, для извилистых — ` 40-50`.
- ` --dry-run` — показать план без модификации БД.
Алгоритм:
1. Первая waypoint всегда сохраняется.
2. Каждая последующая сравнивается с последней оставленной по XY.
Если ≥ ` --min-yd` — сохраняется, иначе пропускается.
3. Последняя waypoint всегда сохраняется (анкер посадки).
4. Если предпоследняя получилась слишком близко к последней — её
тоже выкидывают. Так не возникает «маленький хвост» на финише.
5. Все ID ` node_index` пересчитываются от ` 0` без пропусков.
6. Перед удалением исходные строки копируются в
` custom_taxi_path_nodes_backup_<pathId>_<unix_ts>` — откатить
можно прямым SQL (команда сама печатает rollback в конце).
Пример вывода на боевом маршруте:
` ``
Path 1979: 68 -> 13 waypoints (min_yd=50.0, removed 55)
Backup table: ` custom_taxi_path_nodes_backup_1979_1778283612`
To rollback:
DELETE FROM custom_taxi_path_nodes WHERE path_id = 1979;
INSERT INTO custom_taxi_path_nodes
SELECT * FROM ` custom_taxi_path_nodes_backup_1979_1778283612`;
` ``
Что делать после ` thin`:
` ``
.taxi path validate 1979 # должно быть 0 errors, 0 warnings про <40 yd
.taxi path report 1979 # Min/Max segment в коридоре 40-120 yd
` ``
Затем ` bash tools/moonwell-taxi/redeploy.sh` — экспорт DBC, заливка
в ` var/client-data-live/dbc/`, рестарт worldserver-а .
**Важно:** ` thin` работает только с записями в ` custom_taxi_path_nodes`.
DBC сами по себе не меняются — для актуализации сервера нужен
` redeploy.sh` (или ручной ` export-dbc` + ` cp` + ` restart`). Без этого
in-game проверка увидит старый, плотный маршрут.
---
## Структура БД
Все три таблицы в ` acore_world`. См.
[data/sql/updates/pending_db_world/rev_*.sql](../../data/sql/updates/pending_db_world/).
` ``
custom_taxi_nodes
id int unsigned PRIMARY KEY (модуль выдаёт >= 441)
name varchar(100) (utf8mb4, утаивается во все 16 локалей DBC)
map_id smallint unsigned
position_x float
position_y float
position_z float
mount_alliance int unsigned
mount_horde int unsigned
created_by bigint unsigned (GUID GM-а на момент создания)
created_at timestamp
updated_at timestamp
custom_taxi_paths
id int unsigned PRIMARY KEY (модуль выдаёт >= 1979)
from_taxi_node_id int unsigned (custom ИЛИ vanilla)
to_taxi_node_id int unsigned (custom ИЛИ vanilla)
cost int unsigned (медяки)
is_enabled tinyint(1)
created_by, created_at, updated_at
KEY idx_from_to (from_taxi_node_id, to_taxi_node_id)
custom_taxi_path_nodes
id int unsigned AUTO_INCREMENT (внутренний; DBC ID — на экспорте)
path_id int unsigned
node_index int unsigned (UNIQUE с path_id, 0-based)
map_id smallint unsigned
position_x, _y, _z float
flags int unsigned (TaxiPathNode flags: teleport=1, stop=2 ...)
delay int unsigned
arrival_event_id int unsigned
departure_event_id int unsigned
comment varchar(255)
created_at timestamp
` ``
ID-диапазоны: модуль предпочитает ` 441..448` для новых нод, а когда
этот диапазон занят, использует свободные дырки внутри ` 1..440`.
Для путей модуль начинает с ` 1979`, сразу после vanilla max ` 1978`.
Схема БД эти правила не enforce-ит, поэтому в теории можно вставить ID
руками, но дальше CLI откажется писать DBC при коллизии с vanilla.
` from`/` to` в ` custom_taxi_paths` намеренно без FK: маршрут регулярно
ведёт от custom-нода к vanilla (как в исходном кейсе ТЗ —
«Златоземье → Штормград»).
---
## Связь с CLI moonwell-taxi
Модуль *только* пишет в БД. DBC генерирует внешний инструмент
[tools/moonwell-taxi/](../../tools/moonwell-taxi/):
` ``bash
# Прочесть custom_taxi_*, смерджить с vanilla DBC из корня репозитория,
# записать оба комплекта (server + client) и проверить sha256.
python3 tools/moonwell-taxi/moonwell_taxi.py export-dbc
# Output:
# build/taxi/export/server/dbc/TaxiNodes.dbc
# build/taxi/export/server/dbc/TaxiPath.dbc
# build/taxi/export/server/dbc/TaxiPathNode.dbc
# build/taxi/export/client/DBFilesClient/TaxiNodes.dbc
# build/taxi/export/client/DBFilesClient/TaxiPath.dbc
# build/taxi/export/client/DBFilesClient/TaxiPathNode.dbc
# + sha256.txt в каждом каталоге
` ``
Если sha256 серверной и клиентской копий не совпадают — CLI выходит с
` exit code 1`, не считая экспорт валидным.
**Важно:** модуль и CLI общаются *только через схему БД*. Никаких
общих хедеров, FIFO или RPC. Меняется схема — обновляется и CLI.
---
## Известные ограничения
- **Только custom_taxi_*, не модифицирует vanilla.** Чтобы поправить
существующую такситочку или путь vanilla, нужны другие средства.
- **Имя пишется в locale slot текущего vanilla DBC.** Exporter
авто-инферит slot и ` Name_lang_mask` из ` TaxiNodes.dbc` (для ruRU это
slot ` 8`). Per-locale override таблицы пока нет.
- **Mount creature ID — авто-подбор по большинству на карте.** Для
Outland/Northrend и нестандартных карт fallback ` 541/541` может
выглядеть как Альянсный грифон у Орды. Меняется UPDATE-ом
` custom_taxi_nodes.mount_horde`.
- **` .taxi markers spawn` спавнит только на текущей карте.** Точки на
других континентах пропускаются — таково ограничение grid-load.
- **` .taxi path test` не запускает реальный flight.** Реальный полёт
работает только через DBC; у ` path test` MVP-уровень — телепорт на
первую точку. Делать ` Player::ActivateTaxiPathTo` для путей не из
DBC требует более глубокой интеграции (out of scope MVP).
- **Сессия записи не персистится.** Рестарт worldserver сбрасывает
состояние ` .taxi record`. Workaround: ` .taxi record start <pathId>`
заново — точки в БД остаются.
- **Конвенция Alliance/Horde в DBC обратна тексту ТЗ.** В реальном
DBC ` MountCreatureID[0]` — Horde, ` [1]` — Alliance
(см. ` ObjectMgr.cpp:7229`). Колонки ` mount_alliance` /
` mount_horde` в БД названы корректно — конверсия делается при записи
в DBC.
---
## FAQ / типичные ошибки
### «Path 1979 has no waypoints — record some first»
Создан путь, но не записано ни одной точки. Запусти
` .taxi record start 1979`, пройди маршрут, нажимая
` .taxi record point` в нужных местах, заверши ` .taxi record finish`.
### «custom_taxi_paths id=N not found»
Путь не существует или ` is_enabled=0`. Список — ` .taxi path list`.
### «from-node N does not exist (custom or vanilla)»
ID не найден ни в ` custom_taxi_nodes`, ни в vanilla ` TaxiNodes.dbc`.
Список — ` .taxi node list <substring>`.
### «An enabled path X -> Y already exists with id Z»
Активный маршрут ` from→to` уже существует. Либо
` UPDATE custom_taxi_paths SET is_enabled=0 WHERE id=Z;`, либо
используй существующий маршрут.
### ` .taxi path validate` показывает много ` < 40 yd` warnings
Сегменты слишком короткие — ` IsNodeIncludedInShortenedPath` их режет
и реальный полёт пройдёт «через» эти точки. Удали лишние через
` .taxi record undo` или прямым ` DELETE` по ` node_index`.
### Marker GO entry 178559 is missing from gameobject_template
На сервере зачищен banner GO. Поменяй ` MARKER_GO_ENTRY` в
[src/cs_taxi_builder.cpp](src/cs_taxi_builder.cpp) на любой видимый
GO из ` gameobject_template` и пересобери модуль.
### После ` export-dbc` сервер видит точку, а клиент — нет
Клиент читает свои ` DBFilesClient/*.dbc` из MPQ-патчей. Проверь, что
файлы из ` build/taxi/export/client/DBFilesClient/` попали в
подключённый патч (например, ` patch-X.MPQ` с приоритетом выше
базовых) и удалён клиентский ` Cache/`.
### ` Server/client DBC match: FAIL` после экспорта
Не должно случаться при штатной работе CLI (server и client пишутся
из одного буфера). Если всё-таки — баг в инструменте, открывайте
issue с ` sha256.txt` обоих комплектов.