Pack Makers Materials and Dungeons - breakinblocks/NeoVitae GitHub Wiki
This page covers the parts of NeoVitae customization that live outside the DataMaps and recipes, namely the data-driven material system, the KubeJS event hooks, custom dungeon room authoring, and the telepose blacklist tags. See Pack Makers Overview for the index and Pack Makers DataMaps and Recipes for tag/recipe details.
NeoVitae's ore processing system is fully data-driven. Materials (dusts, gravels, fragments) are defined in a single JSON config and items are generated at startup via an in-memory resource pack. See Athanor and Materials for the player-facing system.
Location: config/neovitae/materials.json
Each entry defines a processable material:
{
"name": "tin",
"color": "#C8C8D0",
"stages": ["fragment", "gravel", "dust"],
"smelt_to": "create:tin_ingot",
"smelt_xp": 0.7,
"ore_tag": "c:ores/tin",
"raw_tag": "c:raw_materials/tin",
"ingot_tag": "c:ingots/tin",
"display_name": "Tin",
"generative": true,
"gen_ore": "",
"gen_raw": ""
}| Field | Required | Description |
|---|---|---|
name |
Yes | Unique material name. Used in item IDs (neovitae:tin_dust). |
color |
Yes | Hex color for item tinting (e.g. #C8C8D0). |
stages |
Yes | Processing stages to create: fragment, gravel, dust. |
smelt_to |
No | Item ID for smelting output. Omit to skip smelting recipes. |
smelt_xp |
No | XP from smelting (default: 0). |
ore_tag |
No | Tag for ore blocks (e.g. c:ores/tin). Enables ore processing recipes. |
raw_tag |
No | Tag for raw materials (e.g. c:raw_materials/tin). |
ingot_tag |
No | Tag for ingots/gems (e.g. c:ingots/tin). Enables ingot-to-dust recipe. |
display_name |
No | Override display name. Defaults to capitalized material name. |
id_overrides |
No | Map to override generated item IDs per stage. |
generative |
No | Whether this ore feeds NeoVitae's generation systems (Prismatic Demonite drops, dungeon iron-ore deposits, meteors). Defaults to true. Set false to exclude the ore. |
gen_ore |
No | Preferred ore block id for generation (e.g. "thermal:nickel_ore"). Use when a material resolves to multiple ore blocks (stone/deepslate variants, cross-mod duplicates) and you want generation to use one specific block. Defaults to the material's whole c:ores/<name> tag. |
gen_raw |
No | Preferred raw item id for Prismatic Demonite drops (e.g. "thermal:raw_nickel"). Use when a material's raw_tag resolves to multiple items and you want drops to use one specific item. Defaults to every item in the material's raw_tag. |
For each material, the system automatically generates:
- Items. Fragment, gravel, and/or dust items with color-tinted base textures.
-
Tags.
c:fragments/{name},c:gravels/{name},c:dusts/{name}. -
Smelting / blasting recipes. Dust to output item (if
smelt_tois defined). - Athanor recipes. Full ore processing chain (ore/raw -> fragments -> gravel -> dust).
- Tabula Vitae recipes. Ore to dust, fragments to gravel with corrupted dust.
- Item models and translations. Generated in-memory, no files needed.
All recipes use neoforge:item_exists conditions so they silently disable if the output item's mod is not installed.
Any material flagged generative (the default) is automatically woven into NeoVitae's world-generation and reward systems. No recipe or tag authoring is required; a pack that adds platinum, mithril, or any ore the material system detects gets it everywhere for free.
-
Prismatic Demonite drops. Mining Prismatic Demonite in the dungeon drops a random raw material drawn from the
raw_tagof every generative material. - Dungeon iron-ore deposits. The hand-placed ore in dungeon rooms (armory, ore cavern, crane) is swapped at placement time for a random spread of generative ores.
- Meteor ritual. The default meteors include a weighted entry pointing at the generative ore set, so summoned meteors carry the pack's ores.
These three systems all read the auto-populated neovitae:generative_ores block tag, which lists the ore blocks of every generative material. Ores that don't exist in the pack contribute nothing.
To pull a single ore out of all of this, set "generative": false on its material entry. When a material maps to more than one candidate (a stone and deepslate variant, or duplicate ores/raw items from several mods), pin the exact output: gen_ore chooses the block used by the block-placing systems (dungeon deposits, meteors) and gen_raw chooses the raw item used by Prismatic Demonite drops. Without them, generation uses the material's whole c:ores/<name> tag and raw_tag respectively.
/neovitae generate-materials. Requires op permissions.
Scans all c:ores/* tags from installed mods and:
- Discovers ore materials not already in the config.
- Looks up smelting recipes to find the output item.
- Falls back to
c:ingots/{name}orc:gems/{name}tag matching. - Extracts the ore's characteristic color from its block texture (filtering out stone-colored pixels).
- Appends new material entries to the config.
A game restart is required after running the command for new items to appear.
On the very first launch (when materials.json does not exist), the mod automatically runs the ore discovery process after the world loads. Any detected ores are added to the config and a notification is sent to players on join indicating a restart is needed.
- Open
config/neovitae/materials.json. - Add a new entry to the JSON array.
- Restart the game.
- The new material's items, models, tags, and recipes appear automatically.
Remove the entry from materials.json and restart. Existing items in the world will become unknown items.
NeoVitae fires events that can be intercepted with KubeJS to add custom behavior, modify outputs, or cancel operations. Events live on NeoForge.EVENT_BUS.
// server_scripts/blood_magic_rituals.js
// Cancel or modify ritual activation
NeoForgeEvents.onEvent('com.breakinblocks.neovitae.common.event.RitualEvent$Activate', event => {
const ritual = event.getRitual()
const player = event.getPlayer()
const pos = event.getPos()
// Require player to be at night for certain rituals
if (ritual.getName() === 'neovitae:night_ritual' && event.getLevel().isDay()) {
player.displayClientMessage(Component.literal('This ritual can only be performed at night!'), true)
event.setCanceled(true)
return
}
console.log(`Player ${player.getName().getString()} activated ritual at ${pos}`)
})
// React after ritual activation (cannot cancel)
NeoForgeEvents.onEvent('com.breakinblocks.neovitae.common.event.RitualEvent$Activated', event => {
const ritual = event.getRitual()
const player = event.getPlayer()
// Grant advancement, award, etc.
})
// Cancel individual ritual performance ticks
NeoForgeEvents.onEvent('com.breakinblocks.neovitae.common.event.RitualEvent$Perform', event => {
const ritual = event.getRitual()
const level = event.getLevel()
// Pause ritual during rain
if (level.isRaining() && ritual.getName() === 'neovitae:sun_ritual') {
event.setCanceled(true)
}
})
// React when ritual stops
NeoForgeEvents.onEvent('com.breakinblocks.neovitae.common.event.RitualEvent$Stop', event => {
const breakType = event.getBreakType()
console.log(`Ritual stopped: ${breakType}`)
})// server_scripts/blood_magic_imperfect.js
NeoForgeEvents.onEvent('com.breakinblocks.neovitae.common.event.ImperfectRitualEvent$Activate', event => {
const ritual = event.getRitual()
const player = event.getPlayer()
const cost = event.getActivationCost()
// Require specific item in hand
if (ritual.getName() === 'neovitae:special_ritual') {
const mainHand = player.getMainHandItem()
if (!mainHand.is('minecraft:nether_star')) {
player.displayClientMessage(Component.literal('You need a Nether Star!'), true)
event.setCanceled(true)
}
}
})
NeoForgeEvents.onEvent('com.breakinblocks.neovitae.common.event.ImperfectRitualEvent$Activated', event => {
const ritual = event.getRitual()
const player = event.getPlayer()
const pos = event.getPos()
// Spawn particles, play sounds, log for analytics, etc.
})// server_scripts/blood_magic_altar.js
NeoForgeEvents.onEvent('com.breakinblocks.neovitae.common.event.AraVitaeCraftEvent$Crafting', event => {
const input = event.getInput()
const output = event.getOutput()
const tier = event.getTier()
// Chance for bonus output at higher tiers
if (tier >= 3 && Math.random() < 0.1) {
const bonusOutput = output.copy()
bonusOutput.setCount(output.getCount() * 2)
event.setOutput(bonusOutput)
}
})
NeoForgeEvents.onEvent('com.breakinblocks.neovitae.common.event.AraVitaeCraftEvent$Crafted', event => {
const output = event.getOutput()
console.log(`Crafted: ${output.getId()}`)
})| Event Class | Cancellable | Description |
|---|---|---|
RitualEvent$Activate |
Yes | Before ritual activates |
RitualEvent$Activated |
No | After ritual activated |
RitualEvent$Perform |
Yes | Before each ritual tick |
RitualEvent$Stop |
No | When ritual stops |
ImperfectRitualEvent$Activate |
Yes | Before imperfect ritual |
ImperfectRitualEvent$Activated |
No | After imperfect ritual |
AraVitaeCraftEvent$Crafting |
Yes | Before altar craft completes |
AraVitaeCraftEvent$Crafted |
No | After altar craft completes |
ItemBindEvent |
Yes | When binding item to player |
LaminaMaleficusEvent |
Yes | When dagger drains health |
SentientArmourEvent |
Varies | Sentient armor upgrade events |
AlchemyArrayCraftEvent |
Yes | Alchemy array crafting |
- No Java required. All customization via KubeJS scripts.
-
Hot-reloadable. Use
/kubejs reloadto test changes instantly. - Composable. Add multiple behaviors to existing rituals.
- Safe. Cannot break core mod functionality.
- Flexible. Combine with other KubeJS features (quests, rewards, etc.).
For truly new ritual types (new effects, new multiblock structures), see the API Overview.
The Endless Dungeon is assembled from a registry of dungeon rooms, each composed of one or more NBT structures plus a JSON definition. Pack makers can add rooms by dropping the NBT and JSON in the right places and registering the room id with a Java addon.
NeoVitae's room data lives under assets/<namespace>/schematics/ (not data/). NBT structures live under data/<namespace>/structure/. Two parallel directory trees:
data/neovitae/structure/ # NBT structures placed in-world
โโโ four_way_corridor.nbt
โโโ straight_corridor.nbt
โโโ t3_entrance.nbt
โโโ mini_dungeon/
โ โโโ armoury.nbt
โ โโโ crypt.nbt
โ โโโ farm.nbt
โ โโโ library.nbt
โ โโโ portal_nether.nbt
โโโ mines/
โ โโโ mine_key.nbt
โโโ standard/ # full-size rooms (challenge tower, etc.)
โโโ ritual/ # ritual rooms
assets/neovitae/schematics/ # JSON room definitions
โโโ four_way_corridor.json
โโโ t3_entrance.json
โโโ mini_dungeon/...
โโโ standard/...
โโโ mines/...
โโโ room_pools/ # pool lists (weight;room_id)
โโโ connective_corridors.json
โโโ tier1/mini_dungeon.json
โโโ standard/standard_rooms.json
โโโ special/mine_entrances.json
When loaded, a room definition is resolved by ResourceLocation (e.g. neovitae:four_way_corridor) and its JSON is fetched from /assets/<namespace>/schematics/<path>.json. The NBT structures it references live at data/<namespace>/structure/<path>.nbt.
A room definition is a single JSON object with these fields (output of DungeonRoomProvider in the NeoVitae repo):
| Field | Type | Description |
|---|---|---|
structureMap |
{ "<namespace:path>": {x,y,z} } |
One or more NBT structures and their placement offsets relative to the room origin. Multiple structures can share an offset; the synthesizer picks one at random per generation. |
descriptorList |
Array of {minimumOffset, maximumOffset}
|
One or more AABB regions describing the room's collision footprint. Used to reject overlap with other rooms. |
doorMap |
{ "<doorType>": { "<facing>": [{x,y,z}, ...] } } |
Door positions grouped by door type (default, mine, custom) and facing direction (north, south, east, west). |
indexToDoorMap |
{ "<index>": [{x,y,z}, ...] } |
Groups doors into numbered indices. Doors in the same index share the same connected-room-pool list. |
indexToRoomTypeMap |
{ "<index>": ["<roomPoolId>" or "#<id>" or "$<id>"] } |
For each index, the list of room pools eligible to attach. Prefix # = special pool, $ = deadend pool, no prefix = normal pool. |
requiredDoorMap |
{ "<wantedDoorType>": [{x,y,z}, ...] } |
When a door at a position needs the connected room to expose a specific door type (used by waterway/asymmetric connectors). |
doorCoverMap |
{ "<index>": {minimumOffset, maximumOffset} } |
Per-index AABB of blocks filled in when a door is sealed (no room connects). Default is a 3ร3ร1 frame in front of the door. |
dungeonWeight |
Integer | Spawn weight in the parent pool. Default 1. |
oreDensity |
Float | 0.0 - 1.0. Fraction of raw dungeon stone in the room converted to Demonite Ore (drops raw demonite) or, more rarely, Prismatic Demonite. |
spawnLocation |
{x,y,z} |
Player spawn offset (entrance rooms only). |
controllerOffset |
{x,y,z} |
Dungeon-controller block offset (entrance rooms only). |
portalOffset |
{x,y,z} |
Portal/exit offset (entrance rooms only). |
This is the actual assets/neovitae/schematics/four_way_corridor.json ships in NeoVitae:
{
"controllerOffset": { "x": 0, "y": 0, "z": 0 },
"descriptorList": [
{
"minimumOffset": { "x": 0, "y": 0, "z": 0 },
"maximumOffset": { "x": 11, "y": 6, "z": 11 }
}
],
"doorCoverMap": {},
"doorMap": {
"default": {
"north": [ { "x": 5, "y": 0, "z": 0 } ],
"south": [ { "x": 5, "y": 0, "z": 10 } ],
"west": [ { "x": 0, "y": 0, "z": 5 } ],
"east": [ { "x": 10, "y": 0, "z": 5 } ]
}
},
"dungeonWeight": 1,
"indexToDoorMap": {
"1": [
{ "x": 5, "y": 0, "z": 0 },
{ "x": 5, "y": 0, "z": 10 },
{ "x": 0, "y": 0, "z": 5 },
{ "x": 10, "y": 0, "z": 5 }
]
},
"indexToRoomTypeMap": {
"1": [
"neovitae:room_pools/standard/standard_rooms",
"neovitae:room_pools/connective_corridors",
"#neovitae:room_pools/special/mine_entrances",
"#neovitae:room_pools/standard/mine_key"
]
},
"oreDensity": 0.0,
"portalOffset": { "x": 0, "y": 0, "z": 0 },
"requiredDoorMap": {},
"spawnLocation": { "x": 0, "y": 0, "z": 0 },
"structureMap": {
"neovitae:four_way_corridor": { "x": 0, "y": 0, "z": 0 }
}
}All four doors share index 1, which connects to a mix of standard rooms, connective corridors, and special pools.
A room pool is a JSON list of "weight;namespace:room_id" strings, served from assets/<namespace>/schematics/room_pools/<path>.json:
[
"2;neovitae:four_way_corridor",
"3;neovitae:overlapped_corridor",
"4;neovitae:straight_corridor",
"2;neovitae:t_corridor"
]When the synthesizer needs to attach a room to a door, it picks an entry weighted-random from the named pool, rotates it to match the door facing, and validates that none of its descriptorList AABBs overlap an existing placed room.
The shipped pools cover:
| Pool | Used by |
|---|---|
room_pools/connective_corridors |
Doors that prefer hallways |
room_pools/standard/standard_rooms |
Standard dungeon room slots |
room_pools/tier1/mini_dungeon |
T3 entrance interior rooms |
room_pools/standard/standard_deadend |
Deadend fillers for standard tier |
room_pools/mines/* |
Mine-tier rooms, corridors, entrances |
room_pools/special/mine_entrances |
Doors that may upgrade to a mine entrance |
room_pools/standard/mine_key |
Mine key special rooms |
- Build the room in a creative test world. Use a Structure Block to save it as an NBT file.
-
Drop the NBT into
data/<your_namespace>/structure/<path>.nbt. The path you choose here is the same path you'll use instructureMap. -
Author the JSON definition at
assets/<your_namespace>/schematics/<room_id>.jsonusing the schema above.- Place door positions at the outside edge of where the door block sits (the door cover AABB will fill in around it).
- Set
descriptorListto fully enclose the structure footprint plus any overhangs. - For irregular rooms (e.g. an L-shape), use multiple rectangles in
descriptorList.
-
Add the room to a pool by appending its weighted id to the relevant pool file under
assets/<your_namespace>/schematics/room_pools/. -
Register the id: a small Java addon needs to call
DungeonRoomRegistry.registerUnloadedDungeonRoom(new ResourceLocation("your_namespace", "your_room_id"))during mod setup. Pure-datapack rooms can be added by overriding NeoVitae's existing pool JSONs and reusing room ids already registered.
-
Reference NeoVitae's generated JSONs in the repo at
src/generated/resources/assets/neovitae/schematics/. They cover every door arrangement (single door, multi-level, asymmetric waterway connectors, multi-NBT rooms) and are the authoritative examples. -
Indices group doors that should accept the same neighbor pool. A four-way corridor with one index attaches anything to any door; a station with
index: 1north/south for mine corridors andindex: 2east for a side passage attaches different pools per direction. - Ore density is per-room: the fraction of raw dungeon stone converted to Demonite Ore (and occasionally Prismatic Demonite). Mines use 0.2-0.4, ore-cavern rooms 0.6, mine-key/deadend rooms 0.8.
-
/neovitae dungeon-showcaseplaces every registered structure NBT in a grid for visual review. Use this to verify your NBT loads correctly before wiring up the JSON definition.
NeoVitae exposes two telepose_blacklist tags that prevent the Teleposer from moving blocks or entities. See Teleposer for the player-facing tool.
Location: data/neovitae/tags/block/telepose_blacklist.json
Blocks in this tag cannot be teleposed. Useful for protecting bedrock-equivalents, modded "cannot be moved" blocks, or pack-defined infrastructure.
{
"replace": false,
"values": [
"minecraft:bedrock",
"minecraft:end_portal_frame",
"some_mod:reactor_core"
]
}Location: data/neovitae/tags/entity_type/telepose_blacklist.json
Entities in this tag cannot be teleposed. Useful for bosses, quest NPCs, and anything that should stay put.
{
"replace": false,
"values": [
"minecraft:wither",
"minecraft:ender_dragon",
"some_mod:quest_villager"
]
}Both tags resolve at telepose time; changes take effect after /reload.