taxi builder

This commit is contained in:
2026-05-11 18:35:47 +04:00
parent 0a3d3b6afa
commit 13747727cf
21 changed files with 3578 additions and 0 deletions
+753
View File
@@ -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` обоих комплектов.