Updated Docs, and Cortections

This commit is contained in:
Brian Neumann-Fopiano
2026-08-08 00:29:48 -06:00
parent c40e1cf152
commit 506787f51a
70 changed files with 9485 additions and 3050 deletions
@@ -0,0 +1,385 @@
# 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`.
## 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:
```json
{
"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:
```json
{
"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).
### Real overworld loot sample
From `loot/global-treasure.json` (abbreviated):
```json
{
"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:
```json
{"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` (11024) | Amplifier; `-1` disables potion application |
| `potionTicksMin` / `potionTicksMax` | `75` / `155` | Random potion duration in ticks |
| `sound` | `null` | Sound registry key |
| `soundDistance` | `12` (0512) | 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` (3232) | Random vertical surface offset |
| `particleCount` | `0` (0512) | Count; zero lets the alternate XYZ values behave as motion on Bukkit |
| `particleDistance` | `20` (064) | Forward sampling distance |
| `particleDistanceWidth` | `24` (0128) | Side-to-side sampling radius |
| `particleAway` | `5` (016) | Minimum forward offset |
| `particleAltX` / `particleAltY` / `particleAltZ` | `0` (88) | 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` (88) | 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:
```json
{
"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 (024) |
| `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`:
```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:
```json
{
"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 |
```json
{
"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`).
## Authoring checklist
1. Write `loot/<key>.json` tables; reference them from dim/region/biome `loot.tables` or object `loot[].name`.
2. Write `entities/<key>.json` with at least `type`.
3. Write `spawners/<key>.json` with `spawns` entity keys and rates.
4. Add spawner keys to `entitySpawners` on dimension, region, and/or biome — or attach via markers on object placements.
5. Optional markers under `markers/` plus placement `markers` arrays for structure-bound spawns.
6. Validate with studio open + hotload; use studio loot preview for chests.