]> git.lizzy.rs Git - BoundingBoxOutlineReloaded.git/commitdiff
Initial commit
authorIrtimaled <irtimaled@gmail.com>
Sat, 31 Jan 2015 13:07:07 +0000 (13:07 +0000)
committerIrtimaled <irtimaled@gmail.com>
Sat, 31 Jan 2015 13:07:07 +0000 (13:07 +0000)
19 files changed:
.gitignore [new file with mode: 0644]
README.md [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBox.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxCache.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxDeserializer.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxMessage.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxMessageHandler.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxOutlineReloaded.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxSerializer.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxSlimeChunk.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxStructure.java [new file with mode: 0644]
java/com/irtimaled/bbor/BoundingBoxVillage.java [new file with mode: 0644]
java/com/irtimaled/bbor/ClientProxy.java [new file with mode: 0644]
java/com/irtimaled/bbor/CommonProxy.java [new file with mode: 0644]
java/com/irtimaled/bbor/ConfigManager.java [new file with mode: 0644]
java/com/irtimaled/bbor/DimensionProcessor.java [new file with mode: 0644]
java/com/irtimaled/bbor/ServerProxy.java [new file with mode: 0644]
resources/assets/lang/en_US.lang [new file with mode: 0644]
resources/mcmod.info [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..e43b0f9
--- /dev/null
@@ -0,0 +1 @@
+.DS_Store
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..8e7eeeb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,74 @@
+# BoundingBoxOutlineReloaded
+
+BoundingBoxOutlineReloaded is a mod for Minecraft 1.8 using Forge API.
+
+# Why did I make it?
+
+I loved 4poc's BBOutline mod but the only version I could get to work consistently was for Minecraft 1.6.4. This is fine if you want Nether Fortress bounding boxes but if you need witch huts the new block types can cause Minecraft 1.6.4 to crash horribly; and don't get me started on item frames crashing Minecraft 1.6.4!
+
+In addition to this not working with newer worlds, the way it bounds villages lacks the finesse of KaboPC's VillageMarker mod, and any new structures introduced in Minecraft like Ocean Monuments are missing entirely.
+
+# What it does
+
+This mod highlights in a variety of colours and styles the different structures & features of the game:-
+- Nether Fortresses; red boxes bound each individual area where Blaze, Wither Skeletons & normal Skeletons will spawn. Time for a beacon methinks!
+- Witch Huts; blue boxes reveal everywhere only witches spawn. Witch farm anyone?
+- Desert Temples; orange boxes envelop the pyramid and towers. Go grab some loot but beware TNT boobie traps!
+- Jungle Temples; green boxes surround the temple. Indianna Jones would've love these!
+- Ocean Monuments; cyan boxes indicate where guardians spawn. New sea lantern and prismarine block types FTW.
+- Strongholds; yellow boxes show each room in the stronghold. Does anyone make anything with silverfish spawners?
+- Mine Shafts; light gray boxes illustrate each of the mine shafts. Cobwebs... grrr!
+- Villages; multicoloured spheres encircle  the village, with boxes marking if and where iron golems will spawn. You should see the iron titan... CRAZY!
+- Slime chunks; bright green boxes highlight where slimes will spawn, with a dynamic box that rises to where the players feet are to help find them from the surface. Bouncy... bouncy...
+
+# How it works
+
+As chunks are loaded the game provides metadata about all the different structures & features in those chunks.  The mod interprets this meta data, caches the results, and renders the bounding boxes to the screen.  In an SMP environment this data is not present on the clients so the mod needs to run on the server where the processing happens and then the relevant metadata is sent to the clients for them to render.
+
+To toggle the rendering of the bounding boxes press B.
+
+# Installing
+
+Make sure you have Forge 1.8 installed then drop the jar file into the mods/1.8 folder.  Remember this will need to be installed on client and server in an SMP scenario.
+
+# Using
+
+Press B, sit back and enjoy the goodness flowing onto your screen.
+
+# Configuring
+
+The keyboard shortcut can be configured in the standard Controls screen.
+
+There are two ways to edit the config - in game or cfg file editing (although cfg editing is the only option on SMP servers.)
+
+The following options are available for configuration
+
+Option | Client/Server | Description | Cfg File Key | Cfg File Values | Default
+--- | --- | --- | --- | --- | ---
+Nether Fortresses | Both | Process/Render Nether Fortresses | drawDesertTemples | true/false | true
+Witch Huts | Both | Process/Render Witch Huts | drawWitchHuts | true/false | true
+Desert Temples | Both | Process/Render Desert Temples | drawDesertTemples | true/false | true
+Jungle Temples | Both | Process/Render Jungle Temples | drawJungleTemples | true/false | true
+Ocean Monuments | Both | Process/Render Ocean Monuments | drawOceanMonuments | true/false | true
+Strongholds | Both | Process/Render Strongholds | drawStrongholds | true/false | false
+Mine Shafts | Both | Process/Render Mine Shafts | drawMineShafts | true/false | false
+Villages | Both | Process/Render Villages | drawVillages | true/false | true
+Village spheres | Client | Render Villages as spheres instead of cuboids | renderVillageAsSphere | true/false | true
+Village Iron Golem Spawn Area | Client | Render Iron Golem Spawn Area within valid Villages | drawIronGolemSpawnArea | true/false | true
+Slime Chunks | Both | Process/Render Slime Chunks | drawSlimeChunks | true/false | true
+Slime Chunks Maximum Y | Client | Maximum Y value of the dynamic slime chunk boxes | slimeChunkMaxY | 0/40-255 (0 = no limit) | 0
+
+### In game
+
+*Note: In game editing is incomplete.*
+
+Forge provides a UI to edit configuration of mods - this mod provides the ability to control many aspects of the mods behaviour.  On the home screen of Minecraft select the Mods button, select this mod and click on the config button.
+
+### Cfg file editing
+
+Open the config/BBOutlineReloaded.cfg file with your text editor of choice and change the settings.  Simples!  Minecraft (including servers) will need to be restarted for the settings to take effect.
+
+# Links
+- Forge 1.8 - [Download](http://files.minecraftforge.net/minecraftforge/1.8)
+- 4poc's BBOutline mod - [Forum](http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/1286555-bounding-box-outline) | [Source](http://www.github.com/4poc/bboutline)
+- KaboPC's VillageMarker mod - [Forum](http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/1288327-village-marker-mod)
diff --git a/java/com/irtimaled/bbor/BoundingBox.java b/java/com/irtimaled/bbor/BoundingBox.java
new file mode 100644 (file)
index 0000000..72b24e7
--- /dev/null
@@ -0,0 +1,69 @@
+package com.irtimaled.bbor;
+
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.BlockPos;
+
+import java.awt.*;
+
+public abstract class BoundingBox {
+    private final Color color;
+    private final BlockPos minBlockPos;
+    private final BlockPos maxBlockPos;
+
+    protected BoundingBox(BlockPos minBlockPos, BlockPos maxBlockPos, Color color) {
+        this.minBlockPos = minBlockPos;
+        this.maxBlockPos = maxBlockPos;
+        this.color = color;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + minBlockPos.hashCode();
+        result = prime * result + maxBlockPos.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        BoundingBox other = (BoundingBox) obj;
+        if (!minBlockPos.equals(other.minBlockPos))
+            return false;
+        if (!maxBlockPos.equals(other.maxBlockPos))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "(" + minBlockPos.toString() + "; " + maxBlockPos.toString() + ")";
+    }
+
+    public AxisAlignedBB toAxisAlignedBB() {
+        return AxisAlignedBB.fromBounds(minBlockPos.getX(),
+                minBlockPos.getY(),
+                minBlockPos.getZ(),
+                maxBlockPos.getX(),
+                maxBlockPos.getY(),
+                maxBlockPos.getZ());
+    }
+
+    public BlockPos getMinBlockPos() {
+        return minBlockPos;
+    }
+
+    public BlockPos getMaxBlockPos() {
+        return maxBlockPos;
+    }
+
+    public Color getColor() {
+        return color;
+    }
+}
\ No newline at end of file
diff --git a/java/com/irtimaled/bbor/BoundingBoxCache.java b/java/com/irtimaled/bbor/BoundingBoxCache.java
new file mode 100644 (file)
index 0000000..0c60ca0
--- /dev/null
@@ -0,0 +1,17 @@
+package com.irtimaled.bbor;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BoundingBoxCache {
+
+    protected ConcurrentHashMap<BoundingBox, Set<BoundingBox>> cache = new ConcurrentHashMap<BoundingBox, Set<BoundingBox>>();
+
+    public Map<BoundingBox, Set<BoundingBox>> getBoundingBoxes() {
+        return cache;
+    }
+
+    public synchronized void refresh() {
+    }
+}
diff --git a/java/com/irtimaled/bbor/BoundingBoxDeserializer.java b/java/com/irtimaled/bbor/BoundingBoxDeserializer.java
new file mode 100644 (file)
index 0000000..37202ae
--- /dev/null
@@ -0,0 +1,51 @@
+package com.irtimaled.bbor;
+
+import io.netty.buffer.ByteBuf;
+import net.minecraft.util.BlockPos;
+import net.minecraftforge.fml.common.network.ByteBufUtils;
+
+import java.awt.*;
+
+public class BoundingBoxDeserializer {
+    public static BoundingBox deserialize(ByteBuf buf) {
+        char type = (char) ByteBufUtils.readVarShort(buf);
+        switch (type) {
+            case 'V':
+                return deserializeVillage(buf);
+            case 'S':
+                return deserializeStructure(buf);
+            case 'C':
+                return deserializeSlimeChunk(buf);
+        }
+        return null;
+    }
+
+    private static BoundingBox deserializeSlimeChunk(ByteBuf buf) {
+        BlockPos minBlockPos = deserializeBlockPos(buf);
+        BlockPos maxBlockPos = deserializeBlockPos(buf);
+        Color color = new Color(ByteBufUtils.readVarInt(buf, 5));
+        return BoundingBoxSlimeChunk.from(minBlockPos, maxBlockPos, color);
+    }
+
+    private static BoundingBox deserializeStructure(ByteBuf buf) {
+        BlockPos minBlockPos = deserializeBlockPos(buf);
+        BlockPos maxBlockPos = deserializeBlockPos(buf);
+        Color color = new Color(ByteBufUtils.readVarInt(buf, 5));
+        return BoundingBoxStructure.from(minBlockPos, maxBlockPos, color);
+    }
+
+    private static BoundingBox deserializeVillage(ByteBuf buf) {
+        BlockPos center = deserializeBlockPos(buf);
+        int radius = ByteBufUtils.readVarInt(buf, 5);
+        boolean spawnsIronGolems = ByteBufUtils.readVarShort(buf) == 1;
+        Color color = new Color(ByteBufUtils.readVarInt(buf, 5));
+        return BoundingBoxVillage.from(center, radius, spawnsIronGolems, color);
+    }
+
+    private static BlockPos deserializeBlockPos(ByteBuf buf) {
+        int x = ByteBufUtils.readVarInt(buf, 5);
+        int y = ByteBufUtils.readVarInt(buf, 5);
+        int z = ByteBufUtils.readVarInt(buf, 5);
+        return new BlockPos(x, y, z);
+    }
+}
\ No newline at end of file
diff --git a/java/com/irtimaled/bbor/BoundingBoxMessage.java b/java/com/irtimaled/bbor/BoundingBoxMessage.java
new file mode 100644 (file)
index 0000000..872d794
--- /dev/null
@@ -0,0 +1,58 @@
+package com.irtimaled.bbor;
+
+import io.netty.buffer.ByteBuf;
+import net.minecraftforge.fml.common.network.ByteBufUtils;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class BoundingBoxMessage implements IMessage {
+    private int dimension;
+    private BoundingBox key;
+    private Set<BoundingBox> boundingBoxes;
+
+    public static BoundingBoxMessage from(int dimension, BoundingBox key, Set<BoundingBox> boundingBoxes) {
+        BoundingBoxMessage message = new BoundingBoxMessage();
+        message.dimension = dimension;
+        message.key = key;
+        message.boundingBoxes = boundingBoxes;
+        return message;
+    }
+
+    @Override
+    public void fromBytes(ByteBuf buf) {
+        dimension = ByteBufUtils.readVarShort(buf);
+        key = BoundingBoxDeserializer.deserialize(buf);
+        boundingBoxes = new HashSet<BoundingBox>();
+        while (buf.isReadable()) {
+            BoundingBox boundingBox = BoundingBoxDeserializer.deserialize(buf);
+            boundingBoxes.add(boundingBox);
+        }
+        if (boundingBoxes.size() == 0)
+            boundingBoxes.add(key);
+    }
+
+    @Override
+    public void toBytes(ByteBuf buf) {
+        ByteBufUtils.writeVarShort(buf, dimension);
+        BoundingBoxSerializer.serialize(key, buf);
+        if (boundingBoxes.size() > 1) {
+            for (BoundingBox boundingBox : boundingBoxes) {
+                BoundingBoxSerializer.serialize(boundingBox, buf);
+            }
+        }
+    }
+
+    public int getDimension() {
+        return dimension;
+    }
+
+    public BoundingBox getKey() {
+        return key;
+    }
+
+    public Set<BoundingBox> getBoundingBoxes() {
+        return boundingBoxes;
+    }
+}
diff --git a/java/com/irtimaled/bbor/BoundingBoxMessageHandler.java b/java/com/irtimaled/bbor/BoundingBoxMessageHandler.java
new file mode 100644 (file)
index 0000000..4be22a6
--- /dev/null
@@ -0,0 +1,22 @@
+package com.irtimaled.bbor;
+
+import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
+import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
+
+import java.util.Map;
+
+public class BoundingBoxMessageHandler implements IMessageHandler<BoundingBoxMessage, IMessage> {
+    @Override
+    public IMessage onMessage(BoundingBoxMessage message, MessageContext ctx) {
+
+        Map<Integer, BoundingBoxCache> boundingBoxCacheMap = BoundingBoxOutlineReloaded.proxy.boundingBoxCacheMap;
+        int dimension = message.getDimension();
+        if (!boundingBoxCacheMap.containsKey(dimension)) {
+            boundingBoxCacheMap.put(dimension, new BoundingBoxCache());
+        }
+
+        boundingBoxCacheMap.get(dimension).getBoundingBoxes().put(message.getKey(), message.getBoundingBoxes());
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/java/com/irtimaled/bbor/BoundingBoxOutlineReloaded.java b/java/com/irtimaled/bbor/BoundingBoxOutlineReloaded.java
new file mode 100644 (file)
index 0000000..df56bd6
--- /dev/null
@@ -0,0 +1,45 @@
+package com.irtimaled.bbor;
+
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.fml.common.FMLCommonHandler;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.common.Mod.EventHandler;
+import net.minecraftforge.fml.common.SidedProxy;
+import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;
+
+
+@Mod(modid = BoundingBoxOutlineReloaded.MODID, name = BoundingBoxOutlineReloaded.NAME, version = BoundingBoxOutlineReloaded.VERSION)
+public class BoundingBoxOutlineReloaded {
+
+    public static final String MODID = "bbor";
+    public static final String NAME = "Bounding Box Outline Reloaded";
+    public static final String VERSION = "1.0.0-beta1";
+
+    private ConfigManager configManager;
+
+    public SimpleNetworkWrapper network;
+
+    @Mod.Instance()
+    public static BoundingBoxOutlineReloaded instance;
+
+    @SidedProxy(clientSide = "com.irtimaled.bbor.ClientProxy", serverSide = "com.irtimaled.bbor.ServerProxy")
+    public static CommonProxy proxy;
+
+
+    @EventHandler
+    public void preInit(FMLPreInitializationEvent evt) {
+        configManager = new ConfigManager(evt.getModConfigurationDirectory());
+    }
+
+    @EventHandler
+    public void load(FMLInitializationEvent evt) {
+        MinecraftForge.EVENT_BUS.register(proxy);
+        FMLCommonHandler.instance().bus().register(proxy);
+
+        proxy.configManager = configManager;
+        proxy.init();
+    }
+}
+
diff --git a/java/com/irtimaled/bbor/BoundingBoxSerializer.java b/java/com/irtimaled/bbor/BoundingBoxSerializer.java
new file mode 100644 (file)
index 0000000..d202cd3
--- /dev/null
@@ -0,0 +1,111 @@
+package com.irtimaled.bbor;
+
+import io.netty.buffer.ByteBuf;
+import net.minecraft.util.BlockPos;
+import net.minecraftforge.fml.common.network.ByteBufUtils;
+
+import java.awt.*;
+
+public class BoundingBoxSerializer {
+
+    public static void serialize(BoundingBox boundingBox, ByteBuf buf) {
+        if (boundingBox instanceof BoundingBoxSlimeChunk) {
+            serializeSlimeChunk((BoundingBoxSlimeChunk) boundingBox, buf);
+        } else if (boundingBox instanceof BoundingBoxVillage) {
+            serializeVillage((BoundingBoxVillage) boundingBox, buf);
+        } else if (boundingBox instanceof BoundingBoxStructure) {
+            serializeStructure((BoundingBoxStructure) boundingBox, buf);
+        }
+    }
+
+
+    private static void serializeVillage(BoundingBoxVillage boundingBox, ByteBuf buf) {
+        ByteBufUtils.writeVarShort(buf, 'V');
+        serializeBlockPos(boundingBox.getCenter(), buf);
+        ByteBufUtils.writeVarInt(buf, boundingBox.getRadius(), 5);
+        ByteBufUtils.writeVarShort(buf, boundingBox.getSpawnsIronGolems() ? 1 : 0);
+        serializeColor(boundingBox.getColor(), buf);
+    }
+
+    private static void serializeStructure(BoundingBoxStructure boundingBox, ByteBuf buf) {
+        ByteBufUtils.writeVarShort(buf, 'S');
+        serializeCuboid(boundingBox, buf);
+        serializeColor(boundingBox.getColor(), buf);
+    }
+
+    private static void serializeSlimeChunk(BoundingBoxSlimeChunk boundingBox, ByteBuf buf) {
+        ByteBufUtils.writeVarShort(buf, 'C');
+        serializeCuboid(boundingBox, buf);
+        serializeColor(boundingBox.getColor(), buf);
+    }
+
+    private static void serializeColor(Color color, ByteBuf buf) {
+        ByteBufUtils.writeVarInt(buf, color.getRGB(), 5);
+    }
+
+    private static void serializeCuboid(BoundingBox boundingBox, ByteBuf buf) {
+        serializeBlockPos(boundingBox.getMinBlockPos(), buf);
+        serializeBlockPos(boundingBox.getMaxBlockPos(), buf);
+    }
+
+    private static void serializeBlockPos(BlockPos blockPos, ByteBuf buf) {
+        ByteBufUtils.writeVarInt(buf, blockPos.getX(), 5);
+        ByteBufUtils.writeVarInt(buf, blockPos.getY(), 5);
+        ByteBufUtils.writeVarInt(buf, blockPos.getZ(), 5);
+    }
+    /*
+
+
+        public static void serialize(BoundingBox         boundingBox, StringBuilder sb) {
+            if (boundingBox instanceof BoundingBoxSlimeChunk) {
+                serializeSlimeChunk((BoundingBoxSlimeChunk) boundingBox, sb);
+            } else if (boundingBox instanceof BoundingBoxVillage) {
+                serializeVillage((BoundingBoxVillage) boundingBox, sb);
+            } else if (boundingBox instanceof BoundingBoxStructure) {
+                serializeStructure((BoundingBoxStructure) boundingBox, sb);
+            }
+        }
+
+        private static void serializeVillage(BoundingBoxVillage boundingBox, StringBuilder sb) {
+        sb.append("V/");
+        serializeBlockPos(boundingBox.getCenter(), sb);
+        sb.append('/');
+        sb.append(boundingBox.getRadius());
+        sb.append('/');
+        sb.append(boundingBox.getSpawnsIronGolems());
+        sb.append('/');
+        serializeColor(boundingBox.getColor(), sb);
+    }
+
+    private static void serializeStructure(BoundingBoxStructure boundingBox, StringBuilder sb) {
+        sb.append("S/");
+        serializeCuboid(boundingBox, sb);
+        sb.append('/');
+        serializeColor(boundingBox.getColor(), sb);
+    }
+
+    private static void serializeSlimeChunk(BoundingBoxSlimeChunk boundingBox, StringBuilder sb) {
+        sb.append("SC/");
+        serializeCuboid(boundingBox, sb);
+        sb.append('/');
+        serializeColor(boundingBox.getColor(), sb);
+    }
+
+    private static void serializeColor(Color color, StringBuilder sb) {
+        sb.append(color.getRGB());
+    }
+
+    private static void serializeCuboid(BoundingBox boundingBox, StringBuilder sb) {
+        serializeBlockPos(boundingBox.getMinBlockPos(), sb);
+        sb.append('/');
+        serializeBlockPos(boundingBox.getMaxBlockPos(), sb);
+    }
+
+    private static void serializeBlockPos(BlockPos blockPos, StringBuilder sb) {
+        sb.append(blockPos.getX());
+        sb.append(',');
+        sb.append(blockPos.getY());
+        sb.append(',');
+        sb.append(blockPos.getZ());
+    }*/
+}
diff --git a/java/com/irtimaled/bbor/BoundingBoxSlimeChunk.java b/java/com/irtimaled/bbor/BoundingBoxSlimeChunk.java
new file mode 100644 (file)
index 0000000..8ac1f80
--- /dev/null
@@ -0,0 +1,22 @@
+package com.irtimaled.bbor;
+
+import net.minecraft.util.BlockPos;
+import net.minecraft.world.ChunkCoordIntPair;
+
+import java.awt.*;
+
+public class BoundingBoxSlimeChunk extends BoundingBox {
+    private BoundingBoxSlimeChunk(BlockPos minBlockPos, BlockPos maxBlockPos, Color color) {
+        super(minBlockPos, maxBlockPos, color);
+    }
+
+    public static BoundingBoxSlimeChunk from(ChunkCoordIntPair chunkCoordIntPair, Color color) {
+        BlockPos minBlockPos = new BlockPos(chunkCoordIntPair.getXStart(), 1, chunkCoordIntPair.getZStart());
+        BlockPos maxBlockPos = new BlockPos(chunkCoordIntPair.getXEnd(), 38, chunkCoordIntPair.getZEnd());
+        return new BoundingBoxSlimeChunk(minBlockPos, maxBlockPos, color);
+    }
+
+    public static BoundingBoxSlimeChunk from(BlockPos minBlockPos, BlockPos maxBlockPos, Color color) {
+        return new BoundingBoxSlimeChunk(minBlockPos, maxBlockPos, color);
+    }
+}
\ No newline at end of file
diff --git a/java/com/irtimaled/bbor/BoundingBoxStructure.java b/java/com/irtimaled/bbor/BoundingBoxStructure.java
new file mode 100644 (file)
index 0000000..e93b418
--- /dev/null
@@ -0,0 +1,22 @@
+package com.irtimaled.bbor;
+
+import net.minecraft.util.BlockPos;
+import net.minecraft.world.gen.structure.StructureBoundingBox;
+
+import java.awt.*;
+
+public class BoundingBoxStructure extends BoundingBox {
+    private BoundingBoxStructure(BlockPos minBlockPos, BlockPos maxBlockPos, Color color) {
+        super(minBlockPos, maxBlockPos, color);
+    }
+
+    public static BoundingBoxStructure from(StructureBoundingBox bb, Color color) {
+        BlockPos minBlockPos = new BlockPos(bb.minX, bb.minY, bb.minZ);
+        BlockPos maxBlockPos = new BlockPos(bb.maxX, bb.maxY, bb.maxZ);
+        return new BoundingBoxStructure(minBlockPos, maxBlockPos, color);
+    }
+
+    public static BoundingBoxStructure from(BlockPos minBlockPos, BlockPos maxBlockPos, Color color) {
+        return new BoundingBoxStructure(minBlockPos, maxBlockPos, color);
+    }
+}
\ No newline at end of file
diff --git a/java/com/irtimaled/bbor/BoundingBoxVillage.java b/java/com/irtimaled/bbor/BoundingBoxVillage.java
new file mode 100644 (file)
index 0000000..4af262a
--- /dev/null
@@ -0,0 +1,46 @@
+package com.irtimaled.bbor;
+
+import net.minecraft.util.BlockPos;
+
+import java.awt.*;
+
+public class BoundingBoxVillage extends BoundingBox {
+    private final BlockPos center;
+    private final Integer radius;
+    private final boolean spawnsIronGolems;
+
+    protected BoundingBoxVillage(BlockPos center, Integer radius, Color color, boolean spawnsIronGolems, BlockPos minBlockPos, BlockPos maxBlockPos) {
+        super(minBlockPos, maxBlockPos, color);
+        this.center = center;
+        this.radius = radius;
+        this.spawnsIronGolems = spawnsIronGolems;
+    }
+
+
+    public static BoundingBox from(BlockPos center, Integer radius, boolean spawnsIronGolems, Color color) {
+        BlockPos minBlockPos = new BlockPos(center.getX() - radius,
+                center.getY() - 4,
+                center.getZ() - radius);
+        BlockPos maxBlockPos = new BlockPos(center.getX() + radius,
+                center.getY() + 4,
+                center.getZ() + radius);
+        return new BoundingBoxVillage(center, radius, color, spawnsIronGolems, minBlockPos, maxBlockPos);
+    }
+
+    @Override
+    public String toString() {
+        return "(" + center.toString() + "; " + radius.toString() + ")";
+    }
+
+    public Integer getRadius() {
+        return radius;
+    }
+
+    public BlockPos getCenter() {
+        return center;
+    }
+
+    public boolean getSpawnsIronGolems() {
+        return spawnsIronGolems;
+    }
+}
\ No newline at end of file
diff --git a/java/com/irtimaled/bbor/ClientProxy.java b/java/com/irtimaled/bbor/ClientProxy.java
new file mode 100644 (file)
index 0000000..bcc6a61
--- /dev/null
@@ -0,0 +1,283 @@
+package com.irtimaled.bbor;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.settings.KeyBinding;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.BlockPos;
+import net.minecraft.world.World;
+import net.minecraftforge.client.event.RenderWorldLastEvent;
+import net.minecraftforge.fml.client.registry.ClientRegistry;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.InputEvent;
+import net.minecraftforge.fml.relauncher.ReflectionHelper;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.util.glu.GLU;
+import org.lwjgl.util.glu.Sphere;
+
+import java.awt.*;
+import java.util.Map;
+import java.util.Set;
+
+public class ClientProxy extends CommonProxy {
+
+    @SubscribeEvent
+    public void onKeyInputEvent(InputEvent.KeyInputEvent evt) {
+        if (hotKey.isPressed()) {
+            active = !active;
+        }
+    }
+
+    private boolean active;
+    private KeyBinding hotKey;
+
+    @Override
+    public void init() {
+        super.init();
+        hotKey = new KeyBinding("key.bbor.hotKey", Keyboard.KEY_B, "key.categories.bbor");
+        ClientRegistry.registerKeyBinding(hotKey);
+    }
+
+
+    private double playerX;
+    private double playerY;
+    private double playerZ;
+
+
+    @SubscribeEvent
+    public void renderWorldLastEvent(RenderWorldLastEvent event) {
+        // determine current player location
+        EntityPlayer entityPlayer = Minecraft.getMinecraft().thePlayer;
+        playerX = entityPlayer.lastTickPosX + (entityPlayer.posX - entityPlayer.lastTickPosX) * (double) event.partialTicks;
+        playerY = entityPlayer.lastTickPosY + (entityPlayer.posY - entityPlayer.lastTickPosY) * (double) event.partialTicks;
+        playerZ = entityPlayer.lastTickPosZ + (entityPlayer.posZ - entityPlayer.lastTickPosZ) * (double) event.partialTicks;
+
+
+        if (this.active) {
+            int activeDimensionId = entityPlayer.worldObj.provider.getDimensionId();
+            if (boundingBoxCacheMap.containsKey(activeDimensionId)) {
+                renderBBoxMap(boundingBoxCacheMap.get(activeDimensionId).getBoundingBoxes());
+            }
+        }
+    }
+
+    private void renderBBoxMap(Map<BoundingBox, Set<BoundingBox>> map) {
+
+
+        GL11.glEnable(GL11.GL_BLEND);
+        GL11.glLineWidth(3.0f);
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+        GL11.glDisable(GL11.GL_CULL_FACE);
+
+        if (configManager.alwaysVisible.getBoolean()) {
+            GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
+        }
+
+        for (BoundingBox bb : map.keySet()) {
+            renderBoundingBoxes(map.get(bb));
+        }
+
+        GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
+        GL11.glEnable(GL11.GL_CULL_FACE);
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+        GL11.glDisable(GL11.GL_BLEND);
+
+
+        if (configManager.showDebugInfo.getBoolean()) {
+            Minecraft mc = Minecraft.getMinecraft();
+            ScaledResolution var5 = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
+            int screenWidth = var5.getScaledWidth();
+            mc.entityRenderer.setupOverlayRendering();
+            int count = 0;
+            for (BoundingBox bb : map.keySet()) {
+                count += map.get(bb).size();
+            }
+            String debug = String.format("%d/%d", map.keySet().size(), count);
+            int width = screenWidth - mc.fontRendererObj.getStringWidth(debug);
+
+            mc.fontRendererObj.drawStringWithShadow(debug, width - 2, 2, 16777215);
+        }
+    }
+
+    private void renderBoundingBoxes(Set<BoundingBox> bbList) {
+        World world = Minecraft.getMinecraft().theWorld;
+        Set activeChunks = ReflectionHelper.getPrivateValue(World.class, world, 33);
+        for (BoundingBox bb : bbList) {
+
+            if (activeChunks.contains(world.getChunkFromBlockCoords(bb.getMinBlockPos()).getChunkCoordIntPair()) ||
+                    activeChunks.contains(world.getChunkFromBlockCoords(bb.getMaxBlockPos()).getChunkCoordIntPair())) {
+
+                if (bb instanceof BoundingBoxVillage) {
+                    BoundingBoxVillage villageBB = (BoundingBoxVillage) bb;
+                    if (configManager.renderVillageAsSphere.getBoolean()) {
+                        renderBoundingBoxVillageAsSphere(villageBB);
+                    } else {
+                        renderBoundingBox(villageBB);
+                    }
+                    if (configManager.drawIronGolemSpawnArea.getBoolean() &&
+                            villageBB.getSpawnsIronGolems()) {
+                        renderIronGolemSpawnArea(villageBB);
+                    }
+                } else if (bb instanceof BoundingBoxSlimeChunk) {
+                    renderSlimeChunk((BoundingBoxSlimeChunk) bb);
+                } else {
+                    renderBoundingBox(bb);
+                }
+            }
+        }
+    }
+
+    private void renderSlimeChunk(BoundingBoxSlimeChunk bb) {
+        AxisAlignedBB aaBB = bb.toAxisAlignedBB();
+        Color color = bb.getColor();
+        renderCuboid(aaBB, color);
+
+        double maxY = configManager.slimeChunkMaxY.getInt();
+        if ((maxY == 0) || (playerY < maxY)) {
+            maxY = playerY;
+        }
+
+        if (maxY > 39) {
+            aaBB = new AxisAlignedBB(aaBB.minX, 39, aaBB.minZ, aaBB.maxX, maxY, aaBB.maxZ);
+            renderCuboid(aaBB, color, configManager.fill.getBoolean());
+        }
+    }
+
+    private void renderIronGolemSpawnArea(BoundingBoxVillage villageBB) {
+        BlockPos center = villageBB.getCenter();
+        AxisAlignedBB abb = new AxisAlignedBB(new BlockPos(center.getX() - 8,
+                center.getY() - 3,
+                center.getZ() - 8),
+                new BlockPos(center.getX() + 8,
+                        center.getY() + 4,
+                        center.getZ() + 8));
+        GL11.glLineWidth(2.0f);
+        renderCuboid(abb, villageBB.getColor(), false);
+        GL11.glLineWidth(3.0f);
+    }
+
+    private void renderCuboid(AxisAlignedBB aaBB, Color color) {
+        renderCuboid(aaBB.addCoord(0, 1, 0), color, configManager.fill.getBoolean());
+    }
+
+    private void renderBoundingBox(BoundingBox bb) {
+        AxisAlignedBB aaBB = bb.toAxisAlignedBB();
+        Color color = bb.getColor();
+        renderCuboid(aaBB, color);
+    }
+
+    private void renderBoundingBoxVillageAsSphere(BoundingBoxVillage bb) {
+        BlockPos center = bb.getCenter();
+        int radius = bb.getRadius();
+        Color color = bb.getColor();
+        renderSphere(center, radius, color);
+    }
+
+    private void renderCuboid(AxisAlignedBB aaBB, Color color, boolean fill) {
+        aaBB = offsetAxisAlignedBB(aaBB);
+        if (fill) {
+            GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
+            renderCuboid(aaBB, 30, color);
+            GL11.glEnable(GL11.GL_POLYGON_OFFSET_LINE);
+            GL11.glPolygonOffset(-1.f, -1.f);
+        }
+        GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
+        renderCuboid(aaBB, 255, color);
+    }
+
+    private void renderSphere(BlockPos center, int radius, Color color) {
+        int colorR = color.getRed();
+        int colorG = color.getGreen();
+        int colorB = color.getBlue();
+
+        double x = center.getX() - playerX;
+        double y = center.getY() - playerY;
+        double z = center.getZ() - playerZ;
+        GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_CONSTANT_COLOR);
+        GL11.glPointSize(2.5f);
+        GL11.glTranslated(x, y, z);
+        Sphere sphere = new Sphere();
+        sphere.setDrawStyle(GLU.GLU_POINT);
+        sphere.setNormals(GLU.GLU_FLAT);
+        sphere.setOrientation(GLU.GLU_OUTSIDE);
+        GL11.glColor3f(colorR / 255.0f, colorG / 255.0f, colorB / 255.0f);
+        GL11.glRotatef(90f, 0, 0, 0);
+        sphere.draw(radius, 48, 48);
+        GL11.glRotatef(-90f, 0, 0, 0);
+        GL11.glTranslated(-x, -y, -z);
+        GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
+    }
+
+    private AxisAlignedBB offsetAxisAlignedBB(AxisAlignedBB axisAlignedBB) {
+        double expandBy = 0.005F;
+        return axisAlignedBB
+                .addCoord(1, 0, 1)
+                .expand(expandBy, expandBy, expandBy)
+                .offset(-playerX, -playerY, -playerZ);
+    }
+
+    private void renderCuboid(AxisAlignedBB bb, int alphaChannel, Color color) {
+        Tessellator tessellator = Tessellator.getInstance();
+        WorldRenderer worldRenderer = tessellator.getWorldRenderer();
+
+        int colorR = color.getRed();
+        int colorG = color.getGreen();
+        int colorB = color.getBlue();
+
+        worldRenderer.startDrawing(GL11.GL_QUADS);
+        worldRenderer.setColorRGBA(colorR, colorG, colorB, alphaChannel);
+        worldRenderer.addVertex(bb.minX, bb.minY, bb.minZ);
+        worldRenderer.addVertex(bb.maxX, bb.minY, bb.minZ);
+        worldRenderer.addVertex(bb.maxX, bb.minY, bb.maxZ);
+        worldRenderer.addVertex(bb.minX, bb.minY, bb.maxZ);
+        tessellator.draw();
+
+        if (bb.minY == bb.maxY) {
+            return;
+        }
+
+        worldRenderer.startDrawing(GL11.GL_QUADS);
+        worldRenderer.setColorRGBA(colorR, colorG, colorB, alphaChannel);
+        worldRenderer.addVertex(bb.minX, bb.maxY, bb.minZ);
+        worldRenderer.addVertex(bb.maxX, bb.maxY, bb.minZ);
+        worldRenderer.addVertex(bb.maxX, bb.maxY, bb.maxZ);
+        worldRenderer.addVertex(bb.minX, bb.maxY, bb.maxZ);
+        tessellator.draw();
+
+        worldRenderer.startDrawing(GL11.GL_QUADS);
+        worldRenderer.setColorRGBA(colorR, colorG, colorB, alphaChannel);
+        worldRenderer.addVertex(bb.minX, bb.minY, bb.maxZ);
+        worldRenderer.addVertex(bb.minX, bb.maxY, bb.maxZ);
+        worldRenderer.addVertex(bb.maxX, bb.maxY, bb.maxZ);
+        worldRenderer.addVertex(bb.maxX, bb.minY, bb.maxZ);
+        tessellator.draw();
+
+        worldRenderer.startDrawing(GL11.GL_QUADS);
+        worldRenderer.setColorRGBA(colorR, colorG, colorB, alphaChannel);
+        worldRenderer.addVertex(bb.minX, bb.minY, bb.minZ);
+        worldRenderer.addVertex(bb.minX, bb.maxY, bb.minZ);
+        worldRenderer.addVertex(bb.maxX, bb.maxY, bb.minZ);
+        worldRenderer.addVertex(bb.maxX, bb.minY, bb.minZ);
+        tessellator.draw();
+
+        worldRenderer.startDrawing(GL11.GL_QUADS);
+        worldRenderer.setColorRGBA(colorR, colorG, colorB, alphaChannel);
+        worldRenderer.addVertex(bb.minX, bb.minY, bb.minZ);
+        worldRenderer.addVertex(bb.minX, bb.minY, bb.maxZ);
+        worldRenderer.addVertex(bb.minX, bb.maxY, bb.maxZ);
+        worldRenderer.addVertex(bb.minX, bb.maxY, bb.minZ);
+        tessellator.draw();
+
+        worldRenderer.startDrawing(GL11.GL_QUADS);
+        worldRenderer.setColorRGBA(colorR, colorG, colorB, alphaChannel);
+        worldRenderer.addVertex(bb.maxX, bb.minY, bb.minZ);
+        worldRenderer.addVertex(bb.maxX, bb.minY, bb.maxZ);
+        worldRenderer.addVertex(bb.maxX, bb.maxY, bb.maxZ);
+        worldRenderer.addVertex(bb.maxX, bb.maxY, bb.minZ);
+        tessellator.draw();
+    }
+}
diff --git a/java/com/irtimaled/bbor/CommonProxy.java b/java/com/irtimaled/bbor/CommonProxy.java
new file mode 100644 (file)
index 0000000..3a09c38
--- /dev/null
@@ -0,0 +1,47 @@
+package com.irtimaled.bbor;
+
+import net.minecraft.world.chunk.IChunkProvider;
+import net.minecraft.world.gen.ChunkProviderServer;
+import net.minecraftforge.event.world.ChunkEvent;
+import net.minecraftforge.event.world.WorldEvent;
+import net.minecraftforge.fml.common.FMLLog;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.network.NetworkRegistry;
+import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;
+import net.minecraftforge.fml.relauncher.Side;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class CommonProxy {
+
+    public Map<Integer, BoundingBoxCache> boundingBoxCacheMap = new ConcurrentHashMap<Integer, BoundingBoxCache>();
+
+    public ConfigManager configManager;
+    protected SimpleNetworkWrapper network;
+
+    public void init() {
+        network = NetworkRegistry.INSTANCE.newSimpleChannel("bbor");
+        network.registerMessage(BoundingBoxMessageHandler.class, BoundingBoxMessage.class, 0, Side.CLIENT);
+    }
+
+    @SubscribeEvent
+    public void worldEvent(WorldEvent.Load event) {
+        IChunkProvider chunkProvider = event.world.getChunkProvider();
+        if (chunkProvider instanceof ChunkProviderServer) {
+            chunkProvider = ((ChunkProviderServer) chunkProvider).serverChunkGenerator;
+            long seed = event.world.getSeed();
+            int dimensionId = event.world.provider.getDimensionId();
+            FMLLog.info("create world dimension: %d, %s (chunkprovider: %s) (seed: %d)", dimensionId, event.world.getClass().toString(), chunkProvider.getClass().toString(), seed);
+            boundingBoxCacheMap.put(dimensionId, new DimensionProcessor(configManager, event.world, seed, dimensionId, chunkProvider));
+        }
+    }
+
+    @SubscribeEvent
+    public void chunkEvent(ChunkEvent.Load event) {
+        int dimensionId = event.world.provider.getDimensionId();
+        if (boundingBoxCacheMap.containsKey(dimensionId)) {
+            boundingBoxCacheMap.get(dimensionId).refresh();
+        }
+    }
+}
diff --git a/java/com/irtimaled/bbor/ConfigManager.java b/java/com/irtimaled/bbor/ConfigManager.java
new file mode 100644 (file)
index 0000000..7005c75
--- /dev/null
@@ -0,0 +1,64 @@
+package com.irtimaled.bbor;
+
+import net.minecraftforge.common.config.Configuration;
+import net.minecraftforge.common.config.Property;
+
+import java.io.File;
+
+public class ConfigManager {
+    public Configuration config;
+
+    public Property showDebugInfo;
+    public Property fill;
+    public Property drawVillages;
+    public Property drawDesertTemples;
+    public Property drawJungleTemples;
+    public Property drawWitchHuts;
+    public Property drawStrongholds;
+    public Property drawMineShafts;
+    public Property drawNetherFortresses;
+    public Property drawOceanMonuments;
+    public Property alwaysVisible;
+    public Property renderVillageAsSphere;
+    public Property drawIronGolemSpawnArea;
+    public Property drawSlimeChunks;
+    public Property slimeChunkMaxY;
+
+    private Property SetupBooleanProperty(Configuration config, String category, String configName, Boolean defaultValue, String comment) {
+        Property property = config.get(category, configName, defaultValue);
+        property.comment = comment;
+        property.set(property.getBoolean(defaultValue));
+        return property;
+    }
+
+    private Property SetupIntegerProperty(Configuration config, String category, String configName, int defaultValue, String comment) {
+        Property property = config.get(category, configName, defaultValue);
+        property.comment = comment;
+        property.set(property.getInt(defaultValue));
+        return property;
+    }
+
+    public ConfigManager(File configDir) {
+        config = new Configuration(new File(configDir, "BBOutlineReloaded.cfg"));
+        config.load();
+
+        showDebugInfo = SetupBooleanProperty(config, "general", "showDebugInfo", false, "If set to true debug information will be displayed. (default: false)");
+        fill = SetupBooleanProperty(config, "general", "fill", false, "If set to true the bounding boxes are filled. (default: false)");
+        alwaysVisible = SetupBooleanProperty(config, "general", "alwaysVisible", false, "If set to true boxes will be visible even through other blocks. (default: false)");
+
+        drawVillages = SetupBooleanProperty(config, "features", "drawVillages", true, "If set to true village bounding boxes are drawn. (default: true)");
+        renderVillageAsSphere = SetupBooleanProperty(config, "features", "renderVillageAsSphere", true, "If set to true villages will be drawn as a sphere. (default:true)");
+        drawIronGolemSpawnArea = SetupBooleanProperty(config, "features", "drawIronGolemSpawnArea", true, "If set to true the iron golem spawn area of the village will be drawn. (default:true)");
+        drawDesertTemples = SetupBooleanProperty(config, "features", "drawDesertTemples", true, "If set to true desert temple bounding boxes are drawn. (default: true)");
+        drawJungleTemples = SetupBooleanProperty(config, "features", "drawJungleTemples", true, "If set to true jungle temple bounding boxes are drawn. (default: true)");
+        drawWitchHuts = SetupBooleanProperty(config, "features", "drawWitchHuts", true, "If set to true witch hut bounding boxes are drawn. (default: true)");
+        drawStrongholds = SetupBooleanProperty(config, "features", "drawStrongholds", false, "If set to true stronghold bounding boxes are drawn. (default: false)");
+        drawMineShafts = SetupBooleanProperty(config, "features", "drawMineShafts", false, "If set to true mineshaft bounding boxes are drawn. (default: false)");
+        drawNetherFortresses = SetupBooleanProperty(config, "features", "drawNetherFortresses", true, "If set to true nether fortress bounding boxes are drawn. (default: true)");
+        drawOceanMonuments = SetupBooleanProperty(config, "features", "drawOceanMonuments", true, "If set to true ocean monument bounding boxes are drawn. (default: true)");
+        drawSlimeChunks = SetupBooleanProperty(config, "features", "drawSlimeChunks", true, "If set to true slime chunks bounding boxes are drawn. (default: true)");
+        slimeChunkMaxY = SetupIntegerProperty(config, "features", "slimeChunkMaxY", 0, "The maximum top of the slime chunk bounding box. If set to 0 it will always track the player's feet. (default: 0)");
+
+        config.save();
+    }
+}
\ No newline at end of file
diff --git a/java/com/irtimaled/bbor/DimensionProcessor.java b/java/com/irtimaled/bbor/DimensionProcessor.java
new file mode 100644 (file)
index 0000000..9010054
--- /dev/null
@@ -0,0 +1,224 @@
+package com.irtimaled.bbor;
+
+import net.minecraft.util.BlockPos;
+import net.minecraft.village.Village;
+import net.minecraft.world.ChunkCoordIntPair;
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.IChunkProvider;
+import net.minecraft.world.gen.ChunkProviderGenerate;
+import net.minecraft.world.gen.ChunkProviderHell;
+import net.minecraft.world.gen.structure.ComponentScatteredFeaturePieces;
+import net.minecraft.world.gen.structure.MapGenStructure;
+import net.minecraft.world.gen.structure.StructureComponent;
+import net.minecraft.world.gen.structure.StructureStart;
+import net.minecraftforge.fml.common.FMLLog;
+import net.minecraftforge.fml.relauncher.ReflectionHelper;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+public class DimensionProcessor extends BoundingBoxCache {
+
+    private ConfigManager configManager;
+    private World world;
+    private long seed;
+
+    public DimensionProcessor(ConfigManager configManager, World world, long seed, int dimensionId, IChunkProvider chunkProvider) {
+        this.configManager = configManager;
+        this.world = world;
+        this.seed = seed;
+        this.dimensionId = dimensionId;
+        this.chunkProvider = chunkProvider;
+        villageCache = new HashSet<BoundingBox>();
+        slimeChunkCache = new HashSet<BoundingBox>();
+    }
+
+    private int dimensionId;
+    private IChunkProvider chunkProvider;
+    private Set<BoundingBox> villageCache;
+    private Set<BoundingBox> slimeChunkCache;
+
+    private static <T extends IChunkProvider> Collection<StructureStart> getStructures(T chunkProvider, int method) {
+        Class<T> cpClass = (Class<T>) chunkProvider.getClass();
+        Object structureGenerator = ReflectionHelper.getPrivateValue(cpClass, chunkProvider, method);
+        if (structureGenerator instanceof MapGenStructure) {
+            Map<ChunkCoordIntPair, StructureStart> structureMap = ReflectionHelper.getPrivateValue(MapGenStructure.class, (MapGenStructure) structureGenerator, 1);
+            return structureMap.values();
+        }
+        return Collections.emptyList();
+    }
+
+    private static final int JUNGLE_TEMPLE = 1;
+    private static final int DESERT_TEMPLE = 2;
+    private static final int WITCH_HUT = 3;
+    private static final int OCEAN_MONUMENT = 4;
+    private static final int STRONGHOLD = 5;
+    private static final int MINE_SHAFT = 6;
+    private static final int NETHER_FORTRESS = 7;
+
+    private Map<Integer, Collection<StructureStart>> getStructures() {
+
+        Map<Integer, Collection<StructureStart>> structureMap = new HashMap<Integer, Collection<StructureStart>>();
+        if (chunkProvider instanceof ChunkProviderGenerate) {
+
+            if (configManager.drawDesertTemples.getBoolean()) {
+                structureMap.put(DimensionProcessor.DESERT_TEMPLE, getStructuresWithComponent(getStructures(chunkProvider, 20), ComponentScatteredFeaturePieces.DesertPyramid.class));
+            }
+
+            if (configManager.drawJungleTemples.getBoolean()) {
+                structureMap.put(DimensionProcessor.JUNGLE_TEMPLE, getStructuresWithComponent(getStructures(chunkProvider, 20), ComponentScatteredFeaturePieces.JunglePyramid.class));
+            }
+
+            if (configManager.drawWitchHuts.getBoolean()) {
+                structureMap.put(DimensionProcessor.WITCH_HUT, getStructuresWithComponent(getStructures(chunkProvider, 20), ComponentScatteredFeaturePieces.SwampHut.class));
+            }
+
+            if (configManager.drawOceanMonuments.getBoolean()) {
+                structureMap.put(DimensionProcessor.OCEAN_MONUMENT, getStructures(chunkProvider, 22));
+            }
+
+            if (configManager.drawStrongholds.getBoolean()) {
+                structureMap.put(DimensionProcessor.STRONGHOLD, getStructures(chunkProvider, 17));
+            }
+
+            if (configManager.drawMineShafts.getBoolean()) {
+                structureMap.put(DimensionProcessor.MINE_SHAFT, getStructures(chunkProvider, 19));
+            }
+        } else if (chunkProvider instanceof ChunkProviderHell) {
+
+            if (configManager.drawNetherFortresses.getBoolean()) {
+                structureMap.put(DimensionProcessor.NETHER_FORTRESS, getStructures(chunkProvider, 22));
+            }
+        }
+
+        return structureMap;
+    }
+
+    private Collection<StructureStart> getStructuresWithComponent(Collection<StructureStart> structures, Class structureComponent) {
+        Collection<StructureStart> validStructures = new HashSet<StructureStart>();
+        for (StructureStart structure : structures) {
+            if (structure.getComponents().getFirst().getClass().equals(structureComponent)) {
+                validStructures.add(structure);
+            }
+        }
+        return validStructures;
+    }
+
+    @Override
+    public synchronized void refresh() {
+        Map<Integer, Collection<StructureStart>> structureMap = getStructures();
+        for (Integer structureType : structureMap.keySet()) {
+            Color color = getStructureColor(structureType);
+            for (StructureStart structureStart : structureMap.get(structureType)) {
+                BoundingBox boundingBox = BoundingBoxStructure.from(structureStart.getBoundingBox(), color);
+                if (!cache.containsKey(boundingBox)) {
+                    Set<BoundingBox> structureBoundingBoxes = new HashSet<BoundingBox>();
+                    Iterator structureComponents = structureStart.getComponents().iterator();
+                    while (structureComponents.hasNext()) {
+                        StructureComponent structureComponent = (StructureComponent) structureComponents.next();
+                        structureBoundingBoxes.add(BoundingBoxStructure.from(structureComponent.getBoundingBox(), color));
+                    }
+                    cache.put(boundingBox, structureBoundingBoxes);
+                    FMLLog.info("[%d] new boundingBoxCacheMap entries: %d", dimensionId, structureBoundingBoxes.size());
+                }
+            }
+        }
+
+        if (configManager.drawSlimeChunks.getBoolean() &&
+                dimensionId == 0) {
+            Set<BoundingBox> slimeChunkBoundingBoxes = new HashSet<BoundingBox>();
+            Set<ChunkCoordIntPair> activeChunks = ReflectionHelper.getPrivateValue(World.class, world, 33);
+            for (ChunkCoordIntPair chunk : activeChunks) {
+                if (isSlimeChunk(chunk.chunkXPos, chunk.chunkZPos)) {
+                    slimeChunkBoundingBoxes.add(BoundingBoxSlimeChunk.from(chunk, Color.GREEN));
+                }
+            }
+
+            processDelta(slimeChunkCache, slimeChunkBoundingBoxes);
+
+            slimeChunkCache = slimeChunkBoundingBoxes;
+        }
+
+        if (configManager.drawVillages.getBoolean() &&
+                (world.villageCollectionObj != null)) {
+
+            Set<BoundingBox> villageBoundingBoxes = new HashSet<BoundingBox>();
+            List<Village> villages = world.villageCollectionObj.getVillageList();
+            int c = 0;
+            for (Village village : villages) {
+                BlockPos center = ReflectionHelper.getPrivateValue(Village.class, village, 3);
+                Integer radius = ReflectionHelper.getPrivateValue(Village.class, village, 4);
+                boolean spawnsIronGolems = village.getNumVillagers() >= 10 &&
+                        village.getNumVillageDoors() >= 21;
+                Color color = getVillageColor(c % 6);
+                villageBoundingBoxes.add(BoundingBoxVillage.from(center, radius, spawnsIronGolems, color));
+                ++c;
+            }
+            processDelta(villageCache, villageBoundingBoxes);
+
+            villageCache = villageBoundingBoxes;
+        }
+    }
+
+    private void processDelta(Set<BoundingBox> oldBoundingBoxes, Set<BoundingBox> newBoundingBoxes) {
+        for (BoundingBox bb : oldBoundingBoxes) {
+            if (!newBoundingBoxes.contains(bb)) {
+                cache.remove(bb);
+            } else {
+                if (!cache.containsKey(bb)) {
+                    Set<BoundingBox> boundingBoxes = new HashSet<BoundingBox>();
+                    boundingBoxes.add(bb);
+                    cache.put(bb, boundingBoxes);
+                }
+            }
+        }
+    }
+
+    private boolean isSlimeChunk(int chunkX, int chunkZ) {
+        Random r = new Random(seed +
+                (long) (chunkX * chunkX * 4987142) +
+                (long) (chunkX * 5947611) +
+                (long) (chunkZ * chunkZ) * 4392871L +
+                (long) (chunkZ * 389711) ^ 987234911L);
+        return r.nextInt(10) == 0;
+    }
+
+    private Color getVillageColor(int c) {
+        switch (c) {
+            case 0:
+                return Color.RED;
+            case 1:
+                return Color.MAGENTA;
+            case 2:
+                return Color.BLUE;
+            case 3:
+                return Color.CYAN;
+            case 4:
+                return Color.GREEN;
+            case 5:
+                return Color.YELLOW;
+        }
+        return Color.WHITE;
+    }
+
+    private Color getStructureColor(Integer structureType) {
+        switch (structureType) {
+            case DimensionProcessor.DESERT_TEMPLE:
+                return Color.ORANGE;
+            case DimensionProcessor.JUNGLE_TEMPLE:
+                return Color.GREEN;
+            case DimensionProcessor.WITCH_HUT:
+                return Color.BLUE;
+            case DimensionProcessor.MINE_SHAFT:
+                return Color.LIGHT_GRAY;
+            case DimensionProcessor.NETHER_FORTRESS:
+                return Color.RED;
+            case DimensionProcessor.OCEAN_MONUMENT:
+                return Color.CYAN;
+            case DimensionProcessor.STRONGHOLD:
+                return Color.YELLOW;
+        }
+        return Color.WHITE;
+    }
+}
\ No newline at end of file
diff --git a/java/com/irtimaled/bbor/ServerProxy.java b/java/com/irtimaled/bbor/ServerProxy.java
new file mode 100644 (file)
index 0000000..1c5e923
--- /dev/null
@@ -0,0 +1,77 @@
+package com.irtimaled.bbor;
+
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.server.MinecraftServer;
+import net.minecraftforge.fml.common.FMLLog;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ServerProxy extends CommonProxy {
+
+    public ConcurrentHashMap<EntityPlayerMP, Integer> playerDimensions =
+            new ConcurrentHashMap<EntityPlayerMP, Integer>();
+
+    private Map<EntityPlayerMP, Set<BoundingBox>> playerBoundingBoxesCache = new HashMap<EntityPlayerMP, Set<BoundingBox>>();
+
+    @SubscribeEvent
+    public void playerJoinedWorldEvent(net.minecraftforge.event.entity.EntityJoinWorldEvent evt) {
+        if (evt.entity instanceof EntityPlayerMP) {
+            EntityPlayerMP player = (EntityPlayerMP) evt.entity;
+            int dimension = player.dimension;
+            playerDimensions.put(player, dimension);
+
+            sendToPlayer(player, boundingBoxCacheMap.get(dimension));
+        }
+    }
+
+    @SubscribeEvent
+    public void tickEvent(TickEvent event) {
+        for (EntityPlayerMP player : playerDimensions.keySet()) {
+
+            MinecraftServer mc = MinecraftServer.getServer();
+            if (!mc.getConfigurationManager().playerEntityList.contains(player)) {
+                playerDimensions.remove(player);
+            } else {
+                int dimension = playerDimensions.get(player);
+                if (boundingBoxCacheMap.containsKey(dimension)) {
+                    sendToPlayer(player, boundingBoxCacheMap.get(dimension));
+                }
+            }
+        }
+    }
+
+    private void sendToPlayer(EntityPlayerMP player, BoundingBoxCache boundingBoxCache) {
+        Map<BoundingBox, Set<BoundingBox>> cacheSubset = getBoundingBoxMap(player, boundingBoxCache.getBoundingBoxes());
+
+        int dimension = player.dimension;
+        if (cacheSubset.keySet().size() > 0) {
+            FMLLog.info("send %d entries to %s (%d)", cacheSubset.keySet().size(), player.getDisplayNameString(), dimension);
+        }
+
+        for (BoundingBox key : cacheSubset.keySet()) {
+            Set<BoundingBox> boundingBoxes = cacheSubset.get(key);
+            network.sendTo(BoundingBoxMessage.from(dimension, key, boundingBoxes), player);
+
+            if (!playerBoundingBoxesCache.containsKey(player)) {
+                playerBoundingBoxesCache.put(player, new HashSet<BoundingBox>());
+            }
+            playerBoundingBoxesCache.get(player).add(key);
+        }
+    }
+
+    private Map<BoundingBox, Set<BoundingBox>> getBoundingBoxMap(EntityPlayerMP player, Map<BoundingBox, Set<BoundingBox>> boundingBoxMap) {
+        Map<BoundingBox, Set<BoundingBox>> cacheSubset = new HashMap<BoundingBox, Set<BoundingBox>>();
+        for (BoundingBox key : boundingBoxMap.keySet()) {
+            if (!playerBoundingBoxesCache.containsKey(player) || !playerBoundingBoxesCache.get(player).contains(key)) {
+                cacheSubset.put(key, boundingBoxMap.get(key));
+            }
+        }
+        return cacheSubset;
+    }
+}
\ No newline at end of file
diff --git a/resources/assets/lang/en_US.lang b/resources/assets/lang/en_US.lang
new file mode 100644 (file)
index 0000000..046a9e3
--- /dev/null
@@ -0,0 +1,6 @@
+language.name=English
+language.region=US
+language.code=en_US
+
+key.bbor.hotkey=Toggle On/Off
+key.categories.bbor=Bounding Box Outline Reloaded
diff --git a/resources/mcmod.info b/resources/mcmod.info
new file mode 100644 (file)
index 0000000..f330abb
--- /dev/null
@@ -0,0 +1,17 @@
+[
+{
+  "modid": "bbor",
+  "name": "Bounding Box Outline Reloaded",
+  "description": "Shows the bounding boxes of structures and features.",
+  "version": "${version}",
+  "mcversion": "${mcversion}",
+  "url": "",
+  "updateUrl": "",
+  "authorList": ["irtimaled"],
+  "credits": "Thanks to 4poc & KaboPC.",
+  "logoFile": "",
+  "screenshots": [],
+  "dependencies": []
+}
+]
+