SDK игровых событий · Протокол v1

Передавайте игровые события в Hovermate

Игра или мод могут сообщать об убийствах, победах над боссами, кругах, голах и собственных событиях. Hovermate добавляет маркеры на шкалу времени и автоматически сохраняет яркие моменты, как для встроенных карточек игр.

Клиенты для C#, C++, Java, Python, JavaScript и Lua · По одному файлу без зависимостей · Лицензия MIT

Как это работает

  1. Мод сообщает о событии. Достаточно одного вызова, например event("bossKill"). Клиент отправляет его в фоне и не блокирует игру.
  2. Hovermate ставит маркер. Событие появляется на шкале записи и сессии с вашей подписью.
  3. Игрок выбирает яркие моменты. У игры появляется своя карточка в разделе «Клипы». Для включённых игроком типов событий также сохраняются клипы.

Пока SDK включён, Hovermate записывает адрес, токен и папку входящих событий в connection.json:

connection.json
{
  "version": 1,
  "url": "http://local.hovermate.gg/hovermate/v1/events",
  "token": "<random, base64url, 32 bytes>",
  "inbox": "C:\\Users\\<user>\\AppData\\Local\\Hovermate\\Sdk\\Inbox"
}

Берите url из этого файла и используйте его точно в том виде, в каком он записан; никогда не прописывайте в коде и не меняйте его хост, порт или путь.

Клиенты читают %LOCALAPPDATA%\Hovermate\Sdk\connection.json либо путь из HOVERMATE_SDK_CONNECTION для тестов и изолированных настроек. После ответа 401 файл читается снова: токен меняется при выключении и включении SDK. Если файла нет, Hovermate не установлен или SDK выключен; клиент ничего не отправляет.

Три способа передачи событий

Выберите первый способ, доступный в среде вашего мода.

HTTP

Мод может открывать локальные сетевые подключения.

Отправьте событие методом POST на указанный URL с Authorization: Bearer <token>. Запросы с заголовком Origin отклоняются, поэтому веб-страницы не могут отправлять события.

C#, C++, Java, Python, JavaScript; в GDScript доступен HTTPRequest.

Файлы

Мод может записывать файлы, но не имеет доступа к сети.

Добавляйте по одному JSON-событию на строку в {inbox}\<game.id>.jsonl; токен не нужен. Если среда разрешает запись только в папку мода, как Cyber Engine Tweaks, записывайте там hovermate-events.jsonl. Игрок добавляет этот файл в карточке игры.

Все клиенты при недоступности HTTP переходят на входящую папку; клиент Lua пишет файл в папке мода.

Строки журнала

Скрипты могут писать только в собственный журнал игры.

Выведите [HOVERMATE] , затем один JSON-объект события. Всё до маркера игнорируется. Журналы scriptslog.txt The Witcher 3 и Papyrus.0.log Skyrim и Fallout 4 читаются автоматически, пока игра запущена.

Скрипты Witcher 3, Papyrus и другие игры с журналом скриптов.

HTTP-запрос

POST /hovermate/v1/events HTTP/1.1
Host: local.hovermate.gg
Authorization: Bearer <token from connection.json>
Content-Type: application/json
Content-Length: 95

{"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"kill","label":"Double kill"}

Ответ: 202 {"accepted":1}.

Строка журнала

Строка в журнале игры
[12:00:01] [Script] [HOVERMATE] {"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"bossKill","context":"Act 2"}

Другой журнал игрок добавляет через «Клипы» › карточку игры › Читать события из файла; также его может предложить событие hello.

Быстрый старт

Каждый пример отправляет hello, kill, bossKill и пользовательское событие parry. Это готовые к запуску файлы из папки examples SDK.

C#

Клиент
HovermateEvents.cs
Среда выполнения
netstandard2.0, .NET Framework 4.7.2+ и .NET с C# 7.3: Unity, BepInEx, MelonLoader.
Настройка
Добавьте HovermateEvents.cs в проект мода.
csharp/Program.cs
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run: dotnet run
using System;
using Hovermate.Sdk;

HovermateEvents.Log = Console.WriteLine;
HovermateEvents.Init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
HovermateEvents.Hello(new HovermateOffer[] { "kill", "bossKill", HovermateOffer.Custom("parry", "Parry") });
HovermateEvents.Event("kill", label: "Double kill", tags: new[] { "headshot" }, context: "Act 2");
HovermateEvents.Event("bossKill", label: "The Warden", context: "Act 2");
HovermateEvents.Custom("parry", "Parry", label: "Perfect parry");
Console.WriteLine(HovermateEvents.Flush(2000) ? "sent" : "timed out");
HovermateEvents.Shutdown();

Запустите из examples/csharp (пример рассчитан на .NET 8 и новее):

dotnet run

C++

Клиент
hovermate_events.h
Среда выполнения
C++17 в Windows. Только заголовочный файл; используется WinHTTP и подключается winhttp.lib.
Настройка
Подключите hovermate_events.h. Вызовите hovermate::shutdown() перед выгрузкой DLL, за пределами DllMain.
cpp/example.cpp
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Build (Developer Command Prompt): cl /std:c++17 /EHsc /utf-8 /I..\..\cpp example.cpp
#include <cstdio>
#include "hovermate_events.h"

int main() {
    hovermate::set_log([](const std::string& message) { std::printf("%s\n", message.c_str()); });
    hovermate::init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
    hovermate::hello({ "kill", "bossKill", hovermate::Offer::custom("parry", "Parry") });
    hovermate::event("kill", { "Double kill", { "headshot" }, "Act 2" });
    hovermate::event("bossKill", { "The Warden", {}, "Act 2" });
    hovermate::custom("parry", "Parry", { "Perfect parry", {}, {} });
    std::printf("%s\n", hovermate::flush(2000) ? "sent" : "timed out");
    hovermate::shutdown();
    return 0;
}

Запустите в командной строке разработчика из examples/cpp:

cl /std:c++17 /EHsc /utf-8 /I..\..\cpp example.cpp

Java

Среда выполнения
Java 8 и новее: ModTheSpire, Forge, Fabric.
Настройка
Скопируйте HovermateEvents.java в мод и измените пакет либо переместите его при shading, чтобы копии в разных модах не конфликтовали.
java/Example.java
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run (Java 8+): javac -d out ../../java/HovermateEvents.java Example.java && java -cp out Example
import gg.hovermate.sdk.HovermateEvents;
import gg.hovermate.sdk.HovermateEvents.Offer;
import java.util.Arrays;

public class Example {
    public static void main(String[] args) {
        HovermateEvents.log = System.out::println;
        HovermateEvents.init("com.example.sdkdemo", "SDK Demo", "SdkDemo.exe");
        HovermateEvents.hello(Offer.of("kill"), Offer.of("bossKill"), Offer.custom("parry", "Parry"));
        HovermateEvents.event("kill", "Double kill", Arrays.asList("headshot"), "Act 2");
        HovermateEvents.event("bossKill", "The Warden", null, "Act 2");
        HovermateEvents.custom("parry", "Parry", "Perfect parry", null, null);
        System.out.println(HovermateEvents.flush(2000) ? "sent" : "timed out");
        HovermateEvents.shutdown(1000);
    }
}

Запустите из examples/java:

javac -d out ../../java/HovermateEvents.java Example.java && java -cp out Example

Python

Среда выполнения
Python 3.7 и новее, только стандартная библиотека.
Настройка
Поместите hovermate_events.py рядом со скриптом.
python/example.py
"""Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).

Run from any folder: python example.py
"""
import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'python'))
import hovermate_events as hovermate  # noqa: E402

hovermate.set_log(print)
hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe')
hovermate.hello(['kill', 'bossKill', hovermate.custom_offer('parry', 'Parry')])
hovermate.event('kill', label='Double kill', tags=['headshot'], context='Act 2')
hovermate.event('bossKill', label='The Warden', context='Act 2')
hovermate.custom('parry', 'Parry', label='Perfect parry')
print('sent' if hovermate.flush(2.0) else 'timed out')
hovermate.shutdown()

Запустите из examples/python:

python example.py

JavaScript

Среда выполнения
Node.js 18 и новее, модуль ES.
Настройка
Импортируйте hovermate-events.mjs.
js/example.mjs
// Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py).
// Run with Node.js 18+: node example.mjs
import * as hovermate from '../../js/hovermate-events.mjs';

hovermate.setLog(console.log);
hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe');
hovermate.hello(['kill', 'bossKill', hovermate.customOffer('parry', 'Parry')]);
hovermate.event('kill', { label: 'Double kill', tags: ['headshot'], context: 'Act 2' });
hovermate.event('bossKill', { label: 'The Warden', context: 'Act 2' });
hovermate.custom('parry', 'Parry', { label: 'Perfect parry' });
console.log((await hovermate.flush(2000)) ? 'sent' : 'timed out');
await hovermate.shutdown();

Запустите из examples/js:

node example.mjs

Lua

Среда выполнения
Lua 5.1+ и LuaJIT: Cyber Engine Tweaks и другие среды без сети.
Настройка
Поместите hovermate_events.lua рядом с init.lua. События записываются в hovermate-events.jsonl в папке мода; попросите игроков добавить этот файл в Hovermate.
lua/example.lua
-- Sends hello, kill, bossKill and a custom "parry" event to Hovermate (or to tools/mock-server.py --watch).
-- Run from the folder that stands in for your mod folder: lua path/to/example.lua
-- It writes hovermate-events.jsonl into the current folder.
--
-- In a Cyber Engine Tweaks mod, copy hovermate_events.lua next to init.lua and call it from your events:
--   local hovermate = require("hovermate_events")
--   registerForEvent("onInit", function()
--     hovermate.init("com.example.mymod", "My Mod", "Cyberpunk2077.exe", { log = print })
--     hovermate.hello({ "kill", "bossKill", hovermate.customOffer("parry", "Parry") })
--   end)
-- Then tell your users to add <game>\bin\x64\plugins\cyber_engine_tweaks\mods\<your mod>\hovermate-events.jsonl
-- in Hovermate: Clips > the game's card > Read events from a file.

local here = (arg and arg[0] or ''):match('^(.*[\\/])') or './'
package.path = here .. '../../lua/?.lua;' .. package.path
local hovermate = require('hovermate_events')

hovermate.init('com.example.sdkdemo', 'SDK Demo', 'SdkDemo.exe', { log = print, writer = print })
print('transport', hovermate.transport())
hovermate.hello({ 'kill', 'bossKill', hovermate.customOffer('parry', 'Parry') })
hovermate.event('kill', { label = 'Double kill', tags = { 'headshot' }, context = 'Act 2' })
hovermate.event('bossKill', { label = 'The Warden', context = 'Act 2' })
hovermate.custom('parry', 'Parry', { label = 'Perfect parry' })
print('log line for script-only mods:', hovermate.logLine('kill', { label = 'Double kill' }))
hovermate.shutdown()

Запустите из папки, используемой вместо папки мода:

lua example.lua

Поведение клиентов

ВызовДействие
init(gameId, gameName, exe?)Запускает клиент. gameId соответствует [a-z0-9._-]{3,64} и не меняется между версиями мода; exe — имя исполняемого файла игры.
hello(offers, log?, files?)Объявляет типы событий игры и, при необходимости, журнал или файлы в папке мода для чтения.
event(type, {label, tags, context})Отправляет событие стандартного типа.
custom(customType, customLabel, {…})Отправляет собственный тип события; customLabel задаёт подпись его переключателя ярких моментов.
flush() / shutdown()Дожидается отправки очереди и останавливает фоновый обработчик при выгрузке мода.
  • Вызовы возвращаются сразу и не передают исключения в игру.
  • Фоновый обработчик отправляет события с тайм-аутом 250 мс и один раз повторяет неудачный запрос.
  • Очередь вмещает 256 событий; при переполнении удаляется самое старое.
  • Если HTTP недоступен, события несколько секунд отправляются во входящую папку.
  • Без connection.json клиент ничего не отправляет и повторяет проверку каждые 5 секунд.
  • Каждое событие содержит game.name. Текст обрезается до указанных ниже пределов, а некорректные теги отбрасываются без ошибки.

Клиент Lua (hovermate_events.lua) записывает события сразу. Сначала используется hovermate-events.jsonl в папке мода, затем входящая папка, если доступны абсолютные пути, затем строки [HOVERMATE] через переданную функцию writer, например print. Вызов logLine(type, options) возвращает такую строку для самостоятельного вывода.

Объект события

{ "v": 1,
  "game": { "id": "com.example.mygame", "name": "My Game", "exe": "MyGame.exe" },
  "type": "kill",
  "label": "Double kill",
  "tags": ["headshot"],
  "context": "Act 2 · The City",
  "time": "2026-11-09T21:15:03.120Z" }
ПолеОбязательноПравило
vда1
game.idда[a-z0-9._-]{3,64}: обратное DNS-имя или короткий идентификатор, неизменный между версиями мода
game.nameпервое событиеВ первом событии или hello за сессию; не более 64 символов
game.exeнетТолько имя файла. Связывает события с найденной папкой игры и позволяет этой игре запускать буфер повтора
typeдаСтандартный тип из списка ниже либо custom
customType, customLabelдля custom[a-z0-9_.-]{1,32} и подпись переключателя длиной до 40 символов
labelнетПодпись конкретного события для подсказки маркера и названия клипа; до 80 символов
tagsнетДо 8 тегов, каждый соответствует [a-z0-9_-]{1,24}
contextнетКарта, уровень или область для названий ярких моментов; до 64 символов
timeнетISO-8601 UTC. Hovermate отмечает время получения; значение time в пределах 5 с уточняет эту отметку

Длина считается в кодовых единицах UTF-16: эмодзи занимает 2. Управляющие символы в текстовых полях запрещены. Неизвестные поля игнорируются, поэтому новые версии лишь добавляют поля. Не отправляйте персональные данные: подписи и контекст могут попадать в названия клипов и файлов.

Стандартные типы

29 типов, общих со встроенными карточками игр Hovermate. Список только расширяется; неизвестный стандартный тип отклоняется.

  • kill
  • death
  • assist
  • multikill
  • roundWon
  • roundLost
  • matchWon
  • matchLost
  • mvp
  • goal
  • save
  • objective
  • mission
  • lap
  • bestLap
  • position
  • crash
  • incident
  • penalty
  • pit
  • finish
  • raceWon
  • landing
  • takeoff
  • fightWon
  • levelUp
  • bossKill
  • zone
  • achievement

Пользовательские типы

Для событий, которых нет в стандартном списке. У каждого типа свой переключатель с подписью customLabel; на игру допускается до 200 типов.

{ "v": 1, "game": { "id": "com.example.mygame" },
  "type": "custom", "customType": "parry", "customLabel": "Parry", "label": "Perfect parry" }

hello

Необязательно, но рекомендуется. Создаёт карточку игры с переключателями до первого события, чтобы игрок мог настроить её заранее.

{ "v": 1,
  "game": { "id": "com.example.mygame", "name": "My Game", "exe": "MyGame.exe" },
  "type": "hello",
  "offers": ["kill", "death", "bossKill", { "customType": "parry", "customLabel": "Parry" }],
  "log": "%LOCALAPPDATA%\\MyGame\\Saved\\Logs\\MyGame.log",
  "files": ["C:\\Games\\MyGame\\mods\\my_mod\\hovermate-events.jsonl"] }
  • offers перечисляет стандартные типы и объекты { "customType", "customLabel" }.
  • log предлагает журнал игры со строками [HOVERMATE]; files — файлы событий в папке мода. Перед чтением предложенного пути Hovermate спрашивает разрешение игрока.
  • hello работает через любой способ передачи, включая строку журнала [HOVERMATE].

Ограничения и ответы

ОграничениеЗначение
События на игру20 в секунду. При превышении возвращается 429, и весь запрос отклоняется; hello не учитывается
Пакет{ "events": [ … ] } с 1–100 событиями, целиком или никак. Новую игру нужно назвать в первом упоминающем её событии пакета
Тело HTTP-запроса64 КБ
Строки входящих файлов4 КБ; более длинные строки отбрасываются
Пользовательские типы200 на игру
Тайм-ауты клиентаНе более 250 мс, не более одного повтора; ограниченная очередь удаляет самое старое событие
СтатусЗначение
202Принято: {"accepted": n}
400Некорректные данные: {"error", "detail", "index"}. index указывает первое ошибочное событие пакета
401Токен отсутствует или неверен: перечитайте connection.json
403Запрос содержит заголовок Origin
413Тело запроса больше 64 КБ
429Более 20 событий в секунду для одной игры
405, 411, 415Метод не POST, нет Content-Length либо тип содержимого не JSON

Коды ошибок: invalid_json, version, game, type, custom, label, tags, context, offers, log, files, batch. Некорректные строки файлов учитываются в карточке игры как ошибочные события; Hovermate не записывает их исходный текст в журнал.

Проверка без Hovermate

Тестовый приёмник mock-server.py (Python 3.8+, стандартная библиотека) отвечает как Hovermate. Он создаёт свой connection.json во временной папке, использует те же правила и коды ошибок, читает входящую папку и файлы из --watch, выводит все события и ошибки.

В отличие от Hovermate, тестовый приёмник слушает числовой порт на loopback-адресе и записывает этот адрес в свой connection.json. Это только инструмент для тестов: клиенты используют тот url, который записан в файле.

python tools/mock-server.py
# In another terminal, with the path it printed:
$env:HOVERMATE_SDK_CONNECTION = "C:\Users\you\AppData\Local\Temp\hovermate-sdk-mock-…\connection.json"
python examples/python/example.py
Вывод тестового приёмника
Hovermate SDK mock listening on http://127.0.0.1:52777/hovermate/v1/events
[21:15:03.120] http  com.example.sdkdemo hello offers=kill,bossKill,custom:parry
[21:15:03.131] http  com.example.sdkdemo kill "Double kill" tags=headshot context="Act 2"
[21:15:03.140] http  com.example.sdkdemo bossKill "The Warden" context="Act 2"
[21:15:03.148] http  com.example.sdkdemo custom:parry "Parry" "Perfect parry"
  • --watch FILE читает добавленные строки файла мода hovermate-events.jsonl либо журнала игры со строками [HOVERMATE].
  • --follow-proposals читает пути, предложенные в hello, как если бы игрок разрешил доступ.
  • --record FILE записывает каждое принятое и отклонённое событие строкой JSON для автоматических проверок.

HOVERMATE_SDK_CONNECTION так же работает с самим Hovermate, поэтому тесты не затрагивают реальный файл подключения.

Проверки соответствия

В SDK есть conformance/cases.json: запросы с ожидаемыми ответами Hovermate, от корректных событий до каждого кода ошибки. conformance/run.py отправляет их тестовому или любому другому приёмнику. Hovermate использует этот же файл в своих тестах.

python conformance/run.py
# or against a running receiver:
python conformance/run.py --connection path\to\connection.json

Что видит игрок

  • Первое корректное событие или hello для нового game.id создаёт карточку SDK в «Клипах»: название игры, папка и обложка при совпадении exe с установленной игрой и переключатель каждого объявленного или полученного типа.
  • Изначально яркие моменты выключены: игрок выбирает типы для сохранения клипов. Маркеры записываются всегда.
  • События проходят тот же путь, что и встроенные карточки: журнал, маркеры в клипах и сессиях, окно ярких моментов и уведомления.
  • Если game.exe совпадает с активной игрой, события относятся к ней. Иначе они принимаются только во время записи или работы буфера повтора.
  • Ошибочные события учитываются в статусе карточки, чтобы игрок мог сообщить о проблеме.

Протокол и файлы

Протокол v1 задаёт полный контракт: поиск подключения, четыре способа доставки, объект события, hello и поведение Hovermate.

SDK — клиенты, примеры, инструменты, проверки соответствия и протокол — доступен по MIT, поэтому клиент можно включить в мод. На само Hovermate эта лицензия не распространяется.