Документация Справочник

Протокол W3P

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

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

  • Эталонная реализация — на 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>вы → рантаймХолст: заголовок 64 байта + 256 элементов × 112 байт + пул текста / точек 64 KB; создаётся только после одного вызова canvas_enableseqlock (пишете вы, рантайм читает каждый кадр)
Local\War3Msgs_<pid>рантайм → выКольцо экранных сообщений: полный текст подсказок игры, чата и системных сообщений, 128 записей × 256 байту каждой записи свой порядковый номер
Local\War3Input_<pid>в обе стороныИнтерфейс и ввод: рантайм записывает обратно позицию мыши, точку на земле под курсором и элемент под курсором; вы записываете таблицу горячих клавиш и переключатели мыши; рантайм начинает перехватывать ввод только после одного вызова 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)

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. Чтение событий

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 в цель).

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

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

5. Квитанции

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

Все коды состояния и коды причин см. в разделе Квитанции и коды причин.

6. Роли полос

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

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

В локальном режиме роль объявляет сам клиент (это соглашение, а не граница безопасности). На Арене полосы создаёт процесс-арбитр и выдаёт каждому участнику только полосу player.

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

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