diff --git a/Changelog.txt b/Changelog.txt index 4ae167519..bf0f9f6d3 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -18,7 +18,8 @@ Version 1.3.10-dev = Fixed admin chat being seen by everyone = Fixed issue with UTFDataFormatException occurring on occasion when trying to load Chunklets = Fixed ArrayIndexOutOfBounds error caused when trying to use /xplock after logging in but before gaining XP - = Fixed custom tools not properly respecting the Ability_Enabled flag. + = Fixed custom tools not properly respecting the Ability_Enabled flag. + ! Optimized how player placed blocks are tracked Version 1.3.09 + Added compatibility with AntiCheat (Which I highly recommend to prevent cheating) diff --git a/src/main/java/com/gmail/nossr50/listeners/BlockListener.java b/src/main/java/com/gmail/nossr50/listeners/BlockListener.java index fa514fef5..35714b392 100644 --- a/src/main/java/com/gmail/nossr50/listeners/BlockListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/BlockListener.java @@ -224,7 +224,7 @@ public class BlockListener implements Listener { } //Remove metadata when broken - if (mcMMO.placeStore.isTrue(block) && BlockChecks.shouldBeWatched(block)) { + if (BlockChecks.shouldBeWatched(block)) { mcMMO.placeStore.setFalse(block); } diff --git a/src/main/java/com/gmail/nossr50/listeners/WorldListener.java b/src/main/java/com/gmail/nossr50/listeners/WorldListener.java index 507ceeea4..d15df92f9 100644 --- a/src/main/java/com/gmail/nossr50/listeners/WorldListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/WorldListener.java @@ -4,13 +4,13 @@ import java.io.File; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldSaveEvent; import org.bukkit.event.world.WorldUnloadEvent; import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.runnables.ChunkletUnloader; public class WorldListener implements Listener { @EventHandler @@ -31,13 +31,8 @@ public class WorldListener implements Listener { mcMMO.placeStore.saveWorld(event.getWorld()); } - @EventHandler - public void onChunkLoad(ChunkLoadEvent event) { - mcMMO.placeStore.chunkLoaded(event.getChunk().getX(), event.getChunk().getZ(), event.getChunk().getWorld()); - } - @EventHandler public void onChunkUnload(ChunkUnloadEvent event) { - mcMMO.placeStore.chunkUnloaded(event.getChunk().getX(), event.getChunk().getZ(), event.getChunk().getWorld()); + ChunkletUnloader.addToList(event.getChunk()); } } diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index 19a9fb8b6..38bc3bc6b 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -9,7 +9,6 @@ import java.util.List; import net.shatteredlands.shatt.backup.ZipLibrary; import org.bukkit.OfflinePlayer; -import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; @@ -66,6 +65,7 @@ import com.gmail.nossr50.listeners.WorldListener; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.runnables.BleedTimer; +import com.gmail.nossr50.runnables.ChunkletUnloader; import com.gmail.nossr50.runnables.SaveTimer; import com.gmail.nossr50.runnables.SkillMonitor; import com.gmail.nossr50.runnables.SpoutStart; @@ -183,12 +183,14 @@ public class mcMMO extends JavaPlugin { //Schedule Spout Activation 1 second after start-up scheduler.scheduleSyncDelayedTask(this, new SpoutStart(this), 20); - //Periodic save timer (Saves every 10 minutes) + //Periodic save timer (Saves every 10 minutes by default) scheduler.scheduleSyncRepeatingTask(this, new SaveTimer(this), 0, configInstance.getSaveInterval() * 1200); //Regen & Cooldown timer (Runs every second) scheduler.scheduleSyncRepeatingTask(this, new SkillMonitor(this), 0, 20); //Bleed timer (Runs every two seconds) scheduler.scheduleSyncRepeatingTask(this, new BleedTimer(), 0, 40); + //Chunklet unloader (Runs every 20 seconds by default) + scheduler.scheduleSyncRepeatingTask(this, new ChunkletUnloader(), 0, ChunkletUnloader.RUN_INTERVAL * 20); registerCommands(); @@ -224,10 +226,6 @@ public class mcMMO extends JavaPlugin { // Get our ChunkletManager placeStore = ChunkletManagerFactory.getChunkletManager(); - - for (World world : getServer().getWorlds()) { - placeStore.loadWorld(world); - } } /** diff --git a/src/main/java/com/gmail/nossr50/runnables/ChunkletUnloader.java b/src/main/java/com/gmail/nossr50/runnables/ChunkletUnloader.java new file mode 100644 index 000000000..dca881d40 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/ChunkletUnloader.java @@ -0,0 +1,53 @@ +package com.gmail.nossr50.runnables; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.Chunk; + +import com.gmail.nossr50.mcMMO; + +public class ChunkletUnloader implements Runnable { + private static Map unloadedChunks = new HashMap(); + private static int minimumInactiveTime = 60; //Should be a multiple of RUN_INTERVAL for best performance + public static int RUN_INTERVAL = 20; + + public static void addToList(Chunk chunk) { + //Unfortunately we can't use Map.contains() because Chunks are always new objects + //This method isn't efficient enough for me + for (Chunk otherChunk : unloadedChunks.keySet()) { + if (chunk.getX() == otherChunk.getX() && chunk.getZ() == otherChunk.getZ()) { + return; + } + } + + unloadedChunks.put(chunk, 0); + } + + @Override + public void run() { + for (Iterator> it = unloadedChunks.entrySet().iterator() ; it.hasNext() ; ) { + Entry entry = it.next(); + Chunk chunk = entry.getKey(); + + if (!chunk.isLoaded()) { + int inactiveTime = entry.getValue() + RUN_INTERVAL; + + //Chunklets are unloaded only if their chunk has been unloaded for minimumInactiveTime + if (inactiveTime >= minimumInactiveTime) { + mcMMO.placeStore.chunkUnloaded(chunk.getX(), chunk.getZ(), chunk.getWorld()); + it.remove(); + continue; + } + + unloadedChunks.put(entry.getKey(), inactiveTime); + } + else { + //Just remove the entry if the chunk has been reloaded. + it.remove(); + } + } + } +} diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkletManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkletManager.java index 65a34926a..245ef4889 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkletManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkletManager.java @@ -4,11 +4,21 @@ 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 + */ + public void loadChunklet(int cx, int cy, int cz, World world); + /** * Informs the ChunkletManager a chunk is loaded, it should load appropriate data * - * @param cx Chunk X coordiate that is loaded - * @param cz Chunk Z coordiate that is loaded + * @param cx Chunk X coordinate that is loaded + * @param cz Chunk Z coordinate that is loaded * @param world World that the chunk was loaded in */ public void chunkLoaded(int cx, int cz, World world); @@ -16,8 +26,8 @@ public interface ChunkletManager { /** * Informs the ChunkletManager a chunk is unloaded, it should unload and save appropriate data * - * @param cx Chunk X coordiate that is unloaded - * @param cz Chunk Z coordiate that is unloaded + * @param cx Chunk X coordinate that is unloaded + * @param cz Chunk Z coordinate that is unloaded * @param world World that the chunk was unloaded in */ public void chunkUnloaded(int cx, int cz, World world); diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java index 6dab99b60..1aa944d25 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java @@ -12,7 +12,6 @@ import java.io.UTFDataFormatException; import java.util.HashMap; import org.bukkit.Bukkit; -import org.bukkit.Chunk; import org.bukkit.World; import org.bukkit.block.Block; @@ -22,26 +21,42 @@ public class HashChunkletManager implements ChunkletManager { private HashMap store = new HashMap(); @Override - public void chunkLoaded(int cx, int cz, World world) { + 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; - for(int y = 0; y < 4; y++) { - File yFile = new File(czDir, "" + y); - if(!yFile.exists()) { - continue; - } else { - ChunkletStore in = deserializeChunkletStore(yFile); - if(in != null) { - store.put(world.getName() + "," + cx + "," + cz + "," + y, in); - } - } + ChunkletStore in = deserializeChunkletStore(yFile); + if(in != null) { + store.put(world.getName() + "," + cx + "," + cz + "," + cy, in); } } + @Override + public void chunkLoaded(int cx, int cz, World world) { + //File dataDir = new File(world.getWorldFolder(), "mcmmo_data"); + //File cxDir = new File(dataDir, "" + cx); + //if(!cxDir.exists()) return; + //File czDir = new File(cxDir, "" + cz); + //if(!czDir.exists()) return; + + //for(int y = 0; y < 4; y++) { + // File yFile = new File(czDir, "" + y); + // if(!yFile.exists()) { + // continue; + // } else { + // ChunkletStore in = deserializeChunkletStore(yFile); + // if(in != null) { + // store.put(world.getName() + "," + cx + "," + cz + "," + y, in); + // } + // } + //} + } + @Override public void chunkUnloaded(int cx, int cz, World world) { File dataDir = new File(world.getWorldFolder(), "mcmmo_data"); @@ -97,9 +112,9 @@ public class HashChunkletManager implements ChunkletManager { @Override public void loadWorld(World world) { - for(Chunk chunk : world.getLoadedChunks()) { - this.chunkLoaded(chunk.getX(), chunk.getZ(), world); - } + //for(Chunk chunk : world.getLoadedChunks()) { + // this.chunkLoaded(chunk.getX(), chunk.getZ(), world); + //} } @Override @@ -122,7 +137,15 @@ public class HashChunkletManager implements ChunkletManager { int cx = x / 16; int cz = z / 16; int cy = y / 64; - if(!store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) return false; + 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; @@ -147,13 +170,20 @@ public class HashChunkletManager implements ChunkletManager { int iz = Math.abs(z) % 16; int iy = Math.abs(y) % 64; - ChunkletStore cStore; - if(!store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) { + 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 = store.get(world.getName() + "," + cx + "," + cz + "," + cy); cStore.setTrue(ix, iy, iz); } @@ -172,12 +202,18 @@ public class HashChunkletManager implements ChunkletManager { int iz = Math.abs(z) % 16; int iy = Math.abs(y) % 64; - ChunkletStore cStore; - if(!store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) { - return; // No need to make a store for something we will be setting to false + 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 = store.get(world.getName() + "," + cx + "," + cz + "," + cy); cStore.setFalse(ix, iy, iz); } diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkletManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkletManager.java index 4934bbf18..2f1013faf 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkletManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkletManager.java @@ -9,6 +9,11 @@ import org.bukkit.block.Block; * 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) { + return; + } + @Override public void chunkLoaded(int cx, int cz, World world) { return;