diff --git a/pom.xml b/pom.xml index 6dce9e08b..85553551c 100755 --- a/pom.xml +++ b/pom.xml @@ -121,6 +121,10 @@ + + net.kyori.examination + com.gmail.nossr50.kyori.examination + net.kyori.adventure com.gmail.nossr50.mcmmo.kyori.adventure diff --git a/src/main/java/com/gmail/nossr50/listeners/BlockListener.java b/src/main/java/com/gmail/nossr50/listeners/BlockListener.java index a2a4c2a76..a6d8e7900 100644 --- a/src/main/java/com/gmail/nossr50/listeners/BlockListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/BlockListener.java @@ -253,12 +253,18 @@ public class BlockListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onBlockGrow(BlockGrowEvent event) { + Block block = event.getBlock(); + World world = block.getWorld(); + /* WORLD BLACKLIST CHECK */ - if(WorldBlacklist.isWorldBlacklisted(event.getBlock().getWorld())) + if(WorldBlacklist.isWorldBlacklisted(world)) return; - BlockState blockState = event.getBlock().getState(); - mcMMO.getPlaceStore().setFalse(blockState); + // Minecraft is dumb, the events still throw when a plant "grows" higher than the max block height. Even though no new block is created + if (block.getY() >= world.getMaxHeight()) + return; + + mcMMO.getPlaceStore().setFalse(block); } /** diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index f3f6bb9ac..c956b65f4 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -345,7 +345,6 @@ public class mcMMO extends JavaPlugin { formulaManager.saveFormula(); holidayManager.saveAnniversaryFiles(); - placeStore.cleanUp(); // Cleanup empty metadata stores placeStore.closeAll(); } diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java b/src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java index dce7ab174..a04ed501b 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java @@ -2,32 +2,36 @@ package com.gmail.nossr50.util.blockmeta; import org.bukkit.Bukkit; import org.bukkit.World; +import org.jetbrains.annotations.NotNull; 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; +public class BitSetChunkStore implements ChunkStore { 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 final int cx; + private final int cz; + private final int worldHeight; + private final UUID worldUid; + // 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 final BitSet store; + + private transient boolean dirty = false; + + public BitSetChunkStore(@NotNull World world, int cx, int cz) { + this(world.getUID(), world.getMaxHeight(), cx, cz); } - private BitSetChunkStore() {} + private BitSetChunkStore(@NotNull UUID worldUid, int worldHeight, int cx, int cz) { + this.cx = cx; + this.cz = cz; + this.worldUid = worldUid; + this.worldHeight = worldHeight; + this.store = new BitSet(16 * 16 * worldHeight); + } @Override public boolean isDirty() { @@ -50,7 +54,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable { } @Override - public UUID getWorldId() { + public @NotNull UUID getWorldId() { return worldUid; } @@ -81,58 +85,24 @@ public class BitSetChunkStore implements ChunkStore, Serializable { } private int coordToIndex(int x, int y, int z) { + return coordToIndex(x, y, z, worldHeight); + } + + private static int coordToIndex(int x, int y, int z, int worldHeight) { if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16) - throw new IndexOutOfBoundsException(); + throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Height: %d", x, y, z, worldHeight)); return (z * 16 + x) + (256 * y); } - private void fixWorldHeight() { + private static int getWorldHeight(UUID worldUid, int storedWorldHeight) + { 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; + return storedWorldHeight; - // 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(); + return world.getMaxHeight(); } private void serialize(DataOutputStream out) throws IOException { @@ -153,7 +123,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable { dirty = false; } - private static BitSetChunkStore deserialize(DataInputStream in) throws IOException { + private static BitSetChunkStore deserialize(@NotNull DataInputStream in) throws IOException { int magic = in.readInt(); // Can be used to determine the format of the file int fileVersionNumber = in.readInt(); @@ -161,28 +131,36 @@ public class BitSetChunkStore implements ChunkStore, Serializable { 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(); + UUID worldUid = new UUID(msb, lsb); + int cx = in.readInt(); + int cz = in.readInt(); - chunkStore.worldHeight = in.readInt(); + int worldHeight = in.readInt(); byte[] temp = new byte[in.readInt()]; in.readFully(temp); - chunkStore.store = BitSet.valueOf(temp); + BitSet stored = BitSet.valueOf(temp); + + int currentWorldHeight = getWorldHeight(worldUid, worldHeight); + + boolean worldHeightShrunk = currentWorldHeight < worldHeight; + // Lop off extra data if world height has shrunk + if (worldHeightShrunk) + stored.clear(coordToIndex(16, currentWorldHeight, 16, worldHeight), stored.length()); + + BitSetChunkStore chunkStore = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz); + chunkStore.store.or(stored); + chunkStore.dirty = worldHeightShrunk; // In the expanded case there is no reason to re-write it unless the data changes - chunkStore.fixWorldHeight(); return chunkStore; } public static class Serialization { - public static final short STREAM_MAGIC = (short)0xACDC; + public static final short STREAM_MAGIC = (short)0xACDC; // Rock on - public static ChunkStore readChunkStore(DataInputStream inputStream) throws IOException { + public static @NotNull ChunkStore readChunkStore(DataInputStream inputStream) throws IOException { if (inputStream.markSupported()) inputStream.mark(2); short magicNumber = inputStream.readShort(); @@ -196,7 +174,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable { { // 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) & 0xFF); pushbackInputStream.unread((magicNumber >>> 8) & 0xFF); inputStream = new DataInputStream(pushbackInputStream); } @@ -216,8 +194,61 @@ public class BitSetChunkStore implements ChunkStore, Serializable { ((BitSetChunkStore)chunkStore).serialize(outputStream); } - // Handles loading the old serialized classes even though we have changed name/package + // Handles loading the old serialized class private static class LegacyDeserializationInputStream extends ObjectInputStream { + private static class LegacyChunkStoreDeserializer implements Serializable + { + private static final long serialVersionUID = -1L; + + private int cx; + private int cz; + private int worldHeight; + private UUID worldUid; + private boolean[][][] store; + + private LegacyChunkStoreDeserializer() {} + + @Deprecated + private void writeObject(ObjectOutputStream out) throws IOException { + throw new UnsupportedOperationException("You goofed."); + } + + @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(); + + store = (boolean[][][]) in.readObject(); + worldHeight = store[0][0].length; + } + + public BitSetChunkStore convert() + { + int currentWorldHeight = getWorldHeight(worldUid, worldHeight); + + BitSetChunkStore converted = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz); + + // Read old data into new chunkstore + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < worldHeight && y < currentWorldHeight; y++) { + converted.store.set(converted.coordToIndex(x, y, z), store[x][z][y]); + } + } + } + // Mark dirty so it will be re-written in new format on close + converted.dirty = true; + return converted; + } + } + + public LegacyDeserializationInputStream(InputStream in) throws IOException { super(in); enableResolveObject(true); @@ -227,13 +258,14 @@ public class BitSetChunkStore implements ChunkStore, Serializable { 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 ObjectStreamClass.lookup(LegacyChunkStoreDeserializer.class); return read; } public ChunkStore readLegacyChunkStore(){ try { - return (ChunkStore) readObject(); + LegacyChunkStoreDeserializer deserializer = (LegacyChunkStoreDeserializer)readObject(); + return deserializer.convert(); } catch (IOException | ClassNotFoundException e) { return null; } diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManager.java index 5cb50ac7f..a0b61ba6f 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManager.java @@ -1,126 +1,10 @@ package com.gmail.nossr50.util.blockmeta; import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; +import org.jetbrains.annotations.NotNull; -public interface ChunkManager { +public interface ChunkManager extends UserBlockTracker { void closeAll(); - - /** - * Saves a given Chunk's Chunklet data - * - * @param cx Chunk X coordinate that is to be saved - * @param cz Chunk Z coordinate that is to be saved - * @param world World that the Chunk is in - */ - void saveChunk(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); - - /** - * Save all ChunkletStores - */ - void saveAll(); - - /** - * 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); - - /** - * Check to see if a given BlockState location is set to true - * - * @param blockState BlockState to check - * @return true if the given BlockState location is set to true, false if otherwise - */ - boolean isTrue(BlockState blockState); - - /** - * 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 BlockState location to true, should create stores as necessary if the location does not exist - * - * @param blockState BlockState location to set - */ - void setTrue(BlockState blockState); - - /** - * 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); - - /** - * Set a given BlockState location to false, should not create stores if one does not exist for the given location - * - * @param blockState BlockState location to set - */ - void setFalse(BlockState blockState); - - /** - * Delete any ChunkletStores that are empty - */ - void cleanUp(); + void chunkUnloaded(int cx, int cz, @NotNull World world); + void unloadWorld(@NotNull World world); } diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManagerFactory.java b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManagerFactory.java index a290c5e2a..e2c47662e 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManagerFactory.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManagerFactory.java @@ -1,9 +1,10 @@ package com.gmail.nossr50.util.blockmeta; import com.gmail.nossr50.config.HiddenConfig; +import org.jetbrains.annotations.NotNull; public class ChunkManagerFactory { - public static ChunkManager getChunkManager() { + public static @NotNull ChunkManager getChunkManager() { HiddenConfig hConfig = HiddenConfig.getInstance(); if (hConfig.getChunkletsEnabled()) { diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStore.java b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStore.java index eca783ccd..e21006628 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStore.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStore.java @@ -1,6 +1,6 @@ package com.gmail.nossr50.util.blockmeta; -import org.bukkit.World; +import org.jetbrains.annotations.NotNull; import java.util.UUID; @@ -36,7 +36,7 @@ public interface ChunkStore { */ int getChunkZ(); - UUID getWorldId(); + @NotNull UUID getWorldId(); /** * Checks the value at the given coordinates diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java index 888937872..f6d88665f 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java @@ -1,12 +1,16 @@ 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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.io.*; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; import java.util.*; public class HashChunkManager implements ChunkManager { @@ -21,7 +25,10 @@ public class HashChunkManager implements ChunkManager { { if (!chunkStore.isDirty()) continue; - writeChunkStore(Bukkit.getWorld(chunkStore.getWorldId()), chunkStore); + World world = Bukkit.getWorld(chunkStore.getWorldId()); + if (world == null) + continue; // Oh well + writeChunkStore(world, chunkStore); } // Clear in memory chunks chunkMap.clear(); @@ -32,7 +39,7 @@ public class HashChunkManager implements ChunkManager { regionMap.clear(); } - private synchronized ChunkStore readChunkStore(World world, int cx, int cz) throws IOException { + private synchronized @Nullable ChunkStore readChunkStore(@NotNull 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 @@ -43,7 +50,7 @@ public class HashChunkManager implements ChunkManager { } } - private synchronized void writeChunkStore(World world, ChunkStore data) { + private synchronized void writeChunkStore(@NotNull World world, @NotNull ChunkStore data) { if (!data.isDirty()) return; // Don't save unchanged data try { @@ -58,7 +65,7 @@ public class HashChunkManager implements ChunkManager { } } - private synchronized McMMOSimpleRegionFile getSimpleRegionFile(World world, int cx, int cz, boolean createIfAbsent) { + private synchronized @Nullable McMMOSimpleRegionFile getSimpleRegionFile(World world, int cx, int cz, boolean createIfAbsent) { CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz); return regionMap.computeIfAbsent(regionKey, k -> { @@ -73,7 +80,7 @@ public class HashChunkManager implements ChunkManager { }); } - private ChunkStore loadChunk(int cx, int cz, World world) { + private @Nullable ChunkStore loadChunk(int cx, int cz, World world) { try { return readChunkStore(world, cx, cz); } @@ -82,7 +89,7 @@ public class HashChunkManager implements ChunkManager { return null; } - private void unloadChunk(int cx, int cz, World world) { + private void unloadChunk(int cx, int cz, @NotNull World world) { CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz); ChunkStore chunkStore = chunkMap.remove(chunkKey); // Remove from chunk map if (chunkStore == null) @@ -102,56 +109,12 @@ public class HashChunkManager implements ChunkManager { } @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; - + public synchronized void chunkUnloaded(int cx, int cz, @NotNull World world) { 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; - + public synchronized void unloadWorld(@NotNull World world) { UUID wID = world.getUID(); // Save and remove all the chunks @@ -177,18 +140,7 @@ public class HashChunkManager implements ChunkManager { } } - @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; - + private synchronized boolean isTrue(int x, int y, int z, @NotNull World world) { CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z); // Get chunk, load from file if necessary @@ -214,67 +166,36 @@ public class HashChunkManager implements ChunkManager { } @Override - public synchronized boolean isTrue(Block block) { - if (block == null) - return false; - + public synchronized boolean isTrue(@NotNull Block block) { return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld()); } @Override - public synchronized boolean isTrue(BlockState blockState) { - if (blockState == null) - return false; - + public synchronized boolean isTrue(@NotNull BlockState blockState) { 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); + public synchronized void setTrue(@NotNull Block block) { + set(block.getX(), block.getY(), block.getZ(), block.getWorld(), true); } @Override - public synchronized void setTrue(Block block) { - if (block == null) - return; - - setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld()); + public synchronized void setTrue(@NotNull BlockState blockState) { + set(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld(), true); } @Override - public synchronized void setTrue(BlockState blockState) { - if (blockState == null) - return; - - setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld()); + public synchronized void setFalse(@NotNull Block block) { + set(block.getX(), block.getY(), block.getZ(), block.getWorld(), false); } @Override - public synchronized void setFalse(int x, int y, int z, World world) { - set(x, y, z, world, false); + public synchronized void setFalse(@NotNull BlockState blockState) { + set(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld(), 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; - + private synchronized void set(int x, int y, int z, @NotNull World world, boolean value){ CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z); // Get/Load/Create chunkstore @@ -307,15 +228,15 @@ public class HashChunkManager implements ChunkManager { cStore.set(ix, y, iz, value); } - private CoordinateKey blockCoordinateToChunkKey(UUID worldUid, int x, int y, int z) { + private CoordinateKey blockCoordinateToChunkKey(@NotNull UUID worldUid, int x, int y, int z) { return toChunkKey(worldUid, x >> 4, z >> 4); } - private CoordinateKey toChunkKey(UUID worldUid, int cx, int cz){ + private CoordinateKey toChunkKey(@NotNull UUID worldUid, int cx, int cz){ return new CoordinateKey(worldUid, cx, cz); } - private CoordinateKey toRegionKey(UUID worldUid, int cx, int cz) { + private CoordinateKey toRegionKey(@NotNull UUID worldUid, int cx, int cz) { // Compute region index (32x32 chunk regions) int rx = cx >> 5; int rz = cz >> 5; @@ -323,11 +244,11 @@ public class HashChunkManager implements ChunkManager { } private static final class CoordinateKey { - public final UUID worldID; + public final @NotNull UUID worldID; public final int x; public final int z; - private CoordinateKey(UUID worldID, int x, int z) { + private CoordinateKey(@NotNull UUID worldID, int x, int z) { this.worldID = worldID; this.x = x; this.z = z; @@ -348,7 +269,4 @@ public class HashChunkManager implements ChunkManager { return Objects.hash(worldID, x, z); } } - - @Override - public synchronized void cleanUp() {} } diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/McMMOSimpleRegionFile.java b/src/main/java/com/gmail/nossr50/util/blockmeta/McMMOSimpleRegionFile.java index bef730ff4..3f61c9bef 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/McMMOSimpleRegionFile.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/McMMOSimpleRegionFile.java @@ -19,6 +19,9 @@ */ package com.gmail.nossr50.util.blockmeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.io.*; import java.util.BitSet; import java.util.zip.DeflaterOutputStream; @@ -54,7 +57,7 @@ public class McMMOSimpleRegionFile { private final int segmentMask; // File location - private final File parent; + private final @NotNull File parent; // File access private final RandomAccessFile file; @@ -62,7 +65,7 @@ public class McMMOSimpleRegionFile { private final int rx; private final int rz; - public McMMOSimpleRegionFile(File f, int rx, int rz) { + public McMMOSimpleRegionFile(@NotNull File f, int rx, int rz) { this.rx = rx; this.rz = rz; this.parent = f; @@ -104,7 +107,7 @@ public class McMMOSimpleRegionFile { } } - public synchronized DataOutputStream getOutputStream(int x, int z) { + public synchronized @NotNull DataOutputStream getOutputStream(int x, int z) { int index = getChunkIndex(x, z); // Get chunk index return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index))); } @@ -144,7 +147,7 @@ public class McMMOSimpleRegionFile { file.writeInt(chunkNumBytes[index]); } - public synchronized DataInputStream getInputStream(int x, int z) throws IOException { + public synchronized @Nullable 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 diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java index b777fa349..203376780 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java @@ -3,6 +3,7 @@ package com.gmail.nossr50.util.blockmeta; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; +import org.jetbrains.annotations.NotNull; public class NullChunkManager implements ChunkManager { @@ -10,53 +11,30 @@ public class NullChunkManager implements ChunkManager { public void closeAll() {} @Override - public void saveChunk(int cx, int cz, World world) {} + public void chunkUnloaded(int cx, int cz, @NotNull World world) {} @Override - public void chunkUnloaded(int cx, int cz, World world) {} + public void unloadWorld(@NotNull World world) {} @Override - public void saveWorld(World world) {} - - @Override - public void unloadWorld(World world) {} - - @Override - public void saveAll() {} - - @Override - public boolean isTrue(int x, int y, int z, World world) { + public boolean isTrue(@NotNull Block block) { return false; } @Override - public boolean isTrue(Block block) { + public boolean isTrue(@NotNull BlockState blockState) { return false; } @Override - public boolean isTrue(BlockState blockState) { - return false; - } + public void setTrue(@NotNull Block block) {} @Override - public void setTrue(int x, int y, int z, World world) {} + public void setTrue(@NotNull BlockState blockState) {} @Override - public void setTrue(Block block) {} + public void setFalse(@NotNull Block block) {} @Override - public void setTrue(BlockState blockState) {} - - @Override - public void setFalse(int x, int y, int z, World world) {} - - @Override - public void setFalse(Block block) {} - - @Override - public void setFalse(BlockState blockState) {} - - @Override - public void cleanUp() {} + public void setFalse(@NotNull BlockState blockState) {} } diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/UserBlockTracker.java b/src/main/java/com/gmail/nossr50/util/blockmeta/UserBlockTracker.java new file mode 100644 index 000000000..e5428b0c1 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/UserBlockTracker.java @@ -0,0 +1,56 @@ +package com.gmail.nossr50.util.blockmeta; + +import com.gmail.nossr50.mcMMO; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.jetbrains.annotations.NotNull; + +/** + * Contains blockstore methods that are safe for external plugins to access. + * An instance can be retrieved via {@link mcMMO#getPlaceStore() mcMMO.getPlaceStore()} + */ +public interface UserBlockTracker { + /** + * 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(@NotNull Block block); + + /** + * Check to see if a given BlockState location is set to true + * + * @param blockState BlockState to check + * @return true if the given BlockState location is set to true, false if otherwise + */ + boolean isTrue(@NotNull BlockState blockState); + + /** + * Set a given block location to true + * + * @param block Block location to set + */ + void setTrue(@NotNull Block block); + + /** + * Set a given BlockState location to true + * + * @param blockState BlockState location to set + */ + void setTrue(@NotNull BlockState blockState); + + /** + * Set a given block location to false + * + * @param block Block location to set + */ + void setFalse(@NotNull Block block); + + /** + * Set a given BlockState location to false + * + * @param blockState BlockState location to set + */ + void setFalse(@NotNull BlockState blockState); +} diff --git a/src/test/java/ChunkStoreTest.java b/src/test/java/ChunkStoreTest.java index f45b0e0c9..8a090dfaf 100644 --- a/src/test/java/ChunkStoreTest.java +++ b/src/test/java/ChunkStoreTest.java @@ -2,6 +2,8 @@ import com.gmail.nossr50.util.blockmeta.*; import com.google.common.io.Files; import org.bukkit.Bukkit; import org.bukkit.World; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; import org.junit.*; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -140,9 +142,20 @@ public class ChunkStoreTest { @Test public void testRegressionChunkMirrorBug() { ChunkManager chunkManager = new HashChunkManager(); - chunkManager.setTrue(15,0,15, mockWorld); - chunkManager.setFalse(-15, 0, -15, mockWorld); - Assert.assertTrue(chunkManager.isTrue(15, 0, 15, mockWorld)); + Block mockBlockA = mock(Block.class); + Mockito.when(mockBlockA.getX()).thenReturn(15); + Mockito.when(mockBlockA.getZ()).thenReturn(15); + Mockito.when(mockBlockA.getY()).thenReturn(0); + Mockito.when(mockBlockA.getWorld()).thenReturn(mockWorld); + Block mockBlockB = mock(Block.class); + Mockito.when(mockBlockB.getX()).thenReturn(-15); + Mockito.when(mockBlockB.getZ()).thenReturn(-15); + Mockito.when(mockBlockB.getY()).thenReturn(0); + Mockito.when(mockBlockB.getWorld()).thenReturn(mockWorld); + + chunkManager.setTrue(mockBlockA); + chunkManager.setFalse(mockBlockB); + Assert.assertTrue(chunkManager.isTrue(mockBlockA)); } private interface Delegate { @@ -227,7 +240,7 @@ public class ChunkStoreTest { } @Override - public UUID getWorldId() { + public @NotNull UUID getWorldId() { return worldUid; }