Spielereignis-SDK · Protokoll v1

Sende Spielereignisse an Hovermate

Spiele und Mods können Abschüsse, besiegte Bosse, Runden, Tore und eigene Momente melden. Hovermate erstellt Zeitleistenmarkierungen und automatische Highlight-Clips, genau wie bei den integrierten Spielkarten.

Clients für C#, C++, Java, Python, JavaScript und Lua · Je eine Datei ohne Abhängigkeiten · MIT-Lizenz

Funktionsweise

  1. Dein Mod meldet einen Moment. Ein Aufruf genügt, etwa event("bossKill"). Der Client sendet im Hintergrund und blockiert das Spiel nicht.
  2. Hovermate markiert ihn. Jedes Ereignis wird mit deiner Beschriftung auf der Aufnahme- und Sitzungszeitleiste markiert.
  3. Der Spieler wählt Highlights. Dein Spiel erhält eine eigene Karte unter Clips. Für jeden aktivierten Ereignistyp wird zusätzlich ein Highlight-Clip gespeichert.

Bei aktiviertem SDK schreibt Hovermate Adresse, Token und Eingangsordner in 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"
}

Lesen Sie url aus dieser Datei und verwenden Sie es genau so, wie es dort steht; Host, Port oder Pfad nie fest einprogrammieren oder ändern.

Clients lesen %LOCALAPPDATA%\Hovermate\Sdk\connection.json oder für Tests und isolierte Einstellungen den Pfad aus HOVERMATE_SDK_CONNECTION. Nach einer Antwort 401 lesen sie erneut, da sich das Token beim Aus- und Einschalten des SDK ändert. Fehlt die Datei, ist Hovermate nicht installiert oder das SDK ausgeschaltet; Clients bleiben dann still.

Drei Übertragungswege

Verwende den ersten Weg, den deine Mod-Umgebung erlaubt.

HTTP

Dein Mod kann eine lokale Netzwerkverbindung öffnen.

Sende das Ereignis per POST mit Authorization: Bearer <token> an die URL. Anfragen mit einem Origin-Header werden abgewiesen, sodass Webseiten keine Ereignisse senden können.

C#, C++, Java, Python, JavaScript; GDScript kann HTTPRequest verwenden.

Dateien

Dein Mod kann Dateien schreiben, hat aber keinen Netzwerkzugriff.

Hänge pro Zeile ein JSON-Ereignis an {inbox}\<game.id>.jsonl an; ein Token ist nicht nötig. Eine auf den Mod-Ordner beschränkte Sandbox wie Cyber Engine Tweaks schreibt stattdessen dort in hovermate-events.jsonl; der Spieler fügt diese Datei in der Spielkarte hinzu.

Alle Clients weichen auf den Eingangsordner aus; der Lua-Client schreibt in den Mod-Ordner.

Protokollzeilen

Deine Skripte können nur in das eigene Spielprotokoll schreiben.

Gib [HOVERMATE] gefolgt von einem JSON-Ereignis aus; alles vor der Markierung wird ignoriert. scriptslog.txt von The Witcher 3 und Papyrus.0.log von Skyrim und Fallout 4 werden während des Spiels automatisch gelesen.

Witcher-3-Skripte, Papyrus und alle Spiele mit Skriptprotokoll.

Eine HTTP-Anfrage

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"}

Antwort: 202 {"accepted":1}.

Eine Protokollzeile

Eine Zeile im Spielprotokoll
[12:00:01] [Script] [HOVERMATE] {"v":1,"game":{"id":"com.example.mygame","name":"My Game"},"type":"bossKill","context":"Act 2"}

Andere Protokolle fügt der Spieler unter Clips › Spielkarte › Ereignisse aus Datei lesen hinzu; alternativ schlägt dein hello sie vor.

Schnellstart

Jedes Beispiel sendet hello, kill, bossKill und ein eigenes parry-Ereignis. Es sind die ausführbaren Dateien aus dem SDK-Ordner examples.

C#

Laufzeitumgebung
netstandard2.0, .NET Framework 4.7.2+ und .NET mit C# 7.3: Unity, BepInEx, MelonLoader.
Einrichtung
Füge HovermateEvents.cs deinem Mod-Projekt hinzu.
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();

In examples/csharp ausführen (das Beispiel benötigt .NET 8 oder neuer):

dotnet run

C++

Laufzeitumgebung
C++17 unter Windows. Nur Header; nutzt WinHTTP und bindet winhttp.lib ein.
Einrichtung
Binde hovermate_events.h ein. Rufe hovermate::shutdown() vor dem Entladen deiner DLL auf, niemals innerhalb von 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;
}

In einer Entwickler-Eingabeaufforderung in examples/cpp ausführen:

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

Java

Laufzeitumgebung
Java 8 oder neuer: ModTheSpire, Forge, Fabric.
Einrichtung
Kopiere HovermateEvents.java in deinen Mod und ändere das Package oder verlege es beim Shading, damit Kopien anderer Mods nicht kollidieren.
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);
    }
}

In examples/java ausführen:

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

Python

Laufzeitumgebung
Python 3.7 oder neuer, nur Standardbibliothek.
Einrichtung
Lege hovermate_events.py neben dein Skript.
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()

In examples/python ausführen:

python example.py

JavaScript

Laufzeitumgebung
Node.js 18 oder neuer als ES-Modul.
Einrichtung
Importiere 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();

In examples/js ausführen:

node example.mjs

Lua

Laufzeitumgebung
Lua 5.1+ und LuaJIT: Cyber Engine Tweaks und andere Umgebungen ohne Netzwerk.
Einrichtung
Lege hovermate_events.lua neben init.lua. Ereignisse landen in hovermate-events.jsonl im Mod-Ordner; bitte Spieler, diese Datei in Hovermate hinzuzufügen.
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()

Im Ordner ausführen, der deinen Mod-Ordner ersetzt:

lua example.lua

Verhalten der Clients

AufrufWirkung
init(gameId, gameName, exe?)Startet den Client. gameId entspricht [a-z0-9._-]{3,64} und bleibt zwischen Mod-Versionen gleich; exe ist der Name der ausführbaren Spieldatei.
hello(offers, log?, files?)Meldet die verfügbaren Ereignistypen und optional ein Protokoll oder Dateien im Mod-Ordner zum Lesen an.
event(type, {label, tags, context})Sendet einen Standardereignistyp.
custom(customType, customLabel, {…})Sendet einen eigenen Typ; customLabel benennt dessen Highlight-Schalter.
flush() / shutdown()Wartet auf eingereihte Ereignisse und beendet den Hintergrundprozess beim Entladen des Mods.
  • Aufrufe kehren sofort zurück und geben niemals Ausnahmen an das Spiel weiter.
  • Ein Hintergrundprozess sendet mit 250 ms Zeitlimit und wiederholt fehlgeschlagene Anfragen einmal.
  • Die Warteschlange fasst 256 Ereignisse und verwirft das älteste.
  • Ist HTTP nicht erreichbar, gehen Ereignisse einige Sekunden an den Eingangsordner.
  • Ohne connection.json bleibt der Client still und prüft alle 5 Sekunden erneut.
  • Jedes Ereignis enthält game.name. Text wird auf die folgenden Grenzen gekürzt; ungültige Tags werden verworfen, statt Fehler auszulösen.

Der Lua-Client (hovermate_events.lua) schreibt jedes Ereignis sofort. Vorrang hat hovermate-events.jsonl im Mod-Ordner, dann der Eingangsordner bei erlaubten absoluten Pfaden, dann [HOVERMATE]-Zeilen über die übergebene Funktion writer, etwa print. logLine(type, options) liefert eine solche Zeile zum eigenen Ausgeben.

Das Ereignisobjekt

{ "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" }
FeldErforderlichRegel
vja1
game.idja[a-z0-9._-]{3,64}: umgekehrter DNS-Name oder Kurzkennung, zwischen Mod-Versionen unverändert
game.nameerstes EreignisIm ersten Ereignis oder hello der Sitzung; höchstens 64 Zeichen
game.exeneinNur ein Dateiname. Verknüpft Ereignisse mit dem erkannten Spielordner und erlaubt dieser Anwendung, den Replay-Puffer zu starten
typejaEin Standardtyp unten oder custom
customType, customLabelfür custom[a-z0-9_.-]{1,32} und ein Schaltername mit höchstens 40 Zeichen
labelneinText dieses Ereignisses für Markierungsinfo und Clip-Titel; höchstens 80 Zeichen
tagsneinHöchstens 8 Tags, jeweils passend zu [a-z0-9_-]{1,24}
contextneinKarte, Level oder Bereich für Highlight-Titel; höchstens 64 Zeichen
timeneinISO-8601 UTC. Hovermate erfasst die Ankunftszeit; time innerhalb von 5 s präzisiert sie

Längen zählen UTF-16-Codeeinheiten; ein Emoji zählt als 2. Textfelder lehnen Steuerzeichen ab. Unbekannte Felder werden ignoriert, sodass spätere Versionen nur Felder ergänzen. Sende keine personenbezogenen Daten: Beschriftungen und Kontexte können in Clip-Titeln und Dateinamen erscheinen.

Standardtypen

29 gemeinsame Typen mit den integrierten Spielkarten. Die Liste wächst nur; unbekannte Standardtypen werden abgewiesen.

  • 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

Eigene Typen

Für Momente außerhalb der Standardliste. Jeder Typ bekommt einen eigenen Highlight-Schalter mit dem Namen aus customLabel; bis zu 200 je Spiel.

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

hello

Optional und empfohlen. Erstellt die Spielkarte samt Highlight-Schaltern vor dem ersten echten Ereignis, damit Spieler sie vorab einstellen können.

{ "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 enthält Standardtypen und { "customType", "customLabel" }-Objekte.
  • log schlägt ein Spielprotokoll mit [HOVERMATE]-Zeilen vor; files Ereignisdateien im Mod-Ordner. Hovermate fragt den Spieler vor dem Lesen vorgeschlagener Pfade.
  • hello funktioniert über jeden Übertragungsweg, auch als [HOVERMATE]-Protokollzeile.

Grenzen und Antworten

GrenzeWert
Ereignisse pro Spiel20 pro Sekunde. Darüber folgt 429, und nichts aus der Anfrage wird akzeptiert; hello zählt nicht mit
Stapel{ "events": [ … ] } mit 1–100 Ereignissen, ganz oder gar nicht. Ein neues Spiel muss im ersten Ereignis genannt werden, das es im Stapel erwähnt
HTTP-Nachrichtentext64 KB
Eingangs- und Dateizeilen4 KB; längere Zeilen werden verworfen
Eigene Typen200 pro Spiel
Client-ZeitlimitsHöchstens 250 ms, maximal ein Wiederholungsversuch, begrenzte Warteschlange mit Entfernen des ältesten Ereignisses
StatusBedeutung
202Akzeptiert: {"accepted": n}
400Ungültig: {"error", "detail", "index"}. index nennt das erste ungültige Ereignis des Stapels
401Token fehlt oder ist falsch: connection.json erneut lesen
403Die Anfrage enthält einen Origin-Header
413Nachrichtentext über 64 KB
429Mehr als 20 Ereignisse pro Sekunde für ein Spiel
405, 411, 415Nicht POST, fehlendes Content-Length oder kein JSON-Inhaltstyp

Fehlercodes: invalid_json, version, game, type, custom, label, tags, context, offers, log, files, batch. Fehlerhafte Dateizeilen werden als ungültige Ereignisse in der Spielkarte gezählt; Hovermate protokolliert sie niemals im Rohformat.

Ohne Hovermate testen

Der Testempfänger mock-server.py (Python 3.8+, Standardbibliothek) antwortet genau wie Hovermate. Er schreibt eine eigene connection.json in einen temporären Ordner, verwendet dieselben Regeln und Fehlercodes, verfolgt Eingang und mit --watch angegebene Dateien und zeigt jedes Ereignis und jeden Fehler an.

Anders als Hovermate lauscht der Testempfänger auf einem numerischen Loopback-Port und schreibt diese Adresse in seine connection.json. Er ist nur ein Testwerkzeug: Clients verwenden die url, die in der Datei steht.

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
Ausgabe des Testempfängers
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 verfolgt eine hovermate-events.jsonl im Mod-Ordner oder ein Spielprotokoll mit [HOVERMATE]-Zeilen.
  • --follow-proposals liest die von hello vorgeschlagenen Pfade, als hätte der Spieler zugestimmt.
  • --record FILE hängt jedes akzeptierte und abgewiesene Ereignis als JSON-Zeile für automatische Prüfungen an.

HOVERMATE_SDK_CONNECTION funktioniert genauso mit Hovermate selbst; Tests berühren daher nie die echte Verbindungsdatei.

Konformitätsfälle

Das SDK enthält conformance/cases.json: Anfragen mit erwarteten Hovermate-Antworten, von gültigen Ereignissen bis zu jedem Fehlercode. conformance/run.py sendet sie an den Testempfänger oder jeden anderen Empfänger. Hovermate nutzt dieselbe Datei in seinen Tests.

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

Was Spieler sehen

  • Das erste gültige Ereignis oder hello einer neuen game.id erstellt eine SDK-Karte unter Clips: Spielname, Ordner und Cover bei Übereinstimmung von exe mit einem installierten Spiel sowie ein Schalter für jeden angekündigten oder empfangenen Typ.
  • Highlight-Schalter sind anfangs aus: Spieler wählen die Typen zum Speichern von Clips. Markierungen werden immer geschrieben.
  • Ereignisse durchlaufen denselben Weg wie integrierte Karten: Journal, Clip- und Sitzungsmarkierungen, Highlight-Fenster und Benachrichtigungen.
  • Stimmt game.exe mit dem Vordergrundspiel überein, werden Ereignisse diesem Spiel zugeordnet. Sonst werden sie nur bei laufender Aufnahme oder Replay-Puffer angenommen.
  • Ungültige Ereignisse werden im Kartenstatus gezählt, damit Spieler dir Probleme melden können.

Protokoll und Dateien

Das Protokoll v1 ist der vollständige Vertrag: Verbindungserkennung, vier Zustellwege, Ereignisobjekt, hello und Hovermate-Verhalten.

Das SDK mit Clients, Beispielen, Werkzeugen, Konformitätsfällen und Protokoll steht unter MIT; du darfst einen Client in deinem Mod mitliefern. Hovermate selbst ist nicht von dieser Lizenz erfasst.