Explorar o código

Replace Bukkit Metadata for user placed blocks

NuclearW %!s(int64=13) %!d(string=hai) anos
pai
achega
b0157c9bd2

+ 14 - 14
src/main/java/com/gmail/nossr50/listeners/BlockListener.java

@@ -59,16 +59,16 @@ public class BlockListener implements Listener {
         BlockFace direction = event.getDirection();
 
         for (Block b : blocks) {
-            if (b.hasMetadata("mcmmoPlacedBlock")) {
-                b.getRelative(direction).setMetadata("mcmmoNeedsTracking", new FixedMetadataValue(plugin, true));
-                b.removeMetadata("mcmmoPlacedBlock", plugin);
+            if (mcMMO.placeStore.isTrue(b)) {
+                mcMMO.placeStore.setTrue(b.getRelative(direction));
+                mcMMO.placeStore.setFalse(b);
             }
         }
 
         for (Block b : blocks) {
-            if (b.getRelative(direction).hasMetadata("mcmmoNeedsTracking")) {
-                b.getRelative(direction).setMetadata("mcmmoPlacedBlock", new FixedMetadataValue(plugin, true));
-                b.getRelative(direction).removeMetadata("mcmmoNeedsTracking", plugin);
+            if (mcMMO.placeStore.isTrue(b.getRelative(direction))) {
+                mcMMO.placeStore.setTrue(b.getRelative(direction));
+                mcMMO.placeStore.setFalse(b);
             }
         }
     }
@@ -82,9 +82,9 @@ public class BlockListener implements Listener {
     public void onBlockPistonRetract(BlockPistonRetractEvent event) {
         Block block = event.getRetractLocation().getBlock();
 
-        if (block.hasMetadata("mcmmoPlacedBlock")) {
-            block.removeMetadata("mcmmoPlacedBlock", plugin);
-            event.getBlock().getRelative(event.getDirection()).setMetadata("mcmmoPlacedBlock", new FixedMetadataValue(plugin, true));
+        if (mcMMO.placeStore.isTrue(block)) {
+            mcMMO.placeStore.setFalse(block);
+            mcMMO.placeStore.setTrue(event.getBlock().getRelative(event.getDirection()));
         }
     }
 
@@ -108,7 +108,7 @@ public class BlockListener implements Listener {
                 }
                 else {
                     Block newLocation = block.getRelative(0, y + 1, 0);
-                    newLocation.setMetadata("mcmmoPlacedBlock", new FixedMetadataValue(plugin, true));
+                    mcMMO.placeStore.setTrue(newLocation);
                     break;
                 }
             }
@@ -116,7 +116,7 @@ public class BlockListener implements Listener {
 
         /* Check if the blocks placed should be monitored so they do not give out XP in the future */
         if (BlockChecks.shouldBeWatched(mat)) {
-            block.setMetadata("mcmmoPlacedBlock", new FixedMetadataValue(plugin, true));
+            mcMMO.placeStore.setTrue(block);
         }
 
         if (id == Config.getInstance().getRepairAnvilId() && Config.getInstance().getRepairAnvilMessagesEnabled()) {
@@ -193,7 +193,7 @@ public class BlockListener implements Listener {
          * EXCAVATION
          */
 
-        if (BlockChecks.canBeGigaDrillBroken(mat) && Permissions.getInstance().excavation(player) && !block.hasMetadata("mcmmoPlacedBlock")) {
+        if (BlockChecks.canBeGigaDrillBroken(mat) && Permissions.getInstance().excavation(player) && !mcMMO.placeStore.isTrue(block)) {
             if (Config.getInstance().getExcavationRequiresTool() && ItemChecks.isShovel(inhand)) {
                 Excavation.excavationProcCheck(block, player);
             }
@@ -203,8 +203,8 @@ public class BlockListener implements Listener {
         }
 
         //Remove metadata when broken
-        if (block.hasMetadata("mcmmoPlacedBlock") && BlockChecks.shouldBeWatched(mat)) {
-            block.removeMetadata("mcmmoPlacedBlock", plugin);
+        if (mcMMO.placeStore.isTrue(block) && BlockChecks.shouldBeWatched(mat)) {
+            mcMMO.placeStore.setFalse(block);
         }
     }
 

+ 43 - 0
src/main/java/com/gmail/nossr50/listeners/WorldListener.java

@@ -0,0 +1,43 @@
+package com.gmail.nossr50.listeners;
+
+import java.io.File;
+
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.world.ChunkLoadEvent;
+import org.bukkit.event.world.ChunkUnloadEvent;
+import org.bukkit.event.world.WorldLoadEvent;
+import org.bukkit.event.world.WorldSaveEvent;
+import org.bukkit.event.world.WorldUnloadEvent;
+
+import com.gmail.nossr50.mcMMO;
+
+public class WorldListener implements Listener {
+    @EventHandler
+    public void onWorldLoad(WorldLoadEvent event) {
+        File dataDir = new File(event.getWorld().getWorldFolder(), "mcmmo_data");
+        if(!dataDir.exists()) {
+            dataDir.mkdir();
+        }
+    }
+
+    @EventHandler
+    public void onWorldUnload(WorldUnloadEvent event) {
+        mcMMO.placeStore.unloadWorld(event.getWorld());
+    }
+
+    @EventHandler
+    public void onWorldSave(WorldSaveEvent event) {
+        mcMMO.placeStore.saveWorld(event.getWorld());
+    }
+
+    @EventHandler
+    public void onChunkLoad(ChunkLoadEvent event) {
+        mcMMO.placeStore.chunkLoaded(event.getChunk().getX(), event.getChunk().getZ(), event.getChunk().getWorld());
+    }
+
+    @EventHandler
+    public void onChunkUnload(ChunkUnloadEvent event) {
+        mcMMO.placeStore.chunkUnloaded(event.getChunk().getX(), event.getChunk().getZ(), event.getChunk().getWorld());
+    }
+}

+ 13 - 0
src/main/java/com/gmail/nossr50/mcMMO.java

@@ -14,10 +14,13 @@ import com.gmail.nossr50.util.Database;
 import com.gmail.nossr50.util.Leaderboard;
 import com.gmail.nossr50.util.Metrics;
 import com.gmail.nossr50.util.Users;
+import com.gmail.nossr50.util.blockmeta.ChunkletManager;
+import com.gmail.nossr50.util.blockmeta.HashChunkletManager;
 import com.gmail.nossr50.listeners.BlockListener;
 import com.gmail.nossr50.listeners.EntityListener;
 import com.gmail.nossr50.listeners.HardcoreListener;
 import com.gmail.nossr50.listeners.PlayerListener;
+import com.gmail.nossr50.listeners.WorldListener;
 import com.gmail.nossr50.locale.LocaleLoader;
 
 import net.shatteredlands.shatt.backup.ZipLibrary;
@@ -41,6 +44,7 @@ public class mcMMO extends JavaPlugin {
     private final PlayerListener playerListener = new PlayerListener(this);
     private final BlockListener blockListener = new BlockListener(this);
     private final EntityListener entityListener = new EntityListener(this);
+    private final WorldListener worldListener = new WorldListener();
     private final HardcoreListener hardcoreListener = new HardcoreListener();
 
     public HashMap<String, String> aliasMap = new HashMap<String, String>(); //Alias - Command
@@ -50,6 +54,8 @@ public class mcMMO extends JavaPlugin {
     public static Database database;
     public static mcMMO p;
 
+    public static ChunkletManager placeStore = new HashChunkletManager();
+
     /* Jar Stuff */
     public File mcmmo;
 
@@ -84,6 +90,7 @@ public class mcMMO extends JavaPlugin {
         pm.registerEvents(playerListener, this);
         pm.registerEvents(blockListener, this);
         pm.registerEvents(entityListener, this);
+        pm.registerEvents(worldListener, this);
 
         if (configInstance.getHardcoreEnabled()) {
             pm.registerEvents(hardcoreListener, this);
@@ -186,6 +193,12 @@ public class mcMMO extends JavaPlugin {
 
         getServer().getScheduler().cancelTasks(this); //This removes our tasks
 
+        //Save our metadata
+        placeStore.saveAll();
+
+        //Cleanup empty metadata stores
+        placeStore.cleanUp();
+
         //Remove other tasks BEFORE starting the Backup, or we just cancel it straight away.
         try {
             ZipLibrary.mcMMObackup();

+ 2 - 2
src/main/java/com/gmail/nossr50/skills/gathering/BlastMining.java

@@ -52,7 +52,7 @@ public class BlastMining {
                 blocksDropped.add(temp);
                 Mining.miningDrops(temp);
 
-                if (!temp.hasMetadata("mcmmoPlacedBlock")) {
+                if (!mcMMO.placeStore.isTrue(temp)) {
                     for (int i = 1 ; i < extraDrops ; i++) {
                         blocksDropped.add(temp);
                         Mining.miningDrops(temp);
@@ -159,7 +159,7 @@ public class BlastMining {
         }
 
         for (Block block : xp) {
-            if (!block.hasMetadata("mcmmoPlacedBlock")) {
+            if (!mcMMO.placeStore.isTrue(block)) {
                 Mining.miningXP(player, block);
             }
         }

+ 1 - 1
src/main/java/com/gmail/nossr50/skills/gathering/Excavation.java

@@ -112,7 +112,7 @@ public class Excavation {
     public static void gigaDrillBreaker(Player player, Block block) {
         Skills.abilityDurabilityLoss(player.getItemInHand(), Config.getInstance().getAbilityToolDamage());
 
-        if (!block.hasMetadata("mcmmoPlacedBlock")) {
+        if (!mcMMO.placeStore.isTrue(block)) {
             FakePlayerAnimationEvent armswing = new FakePlayerAnimationEvent(player);
             mcMMO.p.getServer().getPluginManager().callEvent(armswing);
 

+ 8 - 8
src/main/java/com/gmail/nossr50/skills/gathering/Herbalism.java

@@ -89,7 +89,7 @@ public class Herbalism {
         switch (type) {
         case BROWN_MUSHROOM:
         case RED_MUSHROOM:
-            if (!block.hasMetadata("mcmmoPlacedBlock")) {
+            if (!mcMMO.placeStore.isTrue(block)) {
                 mat = Material.getMaterial(id);
                 xp = Config.getInstance().getHerbalismXPMushrooms();
             }
@@ -100,7 +100,7 @@ public class Herbalism {
                 Block b = block.getRelative(0, y, 0);
                 if (b.getType().equals(Material.CACTUS)) {
                     mat = Material.CACTUS;
-                    if (!b.hasMetadata("mcmmoPlacedBlock")) {
+                    if (!mcMMO.placeStore.isTrue(b)) {
                         if (herbLevel > MAX_BONUS_LEVEL || random.nextInt(1000) <= herbLevel) {
                             catciDrops++;
                         }
@@ -122,7 +122,7 @@ public class Herbalism {
             break;
 
         case MELON_BLOCK:
-            if (!block.hasMetadata("mcmmoPlacedBlock")) {
+            if (!mcMMO.placeStore.isTrue(block)) {
                 mat = Material.MELON;
                 xp = Config.getInstance().getHerbalismXPMelon();
             }
@@ -137,7 +137,7 @@ public class Herbalism {
 
         case PUMPKIN:
         case JACK_O_LANTERN:
-            if (!block.hasMetadata("mcmmoPlacedBlock")) {
+            if (!mcMMO.placeStore.isTrue(block)) {
                 mat = Material.getMaterial(id);
                 xp = Config.getInstance().getHerbalismXPPumpkin();
             }
@@ -145,7 +145,7 @@ public class Herbalism {
 
         case RED_ROSE:
         case YELLOW_FLOWER:
-            if (!block.hasMetadata("mcmmoPlacedBlock")) {
+            if (!mcMMO.placeStore.isTrue(block)) {
                 mat = Material.getMaterial(id);
                 xp = Config.getInstance().getHerbalismXPFlowers();
             }
@@ -156,7 +156,7 @@ public class Herbalism {
                 Block b = block.getRelative(0, y, 0);
                 if (b.getType().equals(Material.SUGAR_CANE_BLOCK)) {
                     mat = Material.SUGAR_CANE;
-                    if (!b.hasMetadata("mcmmoPlacedBlock")) {
+                    if (!mcMMO.placeStore.isTrue(b)) {
                         if (herbLevel > MAX_BONUS_LEVEL || random.nextInt(1000) <= herbLevel) {
                             caneDrops++;
                         }
@@ -167,14 +167,14 @@ public class Herbalism {
             break;
 
         case VINE:
-            if (!block.hasMetadata("mcmmoPlacedBlock")) {
+            if (!mcMMO.placeStore.isTrue(block)) {
                 mat = type;
                 xp = Config.getInstance().getHerbalismXPVines();
             }
             break;
 
         case WATER_LILY:
-            if (!block.hasMetadata("mcmmoPlacedBlock")) {
+            if (!mcMMO.placeStore.isTrue(block)) {
                 mat = type;
                 xp = Config.getInstance().getHerbalismXPLilyPads();
             }

+ 2 - 2
src/main/java/com/gmail/nossr50/skills/gathering/Mining.java

@@ -212,7 +212,7 @@ public class Mining {
      * @param block The block being broken
      */
     public static void miningBlockCheck(Player player, Block block) {
-        if (block.hasMetadata("mcmmoPlacedBlock") || player.getItemInHand().containsEnchantment(Enchantment.SILK_TOUCH)) {
+        if (mcMMO.placeStore.isTrue(block) || player.getItemInHand().containsEnchantment(Enchantment.SILK_TOUCH)) {
             return;
         }
 
@@ -272,7 +272,7 @@ public class Mining {
         case NETHERRACK:
         case SANDSTONE:
         case STONE:
-            if (block.hasMetadata("mcmmoPlacedBlock")) {
+            if (mcMMO.placeStore.isTrue(block)) {
                 return;
             }
 

+ 4 - 4
src/main/java/com/gmail/nossr50/skills/gathering/WoodCutting.java

@@ -114,7 +114,7 @@ public class WoodCutting {
                         break;
                     }
 
-                    if (!x.hasMetadata("mcmmoPlacedBlock")) {
+                    if (!mcMMO.placeStore.isTrue(x)) {
                         WoodCutting.woodCuttingProcCheck(player, x);
 
                         switch (species) {
@@ -188,7 +188,7 @@ public class WoodCutting {
         Block zNegative = currentBlock.getRelative(0, 0, -1);
         Block yPositive = currentBlock.getRelative(0, 1, 0);
 
-        if (!currentBlock.hasMetadata("mcmmoPlacedBlock")) {
+        if (!mcMMO.placeStore.isTrue(currentBlock)) {
             if (!isTooAggressive(currentBlock, xPositive) && BlockChecks.treeFellerCompatible(xPositive.getType()) && !toBeFelled.contains(xPositive)) {
                 processTreeFelling(xPositive, toBeFelled);
             }
@@ -207,7 +207,7 @@ public class WoodCutting {
         }
 
         if (BlockChecks.treeFellerCompatible(yPositive.getType())) {
-            if(!currentBlock.hasMetadata("mcmmoPlacedBlock") && !toBeFelled.contains(yPositive)) {
+            if(!mcMMO.placeStore.isTrue(currentBlock) && !toBeFelled.contains(yPositive)) {
                 processTreeFelling(yPositive, toBeFelled);
             }
         }
@@ -296,7 +296,7 @@ public class WoodCutting {
         int xp = 0;
         TreeSpecies species = TreeSpecies.getByData(block.getData());
 
-        if (block.hasMetadata("mcmmoPlacedBlock")) {
+        if (mcMMO.placeStore.isTrue(block)) {
             return;
         }
 

+ 106 - 0
src/main/java/com/gmail/nossr50/util/blockmeta/ChunkletManager.java

@@ -0,0 +1,106 @@
+package com.gmail.nossr50.util.blockmeta;
+
+import org.bukkit.World;
+import org.bukkit.block.Block;
+
+public interface ChunkletManager {
+    /**
+     * Informs the ChunkletManager a chunk is loaded, it should load appropriate data
+     * 
+     * @param cx Chunk X coordiate that is loaded
+     * @param cz Chunk Z coordiate that is loaded
+     * @param world World that the chunk was loaded in
+     */
+    public void chunkLoaded(int cx, int cz, World world);
+
+    /**
+     * Informs the ChunkletManager a chunk is unloaded, it should unload and save appropriate data
+     * 
+     * @param cx Chunk X coordiate that is unloaded
+     * @param cz Chunk Z coordiate that is unloaded
+     * @param world World that the chunk was unloaded in
+     */
+    public void chunkUnloaded(int cx, int cz, World world);
+
+    /**
+     * Save all ChunkletStores related to the given world
+     * 
+     * @param world World to save
+     */
+    public void saveWorld(World world);
+
+    /**
+     * Unload all ChunkletStores from memory related to the given world after saving them
+     * 
+     * @param world World to unload
+     */
+    public void unloadWorld(World world);
+
+    /**
+     * Save all ChunkletStores
+     */
+    public void saveAll();
+
+    /**
+     * Unload all ChunkletStores after saving them
+     */
+    public void unloadAll();
+
+    /**
+     * Check to see if a given location is set to true
+     * 
+     * @param x X coordinate to check
+     * @param y Y coordinate to check
+     * @param z Z coordinate to check
+     * @param world World to check in
+     * @return true if the given location is set to true, false if otherwise
+     */
+    public boolean isTrue(int x, int y, int z, World world);
+
+    /**
+     * Check to see if a given block location is set to true
+     * 
+     * @param block Block location to check
+     * @return true if the given block location is set to true, false if otherwise
+     */
+    public boolean isTrue(Block block);
+
+    /**
+     * Set a given location to true, should create stores as necessary if the location does not exist
+     * 
+     * @param x X coordinate to set
+     * @param y Y coordinate to set
+     * @param z Z coordinate to set
+     * @param world World to set in
+     */
+    public void setTrue(int x, int y, int z, World world);
+
+    /**
+     * Set a given block location to true, should create stores as necessary if the location does not exist
+     * 
+     * @param block Block location to set
+     */
+    public void setTrue(Block block);
+
+    /**
+     * Set a given location to false, should not create stores if one does not exist for the given location
+     * 
+     * @param x X coordinate to set
+     * @param y Y coordinate to set
+     * @param z Z coordinate to set
+     * @param world World to set in
+     */
+    public void setFalse(int x, int y, int z, World world);
+
+    /**
+     * Set a given block location to false, should not create stores if one does not exist for the given location
+     * 
+     * @param block Block location to set
+     */
+    public void setFalse(Block block);
+
+    /**
+     * Delete any ChunkletStores that are empty
+     */
+    public void cleanUp();
+}

+ 36 - 0
src/main/java/com/gmail/nossr50/util/blockmeta/ChunkletStore.java

@@ -0,0 +1,36 @@
+package com.gmail.nossr50.util.blockmeta;
+
+import java.io.Serializable;
+
+public interface ChunkletStore extends Serializable {
+    /**
+     * @param x x coordinate in current chunklet
+     * @param y y coordinate in current chunklet
+     * @param z z coordinate in current chunklet
+     * @return true if the value is true at the given coordinates, false if otherwise
+     */
+    public boolean isTrue(int x, int y, int z);
+
+    /**
+     * Set the value to true at the given coordinates
+     * 
+     * @param x x coordinate in current chunklet
+     * @param y y coordinate in current chunklet
+     * @param z z coordinate in current chunklet
+     */
+    public void setTrue(int x, int y, int z);
+
+    /**
+     * Set the value to false at the given coordinates
+     * 
+     * @param x x coordinate in current chunklet
+     * @param y y coordinate in current chunklet
+     * @param z z coordinate in current chunklet
+     */
+    public void setFalse(int x, int y, int z);
+
+    /**
+     * @return true if all values in the chunklet are false, false if otherwise
+     */
+    public boolean isEmpty();
+}

+ 227 - 0
src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java

@@ -0,0 +1,227 @@
+package com.gmail.nossr50.util.blockmeta;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.HashMap;
+
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+
+public class HashChunkletManager implements ChunkletManager {
+    private HashMap<String, ChunkletStore> store = new HashMap<String, ChunkletStore>();
+
+    public void chunkLoaded(int cx, int cz, World world) {
+        File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
+        File cxDir = new File(dataDir, "" + cx);
+        if(!cxDir.exists()) return;
+        File czDir = new File(cxDir, "" + cz);
+        if(!czDir.exists()) return;
+
+        for(int y = 1; y <= 4; y++) {
+            File yFile = new File(czDir, "" + y);
+            if(!yFile.exists()) {
+                continue;
+            } else {
+                ChunkletStore in = deserializeChunkletStore(yFile);
+                if(in != null) {
+                	store.put(world.getName() + "," + cx + "," + cz + "," + y, in);
+                }
+            }
+        }
+    }
+
+    public void chunkUnloaded(int cx, int cz, World world) {
+        boolean found = false;
+
+        for(String key : store.keySet()) {
+            if(key.startsWith(world.getName() + "," + cx + "," + cz)) found = true;
+        }
+
+        if(!found) return;
+
+        File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
+        File cxDir = new File(dataDir, "" + cx);
+        if(!cxDir.exists()) cxDir.mkdir();
+        File czDir = new File(cxDir, "" + cz);
+        if(!czDir.exists()) czDir.mkdir();
+
+        for(int y = 1; y <= 4; y++) {
+            File yFile = new File(czDir, "" + y);
+            if(store.containsKey(world.getName() + "," + cx + "," + cz + "," + y)) {
+                ChunkletStore out = store.get(world.getName() + "," + cx + "," + cz + "," + y);
+                serializeChunkletStore(out, yFile);
+            }
+        }
+    }
+
+    public void saveWorld(World world) {
+        String worldName = world.getName();
+        File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
+
+        for(String key : store.keySet()) {
+            String[] info = key.split(",");
+            if(worldName.equals(info[0])) {
+                File cxDir = new File(dataDir, "" + info[1]);
+                if(!cxDir.exists()) cxDir.mkdir();
+                File czDir = new File(cxDir, "" + info[2]);
+                if(!czDir.exists()) czDir.mkdir();
+
+                File yFile = new File(czDir, "" + info[3]);
+                serializeChunkletStore(store.get(key), yFile);
+            }
+        }
+    }
+
+    public void unloadWorld(World world) {
+    	saveWorld(world);
+
+        String worldName = world.getName();
+
+        for(String key : store.keySet()) {
+        	String tempWorldName = key.split(",")[0];
+        	if(tempWorldName.equals(worldName)) {
+        	    store.remove(key);
+        	}
+        }
+    }
+
+    public void saveAll() {
+    	for(World world : Bukkit.getWorlds()) {
+    		saveWorld(world);
+    	}
+    }
+
+    public void unloadAll() {
+    	saveAll();
+    	for(World world : Bukkit.getWorlds()) {
+    		unloadWorld(world);
+    	}
+    }
+
+    public boolean isTrue(int x, int y, int z, World world) {
+        int cx = x / 16;
+        int cz = z / 16;
+        int cy = y / 64;
+        if(!store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) return false;
+
+        ChunkletStore check = store.get(world.getName() + "," + cx + "," + cz + "," + cy);
+        int ix = Math.abs(x) % 16;
+        int iz = Math.abs(z) % 16;
+        int iy = Math.abs(y) % 64;
+
+        return check.isTrue(ix, iy, iz);
+    }
+
+    public boolean isTrue(Block block) {
+        return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
+    }
+
+    public void setTrue(int x, int y, int z, World world) {
+        int cx = x / 16;
+        int cz = z / 16;
+        int cy = y / 64;
+
+        int ix = Math.abs(x) % 16;
+        int iz = Math.abs(z) % 16;
+        int iy = Math.abs(y) % 64;
+
+        ChunkletStore cStore;
+        if(!store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) {
+            cStore = new PrimitiveChunkletStore();
+            store.put(world.getName() + "," + cx + "," + cz + "," + cy, cStore);
+        }
+
+        cStore = store.get(world.getName() + "," + cx + "," + cz + "," + cy);
+        cStore.setTrue(ix, iy, iz);
+    }
+
+    public void setTrue(Block block) {
+        setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
+    }
+
+    public void setFalse(int x, int y, int z, World world) {
+        int cx = x / 16;
+        int cz = z / 16;
+        int cy = y / 64;
+
+        int ix = Math.abs(x) % 16;
+        int iz = Math.abs(z) % 16;
+        int iy = Math.abs(y) % 64;
+
+        ChunkletStore cStore;
+        if(!store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) {
+            return;	// No need to make a store for something we will be setting to false
+        }
+
+        cStore = store.get(world.getName() + "," + cx + "," + cz + "," + cy);
+        cStore.setFalse(ix, iy, iz);
+    }
+
+    public void setFalse(Block block) {
+        setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
+    }
+
+    public void cleanUp() {
+        for(String key : store.keySet()) {
+            if(store.get(key).isEmpty()) {
+                String[] info = key.split(",");
+                File dataDir = new File(Bukkit.getWorld(info[0]).getWorldFolder(), "mcmmo_data");
+
+                File cxDir = new File(dataDir, "" + info[1]);
+                if(!cxDir.exists()) continue;
+                File czDir = new File(cxDir, "" + info[2]);
+                if(!czDir.exists()) continue;
+
+                File yFile = new File(czDir, "" + info[3]);
+                yFile.delete();
+
+                //Delete empty directories
+                if(czDir.list().length == 0) czDir.delete();
+                if(cxDir.list().length == 0) cxDir.delete();
+            }
+        }
+    }
+
+    /**
+     * @param cStore ChunkletStore to save
+     * @param location Where on the disk to put it
+     */
+    private void serializeChunkletStore(ChunkletStore cStore, File location) {
+    	try {
+            FileOutputStream fileOut = new FileOutputStream(location);
+            ObjectOutputStream objOut = new ObjectOutputStream(fileOut);
+            objOut.writeObject(cStore);
+            objOut.close();
+            fileOut.close();
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    /**
+     * @param location Where on the disk to read from
+     * @return ChunkletStore from the specified location
+     */
+    private ChunkletStore deserializeChunkletStore(File location) {
+        ChunkletStore storeIn = null;
+
+        try {
+            FileInputStream fileIn = new FileInputStream(location);
+            ObjectInputStream objIn = new ObjectInputStream(fileIn);
+            storeIn = (ChunkletStore) objIn.readObject();
+            objIn.close();
+            fileIn.close();
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        } catch (ClassNotFoundException ex) {
+			ex.printStackTrace();
+		}
+
+        return storeIn;
+    }
+}

+ 31 - 0
src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveChunkletStore.java

@@ -0,0 +1,31 @@
+package com.gmail.nossr50.util.blockmeta;
+
+public class PrimitiveChunkletStore implements ChunkletStore {
+    private static final long serialVersionUID = -3453078050608607478L;
+
+    /** X, Z, Y */
+    private boolean[][][] store = new boolean[16][16][64];
+
+    public boolean isTrue(int x, int y, int z) {
+        return store[x][z][y];
+    }
+
+    public void setTrue(int x, int y, int z) {
+        store[x][z][y] = true;
+    }
+
+    public void setFalse(int x, int y, int z) {
+        store[x][z][y] = false;
+    }
+
+    public boolean isEmpty() {
+        for(int x = 0; x < 16; x++) {
+            for(int z = 0; z < 16; z++) {
+                for(int y = 0; y < 64; y++) {
+                    if(store[x][z][y]) return false;
+                }
+            }
+        }
+        return true;
+    }
+}

+ 1 - 0
src/main/resources/plugin.yml

@@ -8,6 +8,7 @@ website: http://forums.bukkit.org/threads/rpg-mech-mcmmo-v1-3-05-rpg-addiction-r
 
 main: com.gmail.nossr50.mcMMO
 softdepend: [Spout]
+load: STARTUP
 
 commands:
     mchud: