Description
Overview
Every stats, achievement, leveling and map plugin on a Hytale server hooks the same engine events for itself. Block breaks, deaths, pickups, movement, chat. With four such plugins the server runs four copies of the same tracking and the numbers still disagree, because each one applies its own rules.
TrackerAPI does the tracking once. It registers one set of hooks for every interaction the server exposes, works out the facts other plugins used to compute on their own, such as whether a broken block was placed by the same player or who took part in a boss fight, and hands the result to any plugin that asks. Plugins that support it stop registering their own hooks and read from TrackerAPI instead. The server pays for the tracking once, the counts agree, and new plugins get all of it without writing an engine system.
It is a server plugin. Players join with an unmodified client, and it does nothing visible on its own. Install it beside plugins that support it, or build your own on top of it.
What it tracks
Sixty two kinds of event, among them
- Blocks broken, placed, used and damaged, by block and position, with the tool in hand and whether the player placed that block themself.
- Items crafted, picked up, gained, lost, eaten, dropped, equipped and broken, with what came from a chest and which chest.
- Every hit and every death, with attacker, victim, cause, ranged or not, every player who took part in a boss fight and who came through it untouched.
- Creatures spawned, removed and tamed, projectiles fired and landed.
- Players connecting, joining, leaving, changing world, respawning, teleporting, chatting, running commands, changing game mode, discovering zones and instances, entering portals, mounting, sleeping, recording Memories, learning recipes, completing objectives, gaining reputation and having permissions changed.
- A position sample every second with metres walked, swum, glided or ridden, and the jumps, falls, landings, glides, sprints and crouches in between.
- World events starting, signalling and ending, trigger volumes, weather, moon phases, the in game clock, worlds added and removed, chunks generated, loaded, saved and unloaded.
Counters
TrackerAPI keeps lifetime counters for every player who ever joined, in one JSON file per player. Totals such as blocks broken, per id counts such as block_broken:Rock_Stone or kill:Goblin_Scrapper, per world copies, bests such as the biggest hit and the longest session, and server wide totals. A names index answers who a uuid was, and leaderboards over every player are rebuilt every minute. Plugins can add counters under their own prefix and have them saved with everything else.
On the first boot TrackerAPI reads the player files of Feats and Chronicle Stats that are already on the server and seeds its counters from them, taking the larger value where both counted the same thing, so nothing starts from zero.
Journal
An on disk record of past events, one line per event in hourly files per world, compressed when the hour closes, pruned by age and by total size. Ask who broke the blocks around a spot last night with /trackerapi lookup here --radius 8 --hours 12, or search by player, kind and time. The high volume kinds such as damage and position samples stay out of it by default.
Works with
| Plugin | What changes |
|---|---|
| Feats 1.5.0 or newer | Registers none of its own tracking systems and feeds every feat from TrackerAPI. Counts carry on in the Feats files without a gap. |
| Chronicle Stats 1.2.0 or newer | Takes every statistic, record and dashboard number from TrackerAPI and shows "TrackerAPI connected" at the top right of the dashboard and the /stats page. |
| Atlas 1.6.0 or newer | Takes its activity heat layer and kill feed from TrackerAPI. |
Each of them keeps working without TrackerAPI and switches over by itself when it is installed. Each has a config key, Tracker in Feats and tracker in the others, with auto, always and never.
Performance
A hook nobody wants costs one volatile read. A wanted one builds one small record on the world thread and hands it to TrackerAPI's own dispatch thread through a lock free queue, about a hundred nanoseconds and eighty bytes per event in the benchmark. Plugins that subscribe run off the world thread with a time budget per batch, and one that overruns is suspended for five minutes with a warning, so a slow plugin never slows the tick. /trackerapi perf shows what every hook costs with a one line verdict.
Commands
All of them need the trackerapi.admin permission. The console has every permission.
| Command | What it does |
|---|---|
/trackerapi status |
Version, uptime, every subscription with its owner and delivery, queue depth, drops, journal size |
/trackerapi perf |
Cost of every hook on the engine threads with a verdict, perf reset clears it |
/trackerapi recent <kind> [n] |
The last events of a kind |
/trackerapi lookup <player or here> [--radius n] [--kinds a,b] [--hours n] [--limit n] |
Search the journal |
/trackerapi counters <player> [prefix] |
A player's counters |
/trackerapi top <key> [n] |
A leaderboard |
/trackerapi import <feats or chronicle or all> [--force] |
Read the other mods' player files again |
/trackerapi reload |
Read TrackerAPI.json again |
Configuration
mods/Ryvrook_TrackerAPI/TrackerAPI.json is written on the first boot with every key at its default. Missing keys are written back, and values outside their range are clamped with one warning. /trackerapi reload reads it again.
| Key | Default | Meaning |
|---|---|---|
disabledKinds |
[] |
Kinds that are never tracked, by their lower case name such as block_damaged |
countCreative |
false |
Count block, item and combat events of players in Creative mode |
perWorldCounters |
true |
Keep per world copies of the totals |
autosaveSeconds |
60 |
How often the counters are saved |
boardSeconds |
60 |
How often the leaderboards are rebuilt, 0 turns them off |
queueCapacity |
65536 |
Size of the async queue, events past it are dropped and counted |
listenerBudgetMillis |
50 |
How long a listener may take per batch before it is warned, and suspended after twenty such batches in a row |
sampleSeconds |
1 |
Seconds between position samples |
vitalsSeconds |
0 |
Seconds between health, stamina and mana samples, 0 turns them off |
chatRecordText |
false |
Record the text of chat messages, otherwise only the length |
journal.enabled |
true |
Keep the on disk journal |
journal.kinds |
every kind that is not high volume | Kinds written to the journal |
journal.retentionDays |
14 |
Days of journal kept |
journal.maxMegabytes |
1024 |
Size cap for the journal, oldest files go first |
importOnFirstBoot |
true |
Read the Feats and Chronicle Stats player files once |
debugCommands |
false |
Turn on /trackerapi debug |
For plugin developers
Compile against the jar, list Ryvrook:TrackerAPI as an optional dependency in your manifest, keep every use of its classes in one class you load after TrackerApi.isPresent(), and subscribe from TrackerApi.onEnable.
TrackerApi.onEnable(api -> {
api.subscribe("MyPlugin", BlockBroken.class, e -> {
if (!e.playerPlaced()) {
counts.merge(e.blockId(), 1L, Long::sum);
}
});
long kills = api.counters().get(player, Keys.KILLS);
});
Every event is an immutable record with the kind, the time, a sequence number, the world and the player, plus its own fields. Records only ever gain fields and the API only ever gains methods. Listeners run on TrackerAPI's dispatch thread by default and may take their time within the budget. A listener that needs engine state in the same tick can ask for sync delivery and must return in microseconds. The full list of kinds is the EventKind enum in the jar, with one record per kind under dev.ryvrook.trackerapi.api.event.
Compatibility
- Requires the Hytale 0.7.0 release server or newer, tested on 0.7.1.
- No client mod. Players join with an unmodified client.
- Lives beside any other plugin. It never cancels or changes an event, it only observes.
- A
/plugin reloadof TrackerAPI reloads the plugins that use it two seconds later by itself, so reload TrackerAPI first and the rest follows. - Chunk unloads and prefab pastes are read from the world thread only. The engine invokes those two events from its parallel workers, and a plugin that hooks them directly crashes the world, so TrackerAPI does not and the PREFAB_PASTED kind is never published for now.
Changelog
1.0.3
- After a reload of TrackerAPI the plugins that use it are reloaded automatically two seconds later.
- The default listener budget is 50 ms per batch instead of 5 ms.
1.0.2
- Queued bench crafts count one run per craft event. The engine fires one event per finished unit of a bench queue and stamps each with the whole batch quantity, so adding the quantity counted a batch of a hundred as ten thousand.
1.0.1
- Chunk unloads are read from the chunk store's lifecycle callback instead of the engine's unload event, which the engine invokes from a parallel worker. Hooking it crashed the world as soon as chunks unloaded with players online. The prefab paste hook is off for the same reason.
1.0.0
- First release with sixty two event kinds, subscriptions, counters, leaderboards, the journal and the admin commands.
- First boot import of the Feats and Chronicle Stats player files.
- Supported by Feats 1.5.0, Chronicle Stats 1.2.0 and Atlas 1.6.0.
Links
- Source: https://github.com/ryvrook/trackerapi (Private)
- Discord:
https://discord.gg/mKgxyk6qvb
License
MIT


