Install, command, configuration and data reference for server operators. Scuffed Combat Stats is an operator-only balancing tool: it records every Endless Leveling damage-meter fight into a local SQLite database, writes a detailed JSON file for each raid, and includes a guided experiment mode for measuring one variable at a time. It adds nothing that normal players see or use, so there is no gameplay guide for it.
- Plugin version: 0.8.2-pre.1 (plugin name
scuffed-combat-stats)
Requirements
| Plugin | Version | Type |
|---|---|---|
EndlessLevelingCore (Airijko:EndlessLevelingCore) | ≥ 10.6.0 | Required |
The manifest declares ServerVersion >=0.7.0-pre.0 <0.8.0. If Endless Leveling is not present at startup, the plugin logs a warning and does nothing.
Capture depth depends on the Endless Leveling build you run. Each level is detected at startup and switches on automatically; an older Endless Leveling simply skips the levels it does not support.
| Level | Needs from Endless Leveling | What you get |
|---|---|---|
| Base | 10.6.0 or newer | One database row per player per fight: totals, class, race, level, augment loadout, stat line, damage-source split, held weapons |
| Combat telemetry | A build that carries the combat telemetry API (first shipped in 11.10.1) | Per-augment, per-class-ability, per-weapon and per-victim damage tables, plus the ability and swing damage totals |
| Damage breakdown | A build that carries the per-hit damage breakdown API | Stage-by-stage damage decomposition in the per-raid JSON |
The startup log states which levels are active ("combat telemetry detected" and "damage-breakdown API detected" lines).
The guided experiment mode (/scs collectdata) additionally needs the preset-build and augment-slot methods in Endless Leveling's API. When they are missing, the command refuses to start and names what it could not find.
Install notes
- Ship the jar whose name ends in
-all(for exampleScuffedCombatStats-0.8.2-pre.1-all.jar). It is the only build that bundles the SQLite driver. The plain jar contains noorg.sqliteclasses and fails with aNoClassDefFoundErroron the first database write. - Keep only one Scuffed Combat Stats jar in
mods/. Two jars with the same plugin name are refused at boot. - The
-alljar is trimmed to the Windows and Linux SQLite native libraries. The macOS, FreeBSD, Android and Musl natives are excluded. - Capture is on by default. A fresh install starts recording the first fight that Endless Leveling freezes.
Admin & Staff Commands
All commands live under /scs. They are operator-only: the plugin registers no permission nodes and never opens its commands to non-operators, so Hytale's default operator check is the only gate.
Every subcommand must be run by a player in game. They cannot be run from the server console.
| Command | Description |
|---|---|
/scs status | Shows capture ON or OFF, number of stored rows, time since the last fight and how many players it held, the current balance tag, whether an experiment run is active, and the database path |
/scs on | Resume capture. Takes effect immediately and persists across restarts |
/scs off | Stop all capture immediately. Damage listeners, the hotbar sampler, raid windows and fight writes all become no-ops, including for the fight in progress. Persists across restarts |
/scs tag <name> | Set the balance tag stamped on future fights (see below). While an experiment run is active, this retargets the tag the server returns to when the run ends instead |
/scs summary | Per-primary-class aggregate over all stored fights of at least 1 second: sample count, average damage share, damage per minute and mitigation percent. Shows up to 12 classes, highest average damage share first |
/scs best [--min <n>] | Ranks (class, race, weapon) combinations by median damage per hit. --min is the minimum number of rows per combination (default 3, minimum 1). Shows the top 15 and also prints median damage per second at freeze and average crit rate |
/scs raid status | Shows whether raid capture is enabled, whether the damage-breakdown API is present, and every open raid window with its player count, hits, credited damage, stage-detail state and age. Also shows the last file written |
/scs raid last | Path and headline numbers of the most recent per-raid JSON written since the last restart |
/scs collectdata | Start a guided experiment run, or report on the one already going (see below) |
/scs collectdata next | Finish the current step and set up the next one, or finish the run after the last step |
/scs collectdata stop | Abandon the run and restore the previous balance tag |
/scs summary and /scs best run on the database thread, not the world thread, so they do not stall a tick. When there is nothing to show, /scs summary says no fights are captured yet and /scs best suggests lowering --min.
Permissions
None. The plugin defines no permission nodes. Access follows Hytale's default rule for registered commands: operators only.
Configuration
Settings live in mods/ScuffedCombatStats/config.json, created with defaults on first start.
| Key | Default | Effect |
|---|---|---|
enabled | true | Master capture switch. Changed with /scs on and /scs off |
balanceTag | "baseline" | Label stamped on every captured fight. Changed with /scs tag |
minFightTopDamage | 1.0 | A fight whose top player dealt less than this damage is dropped as noise. 0 keeps everything |
raidCaptureEnabled | true | Open a capture window when an Endless Leveling raid starts and write a per-raid JSON when it ends. Independent of enabled, but a raid window only opens while enabled is also true |
raidSampleHitsPerPlayer | 200 | Fully expanded per-hit breakdowns kept per player in the raid JSON, sampled evenly across the whole raid. 0 keeps only the sums |
raidJsonDir | "raids" | Folder under mods/ScuffedCombatStats/ that raid JSON files are written to |
raidIncludeTestRaids | true | Also capture raids that Endless Leveling marks as test raids. They are written with "testRaid": true |
raidSessionMaxMs | 7200000 | Safety limit for one raid window (2 hours). A window open longer than this is force-closed and written with endedBy set to timeout. The check runs when another raid window opens. Builds after 0.8.2-pre.1 also run it on the next hit recorded for the stale window. 0 disables the limit |
The file is read once at startup. Edit it while the server is stopped; there is no reload command. enabled and balanceTag are rewritten by their commands.
Balance tags
The balance tag is a free-text label copied onto every row of a fight. Change it with /scs tag patch-1.2 whenever you push a balance pass, then compare WHERE balance_tag = '...' groups in the database to see the before and after.
Data & storage
Everything the plugin owns sits in mods/ScuffedCombatStats/.
| Path | Contents |
|---|---|
config.json | Settings above |
scuffedcombatstats.db | SQLite database of captured fights (WAL mode, so -wal and -shm files appear beside it while the server runs) |
raids/<eventId>.json | One file per captured raid (folder name set by raidJsonDir) |
experiment-protocol.json | Editable experiment steps, written the first time a run starts |
experiment-run.json | State of the active experiment run. Removed when the run ends |
There is no automatic pruning. The database and the raid folder grow until you delete rows or files yourself.
What a fight row records
When Endless Leveling freezes a damage-meter leaderboard, the plugin writes one row per player into the fights table. Totals, kills, healing, class, race and level are copied from Endless Leveling's own leaderboard entry. Nothing is re-derived except damage share, crit rate and mitigation percent.
| Column group | Contents |
|---|---|
| Fight | captured_at, balance_tag, event_id, world_id, world_name, content_type (RAID, DUNGEON or OTHER), content_name, started_at, stopped_at, duration_ms, party_size, ally_classes (the other party members' primary classes) |
| Player | uuid, name, primary_class, secondary_class, race, level, prestige, temp_profile, profile_index, augments_json (loadout at freeze time) |
| Damage | total_damage, dps_at_freeze, damage_share, hit_count, crit_count, crit_rate, kill_count, best_kill_streak |
| Defence and support | total_taken, total_mitigated, mitigation_pct, total_healing, total_shielding, total_buff |
| Damage by source | src_direct, src_bonus, src_dot, src_true, src_summon, plus unattributed, src_overflow and sources_balanced |
| Ability and swing | ability_damage_total, swing_damage_total (needs combat telemetry) |
| Weapon | weapon_item_id and weapon_source |
| Timing | first_damage_ms, last_damage_ms |
| Provenance | addon_version, el_version, server_mod_fingerprint |
Notes on specific values:
dps_at_freezeis Endless Leveling's rolling 5-second damage rate at the moment the fight froze, not a fight average. A fight that ended in a lull reads near zero. Dividetotal_damagebyduration_msfor an average.- The five
src_*columns are the plugin's own observation of the damage stream. Whatever is missing against Endless Leveling's total goes inunattributed, and any excess goes insrc_overflow. Nothing is scaled to fit.sources_balancedis 1 when the gap is within 2 percent of the fight total. weapon_sourcesays how the weapon was chosen:PHASE2_TOP(largest damage in the per-weapon table),HELD_TOP(item held longest) orHELD_AT_FREEZE(item in hand when the fight froze).server_mod_fingerprintis a short hash of the sorted jar file names inmods/. Equal values mean the same mod set. Rows written in the first moments after startup may carry empty provenance values.- Leaderboards with no entries, or where the top player's damage is below
minFightTopDamage, are not stored.
Related tables
| Table | One row per | Columns |
|---|---|---|
augment_damage | fight row and augment | fight_id, augment, damage |
ability_damage | fight row and class ability | fight_id, ability, damage |
weapon_damage | fight row and weapon | fight_id, weapon, damage |
victim_damage | fight row and victim type | fight_id, victim, damage, max_health, overkill |
fight_attributes | fight row and attribute | fight_id, attribute, race_base, skill_bonus, external_bonus, total |
fight_items | fight row and held item | fight_id, item_id, swings, held_ms |
fight_id joins to fights.id. Augment and ability damage are kept in separate tables because the two id spaces overlap. The first four tables fill only when combat telemetry is available. fight_attributes covers Sorcery, Precision, Ferocity, Strength, Haste, Defense, Life Force and Flow. Augment, ability and victim tables fold ids past 256 per bucket into an (overflow) row so totals stay complete.
The schema upgrades itself on startup. Older databases keep all their rows and gain NULL in any new columns.
Reading the data
The database is a normal SQLite file. Open it with DB Browser for SQLite or DBeaver, or export to CSV. The in-game /scs status output prints its absolute path. To copy it while the server runs, copy the -wal and -shm files together with the main file, or stop the server first.
Per-raid JSON
When an Endless Leveling raid starts, the plugin opens a capture window keyed by the raid's shared world name. When Endless Leveling freezes the raid's leaderboard (completion, admin stop, world removal or shutdown), the plugin writes raids/<eventId>.json. A window that never gets a leaderboard is closed by the raidSessionMaxMs limit (see the config table for when that check runs), and any window still open at shutdown is flushed to disk. Files are written to a temporary file and moved into place, so a reader never sees a half-written file. Windows with no recorded damage write nothing.
The file contains:
raid: event id and name, import id, world names and ids, shard count, total waves, start, stop and duration,testRaid, party size, andendedBy(snapshot,timeoutorshutdown). Anything other thansnapshotcan mean partial totals.provenance: add-on version, Endless Leveling version, mod fingerprint, balance tag, whether the breakdown API was available, and the per-player sample size.totals: hits, credited and computed damage, weapon base damage, true damage, rounding loss and overall amplification.players: for each player, identity, leaderboard figures, capture counters, anamplificationblock (how the raw weapon roll became credited damage, with per-stage factors),stageTotals,stageAverages, damage by source, weapon, ability and augment, andsampleHits(up toraidSampleHitsPerPlayerfully expanded hits).
Without the damage-breakdown API in Endless Leveling, the stage figures are absent (breakdownAvailable is false) and the file carries credited totals and the per-source, weapon, ability and augment splits only. Players who left before the raid ended keep the identity captured on their first hit. For sharded raids spanning several worlds, damage is attributed to the matching window; when two or more raids run at once and a hit arrives from an unseen world, the hit is dropped rather than booked to the wrong raid.
Guided experiment runs
Passive capture cannot answer a class or weapon question when every player differs in gear, augments and skill. An experiment run fixes this by putting one operator into the same preset build at the start of every step so that steps differ in exactly one thing.
Starting a run
- Put at least one preset build in Endless Leveling's builds folder. The first build id Endless Leveling lists is the run's primary build (
*), the second is the comparison build (*alt). - Move to the admin temp profile with
/lvl temp grant. A run rebuilds the operator's character on every step, so it is only allowed on the throwaway admin sandbox profile, never on a real character. - Run
/scs collectdata. It starts the run, applies the first step and prints the instruction, the target, and the tag.
The server has one run at a time. Typing /scs collectdata again reports the status: run id, protocol name, step number, operator, step time, rows recorded on this step against its target, and the instruction. The exception is a step that is not set up (see Tags below): for the operator who started the run, the bare command retries the setup instead.
During a run
- Do what the step says, then run
/scs collectdata next. Eachnextre-applies the step's build and augments and takes over the balance tag. - The duration and fight targets in a step are guidance shown to the operator. Nothing advances automatically.
- Only the operator who started the run can advance or stop it.
/scs offpauses a run without cancelling it. The run keeps its step and tag, and records nothing until/scs on. The status output warns when a run is active with capture off.- After the last step,
nextends the run.stopabandons it at any time. Both restore the balance tag to what it was before the run and deleteexperiment-run.json.
Tags
Each step tags its fights run:<runId>/step:<n>/<tagSuffix>, where <runId> is the start time as yyyyMMdd-HHmmss. If Endless Leveling refuses the build or an augment, the step is marked not set up and its tag gets /SETUPFAIL appended, so analysis can skip rows that did not get the build they asked for. Run /scs collectdata again to retry the setup.
A run survives a restart. On the next boot it comes back on the same step but is held as not set up, because the admin temp profile does not survive a restart. The operator runs /scs collectdata to re-apply the step. Rows captured before that are tagged SETUPFAIL.
Protocol file
The steps are data, in mods/ScuffedCombatStats/experiment-protocol.json. The built-in default is written there the first time a run starts. The file is read when a run starts and the resolved steps are copied into the run, so editing it mid-run does not change a run in progress.
| Key | Meaning |
|---|---|
name, notes | Protocol label and free text |
steps[].enabled | false skips the step (default true) |
steps[].buildId | Preset build id, * for the primary build, *alt for the second build, or - to leave the character as the previous step left it |
steps[].augments | Map of augment slot key (for example ELITE#1) to augment id, applied after the build. Use none to empty a slot |
steps[].tagSuffix | Distinguishing part of the tag. Lowercased; anything other than letters, digits, -, _ and . becomes -. Defaults to step<n> |
steps[].durationSeconds | Time target shown to the operator, 0 for none |
steps[].targetFights | Fight-count target shown to the operator, 0 for none |
steps[].instruction | Text shown to the operator |
A step that asks for *alt is dropped when only one preset build exists. If the file is missing, unreadable or has no enabled steps, the built-in protocol is used.
The shipped protocol is weapon-isolation-v1. Four enabled steps, each 300 seconds or 3 fights, hold the same build and change only the weapon: weapon-a, weapon-b, weapon-c, then weapon-a-repeat as a drift control. Two further steps ship disabled as worked examples: one ELITE augment (weapon-a-elite-augment) and the second preset build (alt-build-weapon-a).
Integrations
| Plugin | Type | Use |
|---|---|---|
| EndlessLevelingCore | Required | Damage events, leaderboard snapshots, attribute and augment lookups, raid start and end hooks, preset builds for experiment runs |
The plugin only reads from Endless Leveling and from the player's hotbar. It does not change gameplay, damage numbers or player data, apart from the experiment run rebuilding the operator's admin temp profile. It has no documented API for other mods.
Performance
Capture runs on the damage path, so it is built to stay cheap. Fight inserts and every admin query run on a dedicated database thread, raid and config files are written off-thread, and the held-weapon sampler touches the clock only when a player swaps items. /scs off is the quickest way to rule the plugin out when troubleshooting a lag report.
Troubleshooting
| Symptom | Likely cause |
|---|---|
NoClassDefFoundError for org.sqlite on the first write | The plain jar was deployed. Use the -all jar |
| Plugin logs "Endless Leveling not detected - plugin will do nothing" | EndlessLevelingCore is not installed or not loaded |
/scs status shows capture OFF | Someone ran /scs off. The setting persists across restarts, so run /scs on |
fights stored stays 0 | Capture is off, no leaderboard has frozen yet, or every fight stayed under minFightTopDamage |
| Augment, ability, weapon or victim tables are empty | The running Endless Leveling build has no combat telemetry API. Check the startup log |
| Raid JSON has no stage detail | The running Endless Leveling build has no damage-breakdown API. breakdownAvailable is false in the file |
| No raid file after a raid | raidCaptureEnabled is false, capture was off, or the raid recorded no damage. /scs raid status shows open windows and the last file |
/scs collectdata says you are not on the admin temp profile | Run /lvl temp grant, then try again |
/scs collectdata says no preset builds are loaded | Add a build JSON to Endless Leveling's builds folder and reload |
A step shows REFUSED and SETUPFAIL | Endless Leveling rejected the build or an augment the class cannot take. Fix the protocol, then rerun /scs collectdata |