754 lines
34 KiB
Markdown
754 lines
34 KiB
Markdown
# 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` обоих комплектов.
|