Skip to content

Loot System (loottables)

In: Server Owner Guide · Related: Realm & Keystone · Affix System · Blueprints & Objectives · Placeholders · Command Reference

QR's output is driven by loot tables (loottables/*.yml), delivered to players through three dispense channels:

ChannelTriggerWho shares itGrowth scalingSee
Container lootPlace a vanilla chest/barrel/furnace… in the structure, right-click to openPer-player / server-sharedYes (when per-player)§3
Blueprint reward chestRight-click the chest block after a clear unlocks itPer-playerYes§4
Cleanse slot machineSpin via /qr reward after a realm is cleansedPrivately per participantYes§5

🔑 One table can serve multiple channels at once — it's just a recipe of "what to roll, how many times to roll"; the template/blueprint decides who references it.

🖼️ [Image placeholder] Overview diagram of the three dispense flows (table → container/reward chest/slot machine → player) · suggested assets/loot-overview.png


1. Where Loot Tables Live and How They Load

Every table is a .yml file under plugins/QinhRuins/loottables/, and the file name (minus .yml) is the table name. For example, loottables/vault.yml's table name is vault; reference it in a template by writing loot.container-table: vault.

QR ships three example tables:

FileTable nameTypical use
default.ymldefaultFallback table for editor-marked chests (guards against "reward table missing")
vault.ymlvaultContainer loot + conditional group demo
realm.ymlrealmRealm cleanse exclusive jackpot (referenced by default in config.yml)

Run /qr reload after editing to apply.


2. The Table Structure in Full

A complete loot table can use the following fields (all optional except entries):

yaml
# loottables/vault.yml
rolls: 2                              # how many times the main entries are rolled (each roll picks one entry by weight)
containers: [CHEST, BARREL]           # only takes effect as a container table: limits which container types use this table (empty=all)
# vanilla: minecraft:chests/simple_dungeon   # optional: roll a vanilla loot table first, then layer the entries below on top
entries:                             # main entry pool
  - { item: "vanilla:DIAMOND",            weight: 10, amount: "1-3" }
  - { item: "vanilla:GOLD_INGOT",         weight: 20, amount: "2-5" }
  - { item: "vanilla:EXPERIENCE_BOTTLE",  weight: 15, amount: "2-6" }
groups:                             # conditional groups: only join the roll if condition is met
  rich:
    condition: "%player_level% >= 5"  # via PlaceholderAPI; if PAPI isn't installed, this condition is always true
    rolls: 1
    entries:
      - { item: "vanilla:EMERALD", weight: 1, amount: 2 }

2.1 rolls — How Many Times to Roll

rolls decides how many times this table "rolls"; each roll picks one entry at random by weight from the entry pool. rolls: 2 = roughly 2 items output (the same entry may be picked more than once). Minimum is 1.

The cleanse slot machine channel ignores rolls — it flattens the whole table (including groups) into one prize pool and spins out only one as the jackpot (see §5).

2.2 entries — The Entry Pool

Each entry is a single { ... } line, with these fields:

FieldRequiredDescription
itemItem source string, see §2.6
weightWeight (larger = more frequent); defaults to 1 if omitted, minimum 1
amountQuantity; a fixed value 2 or a range "2-5" (random within the range each time); defaults to 1 if omitted
min-growthGrowth threshold: if the player's growth is below this value, the entry doesn't join this roll, see §6.2
uniqueWhen true, weighted by the realm "Hidden Trove" affix (drop rate ×multiplier), see §6.4
yaml
entries:
  - item: "vanilla:NETHERITE_SCRAP"
    weight: 8
    amount: "1-2"
    min-growth: 30        # only droppable at growth ≥30
    unique: true          # an exclusive goodie weighted by the "Hidden Trove" affix

Tip

A reversed amount range (e.g. "5-2") is auto-corrected to 2-5, no need to worry.

2.3 groups — Conditional Groups

groups are extra entry pools that "only open if a condition is met". Each group has its own condition, rolls, entries. The main entries always roll; a group rolls an extra round only when its condition is true.

yaml
groups:
  rich:                                 # group name is arbitrary (identifier only)
    condition: "%player_level% >= 5"    # see the condition rules below
    rolls: 1                            # how many times this group rolls on its own
    entries:
      - { item: "vanilla:EMERALD", weight: 1, amount: 2 }
  vip:
    condition: "%vault_eco_balance% >= 10000"
    rolls: 1
    entries:
      - { item: "qi-legendary_chest", weight: 1 }

condition rules (same line of thinking as realm affix scripts / QS conditions):

  • Written as left-value comparator right-value, with both sides first resolved through PlaceholderAPI before comparison.
  • Supported comparators: >= <= == != > <.
  • If both sides can be turned into numbers → numeric comparison; otherwise string comparison (only == / != are meaningful, case-insensitive).
  • Empty or no parsable comparator = always true — so without PlaceholderAPI installed, condition is always treated as passing, and the group is dispensed as usual.
yaml
condition: "%player_level% >= 5"       # level ≥5
condition: "%player_world% == world"   # in the main world (string equality)
condition: ""                          # always true (equivalent to no condition)

2.4 vanilla — Layer in a Vanilla Loot Table

Fill in a vanilla loot table ID and QR will roll a batch of vanilla loot first, then layer the entries / groups above on top. Good for "reuse vanilla dungeon/fortress drops, then add custom goodies":

yaml
vanilla: minecraft:chests/simple_dungeon   # roll a batch of dungeon-chest vanilla drops first
rolls: 2                                    # then layer 2 extra rolls of custom entries
entries:
  - { item: "qi-rune_shard", weight: 5, amount: "1-2" }

Common vanilla table IDs: minecraft:chests/simple_dungeon (dungeon), minecraft:chests/desert_pyramid (desert temple), minecraft:chests/end_city_treasure (End city), etc.

2.5 containers — Limit Container Types (Container Channel Only)

Only takes effect when used as a container table: declares which container types this table applies to. Empty = all containers within the ruin range use this table; if listed, only these types apply.

yaml
containers: [CHEST, BARREL]     # only chests and barrels apply; dispensers/hoppers in the ruin don't drop

Type names are vanilla material names (uppercase): CHEST TRAPPED_CHEST BARREL FURNACE BLAST_FURNACE SMOKER BREWING_STAND DISPENSER DROPPER HOPPER SHULKER_BOX, etc. Combined with the template's loot.container-tables (per-type tables), you can do "chests drop treasure, brewing stands drop potions"; see §3.3.

2.6 item — Item Source (Any Plugin)

item is parsed by QinhCoreLib's ItemManagerAPI and supports any item source that's been integrated — one line can pull an item from another plugin:

Prefix syntaxSource
vanilla:DIAMOND / minecraft:diamond / DIAMONDVanilla item
qi-<id> / qinhitems:<id>QinhItems
ni-<id> / neigeitems:<id>NeigeItems
mi-<type>:<id> / mmoitems:<type>:<id>MMOItems
ia-<id> / itemsadder:<id>ItemsAdder
ce-<id> / craftengine:<id>CraftEngine
nx-<id> / nexo:<id>Nexo
mm-<id> / mythicmobs:<id>MythicMobs items
yaml
entries:
  - { item: "qi-flame_saber",        weight: 3 }          # QinhItems weapon
  - { item: "mi-SWORD:cutlass",      weight: 2 }          # MMOItems
  - { item: "vanilla:DIAMOND",       weight: 10, amount: "1-3" }

An entry that fails to parse (wrong item ID, the corresponding plugin not installed) is silently skipped — that roll just outputs one fewer item, with no error spam.


3. Channel ① Container Loot

Place vanilla containers (chest/barrel/furnace…) in the structure, and the player right-clicking to open automatically yields loot — no separate marking needed. Whether a container belongs to a ruin is judged automatically by the structure's footprint (the bounding box, the smallest cuboid boundary of the structure) — a container that falls within the structure's bounding box belongs to it, no radius needed.

The master switch and interface are in the vessel section of config.yml:

yaml
vessel:
  enabled: true                  # container loot master switch
  refresh-mode: per-player-once  # per-player-once=once per person | timed=can be reopened to re-roll after a cooldown
  refresh-cooldown: 30m          # the re-roll cooldown for timed mode (30m / 1h / 2h…)
  rows: 3                        # the row count of the opened container interface (1-6)

The template side specifies which table to use and the mode (in templates/<id>/template.yml):

yaml
# template.yml snippet
loot:
  mode: per-player          # see §3.1
  container-table: vault    # all containers use the vault table
  # container-tables:       # or per-container-type tables, see §3.3
  #   CHEST: vault
  #   BREWING_STAND: potion_table

3.1 loot.mode — Per-Player vs Server-Shared

ModeBehaviorGrowthSuits
per-player (default)Each person gets their own batch on first open; the open is recorded and the rest stays unchanged in the interface✅ Quantity scales by growthMultiplayer servers, fair output, everyone gets a share
sharedStuffs a batch of loot straight into the real container, server-shared, first-come-first-served❌ Neutral dispense (ignores growth, ignores the min-growth threshold)Want a "grab-the-chest" competitive feel, single-player servers

Note

Key difference: per-player is QR opening an independent virtual interface for each player (mutually unaffected, auto-saving anything not taken when closed); shared actually fills items into that chest in the world, first to grab wins, and for fairness it does no growth scaling and ignores min-growth.

3.2 refresh-mode — Can It Be Reopened

  • per-player-once (default): each player can open each container only once; right-clicking again afterward shows the contents left from last time.
  • timed: after the refresh-cooldown passes, it can be reopened and re-rolled, making a "timed-refresh supply point".

3.3 Per-Container-Type Tables

loot.container-tables lets different container types use different tables; types not listed fall back to loot.container-table:

yaml
loot:
  mode: per-player
  container-table: vault        # fallback: any container not separately listed uses vault
  container-tables:
    CHEST: treasure_table       # chests drop treasure
    BREWING_STAND: potion_table # brewing stands specifically yield potions (furnaces/brewing stands smartly fill fuel/potion slots)

In shared mode, for furnaces/blast furnaces/smokers/brewing stands, QR smartly fills drops into the corresponding slots (fuel→fuel slot, potions→bottle slots…); whatever doesn't fit drops above the container.


4. Channel ② Blueprint Reward Chest (loot-chests)

The unlock-style chests in a blueprint: only openable after clearing to a specified stage, once per person. Configured in the loot-chests section of blueprint.yml (coordinates relative to the structure origin); see Blueprints & Objectives for details.

yaml
# blueprint.yml snippet (coordinates are x/y/z relative to the structure origin, written directly on the entry, not a pos sub-table)
loot-chests:
  - id: vault_main
    x: 8
    y: 3
    z: 8
    loot-table: vault          # which table to reference
    unlock-stage: 2            # only unlocks once advanced to stage 2 (default 1)
    per-player-once: true      # only openable once per person (default true)
    growth-scaled: true        # quantity scales by growth (default true)
FieldDefaultBehavior
unlock-stage1Only unlocks when the current effective stage ≥ this value; otherwise shows "chest not yet unlocked"
per-player-oncetrueThe open is recorded; reopening shows "you have already opened this chest"
growth-scaledtrueWhen true, quantity scales by growth

The reward chest stuffs loot straight into the inventory (drops on the ground if full) and plays an upgrade sound. It does not go through the vessel interface — it's standalone unlock-reward logic.


5. Channel ③ Cleanse Slot Machine (reward)

The exclusive jackpot after a realm is cleansed (cleared), dispensed in the form of a slot machine spin. The player types /qr reward to open the wheel, clicks the lever to spin once, and spins out only one jackpot from the prize pool.

Workflow

  1. When a realm/ruin is cleansed, QR grants one spin eligibility to each player within 48 blocks nearby and prompts "the clear jackpot is ready".
  2. The player runs /qr reward to open the wheel → clicks the lever → the animation rolls → it locks onto one jackpot, which goes straight into the inventory.
  3. The eligibility is consumed once; those who already collected see "this reward has been collected".

Which Table It Uses

The slot machine uses the template's clear prize-pool table — write reward.clear-table in template.yml (also the usage mentioned in the comments of vault.yml):

yaml
# template.yml snippet
reward:
  clear-table: vault     # the prize-pool table for the cleanse slot machine

Whereas the exclusive loot from a Realm-tier cleanse goes through a separate set, the realm.reward of config.yml (default table realm, privately delivered per participant, with stackable per-tier scaling and currency); see Realm & Keystone. Both belong to "cleanse output", but they're configured in different places:

  • template.yml's reward.clear-table/qr reward slot machine
  • config.yml's realm.reward.loot-table → realm-tier exclusive private delivery

How the Slot Machine Picks a Prize

The slot machine flattens the table's entries + all condition-satisfying group entries into one prize pool (filtered by the min-growth threshold), spins out one by weight, and scales the quantity by growth. When the pool has no entry suited to that player's growth, it shows "no reward suited to your growth right now".


6. Growth Scaling & Affix Synergy

6.1 What Is Growth

"Growth" is QR's unified readout of a player's strength tier, decided by the GrowthProvider:

  • With QinhClass installed → reads the class level;
  • otherwise with MMOCore installed → reads the MMOCore level;
  • neither installed → falls back to the vanilla experience level.

It's used in two places: the loot's min-growth threshold, and quantity scaling.

6.2 min-growth Threshold

An entry's min-growth is the "admission line": if the player's growth is below it, this entry doesn't join this roll (and doesn't appear in the slot machine prize pool either). Used to lock high-tier goodies and force players to grow first.

Exception: shared-mode containers ignore min-growth (neutral dispense, see §3.1).

6.3 Quantity Scales by Growth

When a channel enables growth-scaled (per-player containers, reward chests on by default, the slot machine, realm private delivery), the drop's base quantity is scaled up by growth:

Scaling formula: quantity = base quantity × (1 + growth / 200), with a floor never below the base quantity. Example: base 4, growth 100 → 4 × (1 + 0.5) = 6; growth 200 → 8.

min-growth controls "whether it can drop", growth-scaled controls "how much drops"; together they make high-growth players' output fatter.

6.4 Affix Synergy (greed / Hidden Trove)

Realm-tier output is further amplified by affixes (only on the realm cleanse private-delivery path, see Realm & Keystone):

Affix classActs onEffect
Greed / Golden Touch (greed)Whole-table quantityRaises the roll count / quantity multiplier
Hidden Trove (hidden-trove)unique: true entriesMultiplies the weight of exclusive entries, making rares easier to spin

These multipliers are multiplied with config.yml's realm.loot-bonus (per-tier multiplier, default 1.1× at tier 1 → 2.6× at tier 16):

yaml
# config.yml snippet
realm:
  tiers:
    loot-bonus: "1 + tier * 0.1"   # amplify quantity by realm tier; write "1" to disable per-tier scaling

That is: final quantity ≈ base × growth scaling × per-tier loot-bonus × greed multiplier, with exclusive entries additionally getting the Hidden Trove weight bonus.


7. Placeholders

Loot/progress-related placeholders (require PlaceholderAPI), usable in HUDs, groups.condition, chat, etc.:

PlaceholderMeaning
%qinhruins_ruin%Name of the current ruin
%qinhruins_stage% / %qinhruins_max_stage%Current stage / total stages
%qinhruins_stage_name%Current stage name
%qinhruins_completed%Whether cleared
%qinhruins_objective% / %qinhruins_progress%Current objective / progress
%qinhruins_kills_<mob>%Kill count of a certain mob

For the full list see Placeholders.


8. Quick Recipes by Need

I want…How
Custom items dropping from chestsPlace chests + point loot.container-table at a table with qi- etc. entries
Everyone gets a share, no grabbingloot.mode: per-player
A grab-the-chest competitive feelloot.mode: shared
High-tier goodies locked behind levelAdd min-growth: 30 to the entry
Reuse vanilla dungeon drops then add moreWrite vanilla: minecraft:chests/simple_dungeon + custom entries in the table
Extra drops for VIP/high-levelgroups + condition: "%placeholder% >= N"
Clear-jackpot spintemplate.yml's reward.clear-table + player /qr reward
Brewing stands specifically yield potionsloot.container-tables: { BREWING_STAND: potion_table }

Next Steps