diff --git a/src/main/java/com/tiedup/remake/core/TiedUpMod.java b/src/main/java/com/tiedup/remake/core/TiedUpMod.java index 9fcd3ab..6b13fd9 100644 --- a/src/main/java/com/tiedup/remake/core/TiedUpMod.java +++ b/src/main/java/com/tiedup/remake/core/TiedUpMod.java @@ -581,6 +581,14 @@ public class TiedUpMod { LOGGER.info( "Registered FurnitureServerReloadListener for data-driven furniture definitions" ); + + // Data-driven room theme definitions (server-side, from data//tiedup_room_themes/) + event.addListener( + new com.tiedup.remake.worldgen.RoomThemeReloadListener() + ); + LOGGER.info( + "Registered RoomThemeReloadListener for data-driven room themes" + ); } } } diff --git a/src/main/java/com/tiedup/remake/worldgen/BlockPalette.java b/src/main/java/com/tiedup/remake/worldgen/BlockPalette.java new file mode 100644 index 0000000..81ff97f --- /dev/null +++ b/src/main/java/com/tiedup/remake/worldgen/BlockPalette.java @@ -0,0 +1,74 @@ +package com.tiedup.remake.worldgen; + +import java.util.List; +import java.util.Map; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +/** + * Weighted random block selection with named condition variants. + * + *

Each palette maps condition names (e.g. "default", "bottom_row", "edge", + * "corner", "cap") to a list of weighted block state entries. When a block + * is requested for a condition that has no entries, falls back to "default".

+ */ +public final class BlockPalette { + + /** + * A single block state with its selection weight. + * + * @param state the block state to place + * @param weight relative weight (higher = more likely) + */ + public record WeightedEntry(BlockState state, float weight) {} + + private final Map> variants; + + /** + * @param variants condition name to weighted entries map (defensively copied) + */ + public BlockPalette(Map> variants) { + this.variants = Map.copyOf(variants); + } + + /** + * Pick a random block state for the given condition. + * Falls back to "default" if the condition has no entries. + * + * @param random the random source + * @param condition the condition name (e.g. "default", "bottom_row") + * @return a randomly selected block state + */ + public BlockState pick(RandomSource random, String condition) { + List entries = variants.getOrDefault( + condition, variants.get("default") + ); + if (entries == null || entries.isEmpty()) { + // Should never happen if parsed correctly -- safety fallback + return Blocks.STONE.defaultBlockState(); + } + + float totalWeight = 0; + for (WeightedEntry e : entries) { + totalWeight += e.weight(); + } + + float roll = random.nextFloat() * totalWeight; + float cumulative = 0; + for (WeightedEntry e : entries) { + cumulative += e.weight(); + if (roll < cumulative) return e.state(); + } + + // Floating-point edge case fallback + return entries.get(entries.size() - 1).state(); + } + + /** + * @return unmodifiable map of all condition variants + */ + public Map> variants() { + return variants; + } +} diff --git a/src/main/java/com/tiedup/remake/worldgen/DecorationConfig.java b/src/main/java/com/tiedup/remake/worldgen/DecorationConfig.java new file mode 100644 index 0000000..a9d7692 --- /dev/null +++ b/src/main/java/com/tiedup/remake/worldgen/DecorationConfig.java @@ -0,0 +1,43 @@ +package com.tiedup.remake.worldgen; + +import java.util.List; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +/** + * Configuration for decorative blocks placed in a room theme. + * + *

Captures corner decorations, wall midpoint blocks, optional special + * first-corner placement, furniture cluster, and lighting/chain flags.

+ * + * @param cornerDecorations blocks placed at room corners (each with a Y offset) + * @param wallMidpointBlocks blocks placed at wall midpoints + * @param firstCornerSpecial optional special block for the first corner only + * @param furnitureCluster blocks placed as a furniture group + * @param useTorchLighting whether to place torches for lighting + * @param hasCeilingChain whether to place ceiling chains + */ +public record DecorationConfig( + List cornerDecorations, + List wallMidpointBlocks, + @Nullable PositionedBlock firstCornerSpecial, + List furnitureCluster, + boolean useTorchLighting, + boolean hasCeilingChain +) { + + /** + * A block state with positional offsets for placement. + * + * @param state the block state to place + * @param xOffset horizontal X offset from base position (0 = no offset) + * @param yOffset vertical offset from the base position + * @param zOffset horizontal Z offset from base position (0 = no offset) + */ + public record PositionedBlock(BlockState state, int xOffset, int yOffset, int zOffset) { + /** Convenience constructor for Y-offset-only blocks (most common case). */ + public PositionedBlock(BlockState state, int yOffset) { + this(state, 0, yOffset, 0); + } + } +} diff --git a/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java b/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java index 06c8256..dfea7b0 100644 --- a/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java +++ b/src/main/java/com/tiedup/remake/worldgen/HangingCagePiece.java @@ -26,6 +26,7 @@ import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.ChestBlock; +import net.minecraft.world.level.block.LanternBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -279,7 +280,7 @@ public class HangingCagePiece extends StructurePiece { /** * Fallback: carve a 13x13x12 dungeon room and place the cage inside. - * Randomly selects a theme (Oubliette/Inferno) and layout (Square/Octagonal). + * Randomly selects a data-driven theme and layout (Square/Octagonal). */ private void carveAndPlaceRoom( WorldGenLevel level, @@ -288,9 +289,7 @@ public class HangingCagePiece extends StructurePiece { int centerZ, BoundingBox chunkBB ) { - RoomTheme theme = RoomTheme.values()[random.nextInt( - RoomTheme.values().length - )]; + RoomThemeDefinition theme = RoomThemeRegistry.pickRandomOrFallback(random); RoomLayout layout = RoomLayout.values()[random.nextInt( RoomLayout.values().length )]; @@ -358,62 +357,24 @@ public class HangingCagePiece extends StructurePiece { } } - theme.placeDecorations( - level, - random, - baseX, - baseZ, - floorY, - layout, - chunkBB + placeThemeDecorations( + level, random, baseX, baseZ, floorY, layout, chunkBB, theme ); - RoomTheme.placeSharedPillars( - level, - random, - baseX, - baseZ, - floorY, - layout, - theme, - chunkBB + placeSharedPillars( + level, random, baseX, baseZ, floorY, layout, theme, chunkBB ); - RoomTheme.placeSharedFloorScatter( - level, - random, - baseX, - baseZ, - floorY, - layout, - theme, - chunkBB + placeSharedFloorScatter( + level, random, baseX, baseZ, floorY, layout, theme, chunkBB ); - RoomTheme.placeSharedCeilingDecor( - level, - random, - baseX, - baseZ, - floorY, - layout, - chunkBB + placeSharedCeilingDecor( + level, random, baseX, baseZ, floorY, layout, chunkBB ); - RoomTheme.placeSharedWallLighting( - level, - random, - baseX, - baseZ, - floorY, - layout, - chunkBB + placeSharedWallLighting( + level, random, baseX, baseZ, floorY, layout, chunkBB ); - RoomTheme.placeSharedWallBands( - level, - baseX, - baseZ, - floorY, - layout, - theme, - chunkBB + placeSharedWallBands( + level, baseX, baseZ, floorY, layout, theme, chunkBB ); placeVanillaChest(level, random, baseX, baseZ, floorY, layout, chunkBB); @@ -451,7 +412,7 @@ public class HangingCagePiece extends StructurePiece { TiedUpMod.LOGGER.info( "[HangingCage] Placed cage in carved room (theme={}, layout={}) at {}", - theme.name(), + theme.id(), layout.name(), masterPos.toShortString() ); @@ -655,4 +616,331 @@ public class HangingCagePiece extends StructurePiece { ); } } + + // ── Data-driven theme decorations ─────────────────────────────── + + /** + * Place decorations from the data-driven {@link DecorationConfig}. + * Replaces the old per-enum {@code RoomTheme.placeDecorations()} method. + */ + private static void placeThemeDecorations( + WorldGenLevel level, + RandomSource random, + int baseX, + int baseZ, + int floorY, + RoomLayout layout, + BoundingBox chunkBB, + RoomThemeDefinition theme + ) { + DecorationConfig deco = theme.decorations(); + int[][] corners = layout.innerCorners(); + + // Corner decorations (e.g. cobwebs low+high, soul fire, snow layers) + // Note: only y_offset is used here — x/z offsets are ignored because corners + // are placed at all 4 inner corners and direction logic would differ per corner. + for (DecorationConfig.PositionedBlock pb : deco.cornerDecorations()) { + for (int[] c : corners) { + if (layout.isInShape(c[0], c[1]) && !layout.isWall(c[0], c[1])) { + safeSetBlock(level, + new BlockPos(baseX + c[0], floorY + pb.yOffset(), baseZ + c[1]), + pb.state(), chunkBB); + } + } + } + + // Wall midpoint decorations (e.g. soul lanterns, crying obsidian, sculk veins) + int[][] wallMidpoints = {{6, 1}, {6, 11}, {1, 6}, {11, 6}}; + for (DecorationConfig.PositionedBlock pb : deco.wallMidpointBlocks()) { + for (int[] wp : wallMidpoints) { + if (layout.isWall(wp[0], wp[1])) { + safeSetBlock(level, + new BlockPos(baseX + wp[0], floorY + pb.yOffset(), baseZ + wp[1]), + pb.state(), chunkBB); + } + } + } + + // First corner special (e.g. water cauldron, skull, TNT, sculk catalyst) + // x/z offsets are multiplied by inward direction (toward room center) + if (deco.firstCornerSpecial() != null && corners.length > 0) { + int[] sc = corners[0]; + int dirX = sc[0] < 6 ? 1 : -1; + int dirZ = sc[1] < 6 ? 1 : -1; + int scx = sc[0] + deco.firstCornerSpecial().xOffset() * dirX; + int scz = sc[1] + deco.firstCornerSpecial().zOffset() * dirZ; + if (layout.isInShape(scx, scz) && !layout.isWall(scx, scz)) { + safeSetBlock(level, + new BlockPos(baseX + scx, floorY + deco.firstCornerSpecial().yOffset(), baseZ + scz), + deco.firstCornerSpecial().state(), chunkBB); + } + } + + // Furniture cluster at corners[1] — each item has x/z offsets relative to the + // base furniture position. x_offset/z_offset values are multiplied by the + // inward direction (toward room center) to handle all 4 corner orientations. + if (corners.length > 1) { + int[] fc = corners[1]; + int dirX = fc[0] < 6 ? 1 : -1; + int dirZ = fc[1] < 6 ? 1 : -1; + int fcx = fc[0] + dirX; + int fcz = fc[1] + dirZ; + for (DecorationConfig.PositionedBlock pb : deco.furnitureCluster()) { + int px = fcx + pb.xOffset() * dirX; + int pz = fcz + pb.zOffset() * dirZ; + if (layout.isInShape(px, pz) && !layout.isWall(px, pz)) { + safeSetBlock(level, + new BlockPos(baseX + px, floorY + pb.yOffset(), baseZ + pz), + pb.state(), chunkBB); + } + } + + // Ceiling chain above furniture + if (deco.hasCeilingChain()) { + for (int cy = 8; cy <= 10; cy++) { + safeSetBlock(level, + new BlockPos(baseX + fcx, floorY + cy, baseZ + fcz), + Blocks.CHAIN.defaultBlockState(), chunkBB); + } + } + } + + // Torch lighting (wall torches at outer wall midpoints) -- used by Crypt, Sandstone + if (deco.useTorchLighting()) { + for (int[] wallPos : new int[][] {{6, 0}, {6, 12}, {0, 6}, {12, 6}}) { + if (layout.isWall(wallPos[0], wallPos[1])) { + Direction torchDir = getTorchDirection(wallPos[0], wallPos[1]); + if (torchDir != null) { + safeSetBlock(level, + new BlockPos(baseX + wallPos[0], floorY + 3, baseZ + wallPos[1]), + Blocks.WALL_TORCH.defaultBlockState().setValue( + net.minecraft.world.level.block.WallTorchBlock.FACING, torchDir), + chunkBB); + } + } + } + } + + // Side chains + hanging lanterns (shared by all themes) + placeSharedChains(level, baseX, baseZ, floorY, chunkBB); + placeSharedHangingLanterns(level, baseX, baseZ, floorY, chunkBB); + } + + // ── Shared structural features (moved from RoomTheme) ────────── + + /** Pillar positions -- verified to be inside all 4 layouts. */ + private static final int[][] PILLAR_POSITIONS = { + {4, 4}, {4, 8}, {8, 4}, {8, 8}, + }; + + /** Place 4 full-height pillars at verified positions. */ + private static void placeSharedPillars( + WorldGenLevel level, + RandomSource random, + int baseX, + int baseZ, + int floorY, + RoomLayout layout, + RoomThemeDefinition theme, + BoundingBox chunkBB + ) { + for (int[] p : PILLAR_POSITIONS) { + if (!layout.isInShape(p[0], p[1]) || layout.isWall(p[0], p[1])) continue; + for (int ry = 1; ry <= 10; ry++) { + safeSetBlock(level, + new BlockPos(baseX + p[0], floorY + ry, baseZ + p[1]), + theme.pillarBlock(random, ry), chunkBB); + } + } + } + + /** Place random floor scatter (~12% of interior positions). */ + private static void placeSharedFloorScatter( + WorldGenLevel level, + RandomSource random, + int baseX, + int baseZ, + int floorY, + RoomLayout layout, + RoomThemeDefinition theme, + BoundingBox chunkBB + ) { + for (int rx = 1; rx <= 11; rx++) { + for (int rz = 1; rz <= 11; rz++) { + if (!layout.isInShape(rx, rz) || layout.isWall(rx, rz)) continue; + // Skip pillar positions + if ((rx == 4 || rx == 8) && (rz == 4 || rz == 8)) continue; + // Skip cage center area (5-7, 5-7) + if (rx >= 5 && rx <= 7 && rz >= 5 && rz <= 7) continue; + // Skip corner positions (used by decorations/chests) + if ((rx <= 2 || rx >= 10) && (rz <= 2 || rz >= 10)) continue; + if (random.nextFloat() < 0.12f) { + BlockState scatter = theme.scatterBlock(random); + if (scatter != null) { + safeSetBlock(level, + new BlockPos(baseX + rx, floorY + 1, baseZ + rz), + scatter, chunkBB); + } + } + } + } + } + + /** Place ceiling cobwebs and extra hanging chains. */ + private static void placeSharedCeilingDecor( + WorldGenLevel level, + RandomSource random, + int baseX, + int baseZ, + int floorY, + RoomLayout layout, + BoundingBox chunkBB + ) { + // Cobwebs along walls at ceiling level + int[][] cobwebCandidates = { + {2, 1}, {4, 1}, {8, 1}, {10, 1}, + {2, 11}, {4, 11}, {8, 11}, {10, 11}, + {1, 4}, {1, 8}, {11, 4}, {11, 8}, + }; + for (int[] pos : cobwebCandidates) { + if (layout.isInShape(pos[0], pos[1]) + && !layout.isWall(pos[0], pos[1]) + && random.nextFloat() < 0.45f) { + safeSetBlock(level, + new BlockPos(baseX + pos[0], floorY + 10, baseZ + pos[1]), + Blocks.COBWEB.defaultBlockState(), chunkBB); + } + } + // Extra hanging chains at random interior positions + int[][] chainCandidates = {{5, 3}, {7, 9}, {3, 7}}; + for (int[] pos : chainCandidates) { + if (layout.isInShape(pos[0], pos[1]) + && !layout.isWall(pos[0], pos[1]) + && random.nextFloat() < 0.6f) { + safeSetBlock(level, + new BlockPos(baseX + pos[0], floorY + 10, baseZ + pos[1]), + Blocks.CHAIN.defaultBlockState(), chunkBB); + safeSetBlock(level, + new BlockPos(baseX + pos[0], floorY + 9, baseZ + pos[1]), + Blocks.CHAIN.defaultBlockState(), chunkBB); + } + } + } + + /** Place additional wall-mounted lighting. */ + private static void placeSharedWallLighting( + WorldGenLevel level, + RandomSource random, + int baseX, + int baseZ, + int floorY, + RoomLayout layout, + BoundingBox chunkBB + ) { + // Lanterns on pillar sides (facing center) at ry=1 (on the floor) + int[][] pillarLanternPositions = { + {5, 4}, {4, 7}, {8, 5}, {7, 8}, + }; + for (int[] pos : pillarLanternPositions) { + if (layout.isInShape(pos[0], pos[1]) + && !layout.isWall(pos[0], pos[1])) { + safeSetBlock(level, + new BlockPos(baseX + pos[0], floorY + 1, baseZ + pos[1]), + Blocks.LANTERN.defaultBlockState(), chunkBB); + } + } + // Extra wall sconces at quarter-points + int[][] wallSconces = { + {4, 0}, {8, 0}, {4, 12}, {8, 12}, + {0, 4}, {0, 8}, {12, 4}, {12, 8}, + }; + for (int[] pos : wallSconces) { + if (layout.isWall(pos[0], pos[1]) && random.nextFloat() < 0.5f) { + Direction torchDir = getTorchDirection(pos[0], pos[1]); + if (torchDir != null) { + safeSetBlock(level, + new BlockPos(baseX + pos[0], floorY + 3, baseZ + pos[1]), + Blocks.WALL_TORCH.defaultBlockState().setValue( + net.minecraft.world.level.block.WallTorchBlock.FACING, torchDir), + chunkBB); + } + } + } + } + + /** Place wall accent bands at ry=5 and ry=8. */ + private static void placeSharedWallBands( + WorldGenLevel level, + int baseX, + int baseZ, + int floorY, + RoomLayout layout, + RoomThemeDefinition theme, + BoundingBox chunkBB + ) { + int[][] bandPositions = { + {6, 1}, {6, 11}, {1, 6}, {11, 6}, + {4, 1}, {8, 1}, {4, 11}, {8, 11}, + {1, 4}, {1, 8}, {11, 4}, {11, 8}, + }; + for (int[] pos : bandPositions) { + if (layout.isWall(pos[0], pos[1])) { + // Band at ry=5 + safeSetBlock(level, + new BlockPos(baseX + pos[0], floorY + 5, baseZ + pos[1]), + theme.wallAccentBlock(), chunkBB); + // Optional band at ry=8 + safeSetBlock(level, + new BlockPos(baseX + pos[0], floorY + 8, baseZ + pos[1]), + theme.wallAccentBlock(), chunkBB); + } + } + } + + /** Chains on cage flanks and ceiling corners -- shared by all themes. */ + private static void placeSharedChains( + WorldGenLevel level, + int baseX, + int baseZ, + int floorY, + BoundingBox chunkBB + ) { + for (int[] chainPos : new int[][] {{3, 6}, {9, 6}}) { + for (int ry = 5; ry <= 10; ry++) { + safeSetBlock(level, + new BlockPos(baseX + chainPos[0], floorY + ry, baseZ + chainPos[1]), + Blocks.CHAIN.defaultBlockState(), chunkBB); + } + } + for (int[] chainPos : new int[][] {{3, 3}, {3, 9}, {9, 3}, {9, 9}}) { + safeSetBlock(level, + new BlockPos(baseX + chainPos[0], floorY + 10, baseZ + chainPos[1]), + Blocks.CHAIN.defaultBlockState(), chunkBB); + } + } + + /** Determine torch facing direction for a wall position (torch faces inward). */ + private static Direction getTorchDirection(int rx, int rz) { + if (rz == 0) return Direction.SOUTH; + if (rz == 12) return Direction.NORTH; + if (rx == 0) return Direction.EAST; + if (rx == 12) return Direction.WEST; + return null; + } + + /** Hanging lanterns -- shared by all themes. */ + private static void placeSharedHangingLanterns( + WorldGenLevel level, + int baseX, + int baseZ, + int floorY, + BoundingBox chunkBB + ) { + for (int[] pos : new int[][] {{3, 3}, {9, 9}}) { + safeSetBlock(level, + new BlockPos(baseX + pos[0], floorY + 9, baseZ + pos[1]), + Blocks.LANTERN.defaultBlockState().setValue(LanternBlock.HANGING, true), + chunkBB); + } + } } diff --git a/src/main/java/com/tiedup/remake/worldgen/RoomTheme.java b/src/main/java/com/tiedup/remake/worldgen/RoomTheme.java deleted file mode 100644 index 7c06ada..0000000 --- a/src/main/java/com/tiedup/remake/worldgen/RoomTheme.java +++ /dev/null @@ -1,1368 +0,0 @@ -package com.tiedup.remake.worldgen; - -import javax.annotation.Nullable; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.LanternBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.levelgen.structure.BoundingBox; - -enum RoomTheme { - OUBLIETTE { - @Override - public BlockState wallBlock(RandomSource r, int ry) { - if ( - ry == 1 && r.nextFloat() < 0.30f - ) return Blocks.MOSSY_COBBLESTONE.defaultBlockState(); - return r.nextFloat() < 0.20f - ? Blocks.CRACKED_DEEPSLATE_BRICKS.defaultBlockState() - : Blocks.DEEPSLATE_BRICKS.defaultBlockState(); - } - - @Override - public BlockState floorBlock( - RandomSource r, - int rx, - int rz, - boolean isEdge - ) { - boolean isCorner = (rx == 1 || rx == 11) && (rz == 1 || rz == 11); - if (isCorner) return Blocks.MOSSY_COBBLESTONE.defaultBlockState(); - return r.nextFloat() < 0.15f - ? Blocks.COBBLESTONE.defaultBlockState() - : Blocks.DEEPSLATE_TILES.defaultBlockState(); - } - - @Override - public BlockState ceilingBlock(RandomSource r) { - return r.nextFloat() < 0.20f - ? Blocks.CRACKED_DEEPSLATE_BRICKS.defaultBlockState() - : Blocks.DEEPSLATE_BRICKS.defaultBlockState(); - } - - @Override - public BlockState wallShellBlock() { - return Blocks.DEEPSLATE_BRICKS.defaultBlockState(); - } - - @Override - public void placeDecorations( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ) { - int[][] corners = layout.innerCorners(); - // Cobwebs at corners (low + high) - for (int[] c : corners) { - if ( - layout.isInShape(c[0], c[1]) && !layout.isWall(c[0], c[1]) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + c[0], floorY + 1, baseZ + c[1]), - Blocks.COBWEB.defaultBlockState(), - chunkBB - ); - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + c[0], floorY + 9, baseZ + c[1]), - Blocks.COBWEB.defaultBlockState(), - chunkBB - ); - } - } - // Water cauldron near first corner - int cx = corners[0][0] + 1, - cz = corners[0][1] + 1; - if (layout.isInShape(cx, cz) && !layout.isWall(cx, cz)) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + cx, floorY + 1, baseZ + cz), - Blocks.WATER_CAULDRON.defaultBlockState().setValue( - BlockStateProperties.LEVEL_CAULDRON, - 3 - ), - chunkBB - ); - } - // Soul lanterns on wall midpoints - for (int[] wallPos : new int[][] { - { 6, 1 }, - { 6, 11 }, - { 1, 6 }, - { 11, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 3, - baseZ + wallPos[1] - ), - Blocks.SOUL_LANTERN.defaultBlockState(), - chunkBB - ); - } - } - // Corner furniture: barrel + brewing_stand + chain - int[] fc = corners[1]; - int fcx = fc[0] + (fc[0] < 6 ? 1 : -1); - int fcz = fc[1] + (fc[1] < 6 ? 1 : -1); - if (layout.isInShape(fcx, fcz) && !layout.isWall(fcx, fcz)) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + fcx, floorY + 1, baseZ + fcz), - Blocks.BARREL.defaultBlockState(), - chunkBB - ); - int fcx2 = fcx + (fcx < 6 ? 1 : -1); - if (layout.isInShape(fcx2, fcz) && !layout.isWall(fcx2, fcz)) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + fcx2, floorY + 1, baseZ + fcz), - Blocks.BREWING_STAND.defaultBlockState(), - chunkBB - ); - } - // Chain above barrel - for (int cy = 8; cy <= 10; cy++) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + fcx, floorY + cy, baseZ + fcz), - Blocks.CHAIN.defaultBlockState(), - chunkBB - ); - } - } - // Side chains - placeSharedChains(level, baseX, baseZ, floorY, chunkBB); - // Hanging lanterns - placeSharedHangingLanterns(level, baseX, baseZ, floorY, chunkBB); - } - - @Override - public BlockState pillarBlock(RandomSource r, int ry) { - if ( - ry == 1 || ry == 10 - ) return Blocks.POLISHED_DEEPSLATE.defaultBlockState(); - return Blocks.DEEPSLATE_BRICK_WALL.defaultBlockState(); - } - - @Override - @Nullable - public BlockState scatterBlock(RandomSource r) { - float f = r.nextFloat(); - if (f < 0.40f) return Blocks.COBWEB.defaultBlockState(); - if (f < 0.70f) return Blocks.CANDLE.defaultBlockState() - .setValue(BlockStateProperties.CANDLES, 1 + r.nextInt(3)) - .setValue(BlockStateProperties.LIT, true); - return Blocks.MOSS_CARPET.defaultBlockState(); - } - - @Override - public BlockState wallAccentBlock() { - return Blocks.POLISHED_DEEPSLATE.defaultBlockState(); - } - }, - INFERNO { - @Override - public BlockState wallBlock(RandomSource r, int ry) { - return r.nextFloat() < 0.20f - ? Blocks.CRACKED_NETHER_BRICKS.defaultBlockState() - : Blocks.NETHER_BRICKS.defaultBlockState(); - } - - @Override - public BlockState floorBlock( - RandomSource r, - int rx, - int rz, - boolean isEdge - ) { - if (isEdge) return Blocks.MAGMA_BLOCK.defaultBlockState(); - return r.nextFloat() < 0.08f - ? Blocks.GILDED_BLACKSTONE.defaultBlockState() - : Blocks.BLACKSTONE.defaultBlockState(); - } - - @Override - public BlockState ceilingBlock(RandomSource r) { - return r.nextFloat() < 0.20f - ? Blocks.CRACKED_NETHER_BRICKS.defaultBlockState() - : Blocks.NETHER_BRICKS.defaultBlockState(); - } - - @Override - public BlockState wallShellBlock() { - return Blocks.NETHER_BRICKS.defaultBlockState(); - } - - @Override - public void placeDecorations( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ) { - int[][] corners = layout.innerCorners(); - // Soul fire at corners: soul_sand at ry=0, soul_fire at ry=1 - for (int[] c : corners) { - if ( - layout.isInShape(c[0], c[1]) && !layout.isWall(c[0], c[1]) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + c[0], floorY, baseZ + c[1]), - Blocks.SOUL_SAND.defaultBlockState(), - chunkBB - ); - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + c[0], floorY + 1, baseZ + c[1]), - Blocks.SOUL_FIRE.defaultBlockState(), - chunkBB - ); - } - } - // Crying obsidian accents at wall midpoints - for (int[] wallPos : new int[][] { - { 6, 1 }, - { 6, 11 }, - { 1, 6 }, - { 11, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 2, - baseZ + wallPos[1] - ), - Blocks.CRYING_OBSIDIAN.defaultBlockState(), - chunkBB - ); - } - } - // Soul lanterns on wall midpoints - for (int[] wallPos : new int[][] { - { 6, 1 }, - { 6, 11 }, - { 1, 6 }, - { 11, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 3, - baseZ + wallPos[1] - ), - Blocks.SOUL_LANTERN.defaultBlockState(), - chunkBB - ); - } - } - // Corner furniture: soul_campfire + cauldron(lava) + gilded_blackstone - int[][] icorners = layout.innerCorners(); - int[] ifc = icorners[1]; - int ifcx = ifc[0] + (ifc[0] < 6 ? 1 : -1); - int ifcz = ifc[1] + (ifc[1] < 6 ? 1 : -1); - if (layout.isInShape(ifcx, ifcz) && !layout.isWall(ifcx, ifcz)) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + ifcx, floorY + 1, baseZ + ifcz), - Blocks.SOUL_CAMPFIRE.defaultBlockState(), - chunkBB - ); - int ifcx2 = ifcx + (ifcx < 6 ? 1 : -1); - if ( - layout.isInShape(ifcx2, ifcz) && !layout.isWall(ifcx2, ifcz) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + ifcx2, floorY + 1, baseZ + ifcz), - Blocks.LAVA_CAULDRON.defaultBlockState(), - chunkBB - ); - } - if ( - layout.isInShape(ifcx, ifcz + (ifcz < 6 ? 1 : -1)) && - !layout.isWall(ifcx, ifcz + (ifcz < 6 ? 1 : -1)) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + ifcx, - floorY + 1, - baseZ + ifcz + (ifcz < 6 ? 1 : -1) - ), - Blocks.GILDED_BLACKSTONE.defaultBlockState(), - chunkBB - ); - } - } - // Side chains - placeSharedChains(level, baseX, baseZ, floorY, chunkBB); - // Hanging lanterns - placeSharedHangingLanterns(level, baseX, baseZ, floorY, chunkBB); - } - - @Override - public BlockState pillarBlock(RandomSource r, int ry) { - if ( - ry == 1 || ry == 10 - ) return Blocks.QUARTZ_PILLAR.defaultBlockState(); - return Blocks.NETHER_BRICK_WALL.defaultBlockState(); - } - - @Override - @Nullable - public BlockState scatterBlock(RandomSource r) { - float f = r.nextFloat(); - if (f < 0.40f) return Blocks.SOUL_SAND.defaultBlockState(); - if (f < 0.70f) return Blocks.NETHER_WART_BLOCK.defaultBlockState(); - return Blocks.BONE_BLOCK.defaultBlockState(); - } - - @Override - public BlockState wallAccentBlock() { - return Blocks.RED_NETHER_BRICKS.defaultBlockState(); - } - }, - CRYPT { - @Override - public BlockState wallBlock(RandomSource r, int ry) { - float f = r.nextFloat(); - if ( - ry == 1 && f < 0.20f - ) return Blocks.MOSSY_COBBLESTONE.defaultBlockState(); - if (f < 0.15f) return Blocks.MOSSY_STONE_BRICKS.defaultBlockState(); - if ( - f < 0.30f - ) return Blocks.CRACKED_STONE_BRICKS.defaultBlockState(); - return Blocks.STONE_BRICKS.defaultBlockState(); - } - - @Override - public BlockState floorBlock( - RandomSource r, - int rx, - int rz, - boolean isEdge - ) { - boolean isCorner = (rx == 1 || rx == 11) && (rz == 1 || rz == 11); - if (isCorner) return Blocks.MOSSY_COBBLESTONE.defaultBlockState(); - return r.nextFloat() < 0.20f - ? Blocks.COBBLESTONE.defaultBlockState() - : Blocks.STONE_BRICKS.defaultBlockState(); - } - - @Override - public BlockState ceilingBlock(RandomSource r) { - return r.nextFloat() < 0.20f - ? Blocks.CRACKED_STONE_BRICKS.defaultBlockState() - : Blocks.STONE_BRICKS.defaultBlockState(); - } - - @Override - public BlockState wallShellBlock() { - return Blocks.STONE_BRICKS.defaultBlockState(); - } - - @Override - public void placeDecorations( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ) { - int[][] corners = layout.innerCorners(); - // Cobwebs at corners (low + high) - for (int[] c : corners) { - if ( - layout.isInShape(c[0], c[1]) && !layout.isWall(c[0], c[1]) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + c[0], floorY + 1, baseZ + c[1]), - Blocks.COBWEB.defaultBlockState(), - chunkBB - ); - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + c[0], floorY + 9, baseZ + c[1]), - Blocks.COBWEB.defaultBlockState(), - chunkBB - ); - } - } - // Wall torches at midpoints - for (int[] wallPos : new int[][] { - { 6, 0 }, - { 6, 12 }, - { 0, 6 }, - { 12, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - Direction torchDir = getTorchDirection( - wallPos[0], - wallPos[1] - ); - if (torchDir != null) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 3, - baseZ + wallPos[1] - ), - Blocks.WALL_TORCH.defaultBlockState().setValue( - net.minecraft.world.level.block.WallTorchBlock.FACING, - torchDir - ), - chunkBB - ); - } - } - } - // Skull in first corner - int[] sc = corners[0]; - if ( - layout.isInShape(sc[0], sc[1]) && !layout.isWall(sc[0], sc[1]) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + sc[0], floorY + 1, baseZ + sc[1]), - Blocks.SKELETON_SKULL.defaultBlockState(), - chunkBB - ); - } - // Corner furniture: lectern + candles on floor - int[] cfc = corners[1]; - int cfcx = cfc[0] + (cfc[0] < 6 ? 1 : -1); - int cfcz = cfc[1] + (cfc[1] < 6 ? 1 : -1); - if (layout.isInShape(cfcx, cfcz) && !layout.isWall(cfcx, cfcz)) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + cfcx, floorY + 1, baseZ + cfcz), - Blocks.LECTERN.defaultBlockState(), - chunkBB - ); - int cfcx2 = cfcx + (cfcx < 6 ? 1 : -1); - if ( - layout.isInShape(cfcx2, cfcz) && !layout.isWall(cfcx2, cfcz) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + cfcx2, floorY + 1, baseZ + cfcz), - Blocks.CANDLE.defaultBlockState() - .setValue(BlockStateProperties.CANDLES, 4) - .setValue(BlockStateProperties.LIT, true), - chunkBB - ); - } - } - placeSharedChains(level, baseX, baseZ, floorY, chunkBB); - placeSharedHangingLanterns(level, baseX, baseZ, floorY, chunkBB); - } - - @Override - public BlockState pillarBlock(RandomSource r, int ry) { - if ( - ry == 1 || ry == 10 - ) return Blocks.CHISELED_STONE_BRICKS.defaultBlockState(); - return Blocks.STONE_BRICK_WALL.defaultBlockState(); - } - - @Override - @Nullable - public BlockState scatterBlock(RandomSource r) { - float f = r.nextFloat(); - if (f < 0.40f) return Blocks.COBWEB.defaultBlockState(); - if (f < 0.70f) return Blocks.CANDLE.defaultBlockState() - .setValue(BlockStateProperties.CANDLES, 1 + r.nextInt(3)) - .setValue(BlockStateProperties.LIT, true); - return Blocks.BONE_BLOCK.defaultBlockState(); - } - - @Override - public BlockState wallAccentBlock() { - return Blocks.MOSSY_STONE_BRICKS.defaultBlockState(); - } - }, - ICE { - @Override - public BlockState wallBlock(RandomSource r, int ry) { - float f = r.nextFloat(); - if (f < 0.10f) return Blocks.ICE.defaultBlockState(); - if (f < 0.30f) return Blocks.BLUE_ICE.defaultBlockState(); - return Blocks.PACKED_ICE.defaultBlockState(); - } - - @Override - public BlockState floorBlock( - RandomSource r, - int rx, - int rz, - boolean isEdge - ) { - if (isEdge) return Blocks.BLUE_ICE.defaultBlockState(); - return r.nextFloat() < 0.20f - ? Blocks.SNOW_BLOCK.defaultBlockState() - : Blocks.PACKED_ICE.defaultBlockState(); - } - - @Override - public BlockState ceilingBlock(RandomSource r) { - return r.nextFloat() < 0.20f - ? Blocks.BLUE_ICE.defaultBlockState() - : Blocks.PACKED_ICE.defaultBlockState(); - } - - @Override - public BlockState wallShellBlock() { - return Blocks.PACKED_ICE.defaultBlockState(); - } - - @Override - public void placeDecorations( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ) { - int[][] corners = layout.innerCorners(); - // Snow layers in corners - for (int[] c : corners) { - if ( - layout.isInShape(c[0], c[1]) && !layout.isWall(c[0], c[1]) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + c[0], floorY + 1, baseZ + c[1]), - Blocks.SNOW.defaultBlockState().setValue( - net.minecraft.world.level.block.SnowLayerBlock.LAYERS, - 2 + random.nextInt(3) - ), - chunkBB - ); - } - } - // Lanterns on wall midpoints - for (int[] wallPos : new int[][] { - { 6, 1 }, - { 6, 11 }, - { 1, 6 }, - { 11, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 3, - baseZ + wallPos[1] - ), - Blocks.LANTERN.defaultBlockState(), - chunkBB - ); - } - } - // Ice stalactites near ceiling corners - for (int[] c : corners) { - int cx = c[0], - cz = c[1]; - if (layout.isInShape(cx, cz) && !layout.isWall(cx, cz)) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + cx, floorY + 10, baseZ + cz), - Blocks.ICE.defaultBlockState(), - chunkBB - ); - } - } - // Corner furniture: cauldron(powder_snow) + blue_ice seat + lantern - int[] ifc2 = corners[1]; - int ifc2x = ifc2[0] + (ifc2[0] < 6 ? 1 : -1); - int ifc2z = ifc2[1] + (ifc2[1] < 6 ? 1 : -1); - if ( - layout.isInShape(ifc2x, ifc2z) && !layout.isWall(ifc2x, ifc2z) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + ifc2x, floorY + 1, baseZ + ifc2z), - Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue( - BlockStateProperties.LEVEL_CAULDRON, - 3 - ), - chunkBB - ); - int ifc2x2 = ifc2x + (ifc2x < 6 ? 1 : -1); - if ( - layout.isInShape(ifc2x2, ifc2z) && - !layout.isWall(ifc2x2, ifc2z) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + ifc2x2, floorY + 1, baseZ + ifc2z), - Blocks.BLUE_ICE.defaultBlockState(), - chunkBB - ); - } - if ( - layout.isInShape(ifc2x, ifc2z + (ifc2z < 6 ? 1 : -1)) && - !layout.isWall(ifc2x, ifc2z + (ifc2z < 6 ? 1 : -1)) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + ifc2x, - floorY + 2, - baseZ + ifc2z + (ifc2z < 6 ? 1 : -1) - ), - Blocks.LANTERN.defaultBlockState(), - chunkBB - ); - } - } - placeSharedChains(level, baseX, baseZ, floorY, chunkBB); - placeSharedHangingLanterns(level, baseX, baseZ, floorY, chunkBB); - } - - @Override - public BlockState pillarBlock(RandomSource r, int ry) { - if (ry == 1 || ry == 10) return Blocks.BLUE_ICE.defaultBlockState(); - return r.nextFloat() < 0.5f - ? Blocks.PACKED_ICE.defaultBlockState() - : Blocks.BLUE_ICE.defaultBlockState(); - } - - @Override - @Nullable - public BlockState scatterBlock(RandomSource r) { - float f = r.nextFloat(); - if (f < 0.50f) return Blocks.SNOW.defaultBlockState().setValue( - net.minecraft.world.level.block.SnowLayerBlock.LAYERS, - 1 + r.nextInt(2) - ); - if (f < 0.70f) return Blocks.POWDER_SNOW.defaultBlockState(); - return Blocks.ICE.defaultBlockState(); - } - - @Override - public BlockState wallAccentBlock() { - return Blocks.BLUE_ICE.defaultBlockState(); - } - }, - SCULK { - @Override - public BlockState wallBlock(RandomSource r, int ry) { - float f = r.nextFloat(); - if ( - f < 0.10f - ) return Blocks.CRACKED_DEEPSLATE_BRICKS.defaultBlockState(); - if (f < 0.40f) return Blocks.SCULK.defaultBlockState(); - return Blocks.DEEPSLATE_BRICKS.defaultBlockState(); - } - - @Override - public BlockState floorBlock( - RandomSource r, - int rx, - int rz, - boolean isEdge - ) { - if (isEdge) return Blocks.SCULK.defaultBlockState(); - return r.nextFloat() < 0.30f - ? Blocks.SCULK.defaultBlockState() - : Blocks.DEEPSLATE_TILES.defaultBlockState(); - } - - @Override - public BlockState ceilingBlock(RandomSource r) { - float f = r.nextFloat(); - if ( - f < 0.10f - ) return Blocks.CRACKED_DEEPSLATE_BRICKS.defaultBlockState(); - if (f < 0.30f) return Blocks.SCULK.defaultBlockState(); - return Blocks.DEEPSLATE_BRICKS.defaultBlockState(); - } - - @Override - public BlockState wallShellBlock() { - return Blocks.DEEPSLATE_BRICKS.defaultBlockState(); - } - - @Override - public void placeDecorations( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ) { - // Sculk veins on wall midpoints at ry=2 - for (int[] wallPos : new int[][] { - { 6, 1 }, - { 6, 11 }, - { 1, 6 }, - { 11, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 2, - baseZ + wallPos[1] - ), - Blocks.SCULK_VEIN.defaultBlockState(), - chunkBB - ); - } - } - // Soul lanterns on wall midpoints at ry=3 - for (int[] wallPos : new int[][] { - { 6, 1 }, - { 6, 11 }, - { 1, 6 }, - { 11, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 3, - baseZ + wallPos[1] - ), - Blocks.SOUL_LANTERN.defaultBlockState(), - chunkBB - ); - } - } - // Sculk catalyst in first corner - int[][] corners = layout.innerCorners(); - int[] sc = corners[0]; - if ( - layout.isInShape(sc[0], sc[1]) && !layout.isWall(sc[0], sc[1]) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + sc[0], floorY + 1, baseZ + sc[1]), - Blocks.SCULK_CATALYST.defaultBlockState(), - chunkBB - ); - } - // Corner furniture: sculk_shrieker + sculk_sensor + candle - int[][] scorners = layout.innerCorners(); - int[] sfc = scorners[1]; - int sfcx = sfc[0] + (sfc[0] < 6 ? 1 : -1); - int sfcz = sfc[1] + (sfc[1] < 6 ? 1 : -1); - if (layout.isInShape(sfcx, sfcz) && !layout.isWall(sfcx, sfcz)) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + sfcx, floorY + 1, baseZ + sfcz), - Blocks.SCULK_SHRIEKER.defaultBlockState(), - chunkBB - ); - int sfcx2 = sfcx + (sfcx < 6 ? 1 : -1); - if ( - layout.isInShape(sfcx2, sfcz) && !layout.isWall(sfcx2, sfcz) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + sfcx2, floorY + 1, baseZ + sfcz), - Blocks.SCULK_SENSOR.defaultBlockState(), - chunkBB - ); - } - if ( - layout.isInShape(sfcx, sfcz + (sfcz < 6 ? 1 : -1)) && - !layout.isWall(sfcx, sfcz + (sfcz < 6 ? 1 : -1)) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + sfcx, - floorY + 1, - baseZ + sfcz + (sfcz < 6 ? 1 : -1) - ), - Blocks.CANDLE.defaultBlockState() - .setValue(BlockStateProperties.CANDLES, 3) - .setValue(BlockStateProperties.LIT, true), - chunkBB - ); - } - } - placeSharedChains(level, baseX, baseZ, floorY, chunkBB); - placeSharedHangingLanterns(level, baseX, baseZ, floorY, chunkBB); - } - - @Override - public BlockState pillarBlock(RandomSource r, int ry) { - if (ry == 1 || ry == 10) return Blocks.SCULK.defaultBlockState(); - return r.nextFloat() < 0.3f - ? Blocks.SCULK.defaultBlockState() - : Blocks.DEEPSLATE_TILE_WALL.defaultBlockState(); - } - - @Override - @Nullable - public BlockState scatterBlock(RandomSource r) { - float f = r.nextFloat(); - if (f < 0.50f) return Blocks.SCULK.defaultBlockState(); - if (f < 0.80f) return Blocks.SCULK_VEIN.defaultBlockState(); - return Blocks.MOSS_CARPET.defaultBlockState(); - } - - @Override - public BlockState wallAccentBlock() { - return Blocks.SCULK_CATALYST.defaultBlockState(); - } - }, - SANDSTONE { - @Override - public BlockState wallBlock(RandomSource r, int ry) { - float f = r.nextFloat(); - if (f < 0.10f) return Blocks.CHISELED_SANDSTONE.defaultBlockState(); - if (f < 0.30f) return Blocks.SANDSTONE.defaultBlockState(); - return Blocks.CUT_SANDSTONE.defaultBlockState(); - } - - @Override - public BlockState floorBlock( - RandomSource r, - int rx, - int rz, - boolean isEdge - ) { - return r.nextFloat() < 0.15f - ? Blocks.SAND.defaultBlockState() - : Blocks.SMOOTH_SANDSTONE.defaultBlockState(); - } - - @Override - public BlockState ceilingBlock(RandomSource r) { - return r.nextFloat() < 0.20f - ? Blocks.SANDSTONE.defaultBlockState() - : Blocks.CUT_SANDSTONE.defaultBlockState(); - } - - @Override - public BlockState wallShellBlock() { - return Blocks.CUT_SANDSTONE.defaultBlockState(); - } - - @Override - public void placeDecorations( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ) { - // Wall torches at midpoints - for (int[] wallPos : new int[][] { - { 6, 0 }, - { 6, 12 }, - { 0, 6 }, - { 12, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - Direction torchDir = getTorchDirection( - wallPos[0], - wallPos[1] - ); - if (torchDir != null) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 3, - baseZ + wallPos[1] - ), - Blocks.WALL_TORCH.defaultBlockState().setValue( - net.minecraft.world.level.block.WallTorchBlock.FACING, - torchDir - ), - chunkBB - ); - } - } - } - // Orange terracotta accents at wall midpoints ry=2 - for (int[] wallPos : new int[][] { - { 6, 1 }, - { 6, 11 }, - { 1, 6 }, - { 11, 6 }, - }) { - if (layout.isWall(wallPos[0], wallPos[1])) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + wallPos[0], - floorY + 2, - baseZ + wallPos[1] - ), - Blocks.ORANGE_TERRACOTTA.defaultBlockState(), - chunkBB - ); - } - } - // TNT hidden in first corner - int[][] corners = layout.innerCorners(); - int[] tc = corners[0]; - if ( - layout.isInShape(tc[0], tc[1]) && !layout.isWall(tc[0], tc[1]) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + tc[0], floorY + 1, baseZ + tc[1]), - Blocks.TNT.defaultBlockState(), - chunkBB - ); - } - // Corner furniture: barrel + flower_pot + orange_terracotta bench - int[] ssfc = corners[1]; - int ssfcx = ssfc[0] + (ssfc[0] < 6 ? 1 : -1); - int ssfcz = ssfc[1] + (ssfc[1] < 6 ? 1 : -1); - if ( - layout.isInShape(ssfcx, ssfcz) && !layout.isWall(ssfcx, ssfcz) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + ssfcx, floorY + 1, baseZ + ssfcz), - Blocks.BARREL.defaultBlockState(), - chunkBB - ); - int ssfcx2 = ssfcx + (ssfcx < 6 ? 1 : -1); - if ( - layout.isInShape(ssfcx2, ssfcz) && - !layout.isWall(ssfcx2, ssfcz) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + ssfcx2, floorY + 1, baseZ + ssfcz), - Blocks.FLOWER_POT.defaultBlockState(), - chunkBB - ); - } - if ( - layout.isInShape(ssfcx, ssfcz + (ssfcz < 6 ? 1 : -1)) && - !layout.isWall(ssfcx, ssfcz + (ssfcz < 6 ? 1 : -1)) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + ssfcx, - floorY + 1, - baseZ + ssfcz + (ssfcz < 6 ? 1 : -1) - ), - Blocks.ORANGE_TERRACOTTA.defaultBlockState(), - chunkBB - ); - } - } - placeSharedChains(level, baseX, baseZ, floorY, chunkBB); - placeSharedHangingLanterns(level, baseX, baseZ, floorY, chunkBB); - } - - @Override - public BlockState pillarBlock(RandomSource r, int ry) { - if ( - ry == 1 || ry == 10 - ) return Blocks.CHISELED_SANDSTONE.defaultBlockState(); - return Blocks.SANDSTONE_WALL.defaultBlockState(); - } - - @Override - @Nullable - public BlockState scatterBlock(RandomSource r) { - float f = r.nextFloat(); - if (f < 0.40f) return Blocks.SAND.defaultBlockState(); - if (f < 0.70f) return Blocks.DEAD_BUSH.defaultBlockState(); - return Blocks.CANDLE.defaultBlockState() - .setValue(BlockStateProperties.CANDLES, 1 + r.nextInt(3)) - .setValue(BlockStateProperties.LIT, true); - } - - @Override - public BlockState wallAccentBlock() { - return Blocks.CHISELED_SANDSTONE.defaultBlockState(); - } - }; - - public abstract BlockState wallBlock(RandomSource r, int ry); - - public abstract BlockState floorBlock( - RandomSource r, - int rx, - int rz, - boolean isEdge - ); - - public abstract BlockState ceilingBlock(RandomSource r); - - /** Solid block used for wall positions on the floor layer. */ - public abstract BlockState wallShellBlock(); - - public abstract void placeDecorations( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ); - - /** Block used for pillars (may vary by height). */ - public abstract BlockState pillarBlock(RandomSource r, int ry); - - /** Random scatter block for floor decoration, or null to skip. */ - @Nullable - public abstract BlockState scatterBlock(RandomSource r); - - /** Accent block for decorative wall bands. */ - public abstract BlockState wallAccentBlock(); - - // ── Shared structural features ────────────────────────────────── - - /** Pillar positions -- verified to be inside all 4 layouts. */ - private static final int[][] PILLAR_POSITIONS = { - { 4, 4 }, - { 4, 8 }, - { 8, 4 }, - { 8, 8 }, - }; - - /** Place 4 full-height pillars at verified positions. */ - static void placeSharedPillars( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - RoomTheme theme, - BoundingBox chunkBB - ) { - for (int[] p : PILLAR_POSITIONS) { - if ( - !layout.isInShape(p[0], p[1]) || layout.isWall(p[0], p[1]) - ) continue; - for (int ry = 1; ry <= 10; ry++) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + p[0], floorY + ry, baseZ + p[1]), - theme.pillarBlock(random, ry), - chunkBB - ); - } - } - } - - /** Place random floor scatter (~12% of interior positions). */ - static void placeSharedFloorScatter( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - RoomTheme theme, - BoundingBox chunkBB - ) { - for (int rx = 1; rx <= 11; rx++) { - for (int rz = 1; rz <= 11; rz++) { - if ( - !layout.isInShape(rx, rz) || layout.isWall(rx, rz) - ) continue; - // Skip pillar positions - if ((rx == 4 || rx == 8) && (rz == 4 || rz == 8)) continue; - // Skip cage center area (5-7, 5-7) - if (rx >= 5 && rx <= 7 && rz >= 5 && rz <= 7) continue; - // Skip corner positions (used by decorations/chests) - if ((rx <= 2 || rx >= 10) && (rz <= 2 || rz >= 10)) continue; - if (random.nextFloat() < 0.12f) { - BlockState scatter = theme.scatterBlock(random); - if (scatter != null) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + rx, floorY + 1, baseZ + rz), - scatter, - chunkBB - ); - } - } - } - } - } - - /** Place ceiling cobwebs and extra hanging chains. */ - static void placeSharedCeilingDecor( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ) { - // Cobwebs along walls at ceiling level - int[][] cobwebCandidates = { - { 2, 1 }, - { 4, 1 }, - { 8, 1 }, - { 10, 1 }, - { 2, 11 }, - { 4, 11 }, - { 8, 11 }, - { 10, 11 }, - { 1, 4 }, - { 1, 8 }, - { 11, 4 }, - { 11, 8 }, - }; - for (int[] pos : cobwebCandidates) { - if ( - layout.isInShape(pos[0], pos[1]) && - !layout.isWall(pos[0], pos[1]) && - random.nextFloat() < 0.45f - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + pos[0], floorY + 10, baseZ + pos[1]), - Blocks.COBWEB.defaultBlockState(), - chunkBB - ); - } - } - // Extra hanging chains at random interior positions - int[][] chainCandidates = { { 5, 3 }, { 7, 9 }, { 3, 7 } }; - for (int[] pos : chainCandidates) { - if ( - layout.isInShape(pos[0], pos[1]) && - !layout.isWall(pos[0], pos[1]) && - random.nextFloat() < 0.6f - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + pos[0], floorY + 10, baseZ + pos[1]), - Blocks.CHAIN.defaultBlockState(), - chunkBB - ); - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + pos[0], floorY + 9, baseZ + pos[1]), - Blocks.CHAIN.defaultBlockState(), - chunkBB - ); - } - } - } - - /** Place additional wall-mounted lighting. */ - static void placeSharedWallLighting( - WorldGenLevel level, - RandomSource random, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - BoundingBox chunkBB - ) { - // Lanterns on pillar sides (facing center) at ry=1 (on the floor) - int[][] pillarLanternPositions = { - { 5, 4 }, - { 4, 7 }, - { 8, 5 }, - { 7, 8 }, - }; - for (int[] pos : pillarLanternPositions) { - if ( - layout.isInShape(pos[0], pos[1]) && - !layout.isWall(pos[0], pos[1]) - ) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + pos[0], floorY + 1, baseZ + pos[1]), - Blocks.LANTERN.defaultBlockState(), - chunkBB - ); - } - } - // Extra wall sconces at quarter-points - int[][] wallSconces = { - { 4, 0 }, - { 8, 0 }, - { 4, 12 }, - { 8, 12 }, - { 0, 4 }, - { 0, 8 }, - { 12, 4 }, - { 12, 8 }, - }; - for (int[] pos : wallSconces) { - if (layout.isWall(pos[0], pos[1]) && random.nextFloat() < 0.5f) { - Direction torchDir = getTorchDirection(pos[0], pos[1]); - if (torchDir != null) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + pos[0], - floorY + 3, - baseZ + pos[1] - ), - Blocks.WALL_TORCH.defaultBlockState().setValue( - net.minecraft.world.level.block.WallTorchBlock.FACING, - torchDir - ), - chunkBB - ); - } - } - } - } - - /** Place wall accent bands at ry=5 and ry=8. */ - static void placeSharedWallBands( - WorldGenLevel level, - int baseX, - int baseZ, - int floorY, - RoomLayout layout, - RoomTheme theme, - BoundingBox chunkBB - ) { - int[][] bandPositions = { - { 6, 1 }, - { 6, 11 }, - { 1, 6 }, - { 11, 6 }, - { 4, 1 }, - { 8, 1 }, - { 4, 11 }, - { 8, 11 }, - { 1, 4 }, - { 1, 8 }, - { 11, 4 }, - { 11, 8 }, - }; - for (int[] pos : bandPositions) { - if (layout.isWall(pos[0], pos[1])) { - // Band at ry=5 - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + pos[0], floorY + 5, baseZ + pos[1]), - theme.wallAccentBlock(), - chunkBB - ); - // Optional band at ry=8 - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + pos[0], floorY + 8, baseZ + pos[1]), - theme.wallAccentBlock(), - chunkBB - ); - } - } - } - - /** Chains on cage flanks and ceiling corners -- shared by all themes. */ - static void placeSharedChains( - WorldGenLevel level, - int baseX, - int baseZ, - int floorY, - BoundingBox chunkBB - ) { - for (int[] chainPos : new int[][] { { 3, 6 }, { 9, 6 } }) { - for (int ry = 5; ry <= 10; ry++) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + chainPos[0], - floorY + ry, - baseZ + chainPos[1] - ), - Blocks.CHAIN.defaultBlockState(), - chunkBB - ); - } - } - for (int[] chainPos : new int[][] { - { 3, 3 }, - { 3, 9 }, - { 9, 3 }, - { 9, 9 }, - }) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos( - baseX + chainPos[0], - floorY + 10, - baseZ + chainPos[1] - ), - Blocks.CHAIN.defaultBlockState(), - chunkBB - ); - } - } - - /** Determine torch facing direction for a wall position (torch faces inward). */ - static Direction getTorchDirection(int rx, int rz) { - if (rz == 0) return Direction.SOUTH; - if (rz == 12) return Direction.NORTH; - if (rx == 0) return Direction.EAST; - if (rx == 12) return Direction.WEST; - return null; - } - - /** Hanging lanterns -- shared by all themes. */ - static void placeSharedHangingLanterns( - WorldGenLevel level, - int baseX, - int baseZ, - int floorY, - BoundingBox chunkBB - ) { - for (int[] pos : new int[][] { { 3, 3 }, { 9, 9 } }) { - HangingCagePiece.safeSetBlock( - level, - new BlockPos(baseX + pos[0], floorY + 9, baseZ + pos[1]), - Blocks.LANTERN.defaultBlockState().setValue( - LanternBlock.HANGING, - true - ), - chunkBB - ); - } - } -} diff --git a/src/main/java/com/tiedup/remake/worldgen/RoomThemeDefinition.java b/src/main/java/com/tiedup/remake/worldgen/RoomThemeDefinition.java new file mode 100644 index 0000000..1b6260e --- /dev/null +++ b/src/main/java/com/tiedup/remake/worldgen/RoomThemeDefinition.java @@ -0,0 +1,78 @@ +package com.tiedup.remake.worldgen; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +/** + * Immutable data-driven room theme definition, replacing the old {@link RoomTheme} enum. + * + *

Loaded from {@code data//tiedup_room_themes/*.json} and stored + * in {@link RoomThemeRegistry}. Convenience methods mirror the old + * abstract API so that {@code HangingCagePiece} can swap with minimal changes.

+ * + * @param id unique identifier (e.g. "tiedup:oubliette") + * @param weight random selection weight (higher = more likely) + * @param wallPalette palette for wall blocks (supports "default" and "bottom_row") + * @param floorPalette palette for floor blocks (supports "default", "edge", "corner") + * @param ceilingPalette palette for ceiling blocks + * @param wallShellBlock single block used for the outer wall shell + * @param wallAccentBlock single accent block (e.g. for frames, trims) + * @param pillarPalette palette for pillars (supports "default" and "cap") + * @param scatterPalette optional palette for scatter decorations (cobwebs, etc.) + * @param decorations decoration configuration (corners, midpoints, furniture, etc.) + */ +public record RoomThemeDefinition( + ResourceLocation id, + int weight, + BlockPalette wallPalette, + BlockPalette floorPalette, + BlockPalette ceilingPalette, + BlockState wallShellBlock, + BlockState wallAccentBlock, + BlockPalette pillarPalette, + @Nullable BlockPalette scatterPalette, + DecorationConfig decorations +) { + + /** + * Pick a wall block. Uses "bottom_row" condition for ry==1, "default" otherwise. + */ + public BlockState wallBlock(RandomSource random, int ry) { + return wallPalette.pick(random, ry == 1 ? "bottom_row" : "default"); + } + + /** + * Pick a floor block. Uses "corner" for corner positions, "edge" for edges, + * "default" for interior positions. + */ + public BlockState floorBlock(RandomSource random, int rx, int rz, boolean isEdge) { + boolean isCorner = (rx == 1 || rx == 11) && (rz == 1 || rz == 11); + return floorPalette.pick( + random, isCorner ? "corner" : isEdge ? "edge" : "default" + ); + } + + /** + * Pick a ceiling block (always "default" condition). + */ + public BlockState ceilingBlock(RandomSource random) { + return ceilingPalette.pick(random, "default"); + } + + /** + * Pick a pillar block. Uses "cap" for top/bottom rows, "default" for the shaft. + */ + public BlockState pillarBlock(RandomSource random, int ry) { + return pillarPalette.pick(random, (ry == 1 || ry == 10) ? "cap" : "default"); + } + + /** + * Pick a scatter block, or null if no scatter palette is defined. + */ + @Nullable + public BlockState scatterBlock(RandomSource random) { + return scatterPalette != null ? scatterPalette.pick(random, "default") : null; + } +} diff --git a/src/main/java/com/tiedup/remake/worldgen/RoomThemeParser.java b/src/main/java/com/tiedup/remake/worldgen/RoomThemeParser.java new file mode 100644 index 0000000..4766658 --- /dev/null +++ b/src/main/java/com/tiedup/remake/worldgen/RoomThemeParser.java @@ -0,0 +1,496 @@ +package com.tiedup.remake.worldgen; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.minecraft.commands.arguments.blocks.BlockStateParser; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.block.state.properties.Property; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +/** + * Parses JSON files into {@link RoomThemeDefinition} instances. + * + *

Uses manual field extraction (not Gson deserialization) for strict + * validation control. Invalid required fields cause the entire definition + * to be rejected; optional fields use safe defaults.

+ * + *

Expected JSON files in {@code data//tiedup_room_themes/}.

+ */ +public final class RoomThemeParser { + + private static final Logger LOGGER = LogManager.getLogger("TiedUpWorldgen"); + private static final String TAG = "[RoomThemeParser]"; + + private RoomThemeParser() {} + + /** + * Parse a JSON input stream into a RoomThemeDefinition. + * + * @param input the JSON input stream + * @param fileId the resource location derived from the file path + * @return the parsed definition, or null if the file is invalid + */ + @Nullable + public static RoomThemeDefinition parse(InputStream input, ResourceLocation fileId) { + try { + JsonObject root = JsonParser.parseReader( + new InputStreamReader(input, StandardCharsets.UTF_8) + ).getAsJsonObject(); + + return parseRoot(root, fileId); + } catch (Exception e) { + LOGGER.error("{} Failed to parse JSON {}: {}", TAG, fileId, e.getMessage()); + return null; + } + } + + /** + * Parse the root JSON object into a RoomThemeDefinition. + */ + @Nullable + private static RoomThemeDefinition parseRoot(JsonObject root, ResourceLocation fileId) { + // --- Required: weight (default 10 if missing, clamped [1, 1000]) --- + int weight = clampInt(getIntOrDefault(root, "weight", 10), 1, 1000); + + // --- Required: wall_palette --- + BlockPalette wallPalette = parsePaletteField(root, "wall_palette", fileId); + if (wallPalette == null) { + LOGGER.error("{} Skipping {}: missing or invalid 'wall_palette'", TAG, fileId); + return null; + } + + // --- Required: floor_palette --- + BlockPalette floorPalette = parsePaletteField(root, "floor_palette", fileId); + if (floorPalette == null) { + LOGGER.error("{} Skipping {}: missing or invalid 'floor_palette'", TAG, fileId); + return null; + } + + // --- Required: ceiling_palette --- + BlockPalette ceilingPalette = parsePaletteField(root, "ceiling_palette", fileId); + if (ceilingPalette == null) { + LOGGER.error("{} Skipping {}: missing or invalid 'ceiling_palette'", TAG, fileId); + return null; + } + + // --- Required: wall_shell (single block string) --- + BlockState wallShell = parseBlockStateField(root, "wall_shell", fileId); + if (wallShell == null) { + LOGGER.error("{} Skipping {}: missing or invalid 'wall_shell'", TAG, fileId); + return null; + } + + // --- Required: wall_accent (single block string) --- + BlockState wallAccent = parseBlockStateField(root, "wall_accent", fileId); + if (wallAccent == null) { + LOGGER.error("{} Skipping {}: missing or invalid 'wall_accent'", TAG, fileId); + return null; + } + + // --- Required: pillar_palette --- + BlockPalette pillarPalette = parsePaletteField(root, "pillar_palette", fileId); + if (pillarPalette == null) { + LOGGER.error("{} Skipping {}: missing or invalid 'pillar_palette'", TAG, fileId); + return null; + } + + // --- Optional: scatter_palette --- + BlockPalette scatterPalette = null; + if (root.has("scatter_palette") && root.get("scatter_palette").isJsonObject()) { + scatterPalette = parsePaletteField(root, "scatter_palette", fileId); + // If scatter_palette is present but invalid, warn but don't reject + if (scatterPalette == null) { + LOGGER.warn( + "{} In {}: 'scatter_palette' present but invalid, ignoring", TAG, fileId + ); + } + } + + // --- Optional: decorations --- + DecorationConfig decorations = new DecorationConfig( + List.of(), List.of(), null, List.of(), false, false + ); + if (root.has("decorations") && root.get("decorations").isJsonObject()) { + DecorationConfig parsed = parseDecorations( + root.getAsJsonObject("decorations"), fileId + ); + if (parsed != null) { + decorations = parsed; + } + } + + return new RoomThemeDefinition( + fileId, weight, + wallPalette, floorPalette, ceilingPalette, + wallShell, wallAccent, + pillarPalette, scatterPalette, + decorations + ); + } + + // ===== Palette Parsing ===== + + /** + * Parse a palette field from the root object. The field value must be a JSON object + * mapping condition names to arrays of weighted entries. + * + *

Example: + *

{
+     *   "default": [{"block": "minecraft:stone_bricks", "weight": 0.8}],
+     *   "bottom_row": [{"block": "minecraft:mossy_stone_bricks", "weight": 1.0}]
+     * }
+ */ + @Nullable + private static BlockPalette parsePaletteField( + JsonObject root, String fieldName, ResourceLocation fileId + ) { + if (!root.has(fieldName) || !root.get(fieldName).isJsonObject()) { + return null; + } + + JsonObject paletteObj = root.getAsJsonObject(fieldName); + Map> variants = new HashMap<>(); + + for (Map.Entry condEntry : paletteObj.entrySet()) { + String condition = condEntry.getKey(); + if (!condEntry.getValue().isJsonArray()) { + LOGGER.warn( + "{} In {} palette '{}': condition '{}' is not an array, skipping", + TAG, fileId, fieldName, condition + ); + continue; + } + + List entries = parseWeightedEntries( + condEntry.getValue().getAsJsonArray(), fileId, fieldName, condition + ); + if (!entries.isEmpty()) { + variants.put(condition, List.copyOf(entries)); + } + } + + if (variants.isEmpty() || !variants.containsKey("default")) { + LOGGER.warn( + "{} In {} palette '{}': no valid 'default' condition found", + TAG, fileId, fieldName + ); + return null; + } + + return new BlockPalette(variants); + } + + /** + * Parse an array of weighted block entries for a single palette condition. + * + *

Each entry is a JSON object with: + *

    + *
  • {@code "block"} — block state string (e.g. "minecraft:candle[lit=true]")
  • + *
  • {@code "weight"} — float weight (default 1.0)
  • + *
  • {@code "random_property"} — optional object with {@code "name"}, {@code "min"}, {@code "max"} + * to expand a single entry into multiple entries with varying integer property values
  • + *
+ */ + private static List parseWeightedEntries( + JsonArray array, ResourceLocation fileId, String paletteName, String condition + ) { + List entries = new ArrayList<>(); + + for (int i = 0; i < array.size(); i++) { + if (!array.get(i).isJsonObject()) { + LOGGER.warn( + "{} In {} palette '{}' condition '{}': entry [{}] is not an object, skipping", + TAG, fileId, paletteName, condition, i + ); + continue; + } + + JsonObject entryObj = array.get(i).getAsJsonObject(); + + String blockStr = getStringOrNull(entryObj, "block"); + if (blockStr == null || blockStr.isEmpty()) { + LOGGER.warn( + "{} In {} palette '{}' condition '{}': entry [{}] missing 'block', skipping", + TAG, fileId, paletteName, condition, i + ); + continue; + } + + BlockState baseState = parseBlockState(blockStr, fileId); + if (baseState == null) { + continue; // parseBlockState already logged warning + } + + float weight = getFloatOrDefault(entryObj, "weight", 1.0f); + if (weight <= 0) { + LOGGER.warn( + "{} In {} palette '{}' condition '{}': entry [{}] has non-positive weight, skipping", + TAG, fileId, paletteName, condition, i + ); + continue; + } + + // Handle random_property expansion + if (entryObj.has("random_property") && entryObj.get("random_property").isJsonObject()) { + List expanded = expandRandomProperty( + baseState, weight, entryObj.getAsJsonObject("random_property"), + fileId, paletteName, condition, i + ); + entries.addAll(expanded); + } else { + entries.add(new BlockPalette.WeightedEntry(baseState, weight)); + } + } + + return entries; + } + + /** + * Expand a single weighted entry into multiple entries by varying an integer + * block state property across a range. + * + *

For example, {@code candle[candles=1]} with {@code random_property: {name: "candles", min: 1, max: 3}} + * and weight 0.30 produces three entries: candles=1 (0.10), candles=2 (0.10), candles=3 (0.10).

+ */ + @SuppressWarnings("unchecked") + private static List expandRandomProperty( + BlockState baseState, float totalWeight, JsonObject propObj, + ResourceLocation fileId, String paletteName, String condition, int entryIndex + ) { + String propName = getStringOrNull(propObj, "name"); + if (propName == null) { + LOGGER.warn( + "{} In {} palette '{}' condition '{}': entry [{}] random_property missing 'name', using base state", + TAG, fileId, paletteName, condition, entryIndex + ); + return List.of(new BlockPalette.WeightedEntry(baseState, totalWeight)); + } + + int min = getIntOrDefault(propObj, "min", 1); + int max = getIntOrDefault(propObj, "max", min); + if (max < min) { + LOGGER.warn( + "{} In {} palette '{}' condition '{}': entry [{}] random_property max < min, using base state", + TAG, fileId, paletteName, condition, entryIndex + ); + return List.of(new BlockPalette.WeightedEntry(baseState, totalWeight)); + } + + Property property = baseState.getBlock().getStateDefinition().getProperty(propName); + if (!(property instanceof IntegerProperty intProp)) { + LOGGER.warn( + "{} In {} palette '{}' condition '{}': entry [{}] property '{}' is not an IntegerProperty, using base state", + TAG, fileId, paletteName, condition, entryIndex, propName + ); + return List.of(new BlockPalette.WeightedEntry(baseState, totalWeight)); + } + + int count = max - min + 1; + float perWeight = totalWeight / count; + List expanded = new ArrayList<>(count); + + for (int val = min; val <= max; val++) { + if (intProp.getPossibleValues().contains(val)) { + expanded.add(new BlockPalette.WeightedEntry( + baseState.setValue(intProp, val), perWeight + )); + } else { + LOGGER.warn( + "{} In {} palette '{}' condition '{}': entry [{}] property '{}' does not accept value {}, skipping", + TAG, fileId, paletteName, condition, entryIndex, propName, val + ); + } + } + + if (expanded.isEmpty()) { + return List.of(new BlockPalette.WeightedEntry(baseState, totalWeight)); + } + + return expanded; + } + + // ===== Block State Parsing ===== + + /** + * Parse a block state string like "minecraft:stone_bricks" or "minecraft:candle[lit=true]" + * using Minecraft's built-in BlockStateParser. + * + * @return the parsed BlockState, or null on failure (logged as warning) + */ + @Nullable + private static BlockState parseBlockState(String blockString, ResourceLocation fileId) { + try { + return BlockStateParser.parseForBlock( + BuiltInRegistries.BLOCK.asLookup(), + blockString, + false // don't allow NBT + ).blockState(); + } catch (Exception e) { + LOGGER.warn( + "{} In {}: invalid block state '{}': {}", + TAG, fileId, blockString, e.getMessage() + ); + return null; + } + } + + /** + * Parse a single block state from a string field in a JSON object. + * + * @return the parsed BlockState, or null if field is missing or invalid + */ + @Nullable + private static BlockState parseBlockStateField( + JsonObject obj, String fieldName, ResourceLocation fileId + ) { + String blockStr = getStringOrNull(obj, fieldName); + if (blockStr == null || blockStr.isEmpty()) { + return null; + } + return parseBlockState(blockStr, fileId); + } + + // ===== Decoration Parsing ===== + + /** + * Parse the "decorations" JSON object into a DecorationConfig. + */ + @Nullable + private static DecorationConfig parseDecorations(JsonObject obj, ResourceLocation fileId) { + try { + List cornerDecos = parsePositionedBlockList( + obj, "corner_decorations", fileId + ); + List wallMidpoints = parsePositionedBlockList( + obj, "wall_midpoint_blocks", fileId + ); + + DecorationConfig.PositionedBlock firstCornerSpecial = null; + if (obj.has("first_corner_special") && obj.get("first_corner_special").isJsonObject()) { + firstCornerSpecial = parsePositionedBlock( + obj.getAsJsonObject("first_corner_special"), fileId + ); + } + + List furnitureCluster = parsePositionedBlockList( + obj, "furniture_cluster", fileId + ); + + boolean useTorchLighting = getBooleanOrDefault(obj, "use_torch_lighting", false); + boolean hasCeilingChain = getBooleanOrDefault(obj, "has_ceiling_chain", false); + + return new DecorationConfig( + cornerDecos, wallMidpoints, firstCornerSpecial, + furnitureCluster, useTorchLighting, hasCeilingChain + ); + } catch (Exception e) { + LOGGER.warn( + "{} In {}: failed to parse decorations: {}", TAG, fileId, e.getMessage() + ); + return null; + } + } + + /** + * Parse an array of positioned blocks from a field. + */ + private static List parsePositionedBlockList( + JsonObject parent, String fieldName, ResourceLocation fileId + ) { + if (!parent.has(fieldName) || !parent.get(fieldName).isJsonArray()) { + return List.of(); + } + + JsonArray array = parent.getAsJsonArray(fieldName); + List result = new ArrayList<>(); + + for (int i = 0; i < array.size(); i++) { + if (!array.get(i).isJsonObject()) continue; + + DecorationConfig.PositionedBlock pb = parsePositionedBlock( + array.get(i).getAsJsonObject(), fileId + ); + if (pb != null) { + result.add(pb); + } + } + + return List.copyOf(result); + } + + /** + * Parse a single positioned block: { "block": "...", "x_offset": 0, "y_offset": 0, "z_offset": 0 } + */ + @Nullable + private static DecorationConfig.PositionedBlock parsePositionedBlock( + JsonObject obj, ResourceLocation fileId + ) { + String blockStr = getStringOrNull(obj, "block"); + if (blockStr == null || blockStr.isEmpty()) return null; + + BlockState state = parseBlockState(blockStr, fileId); + if (state == null) return null; + + int xOffset = getIntOrDefault(obj, "x_offset", 0); + int yOffset = getIntOrDefault(obj, "y_offset", 0); + int zOffset = getIntOrDefault(obj, "z_offset", 0); + return new DecorationConfig.PositionedBlock(state, xOffset, yOffset, zOffset); + } + + // ===== Primitive Helpers ===== + + @Nullable + private static String getStringOrNull(JsonObject obj, String key) { + if (!obj.has(key) || obj.get(key).isJsonNull()) return null; + try { + return obj.get(key).getAsString(); + } catch (Exception e) { + return null; + } + } + + private static int getIntOrDefault(JsonObject obj, String key, int defaultValue) { + if (!obj.has(key) || obj.get(key).isJsonNull()) return defaultValue; + try { + return obj.get(key).getAsInt(); + } catch (Exception e) { + return defaultValue; + } + } + + private static float getFloatOrDefault(JsonObject obj, String key, float defaultValue) { + if (!obj.has(key) || obj.get(key).isJsonNull()) return defaultValue; + try { + return obj.get(key).getAsFloat(); + } catch (Exception e) { + return defaultValue; + } + } + + private static boolean getBooleanOrDefault(JsonObject obj, String key, boolean defaultValue) { + if (!obj.has(key) || obj.get(key).isJsonNull()) return defaultValue; + try { + return obj.get(key).getAsBoolean(); + } catch (Exception e) { + return defaultValue; + } + } + + private static int clampInt(int value, int min, int max) { + return Math.max(min, Math.min(max, value)); + } +} diff --git a/src/main/java/com/tiedup/remake/worldgen/RoomThemeRegistry.java b/src/main/java/com/tiedup/remake/worldgen/RoomThemeRegistry.java new file mode 100644 index 0000000..d7f0224 --- /dev/null +++ b/src/main/java/com/tiedup/remake/worldgen/RoomThemeRegistry.java @@ -0,0 +1,142 @@ +package com.tiedup.remake.worldgen; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +/** + * Thread-safe registry for data-driven room theme definitions. + * + *

Server-authoritative: definitions are loaded from {@code data//tiedup_room_themes/} + * JSON files by the server reload listener. Unlike furniture, there is no client sync + * because room themes are only used during server-side worldgen.

+ * + *

Uses volatile atomic swap to ensure threads always see a consistent snapshot.

+ * + * @see RoomThemeDefinition + */ +public final class RoomThemeRegistry { + + /** + * Volatile reference to an unmodifiable map. {@link #reload} builds a new map + * and swaps atomically; consumer threads always see a consistent snapshot. + */ + private static volatile Map< + ResourceLocation, + RoomThemeDefinition + > DEFINITIONS = Map.of(); + + /** Hardcoded fallback for when the registry is empty (worldgen safety). */ + private static final RoomThemeDefinition FALLBACK = createFallback(); + + private RoomThemeRegistry() {} + + /** + * Atomically replace all definitions with a new set. + * Called by the reload listener after parsing all JSON files. + * + * @param newDefs the new definitions map (will be defensively copied) + */ + public static void reload(Map newDefs) { + DEFINITIONS = Collections.unmodifiableMap(new HashMap<>(newDefs)); + } + + /** + * Get a definition by its unique ID. + * + * @param id the definition ID (e.g. "tiedup:oubliette") + * @return the definition, or null if not found + */ + @Nullable + public static RoomThemeDefinition get(ResourceLocation id) { + return DEFINITIONS.get(id); + } + + /** + * Get all registered definitions. + * + * @return unmodifiable collection of all definitions + */ + public static Collection getAll() { + return DEFINITIONS.values(); + } + + /** + * Pick a random theme proportionally by weight. + * + * @param random the random source + * @return a randomly selected theme, or null if registry is empty + */ + @Nullable + public static RoomThemeDefinition pickRandom(RandomSource random) { + Collection all = DEFINITIONS.values(); + if (all.isEmpty()) return null; + + int totalWeight = 0; + for (RoomThemeDefinition def : all) { + totalWeight += def.weight(); + } + + if (totalWeight <= 0) return null; + + int roll = random.nextInt(totalWeight); + int cumulative = 0; + for (RoomThemeDefinition def : all) { + cumulative += def.weight(); + if (roll < cumulative) return def; + } + + // Fallback (should not happen) + return all.iterator().next(); + } + + /** + * Pick a random theme, or return a hardcoded stone bricks fallback if the + * registry is empty. Guarantees non-null for worldgen safety. + * + * @param random the random source + * @return a theme definition (never null) + */ + public static RoomThemeDefinition pickRandomOrFallback(RandomSource random) { + RoomThemeDefinition picked = pickRandom(random); + return picked != null ? picked : FALLBACK; + } + + /** + * Clear all definitions. Called on world unload or for testing. + */ + public static void clear() { + DEFINITIONS = Map.of(); + } + + /** + * Create a minimal stone bricks fallback theme for when no JSON themes are loaded. + */ + private static RoomThemeDefinition createFallback() { + BlockState stone = Blocks.STONE_BRICKS.defaultBlockState(); + BlockPalette single = new BlockPalette( + Map.of("default", List.of(new BlockPalette.WeightedEntry(stone, 1.0f))) + ); + return new RoomThemeDefinition( + new ResourceLocation("tiedup", "fallback"), + 1, + single, // wall + single, // floor + single, // ceiling + stone, // wallShell + stone, // wallAccent + single, // pillar + null, // no scatter + new DecorationConfig( + List.of(), List.of(), null, List.of(), false, false + ) + ); + } +} diff --git a/src/main/java/com/tiedup/remake/worldgen/RoomThemeReloadListener.java b/src/main/java/com/tiedup/remake/worldgen/RoomThemeReloadListener.java new file mode 100644 index 0000000..9beb894 --- /dev/null +++ b/src/main/java/com/tiedup/remake/worldgen/RoomThemeReloadListener.java @@ -0,0 +1,111 @@ +package com.tiedup.remake.worldgen; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimplePreparableReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Server-side resource reload listener that scans {@code data//tiedup_room_themes/} + * for JSON files and populates the {@link RoomThemeRegistry}. + * + *

Room themes are server-authoritative only (used during worldgen). Unlike furniture, + * there is no client sync packet -- the registry is atomically replaced via + * {@link RoomThemeRegistry#reload(Map)} on each reload.

+ * + *

Registered via {@link net.minecraftforge.event.AddReloadListenerEvent} in + * {@link com.tiedup.remake.core.TiedUpMod.ForgeEvents}.

+ */ +public class RoomThemeReloadListener + extends SimplePreparableReloadListener +{ + + private static final Logger LOGGER = LogManager.getLogger("TiedUpWorldgen"); + + /** Resource directory containing room theme definition JSON files (under data/). */ + private static final String DIRECTORY = "tiedup_room_themes"; + + @Override + protected Void prepare( + ResourceManager resourceManager, + ProfilerFiller profiler + ) { + // No preparation needed -- parsing happens in apply phase + return null; + } + + @Override + protected void apply( + Void nothing, + ResourceManager resourceManager, + ProfilerFiller profiler + ) { + Map newDefs = new HashMap<>(); + + Map resources = + resourceManager.listResources(DIRECTORY, loc -> + loc.getPath().endsWith(".json") + ); + + int skipped = 0; + + for (Map.Entry entry : resources.entrySet()) { + ResourceLocation fileId = entry.getKey(); + Resource resource = entry.getValue(); + + // Derive clean ID from file path: "tiedup:tiedup_room_themes/oubliette.json" -> "tiedup:oubliette" + String rawPath = fileId.getPath(); + String cleanPath = rawPath.substring(rawPath.indexOf(DIRECTORY + "/") + DIRECTORY.length() + 1); + if (cleanPath.endsWith(".json")) { + cleanPath = cleanPath.substring(0, cleanPath.length() - 5); + } + ResourceLocation cleanId = new ResourceLocation(fileId.getNamespace(), cleanPath); + + try (InputStream input = resource.open()) { + RoomThemeDefinition def = RoomThemeParser.parse(input, cleanId); + + if (def != null) { + // Check for duplicate IDs (fileId IS the ID for room themes) + if (newDefs.containsKey(def.id())) { + LOGGER.warn( + "[TiedUpWorldgen] Server: Duplicate room theme ID '{}' from file '{}' -- overwriting previous definition", + def.id(), + fileId + ); + } + + newDefs.put(def.id(), def); + LOGGER.debug( + "[TiedUpWorldgen] Server loaded room theme: {}", + def.id() + ); + } else { + skipped++; + } + } catch (Exception e) { + LOGGER.error( + "[TiedUpWorldgen] Server: Failed to read resource {}: {}", + fileId, + e.getMessage() + ); + skipped++; + } + } + + // Atomically replace all definitions in the registry + RoomThemeRegistry.reload(newDefs); + + LOGGER.info( + "[TiedUpWorldgen] Server loaded {} room theme definitions ({} skipped) from {} JSON files", + newDefs.size(), + skipped, + resources.size() + ); + } +} diff --git a/src/main/resources/data/tiedup/tiedup_room_themes/crypt.json b/src/main/resources/data/tiedup/tiedup_room_themes/crypt.json new file mode 100644 index 0000000..019352d --- /dev/null +++ b/src/main/resources/data/tiedup/tiedup_room_themes/crypt.json @@ -0,0 +1,60 @@ +{ + "weight": 10, + "wall_palette": { + "default": [ + { "block": "minecraft:mossy_stone_bricks", "weight": 0.15 }, + { "block": "minecraft:cracked_stone_bricks", "weight": 0.15 }, + { "block": "minecraft:stone_bricks", "weight": 0.70 } + ], + "bottom_row": [ + { "block": "minecraft:mossy_cobblestone", "weight": 0.20 }, + { "block": "minecraft:cracked_stone_bricks", "weight": 0.10 }, + { "block": "minecraft:stone_bricks", "weight": 0.70 } + ] + }, + "floor_palette": { + "default": [ + { "block": "minecraft:cobblestone", "weight": 0.20 }, + { "block": "minecraft:stone_bricks", "weight": 0.80 } + ], + "corner": [ + { "block": "minecraft:mossy_cobblestone", "weight": 1.0 } + ] + }, + "ceiling_palette": { + "default": [ + { "block": "minecraft:cracked_stone_bricks", "weight": 0.20 }, + { "block": "minecraft:stone_bricks", "weight": 0.80 } + ] + }, + "wall_shell": "minecraft:stone_bricks", + "wall_accent": "minecraft:mossy_stone_bricks", + "pillar_palette": { + "default": [ + { "block": "minecraft:stone_brick_wall", "weight": 1.0 } + ], + "cap": [ + { "block": "minecraft:chiseled_stone_bricks", "weight": 1.0 } + ] + }, + "scatter_palette": { + "default": [ + { "block": "minecraft:cobweb", "weight": 0.40 }, + { "block": "minecraft:candle[lit=true]", "weight": 0.30, "random_property": { "name": "candles", "min": 1, "max": 3 } }, + { "block": "minecraft:bone_block", "weight": 0.30 } + ] + }, + "decorations": { + "corner_decorations": [ + { "block": "minecraft:cobweb", "y_offset": 1 }, + { "block": "minecraft:cobweb", "y_offset": 9 } + ], + "first_corner_special": { "block": "minecraft:skeleton_skull", "y_offset": 1 }, + "furniture_cluster": [ + { "block": "minecraft:lectern", "y_offset": 1 }, + { "block": "minecraft:candle[lit=true,candles=4]", "x_offset": 1, "y_offset": 1 } + ], + "use_torch_lighting": true, + "has_ceiling_chain": false + } +} diff --git a/src/main/resources/data/tiedup/tiedup_room_themes/ice.json b/src/main/resources/data/tiedup/tiedup_room_themes/ice.json new file mode 100644 index 0000000..e4b75eb --- /dev/null +++ b/src/main/resources/data/tiedup/tiedup_room_themes/ice.json @@ -0,0 +1,59 @@ +{ + "weight": 10, + "wall_palette": { + "default": [ + { "block": "minecraft:ice", "weight": 0.10 }, + { "block": "minecraft:blue_ice", "weight": 0.20 }, + { "block": "minecraft:packed_ice", "weight": 0.70 } + ] + }, + "floor_palette": { + "default": [ + { "block": "minecraft:snow_block", "weight": 0.20 }, + { "block": "minecraft:packed_ice", "weight": 0.80 } + ], + "edge": [ + { "block": "minecraft:blue_ice", "weight": 1.0 } + ] + }, + "ceiling_palette": { + "default": [ + { "block": "minecraft:blue_ice", "weight": 0.20 }, + { "block": "minecraft:packed_ice", "weight": 0.80 } + ] + }, + "wall_shell": "minecraft:packed_ice", + "wall_accent": "minecraft:blue_ice", + "pillar_palette": { + "default": [ + { "block": "minecraft:packed_ice", "weight": 0.50 }, + { "block": "minecraft:blue_ice", "weight": 0.50 } + ], + "cap": [ + { "block": "minecraft:blue_ice", "weight": 1.0 } + ] + }, + "scatter_palette": { + "default": [ + { "block": "minecraft:snow[layers=1]", "weight": 0.50, "random_property": { "name": "layers", "min": 1, "max": 2 } }, + { "block": "minecraft:powder_snow", "weight": 0.20 }, + { "block": "minecraft:ice", "weight": 0.30 } + ] + }, + "decorations": { + "corner_decorations": [ + { "block": "minecraft:snow[layers=3]", "y_offset": 1 }, + { "block": "minecraft:ice", "y_offset": 10 } + ], + "wall_midpoint_blocks": [ + { "block": "minecraft:lantern", "y_offset": 3 } + ], + "furniture_cluster": [ + { "block": "minecraft:powder_snow_cauldron[level=3]", "y_offset": 1 }, + { "block": "minecraft:blue_ice", "x_offset": 1, "y_offset": 1 }, + { "block": "minecraft:lantern", "y_offset": 2, "z_offset": 1 } + ], + "use_torch_lighting": false, + "has_ceiling_chain": false + } +} diff --git a/src/main/resources/data/tiedup/tiedup_room_themes/inferno.json b/src/main/resources/data/tiedup/tiedup_room_themes/inferno.json new file mode 100644 index 0000000..a44b075 --- /dev/null +++ b/src/main/resources/data/tiedup/tiedup_room_themes/inferno.json @@ -0,0 +1,58 @@ +{ + "weight": 10, + "wall_palette": { + "default": [ + { "block": "minecraft:cracked_nether_bricks", "weight": 0.20 }, + { "block": "minecraft:nether_bricks", "weight": 0.80 } + ] + }, + "floor_palette": { + "default": [ + { "block": "minecraft:gilded_blackstone", "weight": 0.08 }, + { "block": "minecraft:blackstone", "weight": 0.92 } + ], + "edge": [ + { "block": "minecraft:magma_block", "weight": 1.0 } + ] + }, + "ceiling_palette": { + "default": [ + { "block": "minecraft:cracked_nether_bricks", "weight": 0.20 }, + { "block": "minecraft:nether_bricks", "weight": 0.80 } + ] + }, + "wall_shell": "minecraft:nether_bricks", + "wall_accent": "minecraft:red_nether_bricks", + "pillar_palette": { + "default": [ + { "block": "minecraft:nether_brick_wall", "weight": 1.0 } + ], + "cap": [ + { "block": "minecraft:quartz_pillar", "weight": 1.0 } + ] + }, + "scatter_palette": { + "default": [ + { "block": "minecraft:soul_sand", "weight": 0.40 }, + { "block": "minecraft:nether_wart_block", "weight": 0.30 }, + { "block": "minecraft:bone_block", "weight": 0.30 } + ] + }, + "decorations": { + "corner_decorations": [ + { "block": "minecraft:soul_sand", "y_offset": 0 }, + { "block": "minecraft:soul_fire", "y_offset": 1 } + ], + "wall_midpoint_blocks": [ + { "block": "minecraft:crying_obsidian", "y_offset": 2 }, + { "block": "minecraft:soul_lantern", "y_offset": 3 } + ], + "furniture_cluster": [ + { "block": "minecraft:soul_campfire", "y_offset": 1 }, + { "block": "minecraft:lava_cauldron", "x_offset": 1, "y_offset": 1 }, + { "block": "minecraft:gilded_blackstone", "y_offset": 1, "z_offset": 1 } + ], + "use_torch_lighting": false, + "has_ceiling_chain": false + } +} diff --git a/src/main/resources/data/tiedup/tiedup_room_themes/oubliette.json b/src/main/resources/data/tiedup/tiedup_room_themes/oubliette.json new file mode 100644 index 0000000..4d7462f --- /dev/null +++ b/src/main/resources/data/tiedup/tiedup_room_themes/oubliette.json @@ -0,0 +1,62 @@ +{ + "weight": 10, + "wall_palette": { + "default": [ + { "block": "minecraft:cracked_deepslate_bricks", "weight": 0.20 }, + { "block": "minecraft:deepslate_bricks", "weight": 0.80 } + ], + "bottom_row": [ + { "block": "minecraft:mossy_cobblestone", "weight": 0.30 }, + { "block": "minecraft:cracked_deepslate_bricks", "weight": 0.14 }, + { "block": "minecraft:deepslate_bricks", "weight": 0.56 } + ] + }, + "floor_palette": { + "default": [ + { "block": "minecraft:cobblestone", "weight": 0.15 }, + { "block": "minecraft:deepslate_tiles", "weight": 0.85 } + ], + "corner": [ + { "block": "minecraft:mossy_cobblestone", "weight": 1.0 } + ] + }, + "ceiling_palette": { + "default": [ + { "block": "minecraft:cracked_deepslate_bricks", "weight": 0.20 }, + { "block": "minecraft:deepslate_bricks", "weight": 0.80 } + ] + }, + "wall_shell": "minecraft:deepslate_bricks", + "wall_accent": "minecraft:polished_deepslate", + "pillar_palette": { + "default": [ + { "block": "minecraft:deepslate_brick_wall", "weight": 1.0 } + ], + "cap": [ + { "block": "minecraft:polished_deepslate", "weight": 1.0 } + ] + }, + "scatter_palette": { + "default": [ + { "block": "minecraft:cobweb", "weight": 0.40 }, + { "block": "minecraft:candle[lit=true]", "weight": 0.30, "random_property": { "name": "candles", "min": 1, "max": 3 } }, + { "block": "minecraft:moss_carpet", "weight": 0.30 } + ] + }, + "decorations": { + "corner_decorations": [ + { "block": "minecraft:cobweb", "y_offset": 1 }, + { "block": "minecraft:cobweb", "y_offset": 9 } + ], + "wall_midpoint_blocks": [ + { "block": "minecraft:soul_lantern", "y_offset": 3 } + ], + "first_corner_special": { "block": "minecraft:water_cauldron[level=3]", "x_offset": 1, "y_offset": 1, "z_offset": 1 }, + "furniture_cluster": [ + { "block": "minecraft:barrel", "y_offset": 1 }, + { "block": "minecraft:brewing_stand", "x_offset": 1, "y_offset": 1 } + ], + "use_torch_lighting": false, + "has_ceiling_chain": true + } +} diff --git a/src/main/resources/data/tiedup/tiedup_room_themes/sandstone.json b/src/main/resources/data/tiedup/tiedup_room_themes/sandstone.json new file mode 100644 index 0000000..3bd5296 --- /dev/null +++ b/src/main/resources/data/tiedup/tiedup_room_themes/sandstone.json @@ -0,0 +1,52 @@ +{ + "weight": 10, + "wall_palette": { + "default": [ + { "block": "minecraft:chiseled_sandstone", "weight": 0.10 }, + { "block": "minecraft:sandstone", "weight": 0.20 }, + { "block": "minecraft:cut_sandstone", "weight": 0.70 } + ] + }, + "floor_palette": { + "default": [ + { "block": "minecraft:sand", "weight": 0.15 }, + { "block": "minecraft:smooth_sandstone", "weight": 0.85 } + ] + }, + "ceiling_palette": { + "default": [ + { "block": "minecraft:sandstone", "weight": 0.20 }, + { "block": "minecraft:cut_sandstone", "weight": 0.80 } + ] + }, + "wall_shell": "minecraft:cut_sandstone", + "wall_accent": "minecraft:chiseled_sandstone", + "pillar_palette": { + "default": [ + { "block": "minecraft:sandstone_wall", "weight": 1.0 } + ], + "cap": [ + { "block": "minecraft:chiseled_sandstone", "weight": 1.0 } + ] + }, + "scatter_palette": { + "default": [ + { "block": "minecraft:sand", "weight": 0.40 }, + { "block": "minecraft:dead_bush", "weight": 0.30 }, + { "block": "minecraft:candle[lit=true]", "weight": 0.30, "random_property": { "name": "candles", "min": 1, "max": 3 } } + ] + }, + "decorations": { + "wall_midpoint_blocks": [ + { "block": "minecraft:orange_terracotta", "y_offset": 2 } + ], + "first_corner_special": { "block": "minecraft:tnt", "y_offset": 1 }, + "furniture_cluster": [ + { "block": "minecraft:barrel", "y_offset": 1 }, + { "block": "minecraft:flower_pot", "x_offset": 1, "y_offset": 1 }, + { "block": "minecraft:orange_terracotta", "y_offset": 1, "z_offset": 1 } + ], + "use_torch_lighting": true, + "has_ceiling_chain": false + } +} diff --git a/src/main/resources/data/tiedup/tiedup_room_themes/sculk.json b/src/main/resources/data/tiedup/tiedup_room_themes/sculk.json new file mode 100644 index 0000000..506482d --- /dev/null +++ b/src/main/resources/data/tiedup/tiedup_room_themes/sculk.json @@ -0,0 +1,58 @@ +{ + "weight": 10, + "wall_palette": { + "default": [ + { "block": "minecraft:cracked_deepslate_bricks", "weight": 0.10 }, + { "block": "minecraft:sculk", "weight": 0.30 }, + { "block": "minecraft:deepslate_bricks", "weight": 0.60 } + ] + }, + "floor_palette": { + "default": [ + { "block": "minecraft:sculk", "weight": 0.30 }, + { "block": "minecraft:deepslate_tiles", "weight": 0.70 } + ], + "edge": [ + { "block": "minecraft:sculk", "weight": 1.0 } + ] + }, + "ceiling_palette": { + "default": [ + { "block": "minecraft:cracked_deepslate_bricks", "weight": 0.10 }, + { "block": "minecraft:sculk", "weight": 0.20 }, + { "block": "minecraft:deepslate_bricks", "weight": 0.70 } + ] + }, + "wall_shell": "minecraft:deepslate_bricks", + "wall_accent": "minecraft:sculk_catalyst", + "pillar_palette": { + "default": [ + { "block": "minecraft:sculk", "weight": 0.30 }, + { "block": "minecraft:deepslate_tile_wall", "weight": 0.70 } + ], + "cap": [ + { "block": "minecraft:sculk", "weight": 1.0 } + ] + }, + "scatter_palette": { + "default": [ + { "block": "minecraft:sculk", "weight": 0.50 }, + { "block": "minecraft:sculk_vein", "weight": 0.30 }, + { "block": "minecraft:moss_carpet", "weight": 0.20 } + ] + }, + "decorations": { + "wall_midpoint_blocks": [ + { "block": "minecraft:sculk_vein", "y_offset": 2 }, + { "block": "minecraft:soul_lantern", "y_offset": 3 } + ], + "first_corner_special": { "block": "minecraft:sculk_catalyst", "y_offset": 1 }, + "furniture_cluster": [ + { "block": "minecraft:sculk_shrieker", "y_offset": 1 }, + { "block": "minecraft:sculk_sensor", "x_offset": 1, "y_offset": 1 }, + { "block": "minecraft:candle[lit=true,candles=3]", "y_offset": 1, "z_offset": 1 } + ], + "use_torch_lighting": false, + "has_ceiling_chain": false + } +}