Vonix Server Utilities Vonix Mods v2.2.0 Search GitHub
Vonix Server Utilities/Features/Kits & crates

Kits and crates#

Two independent reward systems share this page. Kits are item bundles defined in a JSON file and claimed on a cooldown. Crates are weighted prize tables backed by virtual keys, spent one key at a time.

Kit definitions#

Kits live in config/vonix_server_utilities/kits.json. The file is created on first launch with three working kits, so the feature is usable before you edit anything.

The root is an object with a single kits array:

{
  "kits": [
    {
      "name": "starter",
      "group": "starter",
      "cooldown_seconds": 3600,
      "one_time": false,
      "items": [
        { "item": "minecraft:stone_sword", "count": 1 },
        { "item": "minecraft:bread", "count": 16 }
      ]
    }
  ]
}

Schema#

Key Type Default Meaning
name string required Kit identifier. Trimmed and lower-cased; used by /kit <name>.
group string kit name Claim-window group. Missing, blank, or non-text values fall back to the kit name.
cooldown_seconds integer 3600 Seconds before the group can be claimed again. Non-numeric values fall back to 3600.
one_time boolean false When true, the group may be claimed once ever instead of on a cooldown.
items array empty List of item objects. An empty list produces a claimable kit that grants nothing.
items[].item string required Namespaced item id, e.g. minecraft:iron_pickaxe. Entries without a string id are skipped.
items[].count integer 1 Stack size to give. Non-numeric values fall back to 1.

An entry with no usable name is skipped with a warning. An entry naming an item id that does not exist in the registry is also skipped, item by item, so one bad id does not discard the rest of the kit.

Group claim windows#

group is the field that makes kits interesting. The cooldown is enforced per group, not per kit name. A player may hold at most one active claim inside a group, while kits in different groups stay independently claimable.

That gives you two useful patterns:

  • Class kits. Give warrior, mage, and ranger the same group value. A player picks one and cannot claim another until the shared cooldown expires.
  • One-time bundles. Give a starter pack "one_time": true. The first claim writes a row; every later claim in that group returns "already claimed".

Because a missing group defaults to the kit name, existing single-kit files keep their old per-kit behaviour with no edits. That default is also why the seeded starter, tools, and food kits are independent despite sharing a file.

Seeded defaults#

On first launch VSU writes three kits:

Kit Cooldown One time Contents
starter 3600 s false stone sword, stone pickaxe, stone axe, 16 bread, 32 torches
tools 7200 s false iron pickaxe, axe, shovel, and hoe
food 1800 s false 32 cooked beef, 2 golden apples, 1 cake

If kits.json is unreadable or its root is not a JSON object, the kit manager logs the failure and seeds the same three kits in memory. A broken file therefore degrades to the defaults rather than leaving the server with no kits.

Using kits#

Command Effect
/kits List loaded kit names
/kit Same as /kits
/kit <name> Claim a kit
/kit reload Re-read kits.json from disk
/vonixsu reload Re-read the properties file, then kits.json

Claim outcomes are explicit:

  • Success — items are added to the inventory; anything that does not fit is dropped at the player's feet.
  • Not found — the name is not loaded.
  • On cooldown — the remaining time is printed in a compact 1h 12m form.
  • Already claimed — the group is one-time and this player has used it.

The eligibility check and the write happen in one transaction against the database, under a process-wide lock, so two simultaneous claims cannot both succeed.

Cooldown storage#

Claims persist in vsu_kit_cooldowns:

CREATE TABLE vsu_kit_cooldowns (
    uuid        TEXT NOT NULL,
    kit_name    TEXT NOT NULL,
    claim_group TEXT NOT NULL,
    last_used   INTEGER NOT NULL,
    PRIMARY KEY (uuid, kit_name)
);

The lookup that decides a claim is SELECT MAX(last_used) ... WHERE uuid=? AND claim_group=?, so the group is what actually gates the window. claim_group was added after the table's first release; databases created before it are upgraded in place on startup:

  1. The column is added with an empty default if it is missing.
  2. Rows with a null or blank group are backfilled from lower(kit_name).
  3. An index on (uuid, claim_group) is created.

The migration never drops the table, never rewrites last_used, and never touches rows that already carry a group. It is safe to run on every start, which is exactly what happens.

Crates#

Crates are virtual prize boxes. A crate has a key type, a set of weighted prizes, and an enabled flag. Opening a crate consumes one key of that type and runs the winning prize's command.

Two crates are created on first start: playtime with key type playtime, and event with key type event. Both are enabled and start with no prizes, so an /crate open on a fresh install reports crate_empty until you add one.

Player commands#

Command Node Effect
/crate list vsu.command.crate List crates, key type, enabled state
/crate open <crate> vsu.command.crate Spend one key, draw a prize, run its command
/crate prizes <crate> vsu.command.crate List prizes with weight and computed percentage
/crate key balance <type> vsu.command.crate Your balance of one key type

Administration#

All of these require vsu.admin.crate at operator level 2.

Command Effect
/crate create <crate> <key_type> Create a crate; fails if the name already exists
/crate delete <crate> Delete a crate and its prizes
/crate enable <crate> Allow the crate to be opened
/crate disable <crate> Refuse opens without deleting anything
/crate prize add <crate> <label> <weight> <command> Add a prize; a new id is allocated per crate
/crate prize remove <crate> <id> Remove one prize by id
/crate key give <player> <type> <amount> Add keys, 1 to 1,000,000
/crate key take <player> <type> <amount> Remove keys; refuses to go below zero

Names and key types must match [a-z0-9_-]{1,32} and are lower-cased. Prize commands are stored without a leading slash, and {player} in the command string is replaced with the claiming player's name at execution time.

Prize percentages are derived, not stored: /crate prizes divides each weight by the sum of positive weights. Weights are integers from 1 to 1,000,000.

Playtime keys#

A tick task runs once per minute and compares each online player's vanilla playtime statistic against playtime_key_interval_minutes. Each completed interval grants one playtime key.

Progress is tracked in vsu_playtime_key_progress by the number of intervals already awarded, so restarts do not double-grant and a player who plays for three hours at a 60-minute interval receives three keys in one pass rather than one.

The award cannot be turned off through the config. playtime_key_interval_minutes is clamped to a minimum of 1 when the file is read, so a value of 0 or a negative number behaves as 1 minute — the most aggressive setting, not the disabled one. Pick a large interval such as 100000 if you want the system effectively dormant.

Prize execution and refund#

Opening a crate is transactional and crash-safe:

  1. Check the crate exists, is enabled, and the player holds at least one key.
  2. Read prizes with a positive weight and draw one by weight.
  3. Debit the key and insert a PENDING claim row in the same transaction.
  4. Run the prize command on the main thread with suppressed output.
  5. On success, mark the claim COMPLETED. On failure or a parse error, refund the key and mark it REFUNDED.

A crash between steps 3 and 5 leaves a PENDING row. On the next start VSU refunds every pending claim it finds, so a server that dies mid-open costs the player nothing.

Crate tables#

Table Purpose
vsu_crates Crate name, key type, enabled flag, updated timestamp
vsu_crate_prizes Prize id, label, command, weight, per crate
vsu_player_keys Key balance per player per key type
vsu_crate_claims One row per open, with status and completion time
vsu_playtime_key_progress Intervals already awarded per player

Column detail for these tables is on Database & storage.

Worked example#

/crate create vote vote
/crate prize add vote "1x diamond" 70 "give {player} minecraft:diamond 1"
/crate prize add vote "32x bread" 25 "give {player} minecraft:bread 32"
/crate prize add vote "netherite ingot" 5 "give {player} minecraft:netherite_ingot 1"
/crate key give Steve vote 3

/crate prizes vote then reports roughly 70.00%, 25.00%, and 5.00%. A player runs /crate open vote three times, spending one key each time; the fourth attempt reports missing_key:vote.

Reload behaviour#

/kit reload re-reads kits.json only. /vonixsu reload re-reads the properties file and then kits.json. Neither touches crate data, because crates live in the database rather than a file.

Editing kits.json while the server runs is safe: nothing watches the file, so your edit takes effect on the next reload or restart, not mid-claim. Cooldowns survive a reload because they are keyed by group name; renaming a group resets the effective window for everyone.