Merge branch 'master' of github.com:mcMMO-Dev/mcMMO

This commit is contained in:
nossr50 2021-01-02 15:01:35 -08:00
commit 7ea3a2bf07
12 changed files with 251 additions and 357 deletions

View File

@ -121,6 +121,10 @@
</artifactSet> </artifactSet>
<!-- <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>--> <!-- <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>-->
<relocations> <relocations>
<relocation>
<pattern>net.kyori.examination</pattern>
<shadedPattern>com.gmail.nossr50.kyori.examination</shadedPattern>
</relocation>
<relocation> <relocation>
<pattern>net.kyori.adventure</pattern> <pattern>net.kyori.adventure</pattern>
<shadedPattern>com.gmail.nossr50.mcmmo.kyori.adventure</shadedPattern> <shadedPattern>com.gmail.nossr50.mcmmo.kyori.adventure</shadedPattern>

View File

@ -253,12 +253,18 @@ public class BlockListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockGrow(BlockGrowEvent event) public void onBlockGrow(BlockGrowEvent event)
{ {
Block block = event.getBlock();
World world = block.getWorld();
/* WORLD BLACKLIST CHECK */ /* WORLD BLACKLIST CHECK */
if(WorldBlacklist.isWorldBlacklisted(event.getBlock().getWorld())) if(WorldBlacklist.isWorldBlacklisted(world))
return; return;
BlockState blockState = event.getBlock().getState(); // Minecraft is dumb, the events still throw when a plant "grows" higher than the max block height. Even though no new block is created
mcMMO.getPlaceStore().setFalse(blockState); if (block.getY() >= world.getMaxHeight())
return;
mcMMO.getPlaceStore().setFalse(block);
} }
/** /**

View File

@ -345,7 +345,6 @@ public class mcMMO extends JavaPlugin {
formulaManager.saveFormula(); formulaManager.saveFormula();
holidayManager.saveAnniversaryFiles(); holidayManager.saveAnniversaryFiles();
placeStore.cleanUp(); // Cleanup empty metadata stores
placeStore.closeAll(); placeStore.closeAll();
} }

View File

@ -2,32 +2,36 @@ package com.gmail.nossr50.util.blockmeta;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import java.io.*; import java.io.*;
import java.util.BitSet; import java.util.BitSet;
import java.util.UUID; import java.util.UUID;
public class BitSetChunkStore implements ChunkStore, Serializable { public class BitSetChunkStore implements ChunkStore {
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 CURRENT_VERSION = 8;
private static final int MAGIC_NUMBER = 0xEA5EDEBB; 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) { private final int cx;
this.cx = cx; private final int cz;
this.cz = cz; private final int worldHeight;
this.worldUid = world.getUID(); private final UUID worldUid;
this.worldHeight = world.getMaxHeight(); // 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.
this.store = new BitSet(16 * 16 * worldHeight); 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 @Override
public boolean isDirty() { public boolean isDirty() {
@ -50,7 +54,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
} }
@Override @Override
public UUID getWorldId() { public @NotNull UUID getWorldId() {
return worldUid; return worldUid;
} }
@ -81,58 +85,24 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
} }
private int coordToIndex(int x, int y, int z) { 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) 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); return (z * 16 + x) + (256 * y);
} }
private void fixWorldHeight() { private static int getWorldHeight(UUID worldUid, int storedWorldHeight)
{
World world = Bukkit.getWorld(worldUid); 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? // 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) if (world == null)
return; return storedWorldHeight;
// Lop off any extra data if the world height has shrunk return world.getMaxHeight();
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 { private void serialize(DataOutputStream out) throws IOException {
@ -153,7 +123,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
dirty = false; dirty = false;
} }
private static BitSetChunkStore deserialize(DataInputStream in) throws IOException { private static BitSetChunkStore deserialize(@NotNull DataInputStream in) throws IOException {
int magic = in.readInt(); int magic = in.readInt();
// Can be used to determine the format of the file // Can be used to determine the format of the file
int fileVersionNumber = in.readInt(); int fileVersionNumber = in.readInt();
@ -161,28 +131,36 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION) if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION)
throw new IOException(); throw new IOException();
BitSetChunkStore chunkStore = new BitSetChunkStore();
long lsb = in.readLong(); long lsb = in.readLong();
long msb = in.readLong(); long msb = in.readLong();
chunkStore.worldUid = new UUID(msb, lsb); UUID worldUid = new UUID(msb, lsb);
chunkStore.cx = in.readInt(); int cx = in.readInt();
chunkStore.cz = in.readInt(); int cz = in.readInt();
chunkStore.worldHeight = in.readInt(); int worldHeight = in.readInt();
byte[] temp = new byte[in.readInt()]; byte[] temp = new byte[in.readInt()];
in.readFully(temp); 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; return chunkStore;
} }
public static class Serialization { 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()) if (inputStream.markSupported())
inputStream.mark(2); inputStream.mark(2);
short magicNumber = inputStream.readShort(); 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. // 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 pushbackInputStream = new PushbackInputStream(inputStream, 2);
pushbackInputStream.unread((magicNumber >>> 0) & 0xFF); pushbackInputStream.unread((magicNumber) & 0xFF);
pushbackInputStream.unread((magicNumber >>> 8) & 0xFF); pushbackInputStream.unread((magicNumber >>> 8) & 0xFF);
inputStream = new DataInputStream(pushbackInputStream); inputStream = new DataInputStream(pushbackInputStream);
} }
@ -216,8 +194,61 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
((BitSetChunkStore)chunkStore).serialize(outputStream); ((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 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 { public LegacyDeserializationInputStream(InputStream in) throws IOException {
super(in); super(in);
enableResolveObject(true); enableResolveObject(true);
@ -227,13 +258,14 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass read = super.readClassDescriptor(); ObjectStreamClass read = super.readClassDescriptor();
if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore")) if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"))
return ObjectStreamClass.lookup(BitSetChunkStore.class); return ObjectStreamClass.lookup(LegacyChunkStoreDeserializer.class);
return read; return read;
} }
public ChunkStore readLegacyChunkStore(){ public ChunkStore readLegacyChunkStore(){
try { try {
return (ChunkStore) readObject(); LegacyChunkStoreDeserializer deserializer = (LegacyChunkStoreDeserializer)readObject();
return deserializer.convert();
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
return null; return null;
} }

View File

@ -1,126 +1,10 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.jetbrains.annotations.NotNull;
import org.bukkit.block.BlockState;
public interface ChunkManager { public interface ChunkManager extends UserBlockTracker {
void closeAll(); void closeAll();
void chunkUnloaded(int cx, int cz, @NotNull World world);
/** void unloadWorld(@NotNull World world);
* 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();
} }

View File

@ -1,9 +1,10 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.config.HiddenConfig; import com.gmail.nossr50.config.HiddenConfig;
import org.jetbrains.annotations.NotNull;
public class ChunkManagerFactory { public class ChunkManagerFactory {
public static ChunkManager getChunkManager() { public static @NotNull ChunkManager getChunkManager() {
HiddenConfig hConfig = HiddenConfig.getInstance(); HiddenConfig hConfig = HiddenConfig.getInstance();
if (hConfig.getChunkletsEnabled()) { if (hConfig.getChunkletsEnabled()) {

View File

@ -1,6 +1,6 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World; import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
@ -36,7 +36,7 @@ public interface ChunkStore {
*/ */
int getChunkZ(); int getChunkZ();
UUID getWorldId(); @NotNull UUID getWorldId();
/** /**
* Checks the value at the given coordinates * Checks the value at the given coordinates

View File

@ -1,12 +1,16 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.mcMMO;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockState; 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.*; import java.util.*;
public class HashChunkManager implements ChunkManager { public class HashChunkManager implements ChunkManager {
@ -21,7 +25,10 @@ public class HashChunkManager implements ChunkManager {
{ {
if (!chunkStore.isDirty()) if (!chunkStore.isDirty())
continue; 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 // Clear in memory chunks
chunkMap.clear(); chunkMap.clear();
@ -32,7 +39,7 @@ public class HashChunkManager implements ChunkManager {
regionMap.clear(); 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); McMMOSimpleRegionFile rf = getSimpleRegionFile(world, cx, cz, false);
if (rf == null) if (rf == null)
return null; // If there is no region file, there can't be a chunk 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()) if (!data.isDirty())
return; // Don't save unchanged data return; // Don't save unchanged data
try { 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); CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz);
return regionMap.computeIfAbsent(regionKey, k -> { 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 { try {
return readChunkStore(world, cx, cz); return readChunkStore(world, cx, cz);
} }
@ -82,7 +89,7 @@ public class HashChunkManager implements ChunkManager {
return null; 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); CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz);
ChunkStore chunkStore = chunkMap.remove(chunkKey); // Remove from chunk map ChunkStore chunkStore = chunkMap.remove(chunkKey); // Remove from chunk map
if (chunkStore == null) if (chunkStore == null)
@ -102,56 +109,12 @@ public class HashChunkManager implements ChunkManager {
} }
@Override @Override
public synchronized void saveChunk(int cx, int cz, World world) { public synchronized void chunkUnloaded(int cx, int cz, @NotNull 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); unloadChunk(cx, cz, world);
} }
@Override @Override
public synchronized void saveWorld(World world) { public synchronized void unloadWorld(@NotNull 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(); UUID wID = world.getUID();
// Save and remove all the chunks // Save and remove all the chunks
@ -177,18 +140,7 @@ public class HashChunkManager implements ChunkManager {
} }
} }
@Override private synchronized boolean isTrue(int x, int y, int z, @NotNull World world) {
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); CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z);
// Get chunk, load from file if necessary // Get chunk, load from file if necessary
@ -214,67 +166,36 @@ public class HashChunkManager implements ChunkManager {
} }
@Override @Override
public synchronized boolean isTrue(Block block) { public synchronized boolean isTrue(@NotNull Block block) {
if (block == null)
return false;
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld()); return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
} }
@Override @Override
public synchronized boolean isTrue(BlockState blockState) { public synchronized boolean isTrue(@NotNull BlockState blockState) {
if (blockState == null)
return false;
return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld()); return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
} }
@Override @Override
public synchronized void setTrue(int x, int y, int z, World world) { public synchronized void setTrue(@NotNull Block block) {
set(x, y, z, world, true); set(block.getX(), block.getY(), block.getZ(), block.getWorld(), true);
} }
@Override @Override
public synchronized void setTrue(Block block) { public synchronized void setTrue(@NotNull BlockState blockState) {
if (block == null) set(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld(), true);
return;
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
} }
@Override @Override
public synchronized void setTrue(BlockState blockState) { public synchronized void setFalse(@NotNull Block block) {
if (blockState == null) set(block.getX(), block.getY(), block.getZ(), block.getWorld(), false);
return;
setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
} }
@Override @Override
public synchronized void setFalse(int x, int y, int z, World world) { public synchronized void setFalse(@NotNull BlockState blockState) {
set(x, y, z, world, false); set(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld(), false);
} }
@Override private synchronized void set(int x, int y, int z, @NotNull World world, boolean value){
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); CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z);
// Get/Load/Create chunkstore // Get/Load/Create chunkstore
@ -307,15 +228,15 @@ public class HashChunkManager implements ChunkManager {
cStore.set(ix, y, iz, value); 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); 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); 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) // Compute region index (32x32 chunk regions)
int rx = cx >> 5; int rx = cx >> 5;
int rz = cz >> 5; int rz = cz >> 5;
@ -323,11 +244,11 @@ public class HashChunkManager implements ChunkManager {
} }
private static final class CoordinateKey { private static final class CoordinateKey {
public final UUID worldID; public final @NotNull UUID worldID;
public final int x; public final int x;
public final int z; 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.worldID = worldID;
this.x = x; this.x = x;
this.z = z; this.z = z;
@ -348,7 +269,4 @@ public class HashChunkManager implements ChunkManager {
return Objects.hash(worldID, x, z); return Objects.hash(worldID, x, z);
} }
} }
@Override
public synchronized void cleanUp() {}
} }

View File

@ -19,6 +19,9 @@
*/ */
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*; import java.io.*;
import java.util.BitSet; import java.util.BitSet;
import java.util.zip.DeflaterOutputStream; import java.util.zip.DeflaterOutputStream;
@ -54,7 +57,7 @@ public class McMMOSimpleRegionFile {
private final int segmentMask; private final int segmentMask;
// File location // File location
private final File parent; private final @NotNull File parent;
// File access // File access
private final RandomAccessFile file; private final RandomAccessFile file;
@ -62,7 +65,7 @@ public class McMMOSimpleRegionFile {
private final int rx; private final int rx;
private final int rz; 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.rx = rx;
this.rz = rz; this.rz = rz;
this.parent = f; 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 int index = getChunkIndex(x, z); // Get chunk index
return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index))); return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
} }
@ -144,7 +147,7 @@ public class McMMOSimpleRegionFile {
file.writeInt(chunkNumBytes[index]); 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 index = getChunkIndex(x, z); // Get chunk index
int byteLength = chunkNumBytes[index]; // Get byte length of data int byteLength = chunkNumBytes[index]; // Get byte length of data

View File

@ -3,6 +3,7 @@ package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.jetbrains.annotations.NotNull;
public class NullChunkManager implements ChunkManager { public class NullChunkManager implements ChunkManager {
@ -10,53 +11,30 @@ public class NullChunkManager implements ChunkManager {
public void closeAll() {} public void closeAll() {}
@Override @Override
public void saveChunk(int cx, int cz, World world) {} public void chunkUnloaded(int cx, int cz, @NotNull World world) {}
@Override @Override
public void chunkUnloaded(int cx, int cz, World world) {} public void unloadWorld(@NotNull World world) {}
@Override @Override
public void saveWorld(World world) {} public boolean isTrue(@NotNull Block block) {
@Override
public void unloadWorld(World world) {}
@Override
public void saveAll() {}
@Override
public boolean isTrue(int x, int y, int z, World world) {
return false; return false;
} }
@Override @Override
public boolean isTrue(Block block) { public boolean isTrue(@NotNull BlockState blockState) {
return false; return false;
} }
@Override @Override
public boolean isTrue(BlockState blockState) { public void setTrue(@NotNull Block block) {}
return false;
}
@Override @Override
public void setTrue(int x, int y, int z, World world) {} public void setTrue(@NotNull BlockState blockState) {}
@Override @Override
public void setTrue(Block block) {} public void setFalse(@NotNull Block block) {}
@Override @Override
public void setTrue(BlockState blockState) {} public void setFalse(@NotNull 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() {}
} }

View File

@ -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);
}

View File

@ -2,6 +2,8 @@ import com.gmail.nossr50.util.blockmeta.*;
import com.google.common.io.Files; import com.google.common.io.Files;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.junit.*; import org.junit.*;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -140,9 +142,20 @@ public class ChunkStoreTest {
@Test @Test
public void testRegressionChunkMirrorBug() { public void testRegressionChunkMirrorBug() {
ChunkManager chunkManager = new HashChunkManager(); ChunkManager chunkManager = new HashChunkManager();
chunkManager.setTrue(15,0,15, mockWorld); Block mockBlockA = mock(Block.class);
chunkManager.setFalse(-15, 0, -15, mockWorld); Mockito.when(mockBlockA.getX()).thenReturn(15);
Assert.assertTrue(chunkManager.isTrue(15, 0, 15, mockWorld)); 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 { private interface Delegate {
@ -227,7 +240,7 @@ public class ChunkStoreTest {
} }
@Override @Override
public UUID getWorldId() { public @NotNull UUID getWorldId() {
return worldUid; return worldUid;
} }