From a32b04ed1483ea2daa28cc30cf6f5aceff09475d Mon Sep 17 00:00:00 2001 From: George Paton Date: Mon, 1 Dec 2025 12:05:56 +1100 Subject: [PATCH] all new NBTStructureLib features up to structure block (coming next) --- .../java/com/hbm/commands/CommandLocate.java | 7 +- .../com/hbm/world/gen/nbt/JigsawPiece.java | 40 ++++- .../com/hbm/world/gen/nbt/JigsawPool.java | 5 + .../com/hbm/world/gen/nbt/NBTStructure.java | 138 +++++++++++++++--- .../com/hbm/world/gen/nbt/SpawnCondition.java | 58 ++++++-- .../gen/nbt/selector/BiomeBlockSelector.java | 10 ++ .../gen/nbt/selector/BiomeFillerSelector.java | 12 ++ .../gen/nbt/selector/BiomeTopSelector.java | 12 ++ .../world/gen/nbt/selector/BrickSelector.java | 15 ++ .../gen/nbt/selector/StoneBrickSelector.java | 24 +++ 10 files changed, 282 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/hbm/world/gen/nbt/selector/BiomeBlockSelector.java create mode 100644 src/main/java/com/hbm/world/gen/nbt/selector/BiomeFillerSelector.java create mode 100644 src/main/java/com/hbm/world/gen/nbt/selector/BiomeTopSelector.java create mode 100644 src/main/java/com/hbm/world/gen/nbt/selector/BrickSelector.java create mode 100644 src/main/java/com/hbm/world/gen/nbt/selector/StoneBrickSelector.java diff --git a/src/main/java/com/hbm/commands/CommandLocate.java b/src/main/java/com/hbm/commands/CommandLocate.java index ec646c817..9c9c225f5 100644 --- a/src/main/java/com/hbm/commands/CommandLocate.java +++ b/src/main/java/com/hbm/commands/CommandLocate.java @@ -13,6 +13,7 @@ import net.minecraft.command.ICommandSender; import net.minecraft.command.PlayerNotFoundException; import net.minecraft.command.WrongUsageException; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ChatComponentText; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.MathHelper; @@ -71,6 +72,8 @@ public class CommandLocate extends CommandBase { ChatComponentTranslation message = new ChatComponentTranslation("commands.locate.success.coordinates", structure.name, pos.chunkXPos * 16, pos.chunkZPos * 16); message.getChatStyle().setColor(EnumChatFormatting.GREEN); sender.addChatMessage(message); + } else if (args[0].equals("list")) { + sender.addChatMessage(new ChatComponentText(String.join(", ", NBTStructure.listStructures()))); } else { throw new WrongUsageException(getCommandUsage(sender), new Object[0]); } @@ -105,9 +108,9 @@ public class CommandLocate extends CommandBase { return Collections.emptyList(); if(args.length == 1) - return getListOfStringsMatchingLastWord(args, "structure"); + return getListOfStringsMatchingLastWord(args, "structure", "list"); - if(args.length == 2) { + if(args.length == 2 && args[0].equals("structure")) { List structures = NBTStructure.listStructures(); return getListOfStringsMatchingLastWord(args, structures.toArray(new String[structures.size()])); } diff --git a/src/main/java/com/hbm/world/gen/nbt/JigsawPiece.java b/src/main/java/com/hbm/world/gen/nbt/JigsawPiece.java index 039f8d154..6db8b75d1 100644 --- a/src/main/java/com/hbm/world/gen/nbt/JigsawPiece.java +++ b/src/main/java/com/hbm/world/gen/nbt/JigsawPiece.java @@ -9,17 +9,47 @@ import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; // Assigned to a Component to build public class JigsawPiece { - // Translates a given name into a jigsaw piece, for serialization - protected static Map jigsawMap = new HashMap<>(); + // Translates a given name into a jigsaw piece, for serialization + protected static Map jigsawMap = new HashMap<>(); public final String name; public final NBTStructure structure; // Block modifiers, for randomization and terrain matching + /** + * Replaces matching blocks using the result of a BlockSelector + */ public Map blockTable; - public boolean conformToTerrain = false; // moves every single column to the terrain (digging out trenches, natural formations) - public boolean alignToTerrain = false; // aligns this component y-level individually, without moving individual columns (village houses) - public int heightOffset = 0; // individual offset for the structure + + /** + * Moves every single column to the terrain (for digging out trenches, natural formations, etc) + */ + public boolean conformToTerrain = false; + + /** + * Aligns this component y-level individually, without moving individual columns (like village houses) + */ + public boolean alignToTerrain = false; + + /** + * Height offset for this structure piece, -1 will sink the floor flush into the ground + */ + public int heightOffset = 0; + + /** + * Will fill any air gap beneath the jigsaw piece using the given selector + */ + public BlockSelector platform; + + /** + * If greater than 0, will limit the number of times this piece can spawn in a structure + */ + public int instanceLimit = 0; + + /** + * If set, will continue generation beyond the defined limits until this piece exists at least once + */ + public boolean required = false; public JigsawPiece(String name, NBTStructure structure) { this(name, structure, 0); diff --git a/src/main/java/com/hbm/world/gen/nbt/JigsawPool.java b/src/main/java/com/hbm/world/gen/nbt/JigsawPool.java index aaf0e70d2..8fbc0c533 100644 --- a/src/main/java/com/hbm/world/gen/nbt/JigsawPool.java +++ b/src/main/java/com/hbm/world/gen/nbt/JigsawPool.java @@ -23,6 +23,11 @@ public class JigsawPool { totalWeight += weight; } + public int getAverageWeight() { + if(pieces.size() == 0) return 1; + return totalWeight / pieces.size(); + } + protected JigsawPool clone() { JigsawPool clone = new JigsawPool(); clone.pieces = new ArrayList<>(this.pieces); diff --git a/src/main/java/com/hbm/world/gen/nbt/NBTStructure.java b/src/main/java/com/hbm/world/gen/nbt/NBTStructure.java index 4109338cc..5c3b50d25 100644 --- a/src/main/java/com/hbm/world/gen/nbt/NBTStructure.java +++ b/src/main/java/com/hbm/world/gen/nbt/NBTStructure.java @@ -18,6 +18,7 @@ import com.hbm.util.Tuple.Pair; import com.hbm.util.Tuple.Quartet; import com.hbm.util.fauxpointtwelve.BlockPos; import com.hbm.world.gen.nbt.SpawnCondition.WorldCoordinate; +import com.hbm.world.gen.nbt.selector.BiomeBlockSelector; import cpw.mods.fml.common.registry.GameRegistry; import net.minecraft.block.*; @@ -57,9 +58,11 @@ public class NBTStructure { private static Map namedMap = new HashMap<>(); - protected static Map> weightedMap = new HashMap<>(); + protected static Map> spawnMap = new HashMap<>(); protected static Map> customSpawnMap = new HashMap<>(); + private static Map> validBiomeCache = new HashMap<>(); + private String name; private boolean isLoaded; @@ -84,6 +87,15 @@ public class NBTStructure { } } + public NBTStructure(String name, InputStream stream) { + this.name = name; + loadStructure(stream); + } + + public String getName() { + return name.substring(0, name.length() - 4); // trim .nbt + } + public static void register() { MapGenStructureIO.registerStructure(Start.class, "NBTStructures"); MapGenStructureIO.func_143031_a(Component.class, "NBTComponents"); @@ -102,10 +114,8 @@ public class NBTStructure { return; } - List weightedList = weightedMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); - for(int i = 0; i < spawn.spawnWeight; i++) { - weightedList.add(spawn); - } + List spawnList = spawnMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); + spawnList.add(spawn); } public static void registerStructure(SpawnCondition spawn, int[] dimensionIds) { @@ -122,10 +132,8 @@ public class NBTStructure { public static void registerNullWeight(int dimensionId, int weight, Predicate predicate) { SpawnCondition spawn = new SpawnCondition(weight, predicate); - List weightedList = weightedMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); - for(int i = 0; i < spawn.spawnWeight; i++) { - weightedList.add(spawn); - } + List spawnList = spawnMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); + spawnList.add(spawn); } // Presents a list of all structures registered (so far) @@ -473,9 +481,7 @@ public class NBTStructure { Block block = transformBlock(state.definition, null, world.rand); int meta = transformMeta(state.definition, null, coordBaseMode); - if(ry < 0 || ry >= world.getHeight()) continue; - Block existing = world.getBlock(rx, ry, rz); - if(existing == Blocks.bedrock) continue; + if(ry < 1) continue; world.setBlock(rx, ry, rz, block, meta, 2); @@ -535,12 +541,30 @@ public class NBTStructure { int minZ = Math.min(rotMinZ, rotMaxZ); int maxZ = Math.max(rotMinZ, rotMaxZ); + if(piece.blockTable != null || piece.platform != null) { + BiomeGenBase biome = world.getWorldChunkManager().getBiomeGenAt(generatingBounds.getCenterX(), generatingBounds.getCenterZ()); + + if(piece.blockTable != null) { + for(BlockSelector selector : piece.blockTable.values()) { + if(selector instanceof BiomeBlockSelector) { + ((BiomeBlockSelector) selector).nextBiome = biome; + } + } + } + + if(piece.platform instanceof BiomeBlockSelector) { + ((BiomeBlockSelector) piece.platform).nextBiome = biome; + } + } + for(int bx = minX; bx <= maxX; bx++) { for(int bz = minZ; bz <= maxZ; bz++) { int rx = rotateX(bx, bz, coordBaseMode) + totalBounds.minX; int rz = rotateZ(bx, bz, coordBaseMode) + totalBounds.minZ; int oy = piece.conformToTerrain ? world.getTopSolidOrLiquidBlock(rx, rz) + piece.heightOffset : totalBounds.minY; + boolean hasBase = false; + for(int by = 0; by < size.y; by++) { BlockState state = blockArray[bx][by][bz]; if(state == null) continue; @@ -550,9 +574,7 @@ public class NBTStructure { Block block = transformBlock(state.definition, piece.blockTable, world.rand); int meta = transformMeta(state.definition, piece.blockTable, coordBaseMode); - if(ry < 0 || ry >= world.getHeight()) continue; - Block existing = world.getBlock(rx, ry, rz); - if(existing == Blocks.bedrock) continue; + if(ry < 1) continue; world.setBlock(rx, ry, rz, block, meta, 2); @@ -560,6 +582,16 @@ public class NBTStructure { TileEntity te = buildTileEntity(world, block, worldItemPalette, state.nbt, coordBaseMode, structureName); world.setTileEntity(rx, ry, rz, te); } + + if(by == 0 && piece.platform != null && !block.getMaterial().isReplaceable()) hasBase = true; + } + + if(hasBase && !piece.conformToTerrain) { + for(int y = oy - 1; y > 0; y--) { + if(!world.getBlock(rx, y, rz).isReplaceable(world, rx, y, rz)) break; + piece.platform.selectBlocks(world.rand, 0, 0, 0, false); + world.setBlock(rx, y, rz, piece.platform.func_151561_a(), piece.platform.getSelectedBlockMetaData(), 2); + } } } } @@ -940,6 +972,8 @@ public class NBTStructure { List queuedComponents = new ArrayList<>(); if(spawn.structure == null) queuedComponents.add(startComponent); + Set requiredPieces = findRequiredPieces(spawn); + // Iterate through and build out all the components we intend to spawn while(!queuedComponents.isEmpty()) { queuedComponents.sort((a, b) -> b.priority - a.priority); // sort by placement priority descending @@ -956,7 +990,10 @@ public class NBTStructure { if(fromComponent.piece.structure.fromConnections == null) continue; int distance = getDistanceTo(fromComponent.getBoundingBox()); - boolean fallbacksOnly = this.components.size() >= spawn.sizeLimit || distance >= spawn.rangeLimit; + + // Only generate fallback pieces once we hit our size limit, unless we have a required component + // Note that there is a HARD limit of 1024 pieces to prevent infinite generation + boolean fallbacksOnly = requiredPieces.size() == 0 && (components.size() >= spawn.sizeLimit || distance >= spawn.rangeLimit) || components.size() > 1024; for(List unshuffledList : fromComponent.piece.structure.fromConnections) { List connectionList = new ArrayList<>(unshuffledList); @@ -994,6 +1031,8 @@ public class NBTStructure { if(nextComponent != null) { addComponent(nextComponent, fromConnection.placementPriority); queuedComponents.add(nextComponent); + + requiredPieces.remove(nextComponent.piece); } else { // If we failed to fit anything in, grab something from the fallback pool, ignoring bounds check // unless we are perfectly abutting another piece, so grid layouts can work! @@ -1043,6 +1082,22 @@ public class NBTStructure { return new BlockPos(x, y, z); } + private Set findRequiredPieces(SpawnCondition spawn) { + Set requiredPieces = new HashSet<>(); + + if(spawn.pools == null) return requiredPieces; + + for(JigsawPool pool : spawn.pools.values()) { + for(Pair weight : pool.pieces) { + if(weight.getKey().required) { + requiredPieces.add(weight.getKey()); + } + } + } + + return requiredPieces; + } + private Component buildNextComponent(Random rand, SpawnCondition spawn, JigsawPool pool, Component fromComponent, JigsawConnection fromConnection) { JigsawPiece nextPiece = pool.get(rand); if(nextPiece == null) { @@ -1050,6 +1105,17 @@ public class NBTStructure { return null; } + if(nextPiece.instanceLimit > 0) { + int instances = 0; + for(Object component : components) { + if(component instanceof Component && ((Component) component).piece == nextPiece) { + instances++; + + if(instances >= nextPiece.instanceLimit) return null; + } + } + } + List connectionPool = nextPiece.structure.getConnectionPool(fromConnection.dir, fromConnection.targetName); if(connectionPool == null || connectionPool.isEmpty()) { MainRegistry.logger.warn("[Jigsaw] No valid connections for: " + fromConnection.targetName + " - in piece: " + nextPiece.name); @@ -1153,7 +1219,7 @@ public class NBTStructure { } } - if (!weightedMap.containsKey(worldObj.provider.dimensionId)) + if (!spawnMap.containsKey(worldObj.provider.dimensionId)) return null; int x = chunkX; @@ -1189,11 +1255,35 @@ public class NBTStructure { } private SpawnCondition findSpawn(BiomeGenBase biome) { - List spawnList = weightedMap.get(worldObj.provider.dimensionId); + Map dimensionCache = validBiomeCache.computeIfAbsent(worldObj.provider.dimensionId, integer -> new HashMap<>()); - for(int i = 0; i < 64; i++) { - SpawnCondition spawn = spawnList.get(rand.nextInt(spawnList.size())); - if(spawn.isValid(biome)) return spawn; + WeightedSpawnList filteredList; + if(!dimensionCache.containsKey(biome.biomeID)) { + List spawnList = spawnMap.get(worldObj.provider.dimensionId); + + filteredList = new WeightedSpawnList(); + for(SpawnCondition spawn : spawnList) { + if(spawn.isValid(biome)) { + filteredList.add(spawn); + filteredList.totalWeight += spawn.spawnWeight; + } + } + + dimensionCache.put(biome.biomeID, filteredList); + } else { + filteredList = dimensionCache.get(biome.biomeID); + } + + if(filteredList.totalWeight == 0) return null; + + int weight = rand.nextInt(filteredList.totalWeight); + + for(SpawnCondition spawn : filteredList) { + weight -= spawn.spawnWeight; + + if(weight < 0) { + return spawn; + } } return null; @@ -1201,4 +1291,10 @@ public class NBTStructure { } + private static class WeightedSpawnList extends ArrayList { + + public int totalWeight = 0; + + } + } diff --git a/src/main/java/com/hbm/world/gen/nbt/SpawnCondition.java b/src/main/java/com/hbm/world/gen/nbt/SpawnCondition.java index f84d3aad8..0c385e51e 100644 --- a/src/main/java/com/hbm/world/gen/nbt/SpawnCondition.java +++ b/src/main/java/com/hbm/world/gen/nbt/SpawnCondition.java @@ -18,34 +18,70 @@ public class SpawnCondition { public final String name; - // If defined, will spawn a single jigsaw piece, for single nbt structures + /** + * If defined, will spawn a single jigsaw piece, for single nbt structures + */ public JigsawPiece structure; - // If defined, will spawn in a non-nbt structure component + /** + * If defined, will spawn in a non-nbt structure component + */ public Function, StructureStart> start; - // If defined, will override regular spawn location checking, for placing at specific coordinates or with special rules + /** + * If defined, will override regular spawn location checking, for placing at specific coordinates or with special rules + */ public Predicate checkCoordinates; - // Our regular spawning mechanics, based on biome, you should generally use these + /** + * Defines whether the current biome is valid for spawning this structure + */ public Predicate canSpawn; + + /** + * The chance of this structure spawning relative to others, + * higher weights will spawn more often. + */ public int spawnWeight = 1; - // Named jigsaw pools that are referenced within the structure + /** + * Named jigsaw pools that are referenced by jigsaw blocks within the structure + */ public Map pools; + + /** + * The name of the "core" pool, which the structure starts generation from, + * must be a name of a pool defined within `pool` + */ public String startPool; - // Maximum amount of components in this structure + /** + * Maximum amount of components in this structure. + * Once the structure reaches this many components, + * it will only generate fallback pieces and stop + * + * Note: there is a hard limit of 1024 pieces to prevent infinite generation, + * even if some pieces are marked as required! + */ public int sizeLimit = 8; - // How far the structure can extend horizontally from the center, maximum of 128 - // This could be increased by changing GenStructure:range from 8, but this is already quite reasonably large + // This could be increased by changing GenStructure:range from 8, but this is + // already quite reasonably large + /** + * How far the structure can extend horizontally from the center, maximum of 128 + */ public int rangeLimit = 128; - // Height modifiers, will clamp height that the start generates at, allowing for: - // * Submarines that must spawn under the ocean surface - // * Bunkers that sit underneath the ground + /** + * Height modifiers, will clamp height that the start generates at, allowing for: + * * Submarines that must spawn under the ocean surface + * * Bunkers that sit underneath the ground + */ public int minHeight = 1; + + /** + * @see minHeight + */ public int maxHeight = 128; protected SpawnCondition(int weight, Predicate predicate) { diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/BiomeBlockSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeBlockSelector.java new file mode 100644 index 000000000..cf700c1a0 --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeBlockSelector.java @@ -0,0 +1,10 @@ +package com.hbm.world.gen.nbt.selector; + +import net.minecraft.world.biome.BiomeGenBase; +import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; + +public abstract class BiomeBlockSelector extends BlockSelector { + + public BiomeGenBase nextBiome; + +} diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/BiomeFillerSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeFillerSelector.java new file mode 100644 index 000000000..73fcf9c9b --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeFillerSelector.java @@ -0,0 +1,12 @@ +package com.hbm.world.gen.nbt.selector; + +import java.util.Random; + +public class BiomeFillerSelector extends BiomeBlockSelector { + + @Override + public void selectBlocks(Random rand, int x, int y, int z, boolean notInterior) { + field_151562_a = nextBiome.fillerBlock; + } + +} diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/BiomeTopSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeTopSelector.java new file mode 100644 index 000000000..862f17ae1 --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/BiomeTopSelector.java @@ -0,0 +1,12 @@ +package com.hbm.world.gen.nbt.selector; + +import java.util.Random; + +public class BiomeTopSelector extends BiomeBlockSelector { + + @Override + public void selectBlocks(Random rand, int x, int y, int z, boolean notInterior) { + field_151562_a = nextBiome.topBlock; + } + +} diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/BrickSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/BrickSelector.java new file mode 100644 index 000000000..98852290d --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/BrickSelector.java @@ -0,0 +1,15 @@ +package com.hbm.world.gen.nbt.selector; + +import java.util.Random; + +import net.minecraft.init.Blocks; +import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; + +public class BrickSelector extends BlockSelector { + + @Override + public void selectBlocks(Random rand, int x, int y, int z, boolean notInterior) { + field_151562_a = Blocks.brick_block; + } + +} diff --git a/src/main/java/com/hbm/world/gen/nbt/selector/StoneBrickSelector.java b/src/main/java/com/hbm/world/gen/nbt/selector/StoneBrickSelector.java new file mode 100644 index 000000000..ff841d08b --- /dev/null +++ b/src/main/java/com/hbm/world/gen/nbt/selector/StoneBrickSelector.java @@ -0,0 +1,24 @@ +package com.hbm.world.gen.nbt.selector; + +import java.util.Random; + +import net.minecraft.init.Blocks; +import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; + +public class StoneBrickSelector extends BlockSelector { + + @Override + public void selectBlocks(Random rand, int x, int y, int z, boolean notInterior) { + field_151562_a = Blocks.stonebrick; + float f = rand.nextFloat(); + + if (f < 0.2F) { + this.selectedBlockMetaData = 2; + } else if (f < 0.5F) { + this.selectedBlockMetaData = 1; + } else { + this.selectedBlockMetaData = 0; + } + } + +}