package com.hbm.world.gen; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import com.hbm.blocks.ModBlocks; import com.hbm.blocks.generic.BlockWand; import com.hbm.config.GeneralConfig; import com.hbm.config.StructureConfig; import com.hbm.handler.ThreeInts; import com.hbm.main.MainRegistry; import com.hbm.util.Tuple.Pair; import com.hbm.util.Tuple.Quartet; import com.hbm.util.fauxpointtwelve.BlockPos; import cpw.mods.fml.common.registry.GameRegistry; import net.minecraft.block.*; import net.minecraft.client.Minecraft; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagInt; import net.minecraft.nbt.NBTTagList; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.MathHelper; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraft.world.biome.BiomeGenBase; import net.minecraft.world.chunk.IChunkProvider; import net.minecraft.world.gen.structure.MapGenStructure; import net.minecraft.world.gen.structure.MapGenStructureIO; import net.minecraft.world.gen.structure.StructureBoundingBox; import net.minecraft.world.gen.structure.StructureComponent; import net.minecraft.world.gen.structure.StructureComponent.BlockSelector; import net.minecraft.world.gen.structure.StructureStart; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.common.util.Constants.NBT; public class NBTStructure { /** * Now with structure support! * * the type of structure to generate is saved into the Component, * meaning this can generate all sorts of different structures, * without having to define and register each structure manually */ protected static Map> weightedMap = new HashMap<>(); // serialization data protected static Map jigsawMap = new HashMap<>(); private String name; private boolean isLoaded; private ThreeInts size; private List> itemPalette; private BlockState[][][] blockArray; private List> fromConnections; private Map> toTopConnections; private Map> toBottomConnections; private Map> toHorizontalConnections; public NBTStructure(ResourceLocation resource) { // Can't use regular resource loading, servers don't know how! InputStream stream = NBTStructure.class.getResourceAsStream("/assets/" + resource.getResourceDomain() + "/" + resource.getResourcePath()); if(stream != null) { name = resource.getResourcePath(); loadStructure(stream); } else { MainRegistry.logger.error("NBT Structure not found: " + resource.getResourcePath()); } } public static void register() { MapGenStructureIO.registerStructure(Start.class, "NBTStructures"); MapGenStructureIO.func_143031_a(Component.class, "NBTComponents"); } // Register a new structure for a given dimension public static void registerStructure(int dimensionId, SpawnCondition spawn) { List weightedList = weightedMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); for(int i = 0; i < spawn.spawnWeight; i++) { weightedList.add(spawn); } } public static void registerStructure(SpawnCondition spawn, int[] dimensionIds) { for(int dimensionId : dimensionIds) { registerStructure(dimensionId, spawn); } } // Add a chance for nothing to spawn at a given valid spawn location public static void registerNullWeight(int dimensionId, int weight) { registerNullWeight(dimensionId, weight, null); } public static void registerNullWeight(int dimensionId, int weight, Predicate predicate) { SpawnCondition spawn = new SpawnCondition() {{ spawnWeight = weight; canSpawn = predicate; }}; List weightedList = weightedMap.computeIfAbsent(dimensionId, integer -> new ArrayList()); for(int i = 0; i < spawn.spawnWeight; i++) { weightedList.add(spawn); } } // Saves a selected area into an NBT structure (+ some of our non-standard stuff to support 1.7.10) public static void saveArea(String filename, World world, int x1, int y1, int z1, int x2, int y2, int z2, Set> exclude) { NBTTagCompound structure = new NBTTagCompound(); NBTTagList nbtBlocks = new NBTTagList(); NBTTagList nbtPalette = new NBTTagList(); NBTTagList nbtItemPalette = new NBTTagList(); // Quick access hash slinging slashers Map, Integer> palette = new HashMap<>(); Map itemPalette = new HashMap<>(); structure.setInteger("version", 1); int ox = Math.min(x1, x2); int oy = Math.min(y1, y2); int oz = Math.min(z1, z2); for(int x = ox; x <= Math.max(x1, x2); x++) { for(int y = oy; y <= Math.max(y1, y2); y++) { for(int z = oz; z <= Math.max(z1, z2); z++) { Pair block = new Pair(world.getBlock(x, y, z), world.getBlockMetadata(x, y, z)); if(exclude.contains(block)) continue; if(block.key instanceof BlockWand) { block.key = ((BlockWand) block.key).exportAs; } int paletteId = palette.size(); if(palette.containsKey(block)) { paletteId = palette.get(block); } else { palette.put(block, paletteId); NBTTagCompound nbtBlock = new NBTTagCompound(); nbtBlock.setString("Name", GameRegistry.findUniqueIdentifierFor(block.key).toString()); NBTTagCompound nbtProp = new NBTTagCompound(); nbtProp.setString("meta", block.value.toString()); nbtBlock.setTag("Properties", nbtProp); nbtPalette.appendTag(nbtBlock); } NBTTagCompound nbtBlock = new NBTTagCompound(); nbtBlock.setInteger("state", paletteId); NBTTagList nbtPos = new NBTTagList(); nbtPos.appendTag(new NBTTagInt(x - ox)); nbtPos.appendTag(new NBTTagInt(y - oy)); nbtPos.appendTag(new NBTTagInt(z - oz)); nbtBlock.setTag("pos", nbtPos); TileEntity te = world.getTileEntity(x, y, z); if(te != null) { NBTTagCompound nbt = new NBTTagCompound(); te.writeToNBT(nbt); nbt.removeTag("x"); nbt.removeTag("y"); nbt.removeTag("z"); nbtBlock.setTag("nbt", nbt); String itemKey = null; if(nbt.hasKey("items")) itemKey = "items"; if(nbt.hasKey("Items")) itemKey = "Items"; if(nbt.hasKey(itemKey)) { NBTTagList items = nbt.getTagList(itemKey, NBT.TAG_COMPOUND); for(int i = 0; i < items.tagCount(); i++) { NBTTagCompound item = items.getCompoundTagAt(i); short id = item.getShort("id"); String name = GameRegistry.findUniqueIdentifierFor(Item.getItemById(id)).toString(); if(!itemPalette.containsKey(id)) { int itemPaletteId = itemPalette.size(); itemPalette.put(id, itemPaletteId); NBTTagCompound nbtItem = new NBTTagCompound(); nbtItem.setShort("ID", id); nbtItem.setString("Name", name); nbtItemPalette.appendTag(nbtItem); } } } } nbtBlocks.appendTag(nbtBlock); } } } structure.setTag("blocks", nbtBlocks); structure.setTag("palette", nbtPalette); structure.setTag("itemPalette", nbtItemPalette); NBTTagList nbtSize = new NBTTagList(); nbtSize.appendTag(new NBTTagInt(Math.abs(x1 - x2) + 1)); nbtSize.appendTag(new NBTTagInt(Math.abs(y1 - y2) + 1)); nbtSize.appendTag(new NBTTagInt(Math.abs(z1 - z2) + 1)); structure.setTag("size", nbtSize); structure.setTag("entities", new NBTTagList()); try { File structureDirectory = new File(Minecraft.getMinecraft().mcDataDir, "structures"); structureDirectory.mkdir(); File structureFile = new File(structureDirectory, filename); CompressedStreamTools.writeCompressed(structure, new FileOutputStream(structureFile)); } catch (Exception ex) { MainRegistry.logger.warn("Failed to save NBT structure", ex); } } private void loadStructure(InputStream inputStream) { try { NBTTagCompound data = CompressedStreamTools.readCompressed(inputStream); // GET SIZE (for offsetting to center) size = parsePos(data.getTagList("size", NBT.TAG_INT)); // PARSE BLOCK PALETTE NBTTagList paletteList = data.getTagList("palette", NBT.TAG_COMPOUND); BlockDefinition[] palette = new BlockDefinition[paletteList.tagCount()]; for(int i = 0; i < paletteList.tagCount(); i++) { NBTTagCompound p = paletteList.getCompoundTagAt(i); String blockName = p.getString("Name"); NBTTagCompound prop = p.getCompoundTag("Properties"); int meta = 0; try { meta = Integer.parseInt(prop.getString("meta")); } catch(NumberFormatException ex) { MainRegistry.logger.info("Failed to parse: " + prop.getString("meta")); meta = 0; } palette[i] = new BlockDefinition(blockName, meta); if(StructureConfig.debugStructures && palette[i].block == Blocks.air) { palette[i] = new BlockDefinition(ModBlocks.wand_air, meta); } } // PARSE ITEM PALETTE (custom shite) if(data.hasKey("itemPalette")) { NBTTagList itemPaletteList = data.getTagList("itemPalette", NBT.TAG_COMPOUND); itemPalette = new ArrayList<>(itemPaletteList.tagCount()); for(int i = 0; i < itemPaletteList.tagCount(); i++) { NBTTagCompound p = itemPaletteList.getCompoundTagAt(i); short id = p.getShort("ID"); String name = p.getString("Name"); itemPalette.add(new Pair<>(id, name)); } } else { itemPalette = null; } // LOAD IN BLOCKS NBTTagList blockData = data.getTagList("blocks", NBT.TAG_COMPOUND); blockArray = new BlockState[size.x][size.y][size.z]; List connections = new ArrayList<>(); for(int i = 0; i < blockData.tagCount(); i++) { NBTTagCompound block = blockData.getCompoundTagAt(i); int state = block.getInteger("state"); ThreeInts pos = parsePos(block.getTagList("pos", NBT.TAG_INT)); BlockState blockState = new BlockState(palette[state]); if(block.hasKey("nbt")) { NBTTagCompound nbt = block.getCompoundTag("nbt"); blockState.nbt = nbt; // Load in connection points for jigsaws if(blockState.definition.block == ModBlocks.wand_jigsaw) { if(toTopConnections == null) toTopConnections = new HashMap<>(); if(toBottomConnections == null) toBottomConnections = new HashMap<>(); if(toHorizontalConnections == null) toHorizontalConnections = new HashMap<>(); int selectionPriority = nbt.getInteger("selection"); int placementPriority = nbt.getInteger("placement"); ForgeDirection direction = ForgeDirection.getOrientation(nbt.getInteger("direction")); String poolName = nbt.getString("pool"); String ourName = nbt.getString("name"); String targetName = nbt.getString("target"); String replaceBlock = nbt.getString("block"); int replaceMeta = nbt.getInteger("meta"); boolean isRollable = nbt.getBoolean("roll"); JigsawConnection connection = new JigsawConnection(pos, direction, poolName, targetName, isRollable, selectionPriority, placementPriority); connections.add(connection); Map> toConnections = null; if(direction == ForgeDirection.UP) { toConnections = toTopConnections; } else if(direction == ForgeDirection.DOWN) { toConnections = toBottomConnections; } else { toConnections = toHorizontalConnections; } List namedConnections = toConnections.computeIfAbsent(ourName, name -> new ArrayList<>()); namedConnections.add(connection); if(!StructureConfig.debugStructures) { blockState = new BlockState(new BlockDefinition(replaceBlock, replaceMeta)); } } } blockArray[pos.x][pos.y][pos.z] = blockState; } // MAP OUT CONNECTIONS + PRIORITIES if(connections.size() > 0) { fromConnections = new ArrayList<>(); connections.sort((a, b) -> b.selectionPriority - a.selectionPriority); // sort by descending priority, highest first // Sort out our from connections, splitting into individual lists for each priority level List innerList = null; int currentPriority = 0; for(JigsawConnection connection : connections) { if(innerList == null || currentPriority != connection.selectionPriority) { innerList = new ArrayList<>(); fromConnections.add(innerList); currentPriority = connection.selectionPriority; } innerList.add(connection); } } isLoaded = true; } catch(Exception e) { MainRegistry.logger.error("Exception reading NBT Structure format", e); } finally { try { inputStream.close(); } catch(IOException e) { // hush } } } private HashMap getWorldItemPalette() { if(itemPalette == null) return null; HashMap worldItemPalette = new HashMap<>(); for(Pair entry : itemPalette) { Item item = (Item)Item.itemRegistry.getObject(entry.getValue()); worldItemPalette.put(entry.getKey(), (short)Item.getIdFromItem(item)); } return worldItemPalette; } private TileEntity buildTileEntity(World world, Block block, HashMap worldItemPalette, NBTTagCompound nbt, int coordBaseMode) { nbt = (NBTTagCompound)nbt.copy(); if(worldItemPalette != null) relinkItems(worldItemPalette, nbt); TileEntity te = TileEntity.createAndLoadEntity(nbt); if(te instanceof INBTTileEntityTransformable) { ((INBTTileEntityTransformable) te).transformTE(world, coordBaseMode); } return te; } public void build(World world, int x, int y, int z) { build(world, x, y, z, 0); } public void build(World world, int x, int y, int z, int coordBaseMode) { if(!isLoaded) { MainRegistry.logger.info("NBTStructure is invalid"); return; } HashMap worldItemPalette = getWorldItemPalette(); boolean swizzle = coordBaseMode == 1 || coordBaseMode == 3; x -= (swizzle ? size.z : size.x) / 2; z -= (swizzle ? size.x : size.z) / 2; int maxX = size.x; int maxZ = size.z; for(int bx = 0; bx < maxX; bx++) { for(int bz = 0; bz < maxZ; bz++) { int rx = rotateX(bx, bz, coordBaseMode) + x; int rz = rotateZ(bx, bz, coordBaseMode) + z; for(int by = 0; by < size.y; by++) { BlockState state = blockArray[bx][by][bz]; if(state == null) continue; int ry = by + y; Block block = transformBlock(state.definition, null, world.rand); int meta = transformMeta(state.definition, null, coordBaseMode); world.setBlock(rx, ry, rz, block, meta, 2); if(state.nbt != null) { TileEntity te = buildTileEntity(world, block, worldItemPalette, state.nbt, coordBaseMode); world.setTileEntity(rx, ry, rz, te); } } } } } protected boolean build(World world, JigsawPiece piece, StructureBoundingBox totalBounds, StructureBoundingBox generatingBounds, int coordBaseMode) { if(!isLoaded) { MainRegistry.logger.info("NBTStructure is invalid"); return false; } HashMap worldItemPalette = getWorldItemPalette(); int sizeX = totalBounds.maxX - totalBounds.minX; int sizeZ = totalBounds.maxZ - totalBounds.minZ; // voxel grid transforms can fuck you up // you have my respect, vaer int absMinX = Math.max(generatingBounds.minX - totalBounds.minX, 0); int absMaxX = Math.min(generatingBounds.maxX - totalBounds.minX, sizeX); int absMinZ = Math.max(generatingBounds.minZ - totalBounds.minZ, 0); int absMaxZ = Math.min(generatingBounds.maxZ - totalBounds.minZ, sizeZ); // A check to see that we're actually inside the generating area at all if(absMinX > sizeX || absMaxX < 0 || absMinZ > sizeZ || absMaxZ < 0) return true; int rotMinX = unrotateX(absMinX, absMinZ, coordBaseMode); int rotMaxX = unrotateX(absMaxX, absMaxZ, coordBaseMode); int rotMinZ = unrotateZ(absMinX, absMinZ, coordBaseMode); int rotMaxZ = unrotateZ(absMaxX, absMaxZ, coordBaseMode); int minX = Math.min(rotMinX, rotMaxX); int maxX = Math.max(rotMinX, rotMaxX); int minZ = Math.min(rotMinZ, rotMaxZ); int maxZ = Math.max(rotMinZ, rotMaxZ); 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; for(int by = 0; by < size.y; by++) { BlockState state = blockArray[bx][by][bz]; if(state == null) continue; int ry = by + oy; Block block = transformBlock(state.definition, piece.blockTable, world.rand); int meta = transformMeta(state.definition, piece.blockTable, coordBaseMode); world.setBlock(rx, ry, rz, block, meta, 2); if(state.nbt != null) { TileEntity te = buildTileEntity(world, block, worldItemPalette, state.nbt, coordBaseMode); world.setTileEntity(rx, ry, rz, te); } } } } return true; } // What a fucken mess, why even implement the IntArray NBT if ye aint gonna use it Moe Yang? private ThreeInts parsePos(NBTTagList pos) { NBTBase xb = (NBTBase)pos.tagList.get(0); int x = ((NBTTagInt)xb).func_150287_d(); NBTBase yb = (NBTBase)pos.tagList.get(1); int y = ((NBTTagInt)yb).func_150287_d(); NBTBase zb = (NBTBase)pos.tagList.get(2); int z = ((NBTTagInt)zb).func_150287_d(); return new ThreeInts(x, y, z); } // NON-STANDARD, items are serialized with IDs, which will differ from world to world! // So our fixed exporter adds an itemPalette, please don't hunt me down for fucking with the spec private void relinkItems(HashMap palette, NBTTagCompound nbt) { NBTTagList items = null; if(nbt.hasKey("items")) items = nbt.getTagList("items", NBT.TAG_COMPOUND); if(nbt.hasKey("Items")) items = nbt.getTagList("Items", NBT.TAG_COMPOUND); if(items == null) return; for(int i = 0; i < items.tagCount(); i++) { NBTTagCompound item = items.getCompoundTagAt(i); item.setShort("id", palette.get(item.getShort("id"))); } } private Block transformBlock(BlockDefinition definition, Map blockTable, Random rand) { if(blockTable != null && blockTable.containsKey(definition.block)) { final BlockSelector selector = blockTable.get(definition.block); selector.selectBlocks(rand, 0, 0, 0, false); // fuck the vanilla shit idc return selector.func_151561_a(); } if(definition.block instanceof INBTTransformable) return ((INBTTransformable) definition.block).transformBlock(definition.block); return definition.block; } private int transformMeta(BlockDefinition definition, Map blockTable, int coordBaseMode) { if(blockTable != null && blockTable.containsKey(definition.block)) { return blockTable.get(definition.block).getSelectedBlockMetaData(); } // Our shit if(definition.block instanceof INBTTransformable) return ((INBTTransformable) definition.block).transformMeta(definition.meta, coordBaseMode); if(coordBaseMode == 0) return definition.meta; // Vanilla shit if(definition.block instanceof BlockStairs) return INBTTransformable.transformMetaStairs(definition.meta, coordBaseMode); if(definition.block instanceof BlockRotatedPillar) return INBTTransformable.transformMetaPillar(definition.meta, coordBaseMode); if(definition.block instanceof BlockDirectional) return INBTTransformable.transformMetaDirectional(definition.meta, coordBaseMode); if(definition.block instanceof BlockTorch) return INBTTransformable.transformMetaTorch(definition.meta, coordBaseMode); if(definition.block instanceof BlockButton) return INBTTransformable.transformMetaTorch(definition.meta, coordBaseMode); if(definition.block instanceof BlockDoor) return INBTTransformable.transformMetaDoor(definition.meta, coordBaseMode); if(definition.block instanceof BlockLever) return INBTTransformable.transformMetaLever(definition.meta, coordBaseMode); if(definition.block instanceof BlockSign) return INBTTransformable.transformMetaDeco(definition.meta, coordBaseMode); if(definition.block instanceof BlockLadder) return INBTTransformable.transformMetaDeco(definition.meta, coordBaseMode); if(definition.block instanceof BlockTripWireHook) return INBTTransformable.transformMetaDirectional(definition.meta, coordBaseMode); return definition.meta; } private int rotateX(int x, int z, int coordBaseMode) { switch(coordBaseMode) { case 1: return size.z - 1 - z; case 2: return size.x - 1 - x; case 3: return z; default: return x; } } private int rotateZ(int x, int z, int coordBaseMode) { switch(coordBaseMode) { case 1: return x; case 2: return size.z - 1 - z; case 3: return size.x - 1 - x; default: return z; } } private int unrotateX(int x, int z, int coordBaseMode) { switch(coordBaseMode) { case 3: return size.x - 1 - z; case 2: return size.x - 1 - x; case 1: return z; default: return x; } } private int unrotateZ(int x, int z, int coordBaseMode) { switch(coordBaseMode) { case 3: return x; case 2: return size.z - 1 - z; case 1: return size.z - 1 - x; default: return z; } } private static class BlockState { final BlockDefinition definition; NBTTagCompound nbt; BlockState(BlockDefinition definition) { this.definition = definition; } } private static class BlockDefinition { final Block block; final int meta; BlockDefinition(String name, int meta) { Block block = Block.getBlockFromName(name); if(block == null) block = Blocks.air; this.block = block; this.meta = meta; } BlockDefinition(Block block, int meta) { this.block = block; this.meta = meta; } } public static class SpawnCondition { // 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 public Function, StructureStart> start; public Predicate canSpawn; public int spawnWeight = 1; // Named jigsaw pools that are referenced within the structure public Map pools; public String startPool; // Maximum amount of components in this structure 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 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 public int minHeight = 1; public int maxHeight = 128; // Can this spawn in the current biome protected boolean isValid(BiomeGenBase biome) { if(canSpawn == null) return true; return canSpawn.test(biome); } protected JigsawPool getPool(String name) { return pools.get(name).clone(); } // Builds all of the pools into neat rows and columns, for editing and debugging! // Make sure structure debug is enabled, or it will no-op // Do not use in generation public void buildAll(World world, int x, int y, int z) { if(!StructureConfig.debugStructures) return; int padding = 5; int oz = 0; for(JigsawPool pool : pools.values()) { int highestWidth = 0; int ox = 0; for(Pair entry : pool.pieces) { NBTStructure structure = entry.key.structure; structure.build(world, x + ox + (structure.size.x / 2), y, z + oz + (structure.size.z / 2)); ox += structure.size.x + padding; highestWidth = Math.max(highestWidth, structure.size.z); } oz += highestWidth + padding; } } } // A set of pieces with weights public static class JigsawPool { // Weighted list of pieces to pick from private List> pieces = new ArrayList<>(); private int totalWeight = 0; public String fallback; private boolean isClone; public void add(JigsawPiece piece, int weight) { if(weight <= 0) throw new IllegalStateException("JigsawPool spawn weight must be positive!"); pieces.add(new Pair<>(piece, weight)); totalWeight += weight; } protected JigsawPool clone() { JigsawPool clone = new JigsawPool(); clone.pieces = new ArrayList<>(this.pieces); clone.fallback = this.fallback; clone.totalWeight = this.totalWeight; clone.isClone = true; return clone; } // If from a clone, will remove from the pool public JigsawPiece get(Random rand) { if(totalWeight <= 0) return null; int weight = rand.nextInt(totalWeight); for(int i = 0; i < pieces.size(); i++) { Pair pair = pieces.get(i); weight -= pair.getValue(); if(weight < 0) { if(isClone) { pieces.remove(i); totalWeight -= pair.getValue(); } return pair.getKey(); } } return null; } } // Assigned to a Component to build public static class JigsawPiece { public final String name; public final NBTStructure structure; // Block modifiers, for randomization and terrain matching 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 public JigsawPiece(String name, NBTStructure structure) { this(name, structure, 0); } public JigsawPiece(String name, NBTStructure structure, int heightOffset) { if(name == null) throw new IllegalStateException("A severe error has occurred in NBTStructure! A jigsaw piece has been registered without a valid name!"); if(jigsawMap.containsKey(name)) throw new IllegalStateException("A severe error has occurred in NBTStructure! A jigsaw piece has been registered with the same name as another: " + name); this.name = name; this.structure = structure; jigsawMap.put(name, this); this.heightOffset = heightOffset; } } // Each jigsaw block in a structure will instance one of these private static class JigsawConnection { private final ThreeInts pos; private final ForgeDirection dir; // what pool should we look through to find a connection private final String poolName; // when we successfully find a pool, what connections in that jigsaw piece can we target private final String targetName; private final boolean isRollable; private final int selectionPriority; private final int placementPriority; private JigsawConnection(ThreeInts pos, ForgeDirection dir, String poolName, String targetName, boolean isRollable, int selectionPriority, int placementPriority) { this.pos = pos; this.dir = dir; this.poolName = poolName; this.targetName = targetName; this.isRollable = isRollable; this.selectionPriority = selectionPriority; this.placementPriority = placementPriority; } } public static class Component extends StructureComponent { JigsawPiece piece; int minHeight = 1; int maxHeight = 128; boolean heightUpdated = false; int priority; // this is fucking hacky but we need a way to update ALL component bounds once a Y-level is determined private Start parent; private JigsawConnection connectedFrom; public Component() {} public Component(SpawnCondition spawn, JigsawPiece piece, Random rand, int x, int z) { this(spawn, piece, rand, x, 0, z, rand.nextInt(4)); } public Component(SpawnCondition spawn, JigsawPiece piece, Random rand, int x, int y, int z, int coordBaseMode) { super(0); this.coordBaseMode = coordBaseMode; this.piece = piece; this.minHeight = spawn.minHeight; this.maxHeight = spawn.maxHeight; switch(this.coordBaseMode) { case 1: case 3: this.boundingBox = new StructureBoundingBox(x, y, z, x + piece.structure.size.z - 1, y + piece.structure.size.y - 1, z + piece.structure.size.x - 1); break; default: this.boundingBox = new StructureBoundingBox(x, y, z, x + piece.structure.size.x - 1, y + piece.structure.size.y - 1, z + piece.structure.size.z - 1); break; } } public Component connectedFrom(JigsawConnection connection) { this.connectedFrom = connection; return this; } // Save to NBT @Override protected void func_143012_a(NBTTagCompound nbt) { nbt.setString("piece", piece.name); nbt.setInteger("min", minHeight); nbt.setInteger("max", maxHeight); nbt.setBoolean("hasHeight", heightUpdated); } // Load from NBT @Override protected void func_143011_b(NBTTagCompound nbt) { piece = jigsawMap.get(nbt.getString("piece")); minHeight = nbt.getInteger("min"); maxHeight = nbt.getInteger("max"); heightUpdated = nbt.getBoolean("hasHeight"); } @Override public boolean addComponentParts(World world, Random rand, StructureBoundingBox box) { if(piece == null) return false; // now we're in the world, update minY/maxY if(!piece.conformToTerrain && !heightUpdated) { int y = MathHelper.clamp_int(getAverageHeight(world, box) + piece.heightOffset, minHeight, maxHeight); if(!piece.alignToTerrain && parent != null) { parent.offsetYHeight(y); } else { offsetYHeight(y); } } return piece.structure.build(world, piece, boundingBox, box, coordBaseMode); } public void offsetYHeight(int y) { boundingBox.minY += y; boundingBox.maxY += y; heightUpdated = true; } // Overrides to fix Mojang's fucked rotations which FLIP instead of rotating in two instances // vaer being in the mines doing this the hard way for years was absolutely not for naught @Override protected int getXWithOffset(int x, int z) { return boundingBox.minX + piece.structure.rotateX(x, z, coordBaseMode); } @Override protected int getYWithOffset(int y) { return boundingBox.minY + y; } @Override protected int getZWithOffset(int x, int z) { return boundingBox.minZ + piece.structure.rotateZ(x, z, coordBaseMode); } private ForgeDirection rotateDir(ForgeDirection dir) { if(dir == ForgeDirection.UP || dir == ForgeDirection.DOWN) return dir; switch(coordBaseMode) { default: return dir; case 1: return dir.getRotation(ForgeDirection.UP); case 2: return dir.getOpposite(); case 3: return dir.getRotation(ForgeDirection.DOWN); } } private int getAverageHeight(World world, StructureBoundingBox box) { int total = 0; int iterations = 0; for(int z = box.minZ; z <= box.maxZ; z++) { for(int x = box.minX; x <= box.maxX; x++) { total += world.getTopSolidOrLiquidBlock(x, z); iterations++; } } if(iterations == 0) return 64; return total / iterations; } private int getNextCoordBase(JigsawConnection fromConnection, JigsawConnection toConnection, Random rand) { if(fromConnection.dir == ForgeDirection.DOWN || fromConnection.dir == ForgeDirection.UP) { if(fromConnection.isRollable) return rand.nextInt(4); return coordBaseMode; } return directionOffsetToCoordBase(fromConnection.dir.getOpposite(), toConnection.dir); } private int directionOffsetToCoordBase(ForgeDirection from, ForgeDirection to) { for(int i = 0; i < 4; i++) { if(from == to) return (i + coordBaseMode) % 4; from = from.getRotation(ForgeDirection.DOWN); } return coordBaseMode; } protected boolean hasIntersectionIgnoringSelf(LinkedList components, StructureBoundingBox box) { for(StructureComponent component : components) { if(component == this) continue; if(component.getBoundingBox() == null) continue; if(component.getBoundingBox().intersectsWith(box)) return true; } return false; } protected boolean isInsideIgnoringSelf(LinkedList components, int x, int y, int z) { for(StructureComponent component : components) { if(component == this) continue; if(component.getBoundingBox() == null) continue; if(component.getBoundingBox().isVecInside(x, y, z)) return true; } return false; } } public static class Start extends StructureStart { public Start() {} @SuppressWarnings("unchecked") public Start(World world, Random rand, SpawnCondition spawn, int chunkX, int chunkZ) { super(chunkX, chunkZ); int x = chunkX << 4; int z = chunkZ << 4; JigsawPiece startPiece = spawn.structure != null ? spawn.structure : spawn.pools.get(spawn.startPool).get(rand); Component startComponent = new Component(spawn, startPiece, rand, x, z); startComponent.parent = this; components.add(startComponent); List queuedComponents = new ArrayList<>(); if(spawn.structure == null) queuedComponents.add(startComponent); // 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 int matchPriority = queuedComponents.get(0).priority; int max = 1; while(max < queuedComponents.size()) { if(queuedComponents.get(max).priority != matchPriority) break; max++; } final int i = rand.nextInt(max); Component fromComponent = queuedComponents.remove(i); if(fromComponent.piece.structure.fromConnections == null) continue; int distance = getDistanceTo(fromComponent.getBoundingBox()); boolean fallbacksOnly = this.components.size() >= spawn.sizeLimit || distance >= spawn.rangeLimit; for(List unshuffledList : fromComponent.piece.structure.fromConnections) { List connectionList = new ArrayList<>(unshuffledList); Collections.shuffle(connectionList, rand); for(JigsawConnection fromConnection : connectionList) { if(fromComponent.connectedFrom == fromConnection) continue; // if we already connected to this piece, don't process if(fallbacksOnly) { String fallback = spawn.pools.get(fromConnection.poolName).fallback; if(fallback != null) { Component fallbackComponent = buildNextComponent(rand, spawn, spawn.pools.get(fallback), fromComponent, fromConnection); addComponent(fallbackComponent, fromConnection.placementPriority); } continue; } JigsawPool nextPool = spawn.getPool(fromConnection.poolName); Component nextComponent = null; // Iterate randomly through the pool, attempting each piece until one fits while(nextPool.totalWeight > 0) { nextComponent = buildNextComponent(rand, spawn, nextPool, fromComponent, fromConnection); if(nextComponent != null && !fromComponent.hasIntersectionIgnoringSelf(components, nextComponent.getBoundingBox())) break; nextComponent = null; } if(nextComponent != null) { addComponent(nextComponent, fromConnection.placementPriority); queuedComponents.add(nextComponent); } 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! if(nextPool.fallback != null) { BlockPos checkPos = getConnectionTargetPosition(fromComponent, fromConnection); if(!fromComponent.isInsideIgnoringSelf(components, checkPos.getX(), checkPos.getY(), checkPos.getZ())) { nextComponent = buildNextComponent(rand, spawn, spawn.pools.get(nextPool.fallback), fromComponent, fromConnection); if(nextComponent != null) addComponent(nextComponent, fromConnection.placementPriority); // don't add to queued list, we don't want to try continue from fallback } } } } } } if(GeneralConfig.enableDebugMode) { MainRegistry.logger.info("[Debug] Spawning NBT structure with " + components.size() + " piece(s) at: " + chunkX * 16 + ", " + chunkZ * 16); String componentList = "[Debug] Components: "; for(Object component : this.components) { componentList += ((Component) component).piece.structure.name + " "; } MainRegistry.logger.info(componentList); } updateBoundingBox(); } @SuppressWarnings("unchecked") private void addComponent(Component component, int placementPriority) { components.add(component); component.parent = this; component.priority = placementPriority; } private BlockPos getConnectionTargetPosition(Component component, JigsawConnection connection) { // The direction this component is extending towards in ABSOLUTE direction ForgeDirection extendDir = component.rotateDir(connection.dir); // Set the starting point for the next structure to the location of the connector block int x = component.getXWithOffset(connection.pos.x, connection.pos.z) + extendDir.offsetX; int y = component.getYWithOffset(connection.pos.y) + extendDir.offsetY; int z = component.getZWithOffset(connection.pos.x, connection.pos.z) + extendDir.offsetZ; return new BlockPos(x, y, z); } private Component buildNextComponent(Random rand, SpawnCondition spawn, JigsawPool pool, Component fromComponent, JigsawConnection fromConnection) { JigsawPiece nextPiece = pool.get(rand); if(nextPiece == null) return null; List connectionPool = getConnectionPool(nextPiece, fromConnection); if(connectionPool == null) return null; JigsawConnection toConnection = connectionPool.get(rand.nextInt(connectionPool.size())); // Rotate our incoming piece to plug it in int nextCoordBase = fromComponent.getNextCoordBase(fromConnection, toConnection, rand); BlockPos pos = getConnectionTargetPosition(fromComponent, fromConnection); // offset the starting point to the connecting point int ox = nextPiece.structure.rotateX(toConnection.pos.x, toConnection.pos.z, nextCoordBase); int oy = toConnection.pos.y; int oz = nextPiece.structure.rotateZ(toConnection.pos.x, toConnection.pos.z, nextCoordBase); return new Component(spawn, nextPiece, rand, pos.getX() - ox, pos.getY() - oy, pos.getZ() - oz, nextCoordBase).connectedFrom(toConnection); } private List getConnectionPool(JigsawPiece nextPiece, JigsawConnection fromConnection) { if(fromConnection.dir == ForgeDirection.DOWN) { return nextPiece.structure.toTopConnections.get(fromConnection.targetName); } else if(fromConnection.dir == ForgeDirection.UP) { return nextPiece.structure.toBottomConnections.get(fromConnection.targetName); } return nextPiece.structure.toHorizontalConnections.get(fromConnection.targetName); } private int getDistanceTo(StructureBoundingBox box) { int x = box.getCenterX(); int z = box.getCenterZ(); return Math.max(Math.abs(x - (func_143019_e() << 4)), Math.abs(z - (func_143018_f() << 4))); } // post loading, update parent reference for loaded components @Override public void func_143017_b(NBTTagCompound nbt) { for(Object o : components) { ((Component) o).parent = this; } } public void offsetYHeight(int y) { for(Object o : components) { Component component = (Component) o; if(component.heightUpdated || component.piece.conformToTerrain || component.piece.alignToTerrain) continue; component.offsetYHeight(y); } } } public static class GenStructure extends MapGenStructure { private SpawnCondition nextSpawn; public void generateStructures(World world, Random rand, IChunkProvider chunkProvider, int chunkX, int chunkZ) { Block[] ablock = new Block[65536]; func_151539_a(chunkProvider, world, chunkX, chunkZ, ablock); generateStructuresInChunk(world, rand, chunkX, chunkZ); } @Override public String func_143025_a() { return "NBTStructures"; } @Override protected boolean canSpawnStructureAtCoords(int chunkX, int chunkZ) { if(!weightedMap.containsKey(worldObj.provider.dimensionId)) return false; int x = chunkX; int z = chunkZ; if(x < 0) x -= StructureConfig.structureMaxChunks - 1; if(z < 0) z -= StructureConfig.structureMaxChunks - 1; x /= StructureConfig.structureMaxChunks; z /= StructureConfig.structureMaxChunks; rand.setSeed((long)x * 341873128712L + (long)z * 132897987541L + this.worldObj.getWorldInfo().getSeed() + (long)996996996 - worldObj.provider.dimensionId); x *= StructureConfig.structureMaxChunks; z *= StructureConfig.structureMaxChunks; x += rand.nextInt(StructureConfig.structureMaxChunks - StructureConfig.structureMinChunks); z += rand.nextInt(StructureConfig.structureMaxChunks - StructureConfig.structureMinChunks); if(chunkX == x && chunkZ == z) { BiomeGenBase biome = this.worldObj.getWorldChunkManager().getBiomeGenAt(chunkX * 16 + 8, chunkZ * 16 + 8); nextSpawn = findSpawn(biome); return nextSpawn != null && (nextSpawn.pools != null || nextSpawn.start != null || nextSpawn.structure != null); } return false; } @Override protected StructureStart getStructureStart(int chunkX, int chunkZ) { if(nextSpawn.start != null) return nextSpawn.start.apply(new Quartet(this.worldObj, this.rand, chunkX, chunkZ)); return new Start(this.worldObj, this.rand, nextSpawn, chunkX, chunkZ); } private SpawnCondition findSpawn(BiomeGenBase biome) { List spawnList = weightedMap.get(worldObj.provider.dimensionId); for(int i = 0; i < 64; i++) { SpawnCondition spawn = spawnList.get(rand.nextInt(spawnList.size())); if(spawn.isValid(biome)) return spawn; } return null; } } }