Prefab System
The Prefab System allows you to create, save, and place pre-built structures containing blocks, fluids, entities, and nested child prefabs with rotation support.
Architecture
Section titled “Architecture”PrefabStore (Singleton)├── PREFAB_CACHE - ConcurrentHashMap of cached BlockSelection objects├── PREFABS_PATH - Default path: "prefabs"├── getPrefab() - Load prefabs from disk (with caching)├── savePrefab() - Save prefabs to disk└── Path resolution - Server, Asset, WorldGen prefabs
BlockSelection├── Block/Fluid/Entity data (thread-safe with ReentrantReadWriteLock)├── Position and Anchor point├── Selection bounds (min/max)└── place()/placeNoReturn() methodsPrefabStore
Section titled “PrefabStore”The PrefabStore singleton manages loading, saving, and caching prefabs:
import com.hypixel.hytale.server.core.prefab.PrefabStore;import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;import java.nio.file.Path;import java.util.Map;
PrefabStore store = PrefabStore.get();
// Load from different locations (relative to respective base paths)BlockSelection serverPrefab = store.getServerPrefab("structures/house.prefab.json");BlockSelection assetPrefab = store.getAssetPrefab("buildings/tower.prefab.json");BlockSelection worldGenPrefab = store.getWorldGenPrefab("dungeons/cave.prefab.json");
// Load from any asset pack (returns null if not found)BlockSelection anyPrefab = store.getAssetPrefabFromAnyPack("trees/oak.prefab.json");
// Load all prefabs from a directoryMap<Path, BlockSelection> prefabs = store.getServerPrefabDir("structures/houses");Map<Path, BlockSelection> assetPrefabs = store.getAssetPrefabDir("buildings");Map<Path, BlockSelection> worldGenPrefabs = store.getWorldGenPrefabDir("dungeons");
// Load from absolute path (with caching)BlockSelection customPrefab = store.getPrefab(Path.of("/absolute/path/to/prefab.prefab.json"));Saving Prefabs
Section titled “Saving Prefabs”BlockSelection selection = /* ... */;
// Save to server prefabs directorystore.saveServerPrefab("mystructure.prefab.json", selection, true);
// Save to asset prefabs directorystore.saveAssetPrefab("mystructure.prefab.json", selection, true);
// Save to world generation prefabs directorystore.saveWorldGenPrefab("mystructure.prefab.json", selection);store.saveWorldGenPrefab("mystructure.prefab.json", selection, true);
// Save to custom absolute pathstore.savePrefab(Path.of("/custom/path/structure.prefab.json"), selection, true);BlockSelection
Section titled “BlockSelection”The BlockSelection class represents a prefab’s content. It is thread-safe, using ReentrantReadWriteLock for concurrent access to blocks and entities.
Creating Selections
Section titled “Creating Selections”import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;import com.hypixel.hytale.math.vector.Vector3i;
// Create empty selectionBlockSelection selection = new BlockSelection();
// Create with initial capacity hintsBlockSelection optimized = new BlockSelection(1000, 10); // 1000 blocks, 10 entities
// Clone an existing selectionBlockSelection copy = new BlockSelection(existingSelection);
// Set world position (used as origin for local coordinates)selection.setPosition(100, 64, 200);
// Set anchor point (offset applied when placing)selection.setAnchor(0, 0, 0);
// Set anchor at world position (converts to local coordinates)selection.setAnchorAtWorldPos(105, 64, 205);
// Set selection boundsselection.setSelectionArea( new Vector3i(-5, 0, -5), // min new Vector3i(5, 10, 5) // max);
// Copy properties from another selectionselection.copyPropertiesFrom(otherSelection);Adding Content
Section titled “Adding Content”import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;import com.hypixel.hytale.component.Holder;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
// Add blocks at world positionint blockId = BlockType.getAssetMap().getAsset("hytale:stone").getBlockId();selection.addBlockAtWorldPos(x, y, z, blockId, rotation, filler, supportValue);
// Add blocks with block state holderHolder<ChunkStore> stateHolder = /* block entity data */;selection.addBlockAtWorldPos(x, y, z, blockId, rotation, filler, supportValue, stateHolder);
// Add blocks at local position (relative to selection's position)selection.addBlockAtLocalPos(localX, localY, localZ, blockId, rotation, filler, supportValue);
// Add empty block (air) at positionselection.addEmptyAtWorldPos(x, y, z);
// Add fluidsint fluidId = Fluid.getAssetMap().getAsset("hytale:water").getAssetIndex();selection.addFluidAtWorldPos(x, y, z, fluidId, (byte) 8);selection.addFluidAtLocalPos(localX, localY, localZ, fluidId, (byte) 8);
// Add entities (adjusts position relative to selection origin)Holder<EntityStore> entityHolder = /* entity data */;selection.addEntityFromWorld(entityHolder);
// Add entity without position adjustmentselection.addEntityHolderRaw(entityHolder);
// Copy block and fluid from world chunkselection.copyFromAtWorld(x, y, z, worldChunk, blockPhysics);Querying Content
Section titled “Querying Content”// Check if block exists at positionboolean hasBlock = selection.hasBlockAtWorldPos(x, y, z);boolean hasLocalBlock = selection.hasBlockAtLocalPos(localX, localY, localZ);
// Get block ID (returns Integer.MIN_VALUE if not found)int blockId = selection.getBlockAtWorldPos(x, y, z);
// Get fluid ID and levelint fluidId = selection.getFluidAtWorldPos(x, y, z); // Integer.MIN_VALUE if not foundbyte fluidLevel = selection.getFluidLevelAtWorldPos(x, y, z); // 0 if not found
// Get support value (for physics)int supportValue = selection.getSupportValueAtWorldPos(x, y, z);
// Get block state holder (for block entities)Holder<ChunkStore> state = selection.getStateAtWorldPos(x, y, z); // nullable
// Get countsint blockCount = selection.getBlockCount();int fluidCount = selection.getFluidCount();int entityCount = selection.getEntityCount();int volume = selection.getSelectionVolume(); // xLength * yLength & zLength (bitwise AND)Iterating Content
Section titled “Iterating Content”import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection.BlockHolder;
// Iterate blocks (receives local coordinates and BlockHolder record)selection.forEachBlock((x, y, z, block) -> { int blockId = block.blockId(); int rotation = block.rotation(); int filler = block.filler(); int supportValue = block.supportValue(); Holder<ChunkStore> holder = block.holder(); // Process block...});
// Iterate fluidsselection.forEachFluid((x, y, z, fluidId, fluidLevel) -> { // Process fluid...});
// Iterate entitiesselection.forEachEntity(entityHolder -> { // Process entity holder...});
// Compare blocks (returns false to stop iteration)boolean allMatch = selection.compare((x, y, z, block) -> { return block.blockId() != 0; // Check condition});Placing Prefabs
Section titled “Placing Prefabs”Basic Placement
Section titled “Basic Placement”import com.hypixel.hytale.server.core.universe.world.World;import com.hypixel.hytale.server.core.command.system.CommandSender;import com.hypixel.hytale.component.ComponentAccessor;import com.hypixel.hytale.component.Ref;import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask;import com.hypixel.hytale.server.core.prefab.selection.standard.FeedbackConsumer;
// Place at position (no undo support, no return value)selection.placeNoReturn(world, new Vector3i(100, 64, 200), componentAccessor);
// Place with feedback sender (for progress reporting)selection.placeNoReturn("feedbackKey", commandSender, world, componentAccessor);
// Place with custom feedback consumerselection.placeNoReturn("feedbackKey", commandSender, FeedbackConsumer.DEFAULT, world, componentAccessor);
// Place with full options (position and block mask)selection.placeNoReturn("feedbackKey", commandSender, FeedbackConsumer.DEFAULT, world, new Vector3i(100, 64, 200), blockMask, componentAccessor);
// Place with undo support (returns BlockSelection with previous state)BlockSelection previousState = selection.place(commandSender, world);
// Place with block mask (exclude certain blocks from replacement)BlockSelection previousState = selection.place(commandSender, world, blockMask);
// Place at specific position with maskBlockSelection previousState = selection.place(commandSender, world, new Vector3i(100, 64, 200), blockMask);
// Undo the placement by placing the previous statepreviousState.place(commandSender, world);Entity Callback
Section titled “Entity Callback”import java.util.function.Consumer;
// Place with entity callback (called for each entity spawned)Consumer<Ref<EntityStore>> entityCallback = entityRef -> { // Called for each entity placed in the world // entityRef is a reference to the spawned entity System.out.println("Entity spawned: " + entityRef);};
BlockSelection previousState = selection.place( commandSender, world, new Vector3i(100, 64, 200), // position (nullable) blockMask, // block mask (nullable) entityCallback);World Matching
Section titled “World Matching”// Check if selection can be placed (all target positions are air or in mask)boolean canPlace = selection.canPlace(world, position, blockIdMask);
// Check if selection matches the world exactly at positionboolean matches = selection.matches(world, position);PrefabRotation
Section titled “PrefabRotation”The PrefabRotation enum handles Y-axis rotation transformations for prefab placement:
import com.hypixel.hytale.server.core.prefab.PrefabRotation;import com.hypixel.hytale.math.vector.Vector3i;import com.hypixel.hytale.math.vector.Vector3d;import com.hypixel.hytale.math.vector.Vector3l;import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation;
// Available rotation valuesPrefabRotation rot0 = PrefabRotation.ROTATION_0;PrefabRotation rot90 = PrefabRotation.ROTATION_90;PrefabRotation rot180 = PrefabRotation.ROTATION_180;PrefabRotation rot270 = PrefabRotation.ROTATION_270;
// Access all valuesPrefabRotation[] allRotations = PrefabRotation.VALUES;
// Parse from string (supports "90" or "ROTATION_90")PrefabRotation parsed = PrefabRotation.valueOfExtended("90");
// Convert from Rotation enumPrefabRotation fromRotation = PrefabRotation.fromRotation(Rotation.Ninety);
// Rotate vectors in-place (around Y axis)Vector3i vec = new Vector3i(5, 0, 3);rot90.rotate(vec); // Modifies vec
Vector3d vecD = new Vector3d(5.0, 0.0, 3.0);rot90.rotate(vecD);
Vector3l vecL = new Vector3l(5L, 0L, 3L);rot90.rotate(vecL);
// Get rotated coordinates without modifying inputint newX = rot90.getX(originalX, originalZ);int newZ = rot90.getZ(originalX, originalZ);
// Get yaw angle in radiansfloat yaw = rot90.getYaw(); // Returns -PI/2 for 90 degrees
// Combine rotations (adds degrees, wraps at 360)PrefabRotation combined = rot90.add(PrefabRotation.ROTATION_180); // Returns ROTATION_270
// Get rotated block rotation indexint rotatedBlockRotation = rot90.getRotation(blockRotationIndex);
// Get rotated filler block offsetint rotatedFiller = rot90.getFiller(fillerPackedValue);Transforming Selections
Section titled “Transforming Selections”import com.hypixel.hytale.math.Axis;import com.hypixel.hytale.math.vector.Vector3f;
// Rotate around any axis by angle (90 degree increments)BlockSelection rotatedY = selection.rotate(Axis.Y, 90);BlockSelection rotatedX = selection.rotate(Axis.X, 180);BlockSelection rotatedZ = selection.rotate(Axis.Z, 270);
// Rotate around a custom origin pointVector3f origin = new Vector3f(50.0f, 64.0f, 50.0f);BlockSelection rotatedAroundOrigin = selection.rotate(Axis.Y, 90, origin);
// Arbitrary rotation (any angle, not limited to 90 degrees)BlockSelection arbitraryRotation = selection.rotateArbitrary(45.0f, 0.0f, 0.0f);
// Flip (mirror) along axisBlockSelection flippedX = selection.flip(Axis.X);BlockSelection flippedY = selection.flip(Axis.Y);BlockSelection flippedZ = selection.flip(Axis.Z);
// Clone the selectionBlockSelection clone = selection.cloneSelection();
// Relativize coordinates (shift to origin)BlockSelection relativized = selection.relativize();BlockSelection relativizedCustom = selection.relativize(originX, originY, originZ);
// Merge another selection into this oneselection.add(otherSelection);PrefabWeights
Section titled “PrefabWeights”Handle weighted random selection for prefabs:
import com.hypixel.hytale.server.core.prefab.PrefabWeights;import java.util.Random;import java.util.function.Function;
// Create new weights instancePrefabWeights weights = new PrefabWeights();
// Set weights for specific prefabsweights.setWeight("oak_tree", 3.0);weights.setWeight("birch_tree", 2.0);
// Set default weight for unlisted prefabs (default is 1.0)weights.setDefaultWeight(1.0);
// Get weight for a specific prefab (returns default if not set)double weight = weights.getWeight("oak_tree"); // Returns 3.0double defaulted = weights.getWeight("pine_tree"); // Returns 1.0 (default)
// Remove a specific weightweights.removeWeight("birch_tree");
// Select weighted random element from arrayRandom random = new Random();String[] names = {"oak_tree", "birch_tree", "pine_tree"};Function<String, String> nameExtractor = name -> name; // Identity functionString selected = weights.get(names, nameExtractor, random);
// Select with specific random value (0.0 to 1.0)String selectedWithValue = weights.get(names, nameExtractor, 0.5);
// Parse from string format "name=weight, name2=weight2"PrefabWeights parsed = PrefabWeights.parse("oak=3.0, birch=2.0");
// Get mapping as stringString mappingString = weights.getMappingString(); // "oak_tree=3.0, birch_tree=2.0"
// Get number of explicitly set weightsint size = weights.size();
// Empty weights singleton (always returns null from get())PrefabWeights none = PrefabWeights.NONE;
// Iterate over entriesfor (var entry : weights.entrySet()) { String name = entry.getKey(); double w = entry.getDoubleValue();}PrefabCopyableComponent
Section titled “PrefabCopyableComponent”Mark entities as copyable when saving prefabs:
import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent;import com.hypixel.hytale.component.ComponentType;import com.hypixel.hytale.component.Holder;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
// Get the component type (registered via EntityModule)ComponentType<EntityStore, PrefabCopyableComponent> componentType = PrefabCopyableComponent.getComponentType();
// Get the singleton instancePrefabCopyableComponent instance = PrefabCopyableComponent.get();
// Add to entity to include in prefab copiesHolder<EntityStore> entityHolder = /* your entity */;entityHolder.addComponent(componentType, instance);Related Classes
Section titled “Related Classes”PrefabLoadException
Section titled “PrefabLoadException”Thrown when a prefab fails to load:
import com.hypixel.hytale.server.core.prefab.PrefabLoadException;
try { BlockSelection prefab = store.getServerPrefab("nonexistent.prefab.json");} catch (PrefabLoadException e) { if (e.getType() == PrefabLoadException.Type.NOT_FOUND) { // Prefab file does not exist }}PrefabSaveException
Section titled “PrefabSaveException”Thrown when a prefab fails to save:
import com.hypixel.hytale.server.core.prefab.PrefabSaveException;
try { store.saveServerPrefab("existing.prefab.json", selection);} catch (PrefabSaveException e) { switch (e.getType()) { case ALREADY_EXISTS -> { /* File exists and overwrite=false */ } case ERROR -> { /* I/O error occurred */ } }}BlockMask
Section titled “BlockMask”Filter which blocks are affected during placement:
import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask;import com.hypixel.hytale.server.core.prefab.selection.mask.BlockFilter;
// Empty mask (allows all blocks)BlockMask empty = BlockMask.EMPTY;
// Parse from string (comma or semicolon separated filter expressions)BlockMask parsed = BlockMask.parse("hytale:stone,hytale:dirt");BlockMask parsedAlt = BlockMask.parse("hytale:stone;hytale:dirt");
// Parse from array of filter stringsBlockMask fromArray = BlockMask.parse(new String[]{"hytale:stone", "hytale:dirt"});
// Combine multiple masksBlockMask combined = BlockMask.combine(mask1, mask2, mask3);
// Configure filter optionsBlockMask configured = mask.withOptions(BlockFilter.FilterType.BLOCK, true);
// Invert the maskmask.setInverted(true);boolean inverted = mask.isInverted();
// Check if position should be excludedboolean excluded = mask.isExcluded(accessor, x, y, z, min, max, blockId);boolean excludedWithFluid = mask.isExcluded(accessor, x, y, z, min, max, blockId, fluidId);
// Get filtersBlockFilter[] filters = mask.getFilters();
// Convert to string representationString maskString = mask.toString(); // e.g., "hytale:stone,hytale:dirt"String verbose = mask.informativeToString(); // Human-readable format
// Use during placementselection.place(feedback, world, position, mask);FeedbackConsumer
Section titled “FeedbackConsumer”Progress callback during placement operations:
import com.hypixel.hytale.server.core.prefab.selection.standard.FeedbackConsumer;
// Default implementation (no-op)FeedbackConsumer defaultConsumer = FeedbackConsumer.DEFAULT;
// Custom progress trackingFeedbackConsumer progressConsumer = (feedbackKey, total, current, sender, accessor) -> { double percent = (current * 100.0) / total; System.out.printf("Placing blocks: %.1f%%\n", percent);};
selection.placeNoReturn("myKey", commandSender, progressConsumer, world, componentAccessor);