mirror of
				https://github.com/mcMMO-Dev/mcMMO.git
				synced 2025-11-04 11:03:43 +01:00 
			
		
		
		
	@@ -0,0 +1,243 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.BitSet;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class BitSetChunkStore implements ChunkStore, Serializable {
 | 
			
		||||
    private static final long serialVersionUID = -1L;
 | 
			
		||||
    transient private boolean dirty = false;
 | 
			
		||||
    // Bitset store conforms to a "bottom-up" bit ordering consisting of a stack of {worldHeight} Y planes, each Y plane consists of 16 Z rows of 16 X bits.
 | 
			
		||||
    private BitSet store;
 | 
			
		||||
    private static final int CURRENT_VERSION = 8;
 | 
			
		||||
    private static final int MAGIC_NUMBER = 0xEA5EDEBB;
 | 
			
		||||
    private int cx;
 | 
			
		||||
    private int cz;
 | 
			
		||||
    private int worldHeight;
 | 
			
		||||
    private UUID worldUid;
 | 
			
		||||
 | 
			
		||||
    public BitSetChunkStore(World world, int cx, int cz) {
 | 
			
		||||
        this.cx = cx;
 | 
			
		||||
        this.cz = cz;
 | 
			
		||||
        this.worldUid = world.getUID();
 | 
			
		||||
        this.worldHeight = world.getMaxHeight();
 | 
			
		||||
        this.store = new BitSet(16 * 16 * worldHeight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private BitSetChunkStore() {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isDirty() {
 | 
			
		||||
        return dirty;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setDirty(boolean dirty) {
 | 
			
		||||
        this.dirty = dirty;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getChunkX() {
 | 
			
		||||
        return cx;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getChunkZ() {
 | 
			
		||||
        return cz;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public UUID getWorldId() {
 | 
			
		||||
        return worldUid;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(int x, int y, int z) {
 | 
			
		||||
        return store.get(coordToIndex(x, y, z));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(int x, int y, int z) {
 | 
			
		||||
        set(x, y, z, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFalse(int x, int y, int z) {
 | 
			
		||||
        set(x, y, z, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void set(int x, int y, int z, boolean value) {
 | 
			
		||||
        store.set(coordToIndex(x, y, z), value);
 | 
			
		||||
        dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isEmpty() {
 | 
			
		||||
        return store.isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int coordToIndex(int x, int y, int z) {
 | 
			
		||||
        if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16)
 | 
			
		||||
            throw new IndexOutOfBoundsException();
 | 
			
		||||
        return (z * 16 + x) + (256 * y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void fixWorldHeight() {
 | 
			
		||||
        World world = Bukkit.getWorld(worldUid);
 | 
			
		||||
 | 
			
		||||
        // Not sure how this case could come up, but might as well handle it gracefully.  Loading a chunkstore for an unloaded world?
 | 
			
		||||
        if (world == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Lop off any extra data if the world height has shrunk
 | 
			
		||||
        int currentWorldHeight = world.getMaxHeight();
 | 
			
		||||
        if (currentWorldHeight < worldHeight)
 | 
			
		||||
        {
 | 
			
		||||
            store.clear(coordToIndex(16, currentWorldHeight, 16), store.length());
 | 
			
		||||
            worldHeight = currentWorldHeight;
 | 
			
		||||
            dirty = true;
 | 
			
		||||
        }
 | 
			
		||||
        // If the world height has grown, update the worldHeight variable, but don't bother marking it dirty as unless something else changes we don't need to force a file write;
 | 
			
		||||
        else if (currentWorldHeight > worldHeight)
 | 
			
		||||
            worldHeight = currentWorldHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    private void writeObject(ObjectOutputStream out) throws IOException {
 | 
			
		||||
        throw new UnsupportedOperationException("Serializable support should only be used for legacy deserialization");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
 | 
			
		||||
        in.readInt(); // Magic number
 | 
			
		||||
        in.readInt(); // Format version
 | 
			
		||||
        long lsb = in.readLong();
 | 
			
		||||
        long msb = in.readLong();
 | 
			
		||||
        worldUid = new UUID(msb, lsb);
 | 
			
		||||
        cx = in.readInt();
 | 
			
		||||
        cz = in.readInt();
 | 
			
		||||
 | 
			
		||||
        boolean[][][] oldStore = (boolean[][][]) in.readObject();
 | 
			
		||||
        worldHeight = oldStore[0][0].length;
 | 
			
		||||
        store = new BitSet(16 * 16 * worldHeight / 8);
 | 
			
		||||
        for (int x = 0; x < 16; x++) {
 | 
			
		||||
            for (int z = 0; z < 16; z++) {
 | 
			
		||||
                for (int y = 0; y < worldHeight; y++) {
 | 
			
		||||
                    store.set(coordToIndex(x, y, z), oldStore[x][z][y]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        dirty = true;
 | 
			
		||||
        fixWorldHeight();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void serialize(DataOutputStream out) throws IOException {
 | 
			
		||||
        out.writeInt(MAGIC_NUMBER);
 | 
			
		||||
        out.writeInt(CURRENT_VERSION);
 | 
			
		||||
 | 
			
		||||
        out.writeLong(worldUid.getLeastSignificantBits());
 | 
			
		||||
        out.writeLong(worldUid.getMostSignificantBits());
 | 
			
		||||
        out.writeInt(cx);
 | 
			
		||||
        out.writeInt(cz);
 | 
			
		||||
        out.writeInt(worldHeight);
 | 
			
		||||
 | 
			
		||||
        // Store the byte array directly so we don't have the object type info overhead
 | 
			
		||||
        byte[] storeData = store.toByteArray();
 | 
			
		||||
        out.writeInt(storeData.length);
 | 
			
		||||
        out.write(storeData);
 | 
			
		||||
 | 
			
		||||
        dirty = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static BitSetChunkStore deserialize(DataInputStream in) throws IOException {
 | 
			
		||||
        int magic = in.readInt();
 | 
			
		||||
        // Can be used to determine the format of the file
 | 
			
		||||
        int fileVersionNumber = in.readInt();
 | 
			
		||||
 | 
			
		||||
        if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION)
 | 
			
		||||
            throw new IOException();
 | 
			
		||||
 | 
			
		||||
        BitSetChunkStore chunkStore = new BitSetChunkStore();
 | 
			
		||||
 | 
			
		||||
        long lsb = in.readLong();
 | 
			
		||||
        long msb = in.readLong();
 | 
			
		||||
        chunkStore.worldUid = new UUID(msb, lsb);
 | 
			
		||||
        chunkStore.cx = in.readInt();
 | 
			
		||||
        chunkStore.cz = in.readInt();
 | 
			
		||||
 | 
			
		||||
        chunkStore.worldHeight = in.readInt();
 | 
			
		||||
        byte[] temp = new byte[in.readInt()];
 | 
			
		||||
        in.readFully(temp);
 | 
			
		||||
        chunkStore.store = BitSet.valueOf(temp);
 | 
			
		||||
 | 
			
		||||
        chunkStore.fixWorldHeight();
 | 
			
		||||
        return chunkStore;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Serialization {
 | 
			
		||||
 | 
			
		||||
        public static final short STREAM_MAGIC = (short)0xACDC;
 | 
			
		||||
 | 
			
		||||
        public static ChunkStore readChunkStore(DataInputStream inputStream) throws IOException {
 | 
			
		||||
            if (inputStream.markSupported())
 | 
			
		||||
                inputStream.mark(2);
 | 
			
		||||
            short magicNumber = inputStream.readShort();
 | 
			
		||||
 | 
			
		||||
            if (magicNumber == ObjectStreamConstants.STREAM_MAGIC) // Java serializable, use legacy serialization
 | 
			
		||||
            {
 | 
			
		||||
                // "Un-read" the magic number for Serializables, they need it to still be in the stream
 | 
			
		||||
                if (inputStream.markSupported())
 | 
			
		||||
                    inputStream.reset(); // Pretend we never read those bytes
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    // Creates a new stream with the two magic number bytes and then the rest of the original stream...   Java is so dumb.  I just wanted to look at two bytes.
 | 
			
		||||
                    PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2);
 | 
			
		||||
                    pushbackInputStream.unread((magicNumber >>> 0) & 0xFF);
 | 
			
		||||
                    pushbackInputStream.unread((magicNumber >>> 8) & 0xFF);
 | 
			
		||||
                    inputStream = new DataInputStream(pushbackInputStream);
 | 
			
		||||
                }
 | 
			
		||||
                return new LegacyDeserializationInputStream(inputStream).readLegacyChunkStore();
 | 
			
		||||
            }
 | 
			
		||||
            else if (magicNumber == STREAM_MAGIC) // Pure bytes format
 | 
			
		||||
            {
 | 
			
		||||
                return BitSetChunkStore.deserialize(inputStream);
 | 
			
		||||
            }
 | 
			
		||||
            throw new IOException("Bad Data Format");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void writeChunkStore(DataOutputStream outputStream, ChunkStore chunkStore) throws IOException {
 | 
			
		||||
            if (!(chunkStore instanceof BitSetChunkStore))
 | 
			
		||||
                throw new InvalidClassException("ChunkStore must be instance of BitSetChunkStore");
 | 
			
		||||
            outputStream.writeShort(STREAM_MAGIC);
 | 
			
		||||
            ((BitSetChunkStore)chunkStore).serialize(outputStream);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Handles loading the old serialized classes even though we have changed name/package
 | 
			
		||||
        private static class LegacyDeserializationInputStream extends ObjectInputStream {
 | 
			
		||||
            public LegacyDeserializationInputStream(InputStream in) throws IOException {
 | 
			
		||||
                super(in);
 | 
			
		||||
                enableResolveObject(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
 | 
			
		||||
                ObjectStreamClass read = super.readClassDescriptor();
 | 
			
		||||
                if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"))
 | 
			
		||||
                    return ObjectStreamClass.lookup(BitSetChunkStore.class);
 | 
			
		||||
                return read;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public ChunkStore readLegacyChunkStore(){
 | 
			
		||||
                try {
 | 
			
		||||
                    return (ChunkStore) readObject();
 | 
			
		||||
                } catch (IOException | ClassNotFoundException e) {
 | 
			
		||||
                    return null;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,59 +1,12 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.block.Block;
 | 
			
		||||
import org.bukkit.block.BlockState;
 | 
			
		||||
import org.bukkit.entity.Entity;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public interface ChunkManager {
 | 
			
		||||
    void closeAll();
 | 
			
		||||
 | 
			
		||||
    ChunkStore readChunkStore(World world, int x, int z) throws IOException;
 | 
			
		||||
 | 
			
		||||
    void writeChunkStore(World world, int x, int z, ChunkStore data);
 | 
			
		||||
 | 
			
		||||
    void closeChunkStore(World world, int x, int z);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a specific chunklet
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunklet X coordinate that needs to be loaded
 | 
			
		||||
     * @param cy Chunklet Y coordinate that needs to be loaded
 | 
			
		||||
     * @param cz Chunklet Z coordinate that needs to be loaded
 | 
			
		||||
     * @param world World that the chunklet needs to be loaded in
 | 
			
		||||
     */
 | 
			
		||||
    void loadChunklet(int cx, int cy, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unload a specific chunklet
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunklet X coordinate that needs to be unloaded
 | 
			
		||||
     * @param cy Chunklet Y coordinate that needs to be unloaded
 | 
			
		||||
     * @param cz Chunklet Z coordinate that needs to be unloaded
 | 
			
		||||
     * @param world World that the chunklet needs to be unloaded from
 | 
			
		||||
     */
 | 
			
		||||
    void unloadChunklet(int cx, int cy, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load a given Chunk's Chunklet data
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunk X coordinate that is to be loaded
 | 
			
		||||
     * @param cz Chunk Z coordinate that is to be loaded
 | 
			
		||||
     * @param world World that the Chunk is in
 | 
			
		||||
     */
 | 
			
		||||
    void loadChunk(int cx, int cz, World world, Entity[] entities);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unload a given Chunk's Chunklet data
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunk X coordinate that is to be unloaded
 | 
			
		||||
     * @param cz Chunk Z coordinate that is to be unloaded
 | 
			
		||||
     * @param world World that the Chunk is in
 | 
			
		||||
     */
 | 
			
		||||
    void unloadChunk(int cx, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Saves a given Chunk's Chunklet data
 | 
			
		||||
     *
 | 
			
		||||
@@ -63,17 +16,6 @@ public interface ChunkManager {
 | 
			
		||||
     */
 | 
			
		||||
    void saveChunk(int cx, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    boolean isChunkLoaded(int cx, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Informs the ChunkletManager a chunk is loaded
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunk X coordinate that is loaded
 | 
			
		||||
     * @param cz Chunk Z coordinate that is loaded
 | 
			
		||||
     * @param world World that the chunk was loaded in
 | 
			
		||||
     */
 | 
			
		||||
    void chunkLoaded(int cx, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Informs the ChunkletManager a chunk is unloaded
 | 
			
		||||
     *
 | 
			
		||||
@@ -97,23 +39,11 @@ public interface ChunkManager {
 | 
			
		||||
     */
 | 
			
		||||
    void unloadWorld(World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load all ChunkletStores from all loaded chunks from this world into memory
 | 
			
		||||
     *
 | 
			
		||||
     * @param world World to load
 | 
			
		||||
     */
 | 
			
		||||
    void loadWorld(World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save all ChunkletStores
 | 
			
		||||
     */
 | 
			
		||||
    void saveAll();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unload all ChunkletStores after saving them
 | 
			
		||||
     */
 | 
			
		||||
    void unloadAll();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check to see if a given location is set to true
 | 
			
		||||
     *
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.config.HiddenConfig;
 | 
			
		||||
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A ChunkStore should be responsible for a 16x16xWorldHeight area of data
 | 
			
		||||
 */
 | 
			
		||||
public interface ChunkStore extends Serializable {
 | 
			
		||||
public interface ChunkStore {
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks the chunk's save state
 | 
			
		||||
     *
 | 
			
		||||
@@ -36,6 +36,8 @@ public interface ChunkStore extends Serializable {
 | 
			
		||||
     */
 | 
			
		||||
    int getChunkZ();
 | 
			
		||||
 | 
			
		||||
    UUID getWorldId();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks the value at the given coordinates
 | 
			
		||||
     *
 | 
			
		||||
@@ -64,15 +66,18 @@ public interface ChunkStore extends Serializable {
 | 
			
		||||
     */
 | 
			
		||||
    void setFalse(int x, int y, int z);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the value 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
 | 
			
		||||
     * @param value value to set
 | 
			
		||||
     */
 | 
			
		||||
    void set(int x, int y, int z, boolean value);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return true if all values in the chunklet are false, false if otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean isEmpty();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set all values in this ChunkletStore to the values from another provided ChunkletStore
 | 
			
		||||
     *
 | 
			
		||||
     * @param otherStore Another ChunkletStore that this one should copy all data from
 | 
			
		||||
     */
 | 
			
		||||
    void copyFrom(ChunkletStore otherStore);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,151 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.block.Block;
 | 
			
		||||
 | 
			
		||||
public interface ChunkletManager {
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a specific chunklet
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunklet X coordinate that needs to be loaded
 | 
			
		||||
     * @param cy Chunklet Y coordinate that needs to be loaded
 | 
			
		||||
     * @param cz Chunklet Z coordinate that needs to be loaded
 | 
			
		||||
     * @param world World that the chunklet needs to be loaded in
 | 
			
		||||
     */
 | 
			
		||||
    void loadChunklet(int cx, int cy, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unload a specific chunklet
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunklet X coordinate that needs to be unloaded
 | 
			
		||||
     * @param cy Chunklet Y coordinate that needs to be unloaded
 | 
			
		||||
     * @param cz Chunklet Z coordinate that needs to be unloaded
 | 
			
		||||
     * @param world World that the chunklet needs to be unloaded from
 | 
			
		||||
     */
 | 
			
		||||
    void unloadChunklet(int cx, int cy, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load a given Chunk's Chunklet data
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunk X coordinate that is to be loaded
 | 
			
		||||
     * @param cz Chunk Z coordinate that is to be loaded
 | 
			
		||||
     * @param world World that the Chunk is in
 | 
			
		||||
     */
 | 
			
		||||
    void loadChunk(int cx, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unload a given Chunk's Chunklet data
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunk X coordinate that is to be unloaded
 | 
			
		||||
     * @param cz Chunk Z coordinate that is to be unloaded
 | 
			
		||||
     * @param world World that the Chunk is in
 | 
			
		||||
     */
 | 
			
		||||
    void unloadChunk(int cx, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Informs the ChunkletManager a chunk is loaded
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunk X coordinate that is loaded
 | 
			
		||||
     * @param cz Chunk Z coordinate that is loaded
 | 
			
		||||
     * @param world World that the chunk was loaded in
 | 
			
		||||
     */
 | 
			
		||||
    void chunkLoaded(int cx, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Informs the ChunkletManager a chunk is unloaded
 | 
			
		||||
     *
 | 
			
		||||
     * @param cx Chunk X coordinate that is unloaded
 | 
			
		||||
     * @param cz Chunk Z coordinate that is unloaded
 | 
			
		||||
     * @param world World that the chunk was unloaded in
 | 
			
		||||
     */
 | 
			
		||||
    void chunkUnloaded(int cx, int cz, World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save all ChunkletStores related to the given world
 | 
			
		||||
     *
 | 
			
		||||
     * @param world World to save
 | 
			
		||||
     */
 | 
			
		||||
    void saveWorld(World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unload all ChunkletStores from memory related to the given world after saving them
 | 
			
		||||
     *
 | 
			
		||||
     * @param world World to unload
 | 
			
		||||
     */
 | 
			
		||||
    void unloadWorld(World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load all ChunkletStores from all loaded chunks from this world into memory
 | 
			
		||||
     *
 | 
			
		||||
     * @param world World to load
 | 
			
		||||
     */
 | 
			
		||||
    void loadWorld(World world);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save all ChunkletStores
 | 
			
		||||
     */
 | 
			
		||||
    void saveAll();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unload all ChunkletStores after saving them
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    void setFalse(Block block);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete any ChunkletStores that are empty
 | 
			
		||||
     */
 | 
			
		||||
    void cleanUp();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.config.HiddenConfig;
 | 
			
		||||
 | 
			
		||||
public class ChunkletManagerFactory {
 | 
			
		||||
    public static ChunkletManager getChunkletManager() {
 | 
			
		||||
        HiddenConfig hConfig = HiddenConfig.getInstance();
 | 
			
		||||
 | 
			
		||||
        if (hConfig.getChunkletsEnabled()) {
 | 
			
		||||
            return new HashChunkletManager();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new NullChunkletManager();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A ChunkletStore should be responsible for a 16x16x64 area of data
 | 
			
		||||
 */
 | 
			
		||||
public interface ChunkletStore extends Serializable {
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks the value 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
 | 
			
		||||
     * @return true if the value is true at the given coordinates, false if otherwise
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    void setFalse(int x, int y, int z);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return true if all values in the chunklet are false, false if otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean isEmpty();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set all values in this ChunkletStore to the values from another provided ChunkletStore
 | 
			
		||||
     *
 | 
			
		||||
     * @param otherStore Another ChunkletStore that this one should copy all data from
 | 
			
		||||
     */
 | 
			
		||||
    void copyFrom(ChunkletStore otherStore);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
public class ChunkletStoreFactory {
 | 
			
		||||
    protected static ChunkletStore getChunkletStore() {
 | 
			
		||||
        // TODO: Add in loading from config what type of store we want.
 | 
			
		||||
        return new PrimitiveExChunkletStore();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,354 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.block.Block;
 | 
			
		||||
import org.bukkit.block.BlockState;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
public class HashChunkManager implements ChunkManager {
 | 
			
		||||
    private final HashMap<CoordinateKey, McMMOSimpleRegionFile> regionMap = new HashMap<>(); // Tracks active regions
 | 
			
		||||
    private final HashMap<CoordinateKey, HashSet<CoordinateKey>> chunkUsageMap = new HashMap<>(); // Tracks active chunks by region
 | 
			
		||||
    private final HashMap<CoordinateKey, ChunkStore> chunkMap = new HashMap<>(); // Tracks active chunks
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void closeAll() {
 | 
			
		||||
        // Save all dirty chunkstores
 | 
			
		||||
        for (ChunkStore chunkStore : chunkMap.values())
 | 
			
		||||
        {
 | 
			
		||||
            if (!chunkStore.isDirty())
 | 
			
		||||
                continue;
 | 
			
		||||
            writeChunkStore(Bukkit.getWorld(chunkStore.getWorldId()), chunkStore);
 | 
			
		||||
        }
 | 
			
		||||
        // Clear in memory chunks
 | 
			
		||||
        chunkMap.clear();
 | 
			
		||||
        chunkUsageMap.clear();
 | 
			
		||||
        // Close all region files
 | 
			
		||||
        for (McMMOSimpleRegionFile rf : regionMap.values())
 | 
			
		||||
            rf.close();
 | 
			
		||||
        regionMap.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized ChunkStore readChunkStore(World world, int cx, int cz) throws IOException {
 | 
			
		||||
        McMMOSimpleRegionFile rf = getSimpleRegionFile(world, cx, cz, false);
 | 
			
		||||
        if (rf == null)
 | 
			
		||||
            return null; // If there is no region file, there can't be a chunk
 | 
			
		||||
        try (DataInputStream in = rf.getInputStream(cx, cz)) { // Get input stream for chunk
 | 
			
		||||
            if (in == null)
 | 
			
		||||
                return null; // No chunk
 | 
			
		||||
            return BitSetChunkStore.Serialization.readChunkStore(in); // Read in the chunkstore
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized void writeChunkStore(World world, ChunkStore data) {
 | 
			
		||||
        if (!data.isDirty())
 | 
			
		||||
            return; // Don't save unchanged data
 | 
			
		||||
        try {
 | 
			
		||||
            McMMOSimpleRegionFile rf = getSimpleRegionFile(world, data.getChunkX(), data.getChunkZ(), true);
 | 
			
		||||
            try (DataOutputStream out = rf.getOutputStream(data.getChunkX(), data.getChunkZ())) {
 | 
			
		||||
                BitSetChunkStore.Serialization.writeChunkStore(out, data);
 | 
			
		||||
            }
 | 
			
		||||
            data.setDirty(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            throw new RuntimeException("Unable to write chunk meta data for " + data.getChunkX() + ", " + data.getChunkZ(), e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized McMMOSimpleRegionFile getSimpleRegionFile(World world, int cx, int cz, boolean createIfAbsent) {
 | 
			
		||||
        CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz);
 | 
			
		||||
 | 
			
		||||
        return regionMap.computeIfAbsent(regionKey, k -> {
 | 
			
		||||
            File worldRegionsDirectory = new File(world.getWorldFolder(), "mcmmo_regions");
 | 
			
		||||
            if (!createIfAbsent && !worldRegionsDirectory.isDirectory())
 | 
			
		||||
                return null; // Don't create the directory on read-only operations
 | 
			
		||||
            worldRegionsDirectory.mkdirs(); // Ensure directory exists
 | 
			
		||||
            File regionFile = new File(worldRegionsDirectory, "mcmmo_" + regionKey.x + "_" + regionKey.z + "_.mcm");
 | 
			
		||||
            if (!createIfAbsent && !regionFile.exists())
 | 
			
		||||
                return null; // Don't create the file on read-only operations
 | 
			
		||||
            return new McMMOSimpleRegionFile(regionFile, regionKey.x, regionKey.z);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ChunkStore loadChunk(int cx, int cz, World world) {
 | 
			
		||||
        try {
 | 
			
		||||
            return readChunkStore(world, cx, cz);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ignored) {}
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void unloadChunk(int cx, int cz, World world) {
 | 
			
		||||
        CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz);
 | 
			
		||||
        ChunkStore chunkStore = chunkMap.remove(chunkKey); // Remove from chunk map
 | 
			
		||||
        if (chunkStore == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (chunkStore.isDirty())
 | 
			
		||||
            writeChunkStore(world, chunkStore);
 | 
			
		||||
 | 
			
		||||
        CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz);
 | 
			
		||||
        HashSet<CoordinateKey> chunkKeys = chunkUsageMap.get(regionKey);
 | 
			
		||||
        chunkKeys.remove(chunkKey); // remove from region file in-use set
 | 
			
		||||
        if (chunkKeys.isEmpty()) // If it was last chunk in region, close the region file and remove it from memory
 | 
			
		||||
        {
 | 
			
		||||
            chunkUsageMap.remove(regionKey);
 | 
			
		||||
            regionMap.remove(regionKey).close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void saveChunk(int cx, int cz, World world) {
 | 
			
		||||
        if (world == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz);
 | 
			
		||||
 | 
			
		||||
        ChunkStore out = chunkMap.get(chunkKey);
 | 
			
		||||
 | 
			
		||||
        if (out == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (!out.isDirty())
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        writeChunkStore(world, out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void chunkUnloaded(int cx, int cz, World world) {
 | 
			
		||||
        if (world == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        unloadChunk(cx, cz, world);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void saveWorld(World world) {
 | 
			
		||||
        if (world == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        UUID wID = world.getUID();
 | 
			
		||||
 | 
			
		||||
        // Save all teh chunks
 | 
			
		||||
        for (ChunkStore chunkStore : chunkMap.values()) {
 | 
			
		||||
            if (!chunkStore.isDirty())
 | 
			
		||||
                continue;
 | 
			
		||||
            if (!wID.equals(chunkStore.getWorldId()))
 | 
			
		||||
                continue;
 | 
			
		||||
            try {
 | 
			
		||||
                writeChunkStore(world, chunkStore);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ignore) { }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void unloadWorld(World world) {
 | 
			
		||||
        if (world == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        UUID wID = world.getUID();
 | 
			
		||||
 | 
			
		||||
        // Save and remove all the chunks
 | 
			
		||||
        List<CoordinateKey> chunkKeys = new ArrayList<>(chunkMap.keySet());
 | 
			
		||||
        for (CoordinateKey chunkKey : chunkKeys) {
 | 
			
		||||
            if (!wID.equals(chunkKey.worldID))
 | 
			
		||||
                continue;
 | 
			
		||||
            ChunkStore chunkStore = chunkMap.remove(chunkKey);
 | 
			
		||||
            if (!chunkStore.isDirty())
 | 
			
		||||
                continue;
 | 
			
		||||
            try {
 | 
			
		||||
                writeChunkStore(world, chunkStore);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ignore) { }
 | 
			
		||||
        }
 | 
			
		||||
        // Clear all the region files
 | 
			
		||||
        List<CoordinateKey> regionKeys = new ArrayList<>(regionMap.keySet());
 | 
			
		||||
        for (CoordinateKey regionKey : regionKeys) {
 | 
			
		||||
            if (!wID.equals(regionKey.worldID))
 | 
			
		||||
                continue;
 | 
			
		||||
            regionMap.remove(regionKey).close();
 | 
			
		||||
            chunkUsageMap.remove(regionKey);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void saveAll() {
 | 
			
		||||
        for (World world : mcMMO.p.getServer().getWorlds()) {
 | 
			
		||||
            saveWorld(world);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized boolean isTrue(int x, int y, int z, World world) {
 | 
			
		||||
        if (world == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z);
 | 
			
		||||
 | 
			
		||||
        // Get chunk, load from file if necessary
 | 
			
		||||
        // Get/Load/Create chunkstore
 | 
			
		||||
        ChunkStore check = chunkMap.computeIfAbsent(chunkKey, k -> {
 | 
			
		||||
            // Load from file
 | 
			
		||||
            ChunkStore loaded = loadChunk(chunkKey.x, chunkKey.z, world);
 | 
			
		||||
            if (loaded == null)
 | 
			
		||||
                return null;
 | 
			
		||||
            // Mark chunk in-use for region tracking
 | 
			
		||||
            chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
 | 
			
		||||
            return loaded;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // No chunk, return false
 | 
			
		||||
        if (check == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        int ix = Math.abs(x) % 16;
 | 
			
		||||
        int iz = Math.abs(z) % 16;
 | 
			
		||||
 | 
			
		||||
        return check.isTrue(ix, y, iz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized boolean isTrue(Block block) {
 | 
			
		||||
        if (block == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized boolean isTrue(BlockState blockState) {
 | 
			
		||||
        if (blockState == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setTrue(int x, int y, int z, World world) {
 | 
			
		||||
        set(x, y, z, world, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setTrue(Block block) {
 | 
			
		||||
        if (block == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setTrue(BlockState blockState) {
 | 
			
		||||
        if (blockState == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setFalse(int x, int y, int z, World world) {
 | 
			
		||||
        set(x, y, z, world, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setFalse(Block block) {
 | 
			
		||||
        if (block == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setFalse(BlockState blockState) {
 | 
			
		||||
        if (blockState == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        setFalse(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized  void set(int x, int y, int z, World world, boolean value){
 | 
			
		||||
        if (world == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z);
 | 
			
		||||
 | 
			
		||||
        // Get/Load/Create chunkstore
 | 
			
		||||
        ChunkStore cStore = chunkMap.computeIfAbsent(chunkKey, k -> {
 | 
			
		||||
            // Load from file
 | 
			
		||||
            ChunkStore loaded = loadChunk(chunkKey.x, chunkKey.z, world);
 | 
			
		||||
            if (loaded != null)
 | 
			
		||||
            {
 | 
			
		||||
                chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
 | 
			
		||||
                return loaded;
 | 
			
		||||
            }
 | 
			
		||||
            // If setting to false, no need to create an empty chunkstore
 | 
			
		||||
            if (!value)
 | 
			
		||||
                return null;
 | 
			
		||||
            // Mark chunk in-use for region tracking
 | 
			
		||||
            chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
 | 
			
		||||
            // Create a new chunkstore
 | 
			
		||||
            return new BitSetChunkStore(world, chunkKey.x, chunkKey.z);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Indicates setting false on empty chunkstore
 | 
			
		||||
        if (cStore == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        // Get block offset (offset from chunk corner)
 | 
			
		||||
        int ix = Math.abs(x) % 16;
 | 
			
		||||
        int iz = Math.abs(z) % 16;
 | 
			
		||||
 | 
			
		||||
        // Set chunk store value
 | 
			
		||||
        cStore.set(ix, y, iz, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private CoordinateKey blockCoordinateToChunkKey(UUID worldUid, int x, int y, int z) {
 | 
			
		||||
        return toChunkKey(worldUid, x >> 4, z >> 4);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private CoordinateKey toChunkKey(UUID worldUid, int cx, int cz){
 | 
			
		||||
        return new CoordinateKey(worldUid, cx, cz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private CoordinateKey toRegionKey(UUID worldUid, int cx, int cz) {
 | 
			
		||||
        // Compute region index (32x32 chunk regions)
 | 
			
		||||
        int rx = cx >> 5;
 | 
			
		||||
        int rz = cz >> 5;
 | 
			
		||||
        return new CoordinateKey(worldUid, rx, rz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final class CoordinateKey {
 | 
			
		||||
        public final UUID worldID;
 | 
			
		||||
        public final int x;
 | 
			
		||||
        public final int z;
 | 
			
		||||
 | 
			
		||||
        private CoordinateKey(UUID worldID, int x, int z) {
 | 
			
		||||
            this.worldID = worldID;
 | 
			
		||||
            this.x = x;
 | 
			
		||||
            this.z = z;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean equals(Object o) {
 | 
			
		||||
            if (this == o) return true;
 | 
			
		||||
            if (o == null || getClass() != o.getClass()) return false;
 | 
			
		||||
            CoordinateKey coordinateKey = (CoordinateKey) o;
 | 
			
		||||
            return x == coordinateKey.x &&
 | 
			
		||||
                    z == coordinateKey.z &&
 | 
			
		||||
                    worldID.equals(coordinateKey.worldID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int hashCode() {
 | 
			
		||||
            return Objects.hash(worldID, x, z);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void cleanUp() {}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,410 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.block.Block;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
 | 
			
		||||
public class HashChunkletManager implements ChunkletManager {
 | 
			
		||||
    public HashMap<String, ChunkletStore> store = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadChunklet(int cx, int cy, 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;
 | 
			
		||||
        }
 | 
			
		||||
        File yFile = new File(czDir, "" + cy);
 | 
			
		||||
        if (!yFile.exists()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ChunkletStore in = deserializeChunkletStore(yFile);
 | 
			
		||||
        if (in != null) {
 | 
			
		||||
            store.put(world.getName() + "," + cx + "," + cz + "," + cy, in);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadChunklet(int cx, int cy, int cz, World world) {
 | 
			
		||||
        File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
 | 
			
		||||
        if (store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) {
 | 
			
		||||
            File cxDir = new File(dataDir, "" + cx);
 | 
			
		||||
            if (!cxDir.exists()) {
 | 
			
		||||
                cxDir.mkdir();
 | 
			
		||||
            }
 | 
			
		||||
            File czDir = new File(cxDir, "" + cz);
 | 
			
		||||
            if (!czDir.exists()) {
 | 
			
		||||
                czDir.mkdir();
 | 
			
		||||
            }
 | 
			
		||||
            File yFile = new File(czDir, "" + cy);
 | 
			
		||||
 | 
			
		||||
            ChunkletStore out = store.get(world.getName() + "," + cx + "," + cz + "," + cy);
 | 
			
		||||
            serializeChunkletStore(out, yFile);
 | 
			
		||||
            store.remove(world.getName() + "," + cx + "," + cz + "," + cy);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadChunk(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 = 0; y < 4; y++) {
 | 
			
		||||
            File yFile = new File(czDir, "" + y);
 | 
			
		||||
            if (!yFile.exists()) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ChunkletStore in = deserializeChunkletStore(yFile);
 | 
			
		||||
            if (in != null) {
 | 
			
		||||
                store.put(world.getName() + "," + cx + "," + cz + "," + y, in);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadChunk(int cx, int cz, World world) {
 | 
			
		||||
        File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
 | 
			
		||||
 | 
			
		||||
        for (int y = 0; y < 4; y++) {
 | 
			
		||||
            if (store.containsKey(world.getName() + "," + cx + "," + cz + "," + y)) {
 | 
			
		||||
                File cxDir = new File(dataDir, "" + cx);
 | 
			
		||||
                if (!cxDir.exists()) {
 | 
			
		||||
                    cxDir.mkdir();
 | 
			
		||||
                }
 | 
			
		||||
                File czDir = new File(cxDir, "" + cz);
 | 
			
		||||
                if (!czDir.exists()) {
 | 
			
		||||
                    czDir.mkdir();
 | 
			
		||||
                }
 | 
			
		||||
                File yFile = new File(czDir, "" + y);
 | 
			
		||||
 | 
			
		||||
                ChunkletStore out = store.get(world.getName() + "," + cx + "," + cz + "," + y);
 | 
			
		||||
                serializeChunkletStore(out, yFile);
 | 
			
		||||
                store.remove(world.getName() + "," + cx + "," + cz + "," + y);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void chunkLoaded(int cx, int cz, World world) {
 | 
			
		||||
        //loadChunk(cx, cz, world);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void chunkUnloaded(int cx, int cz, World world) {
 | 
			
		||||
        unloadChunk(cx, cx, world);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveWorld(World world) {
 | 
			
		||||
        String worldName = world.getName();
 | 
			
		||||
        File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
 | 
			
		||||
        if (!dataDir.exists()) {
 | 
			
		||||
            dataDir.mkdirs();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    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);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadWorld(World world) {
 | 
			
		||||
        //for (Chunk chunk : world.getLoadedChunks()) {
 | 
			
		||||
        //  this.chunkLoaded(chunk.getX(), chunk.getZ(), world);
 | 
			
		||||
        //}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveAll() {
 | 
			
		||||
        for (World world : mcMMO.p.getServer().getWorlds()) {
 | 
			
		||||
            saveWorld(world);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadAll() {
 | 
			
		||||
        saveAll();
 | 
			
		||||
        for (World world : mcMMO.p.getServer().getWorlds()) {
 | 
			
		||||
            unloadWorld(world);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(int x, int y, int z, World world) {
 | 
			
		||||
        int cx = x >> 4;
 | 
			
		||||
        int cz = z >> 4;
 | 
			
		||||
        int cy = y >> 6;
 | 
			
		||||
 | 
			
		||||
        String key = world.getName() + "," + cx + "," + cz + "," + cy;
 | 
			
		||||
 | 
			
		||||
        if (!store.containsKey(key)) {
 | 
			
		||||
            loadChunklet(cx, cy, cz, world);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!store.containsKey(key)) {
 | 
			
		||||
            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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(Block block) {
 | 
			
		||||
        return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(int x, int y, int z, World world) {
 | 
			
		||||
        int cx = x >> 4;
 | 
			
		||||
        int cz = z >> 4;
 | 
			
		||||
        int cy = y >> 6;
 | 
			
		||||
 | 
			
		||||
        int ix = Math.abs(x) % 16;
 | 
			
		||||
        int iz = Math.abs(z) % 16;
 | 
			
		||||
        int iy = Math.abs(y) % 64;
 | 
			
		||||
 | 
			
		||||
        String key = world.getName() + "," + cx + "," + cz + "," + cy;
 | 
			
		||||
 | 
			
		||||
        if (!store.containsKey(key)) {
 | 
			
		||||
            loadChunklet(cx, cy, cz, world);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ChunkletStore cStore = store.get(key);
 | 
			
		||||
 | 
			
		||||
        if (cStore == null) {
 | 
			
		||||
            cStore = ChunkletStoreFactory.getChunkletStore();
 | 
			
		||||
 | 
			
		||||
            store.put(world.getName() + "," + cx + "," + cz + "," + cy, cStore);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cStore.setTrue(ix, iy, iz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(Block block) {
 | 
			
		||||
        setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFalse(int x, int y, int z, World world) {
 | 
			
		||||
        int cx = x >> 4;
 | 
			
		||||
        int cz = z >> 4;
 | 
			
		||||
        int cy = y >> 6;
 | 
			
		||||
 | 
			
		||||
        int ix = Math.abs(x) % 16;
 | 
			
		||||
        int iz = Math.abs(z) % 16;
 | 
			
		||||
        int iy = Math.abs(y) % 64;
 | 
			
		||||
 | 
			
		||||
        String key = world.getName() + "," + cx + "," + cz + "," + cy;
 | 
			
		||||
 | 
			
		||||
        if (!store.containsKey(key)) {
 | 
			
		||||
            loadChunklet(cx, cy, cz, world);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ChunkletStore cStore = store.get(key);
 | 
			
		||||
 | 
			
		||||
        if (cStore == null) {
 | 
			
		||||
            return; // No need to make a store for something we will be setting to false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cStore.setFalse(ix, iy, iz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFalse(Block block) {
 | 
			
		||||
        setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void cleanUp() {
 | 
			
		||||
        for (String key : store.keySet()) {
 | 
			
		||||
            if (store.get(key).isEmpty()) {
 | 
			
		||||
                String[] info = key.split(",");
 | 
			
		||||
                File dataDir = new File(mcMMO.p.getServer().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) {
 | 
			
		||||
        FileOutputStream fileOut = null;
 | 
			
		||||
        ObjectOutputStream objOut = null;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            if (!location.exists()) {
 | 
			
		||||
                location.createNewFile();
 | 
			
		||||
            }
 | 
			
		||||
            fileOut = new FileOutputStream(location);
 | 
			
		||||
            objOut = new ObjectOutputStream(fileOut);
 | 
			
		||||
            objOut.writeObject(cStore);
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException ex) {
 | 
			
		||||
            ex.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
        finally {
 | 
			
		||||
            if (objOut != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    objOut.flush();
 | 
			
		||||
                    objOut.close();
 | 
			
		||||
                }
 | 
			
		||||
                catch (IOException ex) {
 | 
			
		||||
                    ex.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (fileOut != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    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;
 | 
			
		||||
        FileInputStream fileIn = null;
 | 
			
		||||
        ObjectInputStream objIn = null;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            fileIn = new FileInputStream(location);
 | 
			
		||||
            objIn = new ObjectInputStream(new BufferedInputStream(fileIn));
 | 
			
		||||
            storeIn = (ChunkletStore) objIn.readObject();
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException ex) {
 | 
			
		||||
            if (ex instanceof EOFException) {
 | 
			
		||||
                // EOF should only happen on Chunklets that somehow have been corrupted.
 | 
			
		||||
                //mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " could not be read due to an EOFException, data in this area will be lost.");
 | 
			
		||||
                return ChunkletStoreFactory.getChunkletStore();
 | 
			
		||||
            }
 | 
			
		||||
            else if (ex instanceof StreamCorruptedException) {
 | 
			
		||||
                // StreamCorrupted happens when the Chunklet is no good.
 | 
			
		||||
                //mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " is corrupted, data in this area will be lost.");
 | 
			
		||||
                return ChunkletStoreFactory.getChunkletStore();
 | 
			
		||||
            }
 | 
			
		||||
            else if (ex instanceof UTFDataFormatException) {
 | 
			
		||||
                // UTF happens when the Chunklet cannot be read or is corrupted
 | 
			
		||||
                //mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " could not be read due to an UTFDataFormatException, data in this area will be lost.");
 | 
			
		||||
                return ChunkletStoreFactory.getChunkletStore();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ex.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
        catch (ClassNotFoundException ex) {
 | 
			
		||||
            ex.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
        finally {
 | 
			
		||||
            if (objIn != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    objIn.close();
 | 
			
		||||
                }
 | 
			
		||||
                catch (IOException ex) {
 | 
			
		||||
                    ex.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (fileIn != null) {
 | 
			
		||||
                try {
 | 
			
		||||
                    fileIn.close();
 | 
			
		||||
                }
 | 
			
		||||
                catch (IOException ex) {
 | 
			
		||||
                    ex.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Make this less messy, as it is, it's kinda... depressing to do it like this.
 | 
			
		||||
        // Might also make a mess when we move to stacks, but at that point I think I will write a new Manager...
 | 
			
		||||
        // IMPORTANT! If ChunkletStoreFactory is going to be returning something other than PrimitiveEx we need to remove this, as it will be breaking time for old maps
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
        if (!(storeIn instanceof PrimitiveExChunkletStore)) {
 | 
			
		||||
            ChunkletStore tempStore = ChunkletStoreFactory.getChunkletStore();
 | 
			
		||||
            if (storeIn != null) {
 | 
			
		||||
                tempStore.copyFrom(storeIn);
 | 
			
		||||
            }
 | 
			
		||||
            storeIn = tempStore;
 | 
			
		||||
        }
 | 
			
		||||
         */
 | 
			
		||||
 | 
			
		||||
        return storeIn;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,257 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of SpoutPlugin.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
 | 
			
		||||
 * SpoutPlugin is licensed under the GNU Lesser General Public License.
 | 
			
		||||
 *
 | 
			
		||||
 * SpoutPlugin is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * SpoutPlugin is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.BitSet;
 | 
			
		||||
import java.util.zip.DeflaterOutputStream;
 | 
			
		||||
import java.util.zip.InflaterInputStream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * File format:
 | 
			
		||||
 * bytes 0-4096 contain 1024 integer values representing the segment index of each chunk
 | 
			
		||||
 * bytes 4096-8192 contain 1024 integer values representing the byte length of each chunk
 | 
			
		||||
 * bytes 8192-8196 is the integer value of the segment exponent
 | 
			
		||||
 * bytes 8196-12288 are reserved for future use
 | 
			
		||||
 * bytes 12288+ contain the data segments, by default 1024 byte segments.
 | 
			
		||||
 * Chunk data is compressed and stored in 1 or more segments as needed.
 | 
			
		||||
 */
 | 
			
		||||
public class McMMOSimpleRegionFile {
 | 
			
		||||
    private static final int DEFAULT_SEGMENT_EXPONENT = 10; // TODO, analyze real world usage and determine if a smaller segment(512) is worth it or not. (need to know average chunkstore bytesize)
 | 
			
		||||
    private static final int DEFAULT_SEGMENT_SIZE = (int)Math.pow(2, DEFAULT_SEGMENT_EXPONENT); // 1024
 | 
			
		||||
    private static final int RESERVED_HEADER_BYTES = 12288; // This needs to be divisible by segment size
 | 
			
		||||
    private static final int NUM_CHUNKS = 1024; // 32x32
 | 
			
		||||
    private static final int SEEK_CHUNK_SEGMENT_INDICES = 0;
 | 
			
		||||
    private static final int SEEK_CHUNK_BYTE_LENGTHS = 4096;
 | 
			
		||||
    private static final int SEEK_FILE_INFO = 8192;
 | 
			
		||||
    // Chunk info
 | 
			
		||||
    private final int[] chunkSegmentIndex = new int[NUM_CHUNKS];
 | 
			
		||||
    private final int[] chunkNumBytes = new int[NUM_CHUNKS];
 | 
			
		||||
    private final int[] chunkNumSegments = new int[NUM_CHUNKS];
 | 
			
		||||
 | 
			
		||||
    // Segments
 | 
			
		||||
    private final BitSet segments = new BitSet(); // Used to denote which segments are in use or not
 | 
			
		||||
 | 
			
		||||
    // Segment size/mask
 | 
			
		||||
    private final int segmentExponent;
 | 
			
		||||
    private final int segmentMask;
 | 
			
		||||
 | 
			
		||||
    // File location
 | 
			
		||||
    private final File parent;
 | 
			
		||||
    // File access
 | 
			
		||||
    private final RandomAccessFile file;
 | 
			
		||||
 | 
			
		||||
    // Region index
 | 
			
		||||
    private final int rx;
 | 
			
		||||
    private final int rz;
 | 
			
		||||
 | 
			
		||||
    public McMMOSimpleRegionFile(File f, int rx, int rz) {
 | 
			
		||||
        this.rx = rx;
 | 
			
		||||
        this.rz = rz;
 | 
			
		||||
        this.parent = f;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.file = new RandomAccessFile(parent, "rw");
 | 
			
		||||
 | 
			
		||||
            // New file, write out header bytes
 | 
			
		||||
            if (file.length() < RESERVED_HEADER_BYTES) {
 | 
			
		||||
                file.write(new byte[RESERVED_HEADER_BYTES]);
 | 
			
		||||
                file.seek(SEEK_FILE_INFO);
 | 
			
		||||
                file.writeInt(DEFAULT_SEGMENT_EXPONENT);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            file.seek(SEEK_FILE_INFO);
 | 
			
		||||
            this.segmentExponent = file.readInt();
 | 
			
		||||
            this.segmentMask = (1 << segmentExponent) - 1;
 | 
			
		||||
 | 
			
		||||
            // Mark reserved segments reserved
 | 
			
		||||
            int reservedSegments = this.bytesToSegments(RESERVED_HEADER_BYTES);
 | 
			
		||||
            segments.set(0, reservedSegments, true);
 | 
			
		||||
 | 
			
		||||
            // Read chunk header data
 | 
			
		||||
            file.seek(SEEK_CHUNK_SEGMENT_INDICES);
 | 
			
		||||
            for (int i = 0; i < NUM_CHUNKS; i++)
 | 
			
		||||
                chunkSegmentIndex[i] = file.readInt();
 | 
			
		||||
 | 
			
		||||
            file.seek(SEEK_CHUNK_BYTE_LENGTHS);
 | 
			
		||||
            for (int i = 0; i < NUM_CHUNKS; i++) {
 | 
			
		||||
                chunkNumBytes[i] = file.readInt();
 | 
			
		||||
                chunkNumSegments[i] = bytesToSegments(chunkNumBytes[i]);
 | 
			
		||||
                markChunkSegments(i, true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            fixFileLength();
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException fnfe) {
 | 
			
		||||
            throw new RuntimeException(fnfe);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized DataOutputStream getOutputStream(int x, int z) {
 | 
			
		||||
        int index = getChunkIndex(x, z); // Get chunk index
 | 
			
		||||
        return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class McMMOSimpleChunkBuffer extends ByteArrayOutputStream {
 | 
			
		||||
        final McMMOSimpleRegionFile rf;
 | 
			
		||||
        final int index;
 | 
			
		||||
 | 
			
		||||
        McMMOSimpleChunkBuffer(McMMOSimpleRegionFile rf, int index) {
 | 
			
		||||
            super(DEFAULT_SEGMENT_SIZE);
 | 
			
		||||
            this.rf = rf;
 | 
			
		||||
            this.index = index;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void close() throws IOException {
 | 
			
		||||
            rf.write(index, buf, count);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized void write(int index, byte[] buffer, int size) throws IOException {
 | 
			
		||||
        int oldSegmentIndex = chunkSegmentIndex[index]; // Get current segment index
 | 
			
		||||
        markChunkSegments(index, false); // Clear our old segments
 | 
			
		||||
        int newSegmentIndex = findContiguousSegments(oldSegmentIndex, size); // Find contiguous segments to save to
 | 
			
		||||
        file.seek(newSegmentIndex << segmentExponent); // Seek to file location
 | 
			
		||||
        file.write(buffer, 0, size); // Write data
 | 
			
		||||
        // update in memory info
 | 
			
		||||
        chunkSegmentIndex[index] = newSegmentIndex;
 | 
			
		||||
        chunkNumBytes[index] = size;
 | 
			
		||||
        chunkNumSegments[index] = bytesToSegments(size);
 | 
			
		||||
        // Mark segments in use
 | 
			
		||||
        markChunkSegments(index, true);
 | 
			
		||||
        // Update header info
 | 
			
		||||
        file.seek(SEEK_CHUNK_SEGMENT_INDICES + (4 * index));
 | 
			
		||||
        file.writeInt(chunkSegmentIndex[index]);
 | 
			
		||||
        file.seek(SEEK_CHUNK_BYTE_LENGTHS + (4 * index));
 | 
			
		||||
        file.writeInt(chunkNumBytes[index]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized DataInputStream getInputStream(int x, int z) throws IOException {
 | 
			
		||||
        int index = getChunkIndex(x, z); // Get chunk index
 | 
			
		||||
        int byteLength = chunkNumBytes[index]; // Get byte length of data
 | 
			
		||||
 | 
			
		||||
        // No bytes
 | 
			
		||||
        if (byteLength == 0)
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        byte[] data = new byte[byteLength];
 | 
			
		||||
 | 
			
		||||
        file.seek(chunkSegmentIndex[index] << segmentExponent); // Seek to file location
 | 
			
		||||
        file.readFully(data); // Read in the data
 | 
			
		||||
        return new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized void close() {
 | 
			
		||||
        try {
 | 
			
		||||
            file.close();
 | 
			
		||||
            segments.clear();
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException ioe) {
 | 
			
		||||
            throw new RuntimeException("Unable to close file", ioe);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized void markChunkSegments(int index, boolean inUse) {
 | 
			
		||||
        // No bytes used
 | 
			
		||||
        if (chunkNumBytes[index] == 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        int start = chunkSegmentIndex[index];
 | 
			
		||||
        int end = start + chunkNumSegments[index];
 | 
			
		||||
 | 
			
		||||
        // If we are writing, assert we don't write over any in-use segments
 | 
			
		||||
        if (inUse)
 | 
			
		||||
        {
 | 
			
		||||
            int nextSetBit = segments.nextSetBit(start);
 | 
			
		||||
            if (nextSetBit != -1 && nextSetBit < end)
 | 
			
		||||
                throw new IllegalStateException("Attempting to overwrite an in-use segment");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        segments.set(start, end, inUse);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized void fixFileLength() throws IOException {
 | 
			
		||||
        int fileLength = (int)file.length();
 | 
			
		||||
        int extend = -fileLength & segmentMask; // how many bytes do we need to be divisible by segment size
 | 
			
		||||
 | 
			
		||||
        // Go to end of file
 | 
			
		||||
        file.seek(fileLength);
 | 
			
		||||
        // Append bytes
 | 
			
		||||
        file.write(new byte[extend], 0, extend);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized int findContiguousSegments(int hint, int size) {
 | 
			
		||||
        if (size == 0)
 | 
			
		||||
            return 0; // Zero byte data will not claim any chunks anyways
 | 
			
		||||
 | 
			
		||||
        int segments = bytesToSegments(size); // Number of segments we need
 | 
			
		||||
 | 
			
		||||
        // Check the hinted location (previous location of chunk) most of the time we can fit where we were.
 | 
			
		||||
        boolean oldFree = true;
 | 
			
		||||
        for (int i = hint; i < this.segments.size() && i < hint + segments; i++) {
 | 
			
		||||
            if (this.segments.get(i)) {
 | 
			
		||||
                oldFree = false;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // We fit!
 | 
			
		||||
        if (oldFree)
 | 
			
		||||
            return hint;
 | 
			
		||||
 | 
			
		||||
        // Find somewhere to put us
 | 
			
		||||
        int start = 0;
 | 
			
		||||
        int current = 0;
 | 
			
		||||
 | 
			
		||||
        while (current < this.segments.size()) {
 | 
			
		||||
            boolean segmentInUse = this.segments.get(current); // check if segment is in use
 | 
			
		||||
            current++; // Move up a segment
 | 
			
		||||
 | 
			
		||||
            // Move up start if the segment was in use
 | 
			
		||||
            if (segmentInUse)
 | 
			
		||||
                start = current;
 | 
			
		||||
 | 
			
		||||
            // If we have enough segments now, return
 | 
			
		||||
            if (current - start >= segments)
 | 
			
		||||
                return start;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Return the end of the segments (will expand to fit them)
 | 
			
		||||
        return start;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized int bytesToSegments(int bytes) {
 | 
			
		||||
        if (bytes <= 0)
 | 
			
		||||
            return 1;
 | 
			
		||||
 | 
			
		||||
        return ((bytes - 1) >> segmentExponent) + 1; // ((bytes - 1) / segmentSize) + 1
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized int getChunkIndex(int x, int z) {
 | 
			
		||||
        if (rx != (x >> 5) || rz != (z >> 5))
 | 
			
		||||
            throw new IndexOutOfBoundsException();
 | 
			
		||||
 | 
			
		||||
        x = x & 0x1F; // 5 bits (mod 32)
 | 
			
		||||
        z = z & 0x1F; // 5 bits (mod 32)
 | 
			
		||||
 | 
			
		||||
        return (x << 5) + z; // x in the upper 5 bits, z in the lower 5 bits
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,51 +1,17 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.block.Block;
 | 
			
		||||
import org.bukkit.block.BlockState;
 | 
			
		||||
import org.bukkit.entity.Entity;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class NullChunkManager implements ChunkManager {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void closeAll() {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ChunkStore readChunkStore(World world, int x, int z) throws IOException {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void writeChunkStore(World world, int x, int z, ChunkStore data) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void closeChunkStore(World world, int x, int z) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadChunklet(int cx, int cy, int cz, World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadChunklet(int cx, int cy, int cz, World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadChunk(int cx, int cz, World world, Entity[] entities) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadChunk(int cx, int cz, World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveChunk(int cx, int cz, World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isChunkLoaded(int cx, int cz, World world) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void chunkLoaded(int cx, int cz, World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void chunkUnloaded(int cx, int cz, World world) {}
 | 
			
		||||
 | 
			
		||||
@@ -55,15 +21,9 @@ public class NullChunkManager implements ChunkManager {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadWorld(World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadWorld(World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveAll() {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadAll() {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(int x, int y, int z, World world) {
 | 
			
		||||
        return false;
 | 
			
		||||
@@ -1,85 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.block.Block;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A ChunkletManager implementation that does nothing and returns false for all checks.
 | 
			
		||||
 *
 | 
			
		||||
 * Useful for turning off Chunklets without actually doing much work
 | 
			
		||||
 */
 | 
			
		||||
public class NullChunkletManager implements ChunkletManager {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadChunklet(int cx, int cy, int cz, World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadChunklet(int cx, int cy, int cz, World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadChunk(int cx, int cz, World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadChunk(int cx, int cz, World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void chunkLoaded(int cx, int cz, World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void chunkUnloaded(int cx, int cz, World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveWorld(World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadWorld(World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadWorld(World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveAll() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void unloadAll() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(int x, int y, int z, World world) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(Block block) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(int x, int y, int z, World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(Block block) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFalse(int x, int y, int z, World world) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFalse(Block block) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void cleanUp() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
public class PrimitiveChunkletStore implements ChunkletStore {
 | 
			
		||||
    private static final long serialVersionUID = -3453078050608607478L;
 | 
			
		||||
 | 
			
		||||
    /** X, Z, Y */
 | 
			
		||||
    public boolean[][][] store = new boolean[16][16][64];
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(int x, int y, int z) {
 | 
			
		||||
        return store[x][z][y];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(int x, int y, int z) {
 | 
			
		||||
        store[x][z][y] = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFalse(int x, int y, int z) {
 | 
			
		||||
        store[x][z][y] = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void copyFrom(ChunkletStore otherStore) {
 | 
			
		||||
        for (int x = 0; x < 16; x++) {
 | 
			
		||||
            for (int z = 0; z < 16; z++) {
 | 
			
		||||
                for (int y = 0; y < 64; y++) {
 | 
			
		||||
                    store[x][z][y] = otherStore.isTrue(x, y, z);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,180 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta;
 | 
			
		||||
 | 
			
		||||
import java.io.Externalizable;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.ObjectInput;
 | 
			
		||||
import java.io.ObjectOutput;
 | 
			
		||||
 | 
			
		||||
public class PrimitiveExChunkletStore implements ChunkletStore, Externalizable {
 | 
			
		||||
    private static final long serialVersionUID = 8603603827094383873L;
 | 
			
		||||
 | 
			
		||||
    /** X, Z, Y */
 | 
			
		||||
    public boolean[][][] store = new boolean[16][16][64];
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(int x, int y, int z) {
 | 
			
		||||
        return store[x][z][y];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(int x, int y, int z) {
 | 
			
		||||
        store[x][z][y] = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFalse(int x, int y, int z) {
 | 
			
		||||
        store[x][z][y] = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void copyFrom(ChunkletStore otherStore) {
 | 
			
		||||
        for (int x = 0; x < 16; x++) {
 | 
			
		||||
            for (int z = 0; z < 16; z++) {
 | 
			
		||||
                for (int y = 0; y < 64; y++) {
 | 
			
		||||
                    store[x][z][y] = otherStore.isTrue(x, y, z);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void writeExternal(ObjectOutput out) throws IOException {
 | 
			
		||||
        byte[] buffer = new byte[2304]; // 2304 is 16*16*9
 | 
			
		||||
        int bufferIndex = 0;
 | 
			
		||||
 | 
			
		||||
        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]) {
 | 
			
		||||
                        byte[] temp = constructColumn(x, z);
 | 
			
		||||
 | 
			
		||||
                        for (int i = 0; i < 9; i++) {
 | 
			
		||||
                            buffer[bufferIndex] = temp[i];
 | 
			
		||||
                            bufferIndex++;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        out.write(buffer, 0, bufferIndex);
 | 
			
		||||
        out.flush();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // For this we assume that store has been initialized to be all false by now
 | 
			
		||||
    @Override
 | 
			
		||||
    public void readExternal(ObjectInput in) throws IOException {
 | 
			
		||||
        byte[] temp = new byte[9];
 | 
			
		||||
 | 
			
		||||
        // Could probably reorganize this loop to print nasty things if it does not equal 9 or -1
 | 
			
		||||
        while (in.read(temp, 0, 9) == 9) {
 | 
			
		||||
            int x = addressByteX(temp[0]);
 | 
			
		||||
            int z = addressByteZ(temp[0]);
 | 
			
		||||
            boolean[] yColumn = new boolean[64];
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < 8; i++) {
 | 
			
		||||
                for (int j = 0; j < 8; j++) {
 | 
			
		||||
                    yColumn[j + (i * 8)] = (temp[i + 1] & (1 << j)) != 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            store[x][z] = yColumn;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * The column: An array of 9 bytes which represent all y values for a given (x,z) Chunklet-coordinate
 | 
			
		||||
     *
 | 
			
		||||
     * The first byte is an address byte, this provides the x and z values.
 | 
			
		||||
     * The next 8 bytes are all y values from 0 to 63, with each byte containing 8 bits of true/false data
 | 
			
		||||
     *
 | 
			
		||||
     * Each of these 8 bytes address to a y value from right to left
 | 
			
		||||
     *
 | 
			
		||||
     * Examples:
 | 
			
		||||
     * 00000001 represents that the lowest y value in this byte is true, all others are off
 | 
			
		||||
     * 10000000 represents that the highest y value in this byte is true, all others are off
 | 
			
		||||
     * 10000001 represents that the lowest and highest y values in this byte are true, all others are off
 | 
			
		||||
     *
 | 
			
		||||
     * Full columns:
 | 
			
		||||
     * See comment on Address byte for information on how to use that byte
 | 
			
		||||
     *
 | 
			
		||||
     * Example:
 | 
			
		||||
     * ADDRESS_BYTE 10000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000
 | 
			
		||||
     *  - x, z from ADDRESS_BYTE
 | 
			
		||||
     *  - The next byte contains data from 0 to 7
 | 
			
		||||
     *    - 1 is set in the highest bit position, this is 7 in y coordinate
 | 
			
		||||
     *  - The next byte contains data from 8 to 15
 | 
			
		||||
     *    - 1 is set in the lowest bit position, this is 8 in the y coordinate
 | 
			
		||||
     *  Therefore, for this column: There are true values at (x, 7, z) and (x, 8, z)
 | 
			
		||||
     */
 | 
			
		||||
    private byte[] constructColumn(int x, int z) {
 | 
			
		||||
        byte[] column = new byte[9];
 | 
			
		||||
        int index = 1;
 | 
			
		||||
 | 
			
		||||
        column[0] = makeAddressByte(x, z);
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < 8; i++) {
 | 
			
		||||
            byte yCompressed = 0x0;
 | 
			
		||||
            int subColumnIndex = 8 * i;
 | 
			
		||||
            int subColumnEnd = subColumnIndex + 8;
 | 
			
		||||
 | 
			
		||||
            for (int y = subColumnIndex; y < subColumnEnd; y++) {
 | 
			
		||||
                if (store[x][z][y]) {
 | 
			
		||||
                    yCompressed |= 1 << (y % 8);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            column[index] = yCompressed;
 | 
			
		||||
            index++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return column;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * The address byte: A single byte which contains x and z values which correspond to the x and z Chunklet-coordinates
 | 
			
		||||
     *
 | 
			
		||||
     * In Chunklet-coordinates, the only valid values are 0-15, so we can fit both into a single byte.
 | 
			
		||||
     *
 | 
			
		||||
     * The top 4 bits of the address byte are for the x value
 | 
			
		||||
     * The bottom 4 bits of the address byte are for the z value
 | 
			
		||||
     *
 | 
			
		||||
     * Examples:
 | 
			
		||||
     * An address byte with a value 00000001 would be split like so:
 | 
			
		||||
     *  - x = 0000 = 0
 | 
			
		||||
     *  - z = 0001 = 1
 | 
			
		||||
     *  => Chunklet coordinates (0, 1)
 | 
			
		||||
     *
 | 
			
		||||
     * 01011111
 | 
			
		||||
     *  - x = 0101 = 5
 | 
			
		||||
     *  - z = 1111 = 15
 | 
			
		||||
     *  => Chunklet coordinates (5, 15)
 | 
			
		||||
     */
 | 
			
		||||
    protected static byte makeAddressByte(int x, int z) {
 | 
			
		||||
        return (byte) ((x << 4) + z);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static int addressByteX(byte address) {
 | 
			
		||||
        return (address & 0xF0) >>> 4;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static int addressByteZ(byte address) {
 | 
			
		||||
        return address & 0x0F;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
 | 
			
		||||
public class ChunkStoreFactory {
 | 
			
		||||
    protected static ChunkStore getChunkStore(World world, int x, int z) {
 | 
			
		||||
        // TODO: Add in loading from config what type of store we want.
 | 
			
		||||
        return new PrimitiveChunkStore(world, x, z);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,447 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.conversion.BlockStoreConversionZDirectory;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
import org.bukkit.block.Block;
 | 
			
		||||
import org.bukkit.block.BlockState;
 | 
			
		||||
import org.bukkit.entity.Entity;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
public class HashChunkManager implements ChunkManager {
 | 
			
		||||
    private final HashMap<UUID, HashMap<Long, McMMOSimpleRegionFile>> regionFiles = new HashMap<>();
 | 
			
		||||
    public HashMap<String, ChunkStore> store = new HashMap<>();
 | 
			
		||||
    public ArrayList<BlockStoreConversionZDirectory> converters = new ArrayList<>();
 | 
			
		||||
    private final HashMap<UUID, Boolean> oldData = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void closeAll() {
 | 
			
		||||
        for (UUID uid : regionFiles.keySet()) {
 | 
			
		||||
            HashMap<Long, McMMOSimpleRegionFile> worldRegions = regionFiles.get(uid);
 | 
			
		||||
            for (Iterator<McMMOSimpleRegionFile> worldRegionIterator = worldRegions.values().iterator(); worldRegionIterator.hasNext(); ) {
 | 
			
		||||
                McMMOSimpleRegionFile rf = worldRegionIterator.next();
 | 
			
		||||
                if (rf != null) {
 | 
			
		||||
                    rf.close();
 | 
			
		||||
                    worldRegionIterator.remove();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        regionFiles.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized ChunkStore readChunkStore(World world, int x, int z) throws IOException {
 | 
			
		||||
        McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
 | 
			
		||||
        InputStream in = rf.getInputStream(x, z);
 | 
			
		||||
        if (in == null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        try (ObjectInputStream objectStream = new ObjectInputStream(in)) {
 | 
			
		||||
            Object o = objectStream.readObject();
 | 
			
		||||
            if (o instanceof ChunkStore) {
 | 
			
		||||
                return (ChunkStore) o;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw new RuntimeException("Wrong class type read for chunk meta data for " + x + ", " + z);
 | 
			
		||||
        } catch (IOException | ClassNotFoundException e) {
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
            // Assume the format changed
 | 
			
		||||
            return null;
 | 
			
		||||
            //throw new RuntimeException("Unable to process chunk meta data for " + x + ", " + z, e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void writeChunkStore(World world, int x, int z, ChunkStore data) {
 | 
			
		||||
        if (!data.isDirty()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
 | 
			
		||||
            ObjectOutputStream objectStream = new ObjectOutputStream(rf.getOutputStream(x, z));
 | 
			
		||||
            objectStream.writeObject(data);
 | 
			
		||||
            objectStream.flush();
 | 
			
		||||
            objectStream.close();
 | 
			
		||||
            data.setDirty(false);
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            throw new RuntimeException("Unable to write chunk meta data for " + x + ", " + z, e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void closeChunkStore(World world, int x, int z) {
 | 
			
		||||
        McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
 | 
			
		||||
        if (rf != null) {
 | 
			
		||||
            rf.close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized McMMOSimpleRegionFile getSimpleRegionFile(World world, int x, int z) {
 | 
			
		||||
        File directory = new File(world.getWorldFolder(), "mcmmo_regions");
 | 
			
		||||
 | 
			
		||||
        directory.mkdirs();
 | 
			
		||||
 | 
			
		||||
        UUID key = world.getUID();
 | 
			
		||||
 | 
			
		||||
        HashMap<Long, McMMOSimpleRegionFile> worldRegions = regionFiles.computeIfAbsent(key, k -> new HashMap<>());
 | 
			
		||||
 | 
			
		||||
        int rx = x >> 5;
 | 
			
		||||
        int rz = z >> 5;
 | 
			
		||||
 | 
			
		||||
        long key2 = (((long) rx) << 32) | ((rz) & 0xFFFFFFFFL);
 | 
			
		||||
 | 
			
		||||
        McMMOSimpleRegionFile regionFile = worldRegions.get(key2);
 | 
			
		||||
 | 
			
		||||
        if (regionFile == null) {
 | 
			
		||||
            File file = new File(directory, "mcmmo_" + rx + "_" + rz + "_.mcm");
 | 
			
		||||
            regionFile = new McMMOSimpleRegionFile(file, rx, rz);
 | 
			
		||||
            worldRegions.put(key2, regionFile);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return regionFile;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void loadChunklet(int cx, int cy, int cz, World world) {
 | 
			
		||||
        loadChunk(cx, cz, world, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void unloadChunklet(int cx, int cy, int cz, World world) {
 | 
			
		||||
        unloadChunk(cx, cz, world);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void loadChunk(int cx, int cz, World world, Entity[] entities) {
 | 
			
		||||
        if (world == null || store.containsKey(world.getName() + "," + cx + "," + cz)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        UUID key = world.getUID();
 | 
			
		||||
 | 
			
		||||
        if (!oldData.containsKey(key)) {
 | 
			
		||||
            oldData.put(key, (new File(world.getWorldFolder(), "mcmmo_data")).exists());
 | 
			
		||||
        }
 | 
			
		||||
        else if (oldData.get(key)) {
 | 
			
		||||
            if (convertChunk(new File(world.getWorldFolder(), "mcmmo_data"), cx, cz, world, true)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ChunkStore chunkStore = null;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            chunkStore = readChunkStore(world, cx, cz);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception e) { e.printStackTrace(); }
 | 
			
		||||
 | 
			
		||||
        if (chunkStore == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        store.put(world.getName() + "," + cx + "," + cz, chunkStore);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void unloadChunk(int cx, int cz, World world) {
 | 
			
		||||
        saveChunk(cx, cz, world);
 | 
			
		||||
 | 
			
		||||
        if (store.containsKey(world.getName() + "," + cx + "," + cz)) {
 | 
			
		||||
            store.remove(world.getName() + "," + cx + "," + cz);
 | 
			
		||||
 | 
			
		||||
            //closeChunkStore(world, cx, cz);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void saveChunk(int cx, int cz, World world) {
 | 
			
		||||
        if (world == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String key = world.getName() + "," + cx + "," + cz;
 | 
			
		||||
 | 
			
		||||
        if (store.containsKey(key)) {
 | 
			
		||||
            ChunkStore out = store.get(world.getName() + "," + cx + "," + cz);
 | 
			
		||||
 | 
			
		||||
            if (!out.isDirty()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            writeChunkStore(world, cx, cz, out);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized boolean isChunkLoaded(int cx, int cz, World world) {
 | 
			
		||||
        if (world == null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return store.containsKey(world.getName() + "," + cx + "," + cz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void chunkLoaded(int cx, int cz, World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void chunkUnloaded(int cx, int cz, World world) {
 | 
			
		||||
        if (world == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        unloadChunk(cx, cz, world);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void saveWorld(World world) {
 | 
			
		||||
        if (world == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        closeAll();
 | 
			
		||||
        String worldName = world.getName();
 | 
			
		||||
 | 
			
		||||
        List<String> keys = new ArrayList<>(store.keySet());
 | 
			
		||||
        for (String key : keys) {
 | 
			
		||||
            String[] info = key.split(",");
 | 
			
		||||
            if (worldName.equals(info[0])) {
 | 
			
		||||
                try {
 | 
			
		||||
                    saveChunk(Integer.parseInt(info[1]), Integer.parseInt(info[2]), world);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception e) {
 | 
			
		||||
                    // Ignore
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void unloadWorld(World world) {
 | 
			
		||||
        if (world == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String worldName = world.getName();
 | 
			
		||||
 | 
			
		||||
        List<String> keys = new ArrayList<>(store.keySet());
 | 
			
		||||
        for (String key : keys) {
 | 
			
		||||
            String[] info = key.split(",");
 | 
			
		||||
            if (worldName.equals(info[0])) {
 | 
			
		||||
                try {
 | 
			
		||||
                    unloadChunk(Integer.parseInt(info[1]), Integer.parseInt(info[2]), world);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception e) {
 | 
			
		||||
                    // Ignore
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        closeAll();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void loadWorld(World world) {}
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void saveAll() {
 | 
			
		||||
        closeAll();
 | 
			
		||||
 | 
			
		||||
        for (World world : mcMMO.p.getServer().getWorlds()) {
 | 
			
		||||
            saveWorld(world);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void unloadAll() {
 | 
			
		||||
        closeAll();
 | 
			
		||||
 | 
			
		||||
        for (World world : mcMMO.p.getServer().getWorlds()) {
 | 
			
		||||
            unloadWorld(world);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized boolean isTrue(int x, int y, int z, World world) {
 | 
			
		||||
        if (world == null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int cx = x >> 4;
 | 
			
		||||
        int cz = z >> 4;
 | 
			
		||||
 | 
			
		||||
        String key = world.getName() + "," + cx + "," + cz;
 | 
			
		||||
 | 
			
		||||
        if (!store.containsKey(key)) {
 | 
			
		||||
            loadChunk(cx, cz, world, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!store.containsKey(key)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ChunkStore check = store.get(key);
 | 
			
		||||
        int ix = Math.abs(x) % 16;
 | 
			
		||||
        int iz = Math.abs(z) % 16;
 | 
			
		||||
 | 
			
		||||
        return check.isTrue(ix, y, iz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized boolean isTrue(Block block) {
 | 
			
		||||
        if (block == null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized boolean isTrue(BlockState blockState) {
 | 
			
		||||
        if (blockState == null) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setTrue(int x, int y, int z, World world) {
 | 
			
		||||
        if (world == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int cx = x >> 4;
 | 
			
		||||
        int cz = z >> 4;
 | 
			
		||||
 | 
			
		||||
        int ix = Math.abs(x) % 16;
 | 
			
		||||
        int iz = Math.abs(z) % 16;
 | 
			
		||||
 | 
			
		||||
        String key = world.getName() + "," + cx + "," + cz;
 | 
			
		||||
 | 
			
		||||
        if (!store.containsKey(key)) {
 | 
			
		||||
            loadChunk(cx, cz, world, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ChunkStore cStore = store.get(key);
 | 
			
		||||
 | 
			
		||||
        if (cStore == null) {
 | 
			
		||||
            cStore = ChunkStoreFactory.getChunkStore(world, cx, cz);
 | 
			
		||||
            store.put(key, cStore);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cStore.setTrue(ix, y, iz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setTrue(Block block) {
 | 
			
		||||
        if (block == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(BlockState blockState) {
 | 
			
		||||
        if (blockState == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setFalse(int x, int y, int z, World world) {
 | 
			
		||||
        if (world == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int cx = x >> 4;
 | 
			
		||||
        int cz = z >> 4;
 | 
			
		||||
 | 
			
		||||
        int ix = Math.abs(x) % 16;
 | 
			
		||||
        int iz = Math.abs(z) % 16;
 | 
			
		||||
 | 
			
		||||
        String key = world.getName() + "," + cx + "," + cz;
 | 
			
		||||
 | 
			
		||||
        if (!store.containsKey(key)) {
 | 
			
		||||
            loadChunk(cx, cz, world, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ChunkStore cStore = store.get(key);
 | 
			
		||||
 | 
			
		||||
        if (cStore == null) {
 | 
			
		||||
            return; // No need to make a store for something we will be setting to false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cStore.setFalse(ix, y, iz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setFalse(Block block) {
 | 
			
		||||
        if (block == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void setFalse(BlockState blockState) {
 | 
			
		||||
        if (blockState == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setFalse(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public synchronized void cleanUp() {}
 | 
			
		||||
 | 
			
		||||
    public synchronized void convertChunk(File dataDir, int cx, int cz, World world) {
 | 
			
		||||
        convertChunk(dataDir, cx, cz, world, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized boolean convertChunk(File dataDir, int cx, int cz, World world, boolean actually) {
 | 
			
		||||
        if (!actually || !dataDir.exists()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        File cxDir = new File(dataDir, "" + cx);
 | 
			
		||||
        if (!cxDir.exists()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        File czDir = new File(cxDir, "" + cz);
 | 
			
		||||
        if (!czDir.exists()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        boolean conversionSet = false;
 | 
			
		||||
 | 
			
		||||
        for (BlockStoreConversionZDirectory converter : this.converters) {
 | 
			
		||||
            if (converter == null) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (converter.taskID >= 0) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            converter.start(world, cxDir, czDir);
 | 
			
		||||
            conversionSet = true;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!conversionSet) {
 | 
			
		||||
            BlockStoreConversionZDirectory converter = new BlockStoreConversionZDirectory();
 | 
			
		||||
            converter.start(world, cxDir, czDir);
 | 
			
		||||
            converters.add(converter);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of SpoutPlugin.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
 | 
			
		||||
 * SpoutPlugin is licensed under the GNU Lesser General Public License.
 | 
			
		||||
 *
 | 
			
		||||
 * SpoutPlugin is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * SpoutPlugin is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
 | 
			
		||||
import java.io.ByteArrayOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class McMMOSimpleChunkBuffer extends ByteArrayOutputStream {
 | 
			
		||||
    final McMMOSimpleRegionFile rf;
 | 
			
		||||
    final int index;
 | 
			
		||||
 | 
			
		||||
    McMMOSimpleChunkBuffer(McMMOSimpleRegionFile rf, int index) {
 | 
			
		||||
        super(1024);
 | 
			
		||||
        this.rf = rf;
 | 
			
		||||
        this.index = index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void close() throws IOException {
 | 
			
		||||
        rf.write(index, buf, count);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,306 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of SpoutPlugin.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
 | 
			
		||||
 * SpoutPlugin is licensed under the GNU Lesser General Public License.
 | 
			
		||||
 *
 | 
			
		||||
 * SpoutPlugin is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * SpoutPlugin is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Lesser General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.zip.DeflaterOutputStream;
 | 
			
		||||
import java.util.zip.InflaterInputStream;
 | 
			
		||||
 | 
			
		||||
public class McMMOSimpleRegionFile {
 | 
			
		||||
    private RandomAccessFile file;
 | 
			
		||||
    private final int[] dataStart = new int[1024];
 | 
			
		||||
    private final int[] dataActualLength = new int[1024];
 | 
			
		||||
    private final int[] dataLength = new int[1024];
 | 
			
		||||
    private final ArrayList<Boolean> inuse = new ArrayList<>();
 | 
			
		||||
    private int segmentSize;
 | 
			
		||||
    private int segmentMask;
 | 
			
		||||
    private final int rx;
 | 
			
		||||
    private final int rz;
 | 
			
		||||
    private final int defaultSegmentSize;
 | 
			
		||||
    private final File parent;
 | 
			
		||||
    @SuppressWarnings("unused")
 | 
			
		||||
    private long lastAccessTime = System.currentTimeMillis();
 | 
			
		||||
    @SuppressWarnings("unused")
 | 
			
		||||
    private static final long TIMEOUT_TIME = 300000; // 5 min
 | 
			
		||||
 | 
			
		||||
    public McMMOSimpleRegionFile(File f, int rx, int rz) {
 | 
			
		||||
        this(f, rx, rz, 10);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public McMMOSimpleRegionFile(File f, int rx, int rz, int defaultSegmentSize) {
 | 
			
		||||
        this.rx = rx;
 | 
			
		||||
        this.rz = rz;
 | 
			
		||||
        this.defaultSegmentSize = defaultSegmentSize;
 | 
			
		||||
        this.parent = f;
 | 
			
		||||
 | 
			
		||||
        lastAccessTime = System.currentTimeMillis();
 | 
			
		||||
        if (file == null) {
 | 
			
		||||
            try {
 | 
			
		||||
                this.file = new RandomAccessFile(parent, "rw");
 | 
			
		||||
 | 
			
		||||
                if (file.length() < 4096 * 3) {
 | 
			
		||||
                    for (int i = 0; i < 1024 * 3; i++) {
 | 
			
		||||
                        file.writeInt(0);
 | 
			
		||||
                    }
 | 
			
		||||
                    file.seek(4096 * 2);
 | 
			
		||||
                    file.writeInt(defaultSegmentSize);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                file.seek(4096 * 2);
 | 
			
		||||
 | 
			
		||||
                this.segmentSize = file.readInt();
 | 
			
		||||
                this.segmentMask = (1 << segmentSize) - 1;
 | 
			
		||||
 | 
			
		||||
                int reservedSegments = this.sizeToSegments(4096 * 3);
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < reservedSegments; i++) {
 | 
			
		||||
                    while (inuse.size() <= i) {
 | 
			
		||||
                        inuse.add(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    inuse.set(i, true);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                file.seek(0);
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < 1024; i++) {
 | 
			
		||||
                    dataStart[i] = file.readInt();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < 1024; i++) {
 | 
			
		||||
                    dataActualLength[i] = file.readInt();
 | 
			
		||||
                    dataLength[i] = sizeToSegments(dataActualLength[i]);
 | 
			
		||||
                    setInUse(i, true);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                extendFile();
 | 
			
		||||
            }
 | 
			
		||||
            catch (IOException fnfe) {
 | 
			
		||||
                throw new RuntimeException(fnfe);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized final RandomAccessFile getFile() {
 | 
			
		||||
        lastAccessTime = System.currentTimeMillis();
 | 
			
		||||
        if (file == null) {
 | 
			
		||||
            try {
 | 
			
		||||
                this.file = new RandomAccessFile(parent, "rw");
 | 
			
		||||
 | 
			
		||||
                if (file.length() < 4096 * 3) {
 | 
			
		||||
                    for (int i = 0; i < 1024 * 3; i++) {
 | 
			
		||||
                        file.writeInt(0);
 | 
			
		||||
                    }
 | 
			
		||||
                    file.seek(4096 * 2);
 | 
			
		||||
                    file.writeInt(defaultSegmentSize);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                file.seek(4096 * 2);
 | 
			
		||||
 | 
			
		||||
                this.segmentSize = file.readInt();
 | 
			
		||||
                this.segmentMask = (1 << segmentSize) - 1;
 | 
			
		||||
 | 
			
		||||
                int reservedSegments = this.sizeToSegments(4096 * 3);
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < reservedSegments; i++) {
 | 
			
		||||
                    while (inuse.size() <= i) {
 | 
			
		||||
                        inuse.add(false);
 | 
			
		||||
                    }
 | 
			
		||||
                    inuse.set(i, true);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                file.seek(0);
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < 1024; i++) {
 | 
			
		||||
                    dataStart[i] = file.readInt();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (int i = 0; i < 1024; i++) {
 | 
			
		||||
                    dataActualLength[i] = file.readInt();
 | 
			
		||||
                    dataLength[i] = sizeToSegments(dataActualLength[i]);
 | 
			
		||||
                    setInUse(i, true);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                extendFile();
 | 
			
		||||
            }
 | 
			
		||||
            catch (IOException fnfe) {
 | 
			
		||||
                throw new RuntimeException(fnfe);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized boolean testCloseTimeout() {
 | 
			
		||||
        /*
 | 
			
		||||
        if (System.currentTimeMillis() - TIMEOUT_TIME > lastAccessTime) {
 | 
			
		||||
            close();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
         */
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized DataOutputStream getOutputStream(int x, int z) {
 | 
			
		||||
        int index = getChunkIndex(x, z);
 | 
			
		||||
        return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized DataInputStream getInputStream(int x, int z) throws IOException {
 | 
			
		||||
        int index = getChunkIndex(x, z);
 | 
			
		||||
        int actualLength = dataActualLength[index];
 | 
			
		||||
 | 
			
		||||
        if (actualLength == 0) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        byte[] data = new byte[actualLength];
 | 
			
		||||
 | 
			
		||||
        getFile().seek(dataStart[index] << segmentSize);
 | 
			
		||||
        getFile().readFully(data);
 | 
			
		||||
        return new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    synchronized void write(int index, byte[] buffer, int size) throws IOException {
 | 
			
		||||
        int oldStart = setInUse(index, false);
 | 
			
		||||
        int start = findSpace(oldStart, size);
 | 
			
		||||
        getFile().seek(start << segmentSize);
 | 
			
		||||
        getFile().write(buffer, 0, size);
 | 
			
		||||
        dataStart[index] = start;
 | 
			
		||||
        dataActualLength[index] = size;
 | 
			
		||||
        dataLength[index] = sizeToSegments(size);
 | 
			
		||||
        setInUse(index, true);
 | 
			
		||||
        saveFAT();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized void close() {
 | 
			
		||||
        try {
 | 
			
		||||
            if (file != null) {
 | 
			
		||||
                file.seek(4096 * 2);
 | 
			
		||||
                file.close();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            file = null;
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException ioe) {
 | 
			
		||||
            throw new RuntimeException("Unable to close file", ioe);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized int setInUse(int index, boolean used) {
 | 
			
		||||
        if (dataActualLength[index] == 0) {
 | 
			
		||||
            return dataStart[index];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int start = dataStart[index];
 | 
			
		||||
        int end = start + dataLength[index];
 | 
			
		||||
 | 
			
		||||
        for (int i = start; i < end; i++) {
 | 
			
		||||
            while (i > inuse.size() - 1) {
 | 
			
		||||
                inuse.add(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Boolean old = inuse.set(i, used);
 | 
			
		||||
            if (old != null && old == used) {
 | 
			
		||||
                if (old) {
 | 
			
		||||
                    throw new IllegalStateException("Attempting to overwrite an in-use segment");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                throw new IllegalStateException("Attempting to delete empty segment");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return dataStart[index];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized void extendFile() throws IOException {
 | 
			
		||||
        long extend = (-getFile().length()) & segmentMask;
 | 
			
		||||
 | 
			
		||||
        getFile().seek(getFile().length());
 | 
			
		||||
 | 
			
		||||
        while ((extend--) > 0) {
 | 
			
		||||
            getFile().write(0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized int findSpace(int oldStart, int size) {
 | 
			
		||||
        int segments = sizeToSegments(size);
 | 
			
		||||
 | 
			
		||||
        boolean oldFree = true;
 | 
			
		||||
        for (int i = oldStart; i < inuse.size() && i < oldStart + segments; i++) {
 | 
			
		||||
            if (inuse.get(i)) {
 | 
			
		||||
                oldFree = false;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (oldFree) {
 | 
			
		||||
            return oldStart;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int start = 0;
 | 
			
		||||
        int end = 0;
 | 
			
		||||
 | 
			
		||||
        while (end < inuse.size()) {
 | 
			
		||||
            if (inuse.get(end)) {
 | 
			
		||||
                end++;
 | 
			
		||||
                start = end;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                end++;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (end - start >= segments) {
 | 
			
		||||
                return start;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return start;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized int sizeToSegments(int size) {
 | 
			
		||||
        if (size <= 0) {
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ((size - 1) >> segmentSize) + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized Integer getChunkIndex(int x, int z) {
 | 
			
		||||
        if (rx != (x >> 5) || rz != (z >> 5)) {
 | 
			
		||||
            throw new RuntimeException(x + ", " + z + " not in region " + rx + ", " + rz);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        x = x & 0x1F;
 | 
			
		||||
        z = z & 0x1F;
 | 
			
		||||
 | 
			
		||||
        return (x << 5) + z;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private synchronized void saveFAT() throws IOException {
 | 
			
		||||
        getFile().seek(0);
 | 
			
		||||
        for (int i = 0; i < 1024; i++) {
 | 
			
		||||
            getFile().writeInt(dataStart[i]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < 1024; i++) {
 | 
			
		||||
            getFile().writeInt(dataActualLength[i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,147 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.ObjectInputStream;
 | 
			
		||||
import java.io.ObjectOutputStream;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class PrimitiveChunkStore implements ChunkStore {
 | 
			
		||||
    private static final long serialVersionUID = -1L;
 | 
			
		||||
    transient private boolean dirty = false;
 | 
			
		||||
    /** X, Z, Y */
 | 
			
		||||
    public boolean[][][] store;
 | 
			
		||||
    private static final int CURRENT_VERSION = 7;
 | 
			
		||||
    private static final int MAGIC_NUMBER = 0xEA5EDEBB;
 | 
			
		||||
    private int cx;
 | 
			
		||||
    private int cz;
 | 
			
		||||
    private UUID worldUid;
 | 
			
		||||
 | 
			
		||||
    public PrimitiveChunkStore(World world, int cx, int cz) {
 | 
			
		||||
        this.cx = cx;
 | 
			
		||||
        this.cz = cz;
 | 
			
		||||
        this.worldUid = world.getUID();
 | 
			
		||||
        this.store = new boolean[16][16][world.getMaxHeight()];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isDirty() {
 | 
			
		||||
        return dirty;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setDirty(boolean dirty) {
 | 
			
		||||
        this.dirty = dirty;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getChunkX() {
 | 
			
		||||
        return cx;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getChunkZ() {
 | 
			
		||||
        return cz;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isTrue(int x, int y, int z) {
 | 
			
		||||
        return store[x][z][y];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setTrue(int x, int y, int z) {
 | 
			
		||||
        if (y >= store[0][0].length || y < 0)
 | 
			
		||||
            return;
 | 
			
		||||
        store[x][z][y] = true;
 | 
			
		||||
        dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFalse(int x, int y, int z) {
 | 
			
		||||
        if (y >= store[0][0].length || y < 0)
 | 
			
		||||
            return;
 | 
			
		||||
        store[x][z][y] = false;
 | 
			
		||||
        dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isEmpty() {
 | 
			
		||||
        for (int x = 0; x < 16; x++) {
 | 
			
		||||
            for (int z = 0; z < 16; z++) {
 | 
			
		||||
                for (int y = 0; y < store[0][0].length; y++) {
 | 
			
		||||
                    if (store[x][z][y]) {
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void copyFrom(ChunkletStore otherStore) {
 | 
			
		||||
        for (int x = 0; x < 16; x++) {
 | 
			
		||||
            for (int z = 0; z < 16; z++) {
 | 
			
		||||
                for (int y = 0; y < store[0][0].length; y++) {
 | 
			
		||||
                    store[x][z][y] = otherStore.isTrue(x, y, z);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        dirty = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void writeObject(ObjectOutputStream out) throws IOException {
 | 
			
		||||
        out.writeInt(MAGIC_NUMBER);
 | 
			
		||||
        out.writeInt(CURRENT_VERSION);
 | 
			
		||||
 | 
			
		||||
        out.writeLong(worldUid.getLeastSignificantBits());
 | 
			
		||||
        out.writeLong(worldUid.getMostSignificantBits());
 | 
			
		||||
        out.writeInt(cx);
 | 
			
		||||
        out.writeInt(cz);
 | 
			
		||||
        out.writeObject(store);
 | 
			
		||||
 | 
			
		||||
        dirty = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
 | 
			
		||||
        int magic = in.readInt();
 | 
			
		||||
        // Can be used to determine the format of the file
 | 
			
		||||
        int fileVersionNumber = in.readInt();
 | 
			
		||||
 | 
			
		||||
        if (magic != MAGIC_NUMBER) {
 | 
			
		||||
            fileVersionNumber = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        long lsb = in.readLong();
 | 
			
		||||
        long msb = in.readLong();
 | 
			
		||||
        worldUid = new UUID(msb, lsb);
 | 
			
		||||
        cx = in.readInt();
 | 
			
		||||
        cz = in.readInt();
 | 
			
		||||
 | 
			
		||||
        store = (boolean[][][]) in.readObject();
 | 
			
		||||
 | 
			
		||||
        if (fileVersionNumber < 5) {
 | 
			
		||||
            fixArray();
 | 
			
		||||
            dirty = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void fixArray() {
 | 
			
		||||
        boolean[][][] temp = this.store;
 | 
			
		||||
        this.store = new boolean[16][16][Bukkit.getWorld(worldUid).getMaxHeight()];
 | 
			
		||||
        for (int x = 0; x < 16; x++) {
 | 
			
		||||
            for (int z = 0; z < 16; z++) {
 | 
			
		||||
                for (int y = 0; y < store[0][0].length; y++) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        store[x][z][y] = temp[x][y][z];
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception e) { e.printStackTrace(); }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,90 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.conversion;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.config.HiddenConfig;
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import org.bukkit.scheduler.BukkitScheduler;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
public class BlockStoreConversionMain implements Runnable {
 | 
			
		||||
    private int taskID, i;
 | 
			
		||||
    private org.bukkit.World world;
 | 
			
		||||
    BukkitScheduler scheduler;
 | 
			
		||||
    File dataDir;
 | 
			
		||||
    File[] xDirs;
 | 
			
		||||
    BlockStoreConversionXDirectory[] converters;
 | 
			
		||||
 | 
			
		||||
    public BlockStoreConversionMain(org.bukkit.World world) {
 | 
			
		||||
        this.taskID = -1;
 | 
			
		||||
        this.world = world;
 | 
			
		||||
        this.scheduler = mcMMO.p.getServer().getScheduler();
 | 
			
		||||
        this.dataDir = new File(this.world.getWorldFolder(), "mcmmo_data");
 | 
			
		||||
        this.converters = new BlockStoreConversionXDirectory[HiddenConfig.getInstance().getConversionRate()];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void start() {
 | 
			
		||||
        if (this.taskID >= 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run() {
 | 
			
		||||
        if (!this.dataDir.exists()) {
 | 
			
		||||
            softStop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.dataDir.isDirectory()) {
 | 
			
		||||
            this.dataDir.delete();
 | 
			
		||||
            softStop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.dataDir.listFiles().length <= 0) {
 | 
			
		||||
            this.dataDir.delete();
 | 
			
		||||
            softStop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.xDirs = this.dataDir.listFiles();
 | 
			
		||||
 | 
			
		||||
        for (this.i = 0; (this.i < HiddenConfig.getInstance().getConversionRate()) && (this.i < this.xDirs.length); this.i++) {
 | 
			
		||||
            if (this.converters[this.i] == null) {
 | 
			
		||||
                this.converters[this.i] = new BlockStoreConversionXDirectory();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.converters[this.i].start(this.world, this.xDirs[this.i]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        softStop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void stop() {
 | 
			
		||||
        if (this.taskID < 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.scheduler.cancelTask(this.taskID);
 | 
			
		||||
        this.taskID = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void softStop() {
 | 
			
		||||
        stop();
 | 
			
		||||
 | 
			
		||||
        if (this.dataDir.exists() && this.dataDir.isDirectory()) {
 | 
			
		||||
            start();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mcMMO.p.getLogger().info("Finished converting the storage for " + world.getName() + ".");
 | 
			
		||||
 | 
			
		||||
        this.dataDir = null;
 | 
			
		||||
        this.xDirs = null;
 | 
			
		||||
        this.world = null;
 | 
			
		||||
        this.scheduler = null;
 | 
			
		||||
        this.converters = null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,80 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.conversion;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.config.HiddenConfig;
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import org.bukkit.scheduler.BukkitScheduler;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
public class BlockStoreConversionXDirectory implements Runnable {
 | 
			
		||||
    private int taskID, i;
 | 
			
		||||
    private org.bukkit.World world;
 | 
			
		||||
    BukkitScheduler scheduler;
 | 
			
		||||
    File dataDir;
 | 
			
		||||
    File[] zDirs;
 | 
			
		||||
    BlockStoreConversionZDirectory[] converters;
 | 
			
		||||
 | 
			
		||||
    public BlockStoreConversionXDirectory() {
 | 
			
		||||
        this.taskID = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void start(org.bukkit.World world, File dataDir) {
 | 
			
		||||
        this.world = world;
 | 
			
		||||
        this.scheduler = mcMMO.p.getServer().getScheduler();
 | 
			
		||||
        this.converters = new BlockStoreConversionZDirectory[HiddenConfig.getInstance().getConversionRate()];
 | 
			
		||||
        this.dataDir = dataDir;
 | 
			
		||||
 | 
			
		||||
        if (this.taskID >= 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run() {
 | 
			
		||||
        if (!this.dataDir.exists()) {
 | 
			
		||||
            stop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.dataDir.isDirectory()) {
 | 
			
		||||
            this.dataDir.delete();
 | 
			
		||||
            stop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.dataDir.listFiles().length <= 0) {
 | 
			
		||||
            this.dataDir.delete();
 | 
			
		||||
            stop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.zDirs = this.dataDir.listFiles();
 | 
			
		||||
 | 
			
		||||
        for (this.i = 0; (this.i < HiddenConfig.getInstance().getConversionRate()) && (this.i < this.zDirs.length); this.i++) {
 | 
			
		||||
            if (this.converters[this.i] == null) {
 | 
			
		||||
                this.converters[this.i] = new BlockStoreConversionZDirectory();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.converters[this.i].start(this.world, this.dataDir, this.zDirs[this.i]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void stop() {
 | 
			
		||||
        if (this.taskID < 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.scheduler.cancelTask(this.taskID);
 | 
			
		||||
        this.taskID = -1;
 | 
			
		||||
 | 
			
		||||
        this.dataDir = null;
 | 
			
		||||
        this.zDirs = null;
 | 
			
		||||
        this.world = null;
 | 
			
		||||
        this.scheduler = null;
 | 
			
		||||
        this.converters = null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,191 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.blockmeta.conversion;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.HashChunkletManager;
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.PrimitiveChunkletStore;
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.PrimitiveExChunkletStore;
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.HashChunkManager;
 | 
			
		||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore;
 | 
			
		||||
import org.bukkit.scheduler.BukkitScheduler;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
public class BlockStoreConversionZDirectory implements Runnable {
 | 
			
		||||
    public int taskID, cx, cz, x, y, z, y2, xPos, zPos, cxPos, czPos;
 | 
			
		||||
    private String cxs, czs, chunkletName, chunkName;
 | 
			
		||||
    private org.bukkit.World world;
 | 
			
		||||
    private BukkitScheduler scheduler;
 | 
			
		||||
    private File xDir, dataDir;
 | 
			
		||||
    private HashChunkletManager manager;
 | 
			
		||||
    private HashChunkManager newManager;
 | 
			
		||||
    private ChunkletStore tempChunklet;
 | 
			
		||||
    private PrimitiveChunkletStore primitiveChunklet = null;
 | 
			
		||||
    private PrimitiveExChunkletStore primitiveExChunklet = null;
 | 
			
		||||
    private PrimitiveChunkStore currentChunk;
 | 
			
		||||
    private boolean[] oldArray, newArray;
 | 
			
		||||
 | 
			
		||||
    public BlockStoreConversionZDirectory() {
 | 
			
		||||
        this.taskID = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void start(org.bukkit.World world, File xDir, File dataDir) {
 | 
			
		||||
        this.world = world;
 | 
			
		||||
        this.scheduler = mcMMO.p.getServer().getScheduler();
 | 
			
		||||
        this.manager = new HashChunkletManager();
 | 
			
		||||
        this.newManager = (HashChunkManager) mcMMO.getPlaceStore();
 | 
			
		||||
        this.dataDir = dataDir;
 | 
			
		||||
        this.xDir = xDir;
 | 
			
		||||
 | 
			
		||||
        if (this.taskID >= 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run() {
 | 
			
		||||
        if (!this.dataDir.exists()) {
 | 
			
		||||
            stop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.dataDir.isDirectory()) {
 | 
			
		||||
            this.dataDir.delete();
 | 
			
		||||
            stop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.dataDir.listFiles().length <= 0) {
 | 
			
		||||
            this.dataDir.delete();
 | 
			
		||||
            stop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.cxs = this.xDir.getName();
 | 
			
		||||
        this.czs = this.dataDir.getName();
 | 
			
		||||
        this.cx = 0;
 | 
			
		||||
        this.cz = 0;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.cx = Integer.parseInt(this.cxs);
 | 
			
		||||
            this.cz = Integer.parseInt(this.czs);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception e) {
 | 
			
		||||
            this.dataDir.delete();
 | 
			
		||||
            stop();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.manager.loadChunk(this.cx, this.cz, this.world);
 | 
			
		||||
 | 
			
		||||
        for (this.y = 0; this.y < (this.world.getMaxHeight() / 64); this.y++) {
 | 
			
		||||
            this.chunkletName = this.world.getName() + "," + this.cx + "," + this.cz + "," + this.y;
 | 
			
		||||
            this.tempChunklet = this.manager.store.get(this.chunkletName);
 | 
			
		||||
 | 
			
		||||
            if (this.tempChunklet instanceof PrimitiveChunkletStore) {
 | 
			
		||||
                this.primitiveChunklet = (PrimitiveChunkletStore) this.tempChunklet;
 | 
			
		||||
            }
 | 
			
		||||
            else if (this.tempChunklet instanceof PrimitiveExChunkletStore) {
 | 
			
		||||
                this.primitiveExChunklet = (PrimitiveExChunkletStore) this.tempChunklet;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.tempChunklet == null) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.chunkName = this.world.getName() + "," + this.cx + "," + this.cz;
 | 
			
		||||
            this.currentChunk = (PrimitiveChunkStore) this.newManager.store.get(this.chunkName);
 | 
			
		||||
 | 
			
		||||
            if (this.currentChunk != null) {
 | 
			
		||||
                this.xPos = this.cx * 16;
 | 
			
		||||
                this.zPos = this.cz * 16;
 | 
			
		||||
 | 
			
		||||
                for (this.x = 0; this.x < 16; this.x++) {
 | 
			
		||||
                    for (this.z = 0; this.z < 16; this.z++) {
 | 
			
		||||
                        this.cxPos = this.xPos + this.x;
 | 
			
		||||
                        this.czPos = this.zPos + this.z;
 | 
			
		||||
 | 
			
		||||
                        for (this.y2 = (64 * this.y); this.y2 < (64 * this.y + 64); this.y2++) {
 | 
			
		||||
                            try {
 | 
			
		||||
                                if (!this.manager.isTrue(this.cxPos, this.y2, this.czPos, this.world)) {
 | 
			
		||||
                                    continue;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                this.newManager.setTrue(this.cxPos, this.y2, this.czPos, this.world);
 | 
			
		||||
                            }
 | 
			
		||||
                            catch (Exception e) { e.printStackTrace(); }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.newManager.setTrue(this.cx * 16, 0, this.cz * 16, this.world);
 | 
			
		||||
            this.newManager.setFalse(this.cx * 16, 0, this.cz * 16, this.world);
 | 
			
		||||
            this.currentChunk = (PrimitiveChunkStore) this.newManager.store.get(this.chunkName);
 | 
			
		||||
 | 
			
		||||
            for (this.x = 0; this.x < 16; this.x++) {
 | 
			
		||||
                for (this.z = 0; this.z < 16; this.z++) {
 | 
			
		||||
                    if (this.primitiveChunklet != null) {
 | 
			
		||||
                        this.oldArray = this.primitiveChunklet.store[x][z];
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (this.primitiveExChunklet != null) {
 | 
			
		||||
                        this.oldArray = this.primitiveExChunklet.store[x][z];
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    this.newArray = this.currentChunk.store[x][z];
 | 
			
		||||
 | 
			
		||||
                    if (this.oldArray.length < 64) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (this.newArray.length < ((this.y * 64) + 64)) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    System.arraycopy(this.oldArray, 0, this.newArray, (this.y * 64), 64);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.manager.unloadChunk(this.cx, this.cz, this.world);
 | 
			
		||||
        this.newManager.unloadChunk(this.cx, this.cz, this.world);
 | 
			
		||||
 | 
			
		||||
        for (File yFile : dataDir.listFiles()) {
 | 
			
		||||
            if (!yFile.exists()) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            yFile.delete();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void stop() {
 | 
			
		||||
        if (this.taskID < 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.scheduler.cancelTask(taskID);
 | 
			
		||||
        this.taskID = -1;
 | 
			
		||||
 | 
			
		||||
        this.cxs = null;
 | 
			
		||||
        this.czs = null;
 | 
			
		||||
        this.chunkletName = null;
 | 
			
		||||
        this.chunkName = null;
 | 
			
		||||
        this.manager = null;
 | 
			
		||||
        this.xDir = null;
 | 
			
		||||
        this.dataDir = null;
 | 
			
		||||
        this.tempChunklet = null;
 | 
			
		||||
        this.primitiveChunklet = null;
 | 
			
		||||
        this.primitiveExChunklet = null;
 | 
			
		||||
        this.currentChunk = null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user