Funktionsweise
- Dein Mod meldet einen Moment. Ein Aufruf genügt, etwa
event("bossKill"). Der Client sendet im Hintergrund und blockiert das Spiel nicht. - Hovermate markiert ihn. Jedes Ereignis wird mit deiner Beschriftung auf der Aufnahme- und Sitzungszeitleiste markiert.
- 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:
{
"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
[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#
- Client
- HovermateEvents.cs
- 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.
// 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 runC++
- Client
- hovermate_events.h
- 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.
// 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.cppJava
- Client
- HovermateEvents.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.
// 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 ExamplePython
- Client
- hovermate_events.py
- Laufzeitumgebung
- Python 3.7 oder neuer, nur Standardbibliothek.
- Einrichtung
- Lege hovermate_events.py neben dein Skript.
"""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.pyJavaScript
- Client
- hovermate-events.mjs
- Laufzeitumgebung
- Node.js 18 oder neuer als ES-Modul.
- Einrichtung
- Importiere hovermate-events.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.mjsLua
- Client
- hovermate_events.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.
-- 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.luaVerhalten der Clients
| Aufruf | Wirkung |
|---|---|
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.jsonbleibt 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" }| Feld | Erforderlich | Regel |
|---|---|---|
v | ja | 1 |
game.id | ja | [a-z0-9._-]{3,64}: umgekehrter DNS-Name oder Kurzkennung, zwischen Mod-Versionen unverändert |
game.name | erstes Ereignis | Im ersten Ereignis oder hello der Sitzung; höchstens 64 Zeichen |
game.exe | nein | Nur ein Dateiname. Verknüpft Ereignisse mit dem erkannten Spielordner und erlaubt dieser Anwendung, den Replay-Puffer zu starten |
type | ja | Ein Standardtyp unten oder custom |
customType, customLabel | für custom | [a-z0-9_.-]{1,32} und ein Schaltername mit höchstens 40 Zeichen |
label | nein | Text dieses Ereignisses für Markierungsinfo und Clip-Titel; höchstens 80 Zeichen |
tags | nein | Höchstens 8 Tags, jeweils passend zu [a-z0-9_-]{1,24} |
context | nein | Karte, Level oder Bereich für Highlight-Titel; höchstens 64 Zeichen |
time | nein | ISO-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.
killdeathassistmultikillroundWonroundLostmatchWonmatchLostmvpgoalsaveobjectivemissionlapbestLappositioncrashincidentpenaltypitfinishraceWonlandingtakeofffightWonlevelUpbossKillzoneachievement
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"] }offersenthält Standardtypen und{ "customType", "customLabel" }-Objekte.logschlägt ein Spielprotokoll mit[HOVERMATE]-Zeilen vor;filesEreignisdateien im Mod-Ordner. Hovermate fragt den Spieler vor dem Lesen vorgeschlagener Pfade.hellofunktioniert über jeden Übertragungsweg, auch als[HOVERMATE]-Protokollzeile.
Grenzen und Antworten
| Grenze | Wert |
|---|---|
| Ereignisse pro Spiel | 20 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-Nachrichtentext | 64 KB |
| Eingangs- und Dateizeilen | 4 KB; längere Zeilen werden verworfen |
| Eigene Typen | 200 pro Spiel |
| Client-Zeitlimits | Höchstens 250 ms, maximal ein Wiederholungsversuch, begrenzte Warteschlange mit Entfernen des ältesten Ereignisses |
| Status | Bedeutung |
|---|---|
202 | Akzeptiert: {"accepted": n} |
400 | Ungültig: {"error", "detail", "index"}. index nennt das erste ungültige Ereignis des Stapels |
401 | Token fehlt oder ist falsch: connection.json erneut lesen |
403 | Die Anfrage enthält einen Origin-Header |
413 | Nachrichtentext über 64 KB |
429 | Mehr als 20 Ereignisse pro Sekunde für ein Spiel |
405, 411, 415 | Nicht 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.pyHovermate 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 FILEverfolgt einehovermate-events.jsonlim Mod-Ordner oder ein Spielprotokoll mit[HOVERMATE]-Zeilen.--follow-proposalsliest die vonhellovorgeschlagenen Pfade, als hätte der Spieler zugestimmt.--record FILEhä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.jsonWas Spieler sehen
- Das erste gültige Ereignis oder
helloeiner neuengame.iderstellt eine SDK-Karte unter Clips: Spielname, Ordner und Cover bei Übereinstimmung vonexemit 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.exemit 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.