# Протокол W3P

> Полный контракт между рантаймом и внешними программами: восемь блоков общей памяти, чтение состояния мира, чтение событий, отправка команд, квитанции, роли полос, холст, интерфейс и ввод. Читайте эту страницу, если подключаетесь не из Python.

Источник: https://war3ai.com/ru/docs/protocol/

Рантайм и внешние программы обмениваются данными **только через общую память**. Ниже описано всё, что для этого нужно.

- Эталонная реализация — на Python: `sdk/python/w3world.py` (чтение) и `sdk/python/w3fast.py` (запись); размер и смещения каждой структуры заданы там и закреплены тестами;
- **Протокол описывает только семантику и не зависит от версии игры.** При смене версии игры рантайм адаптируется сам, а протокол не меняется; новые поля только дописываются в конец блока, так что старые клиенты продолжают работать.

> **Примечание**
>
> Большинству эта страница не нужна — просто используйте Python SDK. Она понадобится, только если вы хотите подключаться напрямую из C++ / C# / Rust / Go или другого языка либо хотите знать, что происходит под капотом SDK.

## 1. Восемь блоков общей памяти

`<pid>` — идентификатор процесса игры.

| Имя | Направление | Содержимое | Синхронизация |
|---|---|---|---|
| `Local\War3World_<pid>` | рантайм → вы | Состояние мира: заголовок + 16 игроков + до 1024 юнитов + 256 записей с деталями юнитов + 256 предметов на земле + область расширений + таблица производства | seqlock |
| `Local\War3Trees_<pid>` | рантайм → вы | До 4096 разрушаемых объектов (деревья и т. п.), обновляется каждые 2 секунды | seqlock |
| `Local\War3Events_<pid>` | рантайм → вы | Кольцо событий, 8192 записи | у каждой записи свой порядковый номер |
| `Local\War3Map_<pid>` | рантайм → вы | Карта: клетки рельефа (128 на клетку, до 256×256) + границы игровой области + стартовые позиции; вычисляется порциями в первые секунды партии | seqlock (после вычисления больше не меняется) |
| `Local\War3Fast_<pid>` | в обе стороны | Полосы команд: 16 полос × 16 слотов; в каждом слоте одна команда + квитанция; у каждой полосы есть роль | в каждом слоте один писатель и один читатель |
| `Local\War3Canvas_<pid>` | вы → рантайм | [Холст](https://war3ai.com/ru/docs/canvas/): заголовок 64 байта + 256 элементов × 112 байт + пул текста / точек 64 KB; создаётся только после одного вызова `canvas_enable` | seqlock (пишете вы, рантайм читает каждый кадр) |
| `Local\War3Msgs_<pid>` | рантайм → вы | Кольцо экранных сообщений: полный текст подсказок игры, чата и системных сообщений, 128 записей × 256 байт | у каждой записи свой порядковый номер |
| `Local\War3Input_<pid>` | в обе стороны | [Интерфейс и ввод](https://war3ai.com/ru/docs/ui-input/): рантайм записывает обратно позицию мыши, точку на земле под курсором и элемент под курсором; вы записываете таблицу горячих клавиш и переключатели мыши; рантайм начинает перехватывать ввод только после одного вызова `input_enable` | таблица горячих клавиш — seqlock |

**Несколько клиентов одновременно работают с холстом и вводом**: каждый из этих блоков существует в единственном экземпляре, и если клиенты пишут в них независимо, они затирают друг друга. Соглашения такие, и в своём клиенте их тоже нужно соблюдать:

- **Холст**: чтение — изменение — запись под именованным мьютексом `Local\War3CanvasMutex_<pid>`; заменяйте только свои элементы, чужие оставляйте как есть (с перестройкой смещений в пуле); элементы, чей процесс-владелец завершился, и элементы без владельца удаляйте. В элементе `reserved[1]` = идентификатор процесса-владельца, `reserved[2]` = порядковый номер внутри процесса; номера элементов выдаёт счётчик по смещению 60 в заголовке блока (начиная с `0x10000`).
- **Ввод**: каждый клиент регистрирует свои горячие клавиши и переключатели мыши в `Local\War3InputClients_<pid>` (заголовок 16 байт + 16 клиентов × 528 байт), под `Local\War3InputMutex_<pid>` обновляет свою запись, а затем объединяет живых клиентов и записывает результат в блок ввода: горячие клавиши дедуплицируются по паре «код клавиши + модификаторы», переключатели мыши объединяются. События получают все клиенты, и каждый узнаёт свои горячие клавиши по паре «код клавиши + модификаторы». Пока в таблице регистрации есть другие живые клиенты, не отправляйте `input_enable 0`.
- **Рантайм**: кликабельные элементы, чей процесс-владелец завершился, больше не перехватывают клики; раз в 2 секунды рантайм просматривает таблицу регистрации и, если все зарегистрированные клиенты завершились, обнуляет таблицу горячих клавиш и переключатели мыши в блоке ввода.

## 2. Чтение состояния мира (seqlock)

```text
loop:
    s1 = block.seq                (смещение 8, int32)
    if s1 нечётное: повторить     (рантайм сейчас пишет)
    скопировать заголовок + players + units[unitCount] + details[detailCount] + items[itemCount]
    if block.seq != s1: повторить
```

- **Заголовок**: счётчик публикаций (не растёт = публикация остановилась), игровые часы движка, epoch (+1 за каждую партию), номер своего игрока, идёт ли партия, скорость игры, период публикации, сколько микросекунд ушло на сбор этой копии в игровом потоке, порядковый номер событий, время по этапам. Клиент может записать `requestedPeriodMs`, чтобы запросить период публикации (16 ~ 1000 ms).
- **Юнит** (112 байт): пара дескрипторов (**опознавайте юниты по паре дескрипторов** — адреса переиспользуются), четырёхсимвольный код типа, владелец, флаги, координаты, здоровье / мана (с максимумами), текущий приказ + цель приказа, текущая цель (кого юнит реально атакует), уровень героя / опыт / очки навыков, индекс деталей, `visibleTo` (бит p = игрок p видит юнит прямо сейчас).
- **Детали** (288 байт; герои > юниты игроков > крипы; до 256): 12 способностей (код / уровень / флаги / оставшаяся перезарядка в секундах), 8 кодов баффов, 6 ячеек инвентаря.
- **Расширения в конце блока** (только дописываются, прежние смещения не сдвигаются, старые клиенты продолжают работать): область расширений `EXT1` (игровое время суток, скорость смены дня и ночи, число записей в таблице производства) и таблица производства `prods[128]` (здания, которые сейчас нанимают / исследуют / строят / улучшают, очередь, общая длительность, прошедшее время, не застряло ли). **Используйте, только если совпадает magic.**

## 3. Чтение событий

```text
head = ring.writeSeq               (смещение 8)
for seq in (cursor, head]:
    e = ring.events[(seq - 1) % 8192]
    if e.seq > seq:  одно событие потеряно (перезаписано, потому что вы читали слишком медленно)
    elif e.seq != seq: ещё не дописано, прочитать в следующий раз
    else: обработать e
```

Структура события — 64 байта: `seq kind clockMs addr handleLo handleHi typeId owner a b x y value extra`.

- Получаются сравнением двух соседних публикаций (точность = период публикации): `unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started`;
- уровня движка (рантайм записывает их прямо в игровом потоке в момент удара, так что событие есть на **каждый удар**): `damage` (источник, тип урона, тип атаки, позиция, фактически снятое здоровье, урон до учёта брони), `killed` (убийца);
- получается отслеживанием таблицы производства: `production.done` (четырёхсимвольный код готового, категория, сколько игровых секунд заняло; приходит и для противника);
- проверяются рантаймом при каждой публикации: `spell.cast` (способность ушла на перезарядку: `a` — четырёхсимвольный код способности, `b` — уровень, `value` — перезарядка в секундах, `x/y` — точка применения), `player.left` (`a` — номер игрока, `b` — новое состояние слота), `selection.changed` (выделение локального игрока; полный список — в области расширений блока мира), `game.ended` (выход из матча);
- экранные сообщения: `message` (`a` = порядковый номер сообщения, полный текст ищите в общей памяти `Local\War3Msgs_<pid>`: 128 записей × 256 байт, там и подсказки игры, и чат, и системные сообщения; `b` = номер окна сообщений);
- интерфейс и ввод (после `input_enable`): `ui.click` (`a` — id элемента холста, `b` — 1 левая кнопка / 2 правая), `ui.hover`, `hotkey` (`a` — id горячей клавиши, `b` — код виртуальной клавиши), `mouse.world` (`x/y` — координаты на земле, `value` = 1 означает, что клик поглощён); модификаторы всегда в `extra`.

## 4. Отправка команд

1. **Один клиентский объект занимает одну полосу**: захватите `Local\War3FastMutex_<pid>`, найдите свободную полосу (или полосу, процесс-владелец которой умер) и запишите роль, номер игрока и свой pid. Если одному процессу нужны две роли, откройте две полосы;
2. заполните слоты: флаг семантической команды, код операции, `args[11]`, крайний срок `deadlineMs`;
3. когда все слоты записаны, пометьте их отправленными и увеличьте `submitSeq` полосы на 1;
4. дождитесь события `Local\War3FastDone_<pid>_<lane>` (или опрашивайте), прочитайте квитанции и верните слоты.

Рантайм выполняет команды пачками внутри диспетчеризации событий игрового потока: бюджет времени на одно опустошение — **4 ms** (реальный высокоточный таймер); всё, что не уложилось, остаётся до следующей диспетчеризации. **Слоты с истёкшим крайним сроком не выполняются** — ситуации «после снятия паузы старые команды выполнились ещё раз» не бывает.

Индексы `args`: `0..2` юнит (адрес, handle lo, handle hi), `3` номер приказа или четырёхсимвольный код, `4..6` цель, `7/8` x / y (биты float), `9` extra (номер игрока / номер ячейки / переключатель / флаг очереди), `10` mode (0 без цели / 1 в точку / 2 в цель).

### Коды операций

| Код операции | Имя | Описание |
|---|---|---|
| 1 | `point` | Приказ юниту в точку (движение / атака с движением / патруль / атака земли / заклинание в точку). extra bit0 = в очередь (после текущего приказа) |
| 2 | `target` | Приказ юниту на цель (атака правым кликом / добыча / ремонт / заклинание на цель / подбор предмета); цель должна быть видна |
| 3 | `immediate` | Команда без цели (стоп / удерживать позицию / нанять / исследовать / улучшить / заклинание без цели) |
| 4 | `build` | Рабочий строит здание (координаты выравниваются по 32) |
| 5 | `learn` | Герой изучает способность |
| 6 | `use_item` | Использовать ячейку инвентаря с номером extra |
| 7 | `revive` | Воскресить героя в алтаре |
| 8 | `rally` | Точка сбора (в точку / на цель) |
| 9 | `buy` | Магазин продаёт предмет стоящему рядом герою |
| 10 | `item_drop` | Предмет покидает инвентарь: передать союзнику, продать в магазин (`code` = юнит-получатель) или бросить на землю |
| 20 ~ 25 | Запросы | `q_tech` счётчик технологий, `q_feasible` выполнимость, `q_visible` видимость, `q_mine_gold` остаток золота в руднике, `q_captain` капитан компьютерного AI, `q_dead_heroes` список погибших героев |
| 30 | `pause` | Пауза / продолжить |
| 40 ~ 50 | Камера | Прочитать состояние камеры, задать поле, навести на точку, следовать, сбросить, повернуть, границы, сглаживание, показ/скрытие интерфейса, чистая картинка, туман |
| 60 ~ 63 | HUD | Текст кнопки заданий, заголовок и описание панели заданий, обновить, прочитать, открыта ли панель |
| 70 | `jass` | Вызов JASS native по имени (всего 1291): имя и строковые аргументы кладутся в дополнительную область слота, остальные аргументы — в `args` согласно сигнатуре; результат возвращается в `value[0]`. Только для полосы локальных инструментов; вызовы с аргументами-функциями или приостанавливающие поток скрипта всегда отклоняются. См. [JASS-канал](https://war3ai.com/ru/docs/jass/) |
| 71 / 72 | `jass_handle_of` / `jass_unit_of` | Преобразование между юнитом из снимка и дескриптором JASS в обе стороны (пара дескрипторов в снимке — это не дескриптор JASS) |
| 73 | `canvas_enable` | Создаёт общую память холста и ставит перехват отрисовки; отправить может любая полоса (холст рисуется только на экране этой машины). В первый раз ставится перехват — задайте тайм-аут от 2 секунд |
| 74 | `input_enable` | `extra` = 1 — перехватывать ввод окна игры (клики / наведение на элементы холста, горячие клавиши, клики по земле), 0 — вернуть. Блок ввода `Local\War3Input_<pid>`: заголовок 128 байт + 32 горячие клавиши × 16 байт; вы записываете таблицу горячих клавиш и переключатели мыши, рантайм записывает обратно позицию мыши, точку на земле под курсором и элемент под курсором. Отправить может любая полоса (влияет только на локальный ввод). См. [Интерфейс и ввод](https://war3ai.com/ru/docs/ui-input/) |

## 5. Квитанции

Квитанция занимает 52 байта (+8 байт замеров времени): `status`, `engineReturn`, `verdict` (код причины отказа), `orderBefore / orderAfter` (приказ юнита, прочитанный в том же кадре), `value[8]` (результаты запроса), `execUs` (сколько микросекунд команда выполнялась в игровом потоке), `engineUs` (из них — сама функция приказа движка).

Все коды состояния и коды причин см. в разделе [Квитанции и коды причин](https://war3ai.com/ru/docs/reason-codes/).

## 6. Роли полос

| Роль | Что может |
|---|---|
| `dev` | Локальные инструменты: семантические команды (управление юнитами локального игрока) + JASS-канал |
| `player` | Только семантические команды и только для юнитов игрока, которому принадлежит полоса (чужие = `not_owner`) |
| `observer` | Только запросы, камера, чтение состояния панели HUD, включение холста и локального ввода; всё остальное — `forbidden` |

Два AI играют друг против друга = две полосы `player` в одной партии (player 0 / player 1).

> **Внимание**
>
> В локальном режиме роль объявляет сам клиент (это соглашение, а не граница безопасности). На [Арене](https://war3ai.com/ru/arena/) полосы создаёт процесс-арбитр и выдаёт каждому участнику только полосу `player`.

## 7. Семантика, проверенная в реальных партиях

- Правый клик (smart) по врагу = атаковать **именно его** (и цель приказа, и текущая цель — этот юнит); «сырой» приказ атаки, отправленный как команда на цель, только переключает приказ на атаку, не запоминая цель, и юнит уходит бить кого-то другого поблизости;
- движок не принимает команды на цель по невидимым юнитам: с наступлением ночи дальние лагеря уходят в туман войны, и любой правый клик отклоняется (1001);
- «принята» у постройки означает лишь, что рабочий принял приказ: точка в лесу тоже принимается сразу, а неудача случается, только когда рабочий дойдёт до места; явно занятые точки отклоняются сразу;
- героя можно воскресить лишь примерно через 3 игровые секунды после смерти; при нехватке пищи тоже будет отказ (герои занимают пищу);
- предметы в инвентаре не считаются предметами на земле; при подборе приходит `item.removed`;
- на паузе часы движка стоят, но команды отдавать можно;
- у игры, запущенной свёрнутой, симуляция стоит (часы не идут).
