20 KiB
23 - Loot, Entities, Spawners, Markers
Loot tables fill containers and entity drop inventories. Entities describe what to spawn. Spawners schedule ambient and initial spawns. Markers pin spawners to placed objects. Together they form the pack-side entity and loot systems.
Related: 05 - Concepts & Pack Layout.md, 11 - Dimensions.md, 12 - Regions.md, 13 - Biomes.md, 19 - Objects.md, 20 - Object Placement.md, 03 - Configuration.md, 10 - Studio & VSCode Schemas.md.
Tutorial: spawn one loot-bearing entity
This workflow adds a zombie that always rolls one iron nugget, spawns it at night in one biome, and proves each resource edge independently. Prerequisites are a validating pack, a working land biome with load key tutorial/meadow, and world.ambientEntitySpawningSystem: true in settings.json.
Create these files:
loot/tutorial/zombie-drops.json
entities/tutorial/zombie.json
spawners/tutorial/night-zombies.json
loot/tutorial/zombie-drops.json:
{
"name": "Tutorial Zombie Drops",
"minPicked": 1,
"maxPicked": 1,
"maxTries": 1,
"loot": [
{ "type": "iron_nugget", "rarity": 1, "minAmount": 1, "maxAmount": 1 }
]
}
entities/tutorial/zombie.json:
{
"type": "minecraft:zombie",
"loot": {
"mode": "ADD",
"tables": ["tutorial/zombie-drops"]
}
}
spawners/tutorial/night-zombies.json:
{
"group": "NORMAL",
"maxEntitiesPerChunk": 3,
"timeBlock": { "startHour": 20, "endHour": 5 },
"weather": "ANY",
"allowedLightLevels": { "min": 0, "max": 7 },
"maximumRate": { "amount": 4, "per": { "seconds": 30 } },
"spawns": [
{ "entity": "tutorial/zombie", "rarity": 1, "minSpawns": 1, "maxSpawns": 2 }
]
}
Add the spawner key to the existing biome; this is a field excerpt, not a separate JSON file:
{
"entitySpawners": ["tutorial/night-zombies"]
}
- Validate the pack before opening the test world. The validator must resolve the spawner-to-entity edge and the entity's loot table.
- On Bukkit, run
/iris studio spawn tutorial/zombieto prove the entity file loads, then kill it and confirm the nugget drop. On modded, continue directly to ambient spawning because the Studio spawn command is a stub. - Open the pack in Studio, focus
tutorial/meadow, set night, and stand in a dark surface area. Success is an Iris-spawned zombie within the configured rate and chunk cap; killing it rolls the custom table. - If the entity loads manually but never spawns, check the ambient-spawning setting, time, block light, biome attachment, and living-entity cap. If validation fails, fix the first unresolved key in the chain rather than changing rarity or rate.
- Remove biome focus after the chain works. Add effects, markers, gear, passengers, and additional loot only after this minimal path remains repeatable.
Where files live
| Path | Registrant | Role |
|---|---|---|
loot/<key>.json |
IrisLootTable |
Weighted item tables |
entities/<key>.json |
IrisEntity |
Entity type, gear, drops, flags |
spawners/<key>.json |
IrisSpawner |
When/where entity keys spawn |
markers/<key>.json |
IrisMarker |
Marker definition that lists spawner keys |
Keys use pack-relative paths without .json (for example standard/hostile/zombie, temperate/hostile, global-treasure).
Wiring
| Source | Field | Loads |
|---|---|---|
| Dimension | loot (IrisLootReference) |
Global tables for the dimension |
| Region | loot |
Regional tables |
| Biome | loot |
Biome tables |
| Entity | loot |
Drop tables when entity is Lootable |
| Object placement | loot / vanillaLoot |
Containers in placed objects |
| Dimension / region / biome | entitySpawners |
Spawner keys for ambient spawning |
| Object placement | markers (IrisObjectMarker[]) |
Places markers on matching blocks |
| Marker | spawners |
Spawner keys at that marker |
Ambient spawning merges dimension, then region, then surface-biome entitySpawners when settings.json has world.ambientEntitySpawningSystem true (default). Marker spawning uses mantle markers when world.markerEntitySpawningSystem is true (default).
The shipping overworld pack includes entities/standard/..., spawners/<climate>/..., and loot/... resources, but its dimensions, regions, and biomes do not currently reference those spawner keys through entitySpawners. Ambient Iris spawners only fire after a parent lists them.
Loot tables (IrisLootTable)
Folder: loot/.
| Field | Type | Default | Notes |
|---|---|---|---|
name |
string | "" |
Required display name (min length 2) |
rarity |
int ≥ 1 | 1 |
Table 1-in-N chance factor |
minPicked |
int 0..64 | 1 |
Min successful picks per roll |
maxPicked |
int 1..64 | 5 |
Max successful picks per roll |
maxTries |
int 1..256 | 10 |
Cap on pick attempts |
loot |
IrisLoot[] |
[] |
Entries; empty table yields no items |
Roll procedure: RNG picks a random entry each try, keeps items that pass entry rarity and match the requested InventorySlotType, stops at maxPicked successes or maxTries.
Loot entry (IrisLoot, snippet type loot)
| Field | Type | Default | Notes |
|---|---|---|---|
type |
string | "" |
Required. Vanilla material name such as diamond or DIAMOND_SWORD (not minecraft:* except for namespaced third-party items) |
slotTypes |
InventorySlotType |
STORAGE |
STORAGE, FUEL, FURNACE, BLAST_FURNACE, SMOKER |
rarity |
int ≥ 1 | 1 |
Sub-rarity after table pick |
minAmount / maxAmount |
int 1..64 | 1 |
Stack size |
displayName |
string | null | Color codes via & |
lore |
string[] | [] |
Wrapped lore lines |
minDurability / maxDurability |
0..1 | 0 / 1 |
Remaining durability percent |
customModel |
int | null | Custom model data float |
unbreakable |
boolean | false |
|
itemFlags |
string[] | [] |
Bukkit ItemFlag names |
enchantments |
IrisEnchantment[] |
[] |
|
attributes |
IrisAttributeModifier[] |
[] |
|
dyeColor |
string | null | DyeColor name for colorable items |
leatherColor |
string | null | #RRGGBB for leather armor |
customNbt |
object | null | Platform NBT map; also used for third-party items |
Combined rarity is table rarity × entry rarity. Studio debug lore can show table name and combined chance.
Loot reference (IrisLootReference, snippet type loot-registry)
Used on dimensions, regions, biomes, and entities:
{
"loot": {
"mode": "FALLBACK",
"multiplier": 0.5,
"tables": ["temperate/clutter", "temperate/food"]
}
}
| Field | Type | Default | Notes |
|---|---|---|---|
mode |
IrisLootMode |
ADD |
ADD, CLEAR, REPLACE, FALLBACK |
tables |
string[] | [] |
Loot table keys |
multiplier |
double 0..16 | 1 |
Scales loot source counts |
ADD— append to parent tables.CLEAR/REPLACE— drop parent tables then add these (same effective clear-then-add).FALLBACK— use only when no object-level loot already defined.
Overworld dimension example:
{
"loot": {
"mode": "FALLBACK",
"tables": ["global-clutter"]
}
}
Object loot
On IrisObjectPlacement:
| Field | Notes |
|---|---|
loot |
IrisObjectLoot[] — pack table name + optional block filter + weight |
vanillaLoot |
Vanilla loot table bindings |
overrideGlobalLoot |
When true, object tables replace dim/region/biome tables for that placement |
IrisObjectLoot fields: name (loot table key), weight (default 1), filter (block list, empty = all containers), exact (exact block-data match).
Object tile payloads, including authored container NBT, and deferred custom-block identifiers remain in Mantle until the platform post-load materialization iteration completes normally. Generic cleanup and pregeneration cleanup retain these sparse payloads for chunks that have not reached that pass; Bukkit keeps a failed region-scheduled pass retryable without repeating earlier completed passes.
Real overworld loot sample
From loot/global-treasure.json (abbreviated):
{
"name": "Global Treasure",
"rarity": 1,
"maxPicked": 2,
"minPicked": 0,
"maxTries": 20,
"loot": [
{ "type": "diamond", "minAmount": 1, "maxAmount": 3, "rarity": 4, "slotTypes": "STORAGE" },
{ "type": "emerald", "minAmount": 1, "maxAmount": 4, "rarity": 3, "slotTypes": "STORAGE" },
{
"type": "enchanted_book",
"minAmount": 1,
"maxAmount": 1,
"rarity": 14,
"slotTypes": "STORAGE",
"enchantments": [{ "enchantment": "mending", "minLevel": 1, "maxLevel": 1, "chance": 1.0 }]
}
]
}
Entities (IrisEntity)
Folder: entities/.
| Field | Type | Default | Notes |
|---|---|---|---|
type |
string | null | Required namespaced entity type (minecraft:zombie or bare keys resolved by platform). Use unknown with specialType for external plugins |
specialType |
string | "" |
PluginName:MobName via external data service |
applySettingsToCustomMobAnyways |
boolean | false |
Apply Iris gear/flags on custom mobs |
reason |
string | null | CreatureSpawnEvent.SpawnReason name; default NATURAL |
customName / customNameVisible |
string / bool | "" / false |
|
aware / ai |
boolean | true |
Mob awareness / AI |
glowing / gravity / invulnerable / silent |
boolean | false / true / false / false | |
pickupItems / removable / keepEntity / baby |
boolean | false | |
helmet / chestplate / leggings / boots / mainHand / offHand |
IrisLoot |
null | Gear; each entry uses its own rarity |
passengers |
IrisEntity[] |
[] |
Nested riders |
attributes |
IrisAttributeModifier[] |
[] |
|
loot |
IrisLootReference |
empty ADD | Drop tables |
leashHolder |
IrisEntity |
null | Spawns holder and leashes |
spawnEffect |
IrisEffect |
null | On-spawn effect |
spawnEffectRiseOutOfGround |
boolean | false |
Rise-from-ground VFX |
pandaMainGene / pandaHiddenGene |
string | null | Panda genes |
surface |
IrisSurface |
LAND |
Spawn surface check: LAND, ANIMAL, WATER, OVERWORLD, LAVA |
rawCommands |
IrisCommand[] |
[] |
Run with {x} {y} {z} |
Minimal overworld entities are often one field:
{"type": "ZOMBIE"}
Path key: entities/standard/hostile/zombie.json → load key standard/hostile/zombie.
Entity commands (IrisCommand)
rawCommands executes console commands after an entity spawns. The same command object is also used by ambient effect command registries.
| Field | Default | Behavior |
|---|---|---|
commands |
[] |
Required command strings; a leading / is removed and {x}, {y}, {z} are replaced with block coordinates |
delay |
0 |
Delay before first execution, in server ticks; negative values clamp to zero |
repeat |
false |
Repeats indefinitely after the first delay; no cancellation handle is exposed and repeats stop on server restart |
repeatDelay |
100 |
Ticks between repeats; values below one clamp to one |
timeBlock |
any time | Required world-time window |
weather |
ANY |
Required weather: NONE, DOWNFALL, DOWNFALL_WITH_THUNDER, or ANY |
Bukkit and modded entity spawning both apply AI/awareness flags, spawn effects, and raw commands. Spawner time and weather gates are also enforced on both platform families.
Ambient effects (IrisEffect)
Biomes and regions accept effects[]. Each entry performs a 1-in-chance check no more often than interval milliseconds and can combine a potion, sound, particle, and command registry.
| Field | Default / range | Behavior |
|---|---|---|
interval |
150 ms, ≥0 |
Minimum interval between effect attempts |
chance |
50, ≥1 |
One successful attempt in this many intervals |
potionEffect |
"" |
Potion-effect registry key |
potionStrength |
-1 (−1–1024) |
Amplifier; -1 disables potion application |
potionTicksMin / potionTicksMax |
75 / 155 |
Random potion duration in ticks |
sound |
null |
Sound registry key |
soundDistance |
12 (0–512) |
Maximum random sound offset from the player |
minPitch / maxPitch |
0.5 / 1.5 |
Random pitch interval |
volume |
1.5 |
Sound volume |
particleEffect |
null |
Particle registry key; modded supports simple particle types that need no extra particle data |
particleOffset |
0 (−32–32) |
Random vertical surface offset |
particleCount |
0 (0–512) |
Count; zero lets the alternate XYZ values behave as motion on Bukkit |
particleDistance |
20 (0–64) |
Forward sampling distance |
particleDistanceWidth |
24 (0–128) |
Side-to-side sampling radius |
particleAway |
5 (0–16) |
Minimum forward offset |
particleAltX / particleAltY / particleAltZ |
0 (−8–8) |
Particle offset or motion components |
randomAltX / randomAltY / randomAltZ |
true / false / true |
Randomize each alternate component between its negative and positive value |
extra |
0 |
Particle-specific extra value |
commandRegistry |
null |
Optional command casting configuration |
IrisCommandRegistry fields:
| Field | Default | Behavior |
|---|---|---|
rawCommands |
[] |
IrisCommand[] to run |
commandOffsetX / commandOffsetY / commandOffsetZ |
0 (−8–8) |
Coordinate offsets from the player |
commandRandomAltX / commandRandomAltY / commandRandomAltZ |
true / false / true |
Randomize each coordinate within the signed offset |
commandAllRandomLocations |
true |
Pick a new random coordinate for each command object; false reuses one coordinate |
Example:
{
"particleEffect": "minecraft:ash",
"particleCount": 8,
"sound": "minecraft:ambient.cave",
"interval": 1000,
"chance": 8,
"commandRegistry": {
"commandOffsetX": 4,
"commandOffsetZ": 4,
"rawCommands": [{ "commands": ["particle minecraft:smoke {x} {y} {z}"] }]
}
}
Spawners (IrisSpawner)
Folder: spawners/.
| Field | Type | Default | Notes |
|---|---|---|---|
spawns |
IrisEntitySpawn[] |
[] |
Ongoing spawns |
initialSpawns |
IrisEntitySpawn[] |
[] |
Per-chunk initial pass (EXECUTES PER CHUNK) |
maxEntitiesPerChunk |
int | 1 |
Skip if living entities exceed |
timeBlock |
IrisTimeBlock |
any time | startHour / endHour in 24h (0–24) |
weather |
IrisWeather |
ANY |
NONE, DOWNFALL, DOWNFALL_WITH_THUNDER, ANY |
maximumRate |
IrisRate |
infinite | Global rate limit |
maximumRatePerChunk |
IrisRate |
infinite | Per-chunk rate limit |
allowedLightLevels |
IrisRange |
0..15 |
Inclusive block light |
group |
IrisSpawnGroup |
NORMAL |
Placement band |
IrisSpawnGroup:
| Value | Placement |
|---|---|
NORMAL |
Surface land biomes only |
CAVE |
Valid for all biome inferred types; uses cave-floor markers when available |
UNDERWATER |
Sea biomes, between seafloor and surface |
BEACH |
Shore biomes |
IrisRate: amount (0 = infinite) and per (IrisDuration). Empty duration means an infinite rate and is always allowed.
IrisDuration fields are additive:
| Field | Runtime unit |
|---|---|
milliseconds |
1 ms |
minecraftTicks |
50 ms |
seconds / minutes / hours / days |
Real-time units |
minecraftHours |
50 real seconds |
minecraftDays |
20 real minutes |
minecraftWeeks |
7 Minecraft days (2 h 20 min) |
minecraftLunarCycles |
8 Minecraft days (2 h 40 min) |
Entity spawn entry (IrisEntitySpawn, snippet entity-spawn)
| Field | Type | Default | Notes |
|---|---|---|---|
entity |
string | "" |
Required entity key |
rarity |
int ≥ 1 | 1 |
1-in-N for some spawn paths |
minSpawns / maxSpawns |
int ≥ 1 | 1 |
Count range when selected |
Overworld sample spawners/temperate/hostile.json:
{
"group": "NORMAL",
"maximumRate": { "amount": 8, "per": { "seconds": 15 } },
"timeBlock": { "startHour": 20, "endHour": 3 },
"maxEntitiesPerChunk": 2,
"weather": "ANY",
"spawns": [
{ "entity": "standard/hostile/zombie", "rarity": 5, "maxSpawns": 4, "minSpawns": 2 },
{ "entity": "standard/hostile/skeleton", "rarity": 30, "maxSpawns": 2, "minSpawns": 1 }
]
}
To activate ambient spawning, list the spawner key on a parent:
{
"entitySpawners": ["temperate/hostile", "temperate/passive"]
}
Markers (IrisMarker)
Folder: markers/.
| Field | Type | Default | Notes |
|---|---|---|---|
spawners |
string[] | [] |
Spawner keys attached at this marker |
removeOnChange |
boolean | true |
Drop marker when assigned block changes |
emptyAbove |
boolean | true |
Require two air blocks above when placing |
exhaustionChance |
double | 0 |
Chance to remove marker on use; <0 never; ≥1 always on first use |
Placing markers from objects (IrisObjectMarker, snippet object-marker)
On object placement markers[]:
| Field | Type | Notes |
|---|---|---|
mark |
IrisBlockData[] |
Required block types to tag |
marker |
string | Required marker key |
maximumMarkers |
int 1..16 | Default 8 |
exact |
boolean | Exact block-data match |
When marker entity spawning is enabled, MarkerSpawnScanner loads the marker tag, resolves its spawners, and WorldEntitySpawner fires those spawners at the marker position. Marker exhaustion can remove the mantle marker after use.
Custom block drops (IrisBlockDrops)
Dimensions, regions, and biomes accept blockDrops[]. On Bukkit, a matching biome provider runs first; unless any matching biome provider has skipParents: true, matching region and dimension providers are appended in that order.
| Field | Default | Behavior |
|---|---|---|
blocks |
[] |
Required IrisBlockData[] to match |
exactBlocks |
false |
False compares material only; true compares the complete block state |
drops |
[] |
IrisLoot[]; each entry's rarity is rolled independently |
skipParents |
false |
On a matching biome provider, prevents region and dimension drop providers from running for that break |
replaceVanillaDrops |
false |
If any matching provider enables it, suppresses vanilla block drops while retaining Iris drops from all selected providers |
{
"blocks": [{ "block": "minecraft:stone" }],
"exactBlocks": false,
"drops": [{ "type": "flint", "rarity": 4 }],
"skipParents": false,
"replaceVanillaDrops": false
}
The custom block-drop router is a Bukkit runtime feature; modded pack loading preserves the schema but does not install the Bukkit block-break event router.
Runtime settings that gate spawning
From settings.json → world:
| Key | Default | Effect |
|---|---|---|
ambientEntitySpawningSystem |
true |
Dim/region/biome entitySpawners |
markerEntitySpawningSystem |
true |
Marker-driven spawners |
targetSpawnEntitiesPerChunk |
0.95 |
Ambient density target |
asyncTickIntervalMS |
700 |
Spawn tick interval |
forcePersistEntities |
true |
Force entity persistence globally |
Studio command /iris studio loot previews chest loot at the player position (see 10 - Studio & VSCode Schemas.md, 04 - Commands & Permissions.md).
Reference checklist
- Write
loot/<key>.jsontables; reference them from dim/region/biomeloot.tablesor objectloot[].name. - Write
entities/<key>.jsonwith at leasttype. - Write
spawners/<key>.jsonwithspawnsentity keys and rates. - Add spawner keys to
entitySpawnerson dimension, region, and/or biome — or attach via markers on object placements. - Optional markers under
markers/plus placementmarkersarrays for structure-bound spawns. - Validate with studio open + hotload; use studio loot preview for chests.