diff --git a/Plugins/Mineplex.Core.Common/src/mineplex/core/common/util/MapUtil.java b/Plugins/Mineplex.Core.Common/src/mineplex/core/common/util/MapUtil.java index 9df45eeac..1f51c77bf 100644 --- a/Plugins/Mineplex.Core.Common/src/mineplex/core/common/util/MapUtil.java +++ b/Plugins/Mineplex.Core.Common/src/mineplex/core/common/util/MapUtil.java @@ -256,33 +256,36 @@ public class MapUtil @SuppressWarnings({ "rawtypes" }) public static boolean ClearWorldReferences(String worldName) { - HashMap regionfiles = (HashMap) RegionFileCache.a; - - try + synchronized (RegionFileCache.class) { - for (Iterator iterator = regionfiles.entrySet().iterator(); iterator.hasNext();) - { - Map.Entry e = (Map.Entry) iterator.next(); - RegionFile file = (RegionFile) e.getValue(); + HashMap regionfiles = (HashMap) RegionFileCache.a; - try + try + { + for (Iterator iterator = regionfiles.entrySet().iterator(); iterator.hasNext(); ) { - file.c(); - iterator.remove(); - } - catch (Exception ex) - { - ex.printStackTrace(); + Map.Entry e = (Map.Entry) iterator.next(); + RegionFile file = (RegionFile) e.getValue(); + + try + { + file.c(); + iterator.remove(); + } + catch (Exception ex) + { + ex.printStackTrace(); + } } } - } - catch (Exception ex) - { - System.out.println("Exception while removing world reference for '" + worldName + "'!"); - ex.printStackTrace(); - } + catch (Exception ex) + { + System.out.println("Exception while removing world reference for '" + worldName + "'!"); + ex.printStackTrace(); + } - return true; + return true; + } } public static BlockPosition getBlockPos(int x, int y, int z) diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/uhc/UHC.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/uhc/UHC.java index 0dec2a931..d46f2f7f6 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/uhc/UHC.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/game/games/uhc/UHC.java @@ -1,8 +1,13 @@ package nautilus.game.arcade.game.games.uhc; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; import mineplex.core.common.util.C; import mineplex.core.common.util.F; @@ -35,6 +40,19 @@ import nautilus.game.arcade.game.TeamGame; import nautilus.game.arcade.game.Game.GameState; import nautilus.game.arcade.kit.Kit; +import net.minecraft.server.v1_8_R3.BiomeCache; +import net.minecraft.server.v1_8_R3.ChunkCoordIntPair; +import net.minecraft.server.v1_8_R3.ChunkProviderServer; +import net.minecraft.server.v1_8_R3.EmptyChunk; +import net.minecraft.server.v1_8_R3.ExceptionWorldConflict; +import net.minecraft.server.v1_8_R3.FileIOThread; +import net.minecraft.server.v1_8_R3.IChunkLoader; +import net.minecraft.server.v1_8_R3.IChunkProvider; +import net.minecraft.server.v1_8_R3.LongHashMap; +import net.minecraft.server.v1_8_R3.MinecraftServer; +import net.minecraft.server.v1_8_R3.NBTTagCompound; +import net.minecraft.server.v1_8_R3.World; +import net.minecraft.server.v1_8_R3.WorldChunkManager; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Chunk; @@ -46,6 +64,9 @@ import org.bukkit.WorldBorder; import org.bukkit.World.Environment; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.generator.NormalChunkGenerator; +import org.bukkit.craftbukkit.v1_8_R3.util.LongHash; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; @@ -86,19 +107,21 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.scoreboard.DisplaySlot; import org.bukkit.scoreboard.Objective; +import org.spigotmc.WatchdogThread; public class UHC extends TeamGame { private NautHashMap _teamReqs = new NautHashMap(); - + private NautHashMap _deathTime = new NautHashMap(); // private NautHashMap _combatTime = new NautHashMap(); - private boolean _mapLoaded = false; - private double _mapLoadPercent = 0; - private int _chunksPerTick = 1; + private volatile boolean _mapLoaded = false; + private volatile double _mapLoadPercent = 0; + private volatile int _chunksPerTick = 1; + private volatile boolean stopGen = false; + private int _chunkTotal; - private Chunk _chunk = null; private int _chunkX = 0; private int _chunkZ = 0; private int _chunksLoaded = 0; @@ -111,7 +134,7 @@ public class UHC extends TeamGame private long _createTime; private long _serverTime; - + private boolean xrayDebug = false; @@ -139,21 +162,21 @@ public class UHC extends TeamGame { super(manager, type, - new Kit[] - { - new KitUHC(manager) - }, + new Kit[] + { + new KitUHC(manager) + }, - new String[] - { - "10 minutes of no PvP", "Only Golden Apples restore health", "Ores can only be found in caves", - "Borders shrink over time", "Last player/team alive wins!" - }); + new String[] + { + "10 minutes of no PvP", "Only Golden Apples restore health", "Ores can only be found in caves", + "Borders shrink over time", "Last player/team alive wins!" + }); this.HideTeamSheep = true; - + this.StrictAntiHack = true; - + AnnounceStay = false; this.GameTimeout = 10800000; @@ -203,7 +226,7 @@ public class UHC extends TeamGame this.GadgetsDisabled = true; WorldTimeSet = -1; - + CraftRecipes(); // Disable Custom Mob Drops (and EXP Disable) @@ -225,8 +248,8 @@ public class UHC extends TeamGame @Override public void ParseData() { - WorldData.World.setDifficulty(Difficulty.HARD); - + WorldData.World.setDifficulty(Difficulty.HARD); + _chunkX = (int) -(_currentBorder / 16); _chunkZ = (int) -(_currentBorder / 16); _chunkTotal = (int) ((_currentBorder * 2 / 16) * (_currentBorder * 2 / 16)); @@ -368,84 +391,6 @@ public class UHC extends TeamGame return borders; } - @EventHandler - public void loadMap(UpdateEvent event) - { - if (_mapLoaded) - return; - - if (WorldData.World == null) - return; - - if (GetState() != GameState.Recruit) - return; - - // Print Debug - if (event.getType() == UpdateType.SLOWER) - { - Announce(C.cGreen + C.Bold + "Generating Map: " + C.cWhite + getMapLoadETA() + " Remaining...", false); - - TimingManager.endTotal("UHC Generation", true); - return; - } - - if (event.getType() != UpdateType.TICK) - return; - - // Timings - TimingManager.startTotal("UHC Generation"); - - for (int i = 0; i < _chunksPerTick ; i++) - { - // Unload Previous -// if (_chunk != null) -// _chunk.unload(true); - - // Load Chunks - _chunk = WorldData.World.getChunkAt(_chunkX, _chunkZ); - _chunk.load(true); - - // Scan Map - if (_chunkX < _currentBorder / 16) - { - _chunkX++; - } - else if (_chunkZ < _currentBorder / 16) - { - _chunkX = (int) -(_currentBorder / 16); - _chunkZ++; - } - else - { - _mapLoaded = true; - System.out.println("Map Loading Finished!"); - generateSpawns(); - break; - } - - _chunksLoaded++; - } - - _mapLoadPercent = (double)_chunksLoaded / (double)_chunkTotal; - - // Timings - TimingManager.stopTotal("UHC Generation"); - } - - @EventHandler - public void chunkUnload(ChunkUnloadEvent event) - { - //Allow unloading after players in - if (IsLive()) - return; - - if (WorldData.World != null && event.getWorld().equals(WorldData.World)) - { - System.out.println("Disallowing Unload of World"); - event.setCancelled(true); - } - } - public void generateSpawns() { // Wipe Spawns @@ -467,7 +412,7 @@ public class UHC extends TeamGame double dist = (2 * _currentBorder) / (Math.sqrt(this.GetPlayers(true).size()) + 3); // Ensure distance between Teams - 500 Attempts - for (int i=0 ; i<500 ; i++) + for (int i = 0; i < 500; i++) { boolean clash = false; @@ -500,7 +445,7 @@ public class UHC extends TeamGame double dist = (2 * _currentBorder) / (Math.sqrt(GetTeamList().size()) + 3); // Ensure distance between Teams - 500 Attempts - for (int i=0 ; i<500 ; i++) + for (int i = 0; i < 500; i++) { boolean clash = false; @@ -622,63 +567,183 @@ public class UHC extends TeamGame } } - /*@EventHandler - public void WorldBoundaryCheck(UpdateEvent event) - { - if (event.getType() != UpdateType.FASTER) - return; - - for (Player player : GetPlayers(true)) - { - //Damage - if (Math.abs(player.getLocation().getX()) > _borderSize || Math.abs(player.getLocation().getZ()) > _borderSize) - { - player.damage(0.75); - } - } - } - @EventHandler - public void WorldBoundaryShrink(UpdateEvent event) + public void generateWorld(GameStateChangeEvent event) { - if (!IsLive()) + if (event.GetState() == GameState.Dead) + { + stopGen = true; return; + } - if (event.getType() == UpdateType.SLOW) - if (_borderSize > 16) - _borderSize--; - } - - //@EventHandler - public void WorldBoundarySet(GameStateChangeEvent event) - { if (event.GetState() != GameState.Recruit) + { return; + } - long time = System.currentTimeMillis(); - - this.WorldData.MinX = -_borderSize; - this.WorldData.MaxX = _borderSize; - this.WorldData.MinZ = -_borderSize; - this.WorldData.MaxZ = _borderSize; - - this.WorldData.MinY = -1000; - this.WorldData.MaxY = 1000; - - //Find Y Max - for (int x=-16 ; x<16 ; x++) - for (int z=-16 ; z<16 ; z++) + new Thread(() -> + { + try { - int y = UtilBlock.getHighest(WorldData.World, x, z).getY(); + Field fileIOThreadB = FileIOThread.class.getDeclaredField("b"); + fileIOThreadB.setAccessible(true); - if (y > _yMax) - _yMax = y; + // This list is the list of chunks to be saved on the File IO Thread + List list = (List) fileIOThreadB.get(FileIOThread.a()); + + net.minecraft.server.v1_8_R3.WorldServer worldServer = ((CraftWorld) WorldData.World).getHandle(); + + WorldChunkManager manager = worldServer.getWorldChunkManager(); + + Field biomeCacheField = manager.getClass().getDeclaredField("d"); + biomeCacheField.setAccessible(true); + + // A thread safe BiomeCache + // The implementation is literally a copy/paste from the original BiomeCache, but with some synchronization + // Reason being while the server is ticking the world (for some reason, if you want to dig through the entire Arcade codebase go for it) + // it stores stuff in the BiomeCache, and chunk gen needs that BiomeCache info too + // Causing desynchronization in the cache + biomeCacheField.set(manager, new BiomeCache(manager) + { + private final Object _lock = new Object(); + + private long _lastCleanTime; // b -> _lastCleanTime + private Map _blockByCoord = new HashMap<>(); // LongHashMap -> HashMap, c -> _blockByCoord + private List _blocks = new ArrayList<>(); // d -> _blocks + + @Override + public BiomeCache.BiomeCacheBlock a(int x, int z) + { + x >>= 4; + z >>= 4; + long var3 = hash(x, z); + BiomeCache.BiomeCacheBlock var5 = this._blockByCoord.get(var3); + if (var5 == null) + { + var5 = new BiomeCache.BiomeCacheBlock(x, z); + synchronized (_lock) + { + this._blockByCoord.put(var3, var5); + this._blocks.add(var5); + } + } + + var5.e = MinecraftServer.az(); + return var5; + } + + @Override + public void a() + { + long currentTime = MinecraftServer.az(); + long deltaTime = currentTime - this._lastCleanTime; + if (deltaTime > 7500L || deltaTime < 0L) + { + this._lastCleanTime = currentTime; + + synchronized (_lock) + { + for (int i = 0; i < this._blocks.size(); ++i) + { + BiomeCache.BiomeCacheBlock biomeCacheBlock = (BiomeCache.BiomeCacheBlock) this._blocks.get(i); + long var7 = currentTime - biomeCacheBlock.e; + if (var7 > 30000L || var7 < 0L) + { + this._blocks.remove(i--); + this._blockByCoord.remove(hash(biomeCacheBlock.c, biomeCacheBlock.d)); + } + } + } + } + } + + private long hash(int x, int z) + { + return (long) x & 4294967295L | ((long) z & 4294967295L) << 32; + } + }); + + ChunkProviderServer cps = worldServer.chunkProviderServer; + IChunkProvider icp = cps.chunkProvider; + System.out.println("Using chunk provider " + icp.getClass()); + + TimingManager.start("Map Generation"); + + long start = System.currentTimeMillis(); + long last = start; + + while (!stopGen) + { + long now = System.currentTimeMillis(); + if ((now - last) >= 4000) + { + Announce(C.cGreen + C.Bold + "Generating Map: " + C.cWhite + getMapLoadETA() + " Remaining...", false); + last = now; + } + + long hash = LongHash.toLong(_chunkX, _chunkZ); + + // This is just a shortcut to how the Minecraft server would have generated a chunk if it doesn't exist. + // This should always create a chunk because we're not loading any chunks beforehand... + // /me looks at new maintainer + net.minecraft.server.v1_8_R3.Chunk chunk = icp.getOrCreateChunk(_chunkX, _chunkZ); + + // Run the copypasted code for chunk saving. + cps.saveChunk(chunk); + cps.saveChunkNOP(chunk); + cps.unloadQueue.remove(_chunkX, _chunkZ); + cps.chunks.remove(hash); + + if (_chunkX < _currentBorder / 16) + { + _chunkX++; + } + else if (_chunkZ < _currentBorder / 16) + { + _chunkX = (int) -(_currentBorder / 16); + _chunkZ++; + } + else + { + _mapLoaded = true; + System.out.println("Map Loading Finished! Took " + TimeUnit.MILLISECONDS.toSeconds(now - start) + " seconds"); + break; + } + + _chunksLoaded++; + + // Clamp it so we don't get 101% + _mapLoadPercent = UtilMath.clamp((double) _chunksLoaded / (double) _chunkTotal, 0.0, 1.0); + _chunksPerTick = (int) (_chunksLoaded / ((now - start) / 50.0)); + } + + TimingManager.stop("Map Generation"); + + if (stopGen) + { + return; + } + + TimingManager.start("Map Saving"); + + // Wait for all the chunks to save (but do we need this?) + while (!list.isEmpty()) + { + Thread.sleep(100); + } + + TimingManager.stop("Map Saving"); + + getArcadeManager().runSync(this::generateSpawns); } - - System.out.println("Y Max: " + _yMax); - - System.out.println("Time: " + UtilTime.MakeStr(System.currentTimeMillis() - time)); - }*/ + catch (Throwable t) + { + // todo proper exception handling + // maybe force shutdown? + t.printStackTrace(); + } + }, "WorldGen Thread").start(); + } @EventHandler public void WorldBoundaryYLimit(BlockPlaceEvent event) @@ -865,45 +930,6 @@ public class UHC extends TeamGame event.setLeaveMessage(null); } - /* - @EventHandler(priority = EventPriority.LOWEST) - public void PlayerQuit(PlayerQuitEvent event) - { - Player player = event.getPlayer(); - - GameTeam team = GetTeam(player); - if (team == null) return; - - if (!team.IsAlive(player)) - return; - - team.RemovePlayer(player); - - if (player.isDead()) - return; - - - if (true) - { - //Announcement - Announce(team.GetColor() + C.Bold + player.getName() + " was killed for disconnecting."); - - player.damage(5000); - return; - } - - - if (_combatTime.containsKey(player.getName()) && !UtilTime.elapsed(_combatTime.get(player.getName()), 15000)) - { - //Announcement - Announce(team.GetColor() + C.Bold + player.getName() + " was killed for disconnecting during combat."); - - player.damage(5000); - return; - } - } - */ - @EventHandler public void CreatureCull(UpdateEvent event) { @@ -1074,37 +1100,6 @@ public class UHC extends TeamGame } } - /* - @EventHandler - public void HeadEat(PlayerInteractEvent event) - { - if (UtilGear.isMat(event.getPlayer().getItemInHand(), Material.SKULL_ITEM)) - { - UtilPlayer.message(event.getPlayer(), "You ate " + event.getPlayer().getItemInHand().getItemMeta().getDisplayName() + ChatColor.RESET + "."); - - (new PotionEffect(PotionEffectType.ABSORPTION, 2400, 0)).apply(event.getPlayer()); - (new PotionEffect(PotionEffectType.REGENERATION, 200, 1)).apply(event.getPlayer()); - - event.getPlayer().setItemInHand(null); - event.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void DamageHealCancel(EntityDamageEvent event) - { - if (event.isCancelled()) - return; - - if (event.getEntity() instanceof Player) - { - Player player = (Player)event.getEntity(); - player.removePotionEffect(PotionEffectType.REGENERATION); - - UtilPlayer.message(player, "You took damage and lost " + F.elem(C.cGreen + "Regeneration") + ChatColor.RESET + "."); - } - }*/ - @EventHandler public void ConsumeHeadApple(PlayerItemConsumeEvent event) { @@ -1139,30 +1134,6 @@ public class UHC extends TeamGame { if (event.getMessage().startsWith("/kill")) event.setCancelled(true); - - // if (event.getMessage().startsWith("/uhc time day")) - // { - // this.WorldTimeSet = 4000; - // event.setCancelled(true); - // - // Announce(event.getPlayer().getName() + " set time to Always Day!"); - // } - // - // if (event.getMessage().startsWith("/uhc time night")) - // { - // this.WorldTimeSet = 16000; - // event.setCancelled(true); - // - // Announce(event.getPlayer().getName() + " set time to Always Night!"); - // } - // - // if (event.getMessage().startsWith("/uhc time cycle")) - // { - // this.WorldTimeSet = -1; - // event.setCancelled(true); - // - // Announce(event.getPlayer().getName() + " set time to Day and Night!"); - // } } @EventHandler(priority = EventPriority.LOWEST) @@ -1645,27 +1616,27 @@ public class UHC extends TeamGame return _mapLoaded; } - public String getMapLoadPercent() + public String getMapLoadPercent() { - return (int)(_mapLoadPercent * 100) + "%"; + return (int) (_mapLoadPercent * 100) + "%"; } - - public String getMapLoadETA() + + public String getMapLoadETA() { int chunksToGo = _chunkTotal - _chunksLoaded; - - return UtilTime.MakeStr((long) ((double)chunksToGo / (double)(_chunksPerTick * 20) * 1000d), 1); + + return UtilTime.MakeStr((long) ((double) chunksToGo / (double) (_chunksPerTick * 20) * 1000d), 1); } - + @EventHandler(priority = EventPriority.HIGH) public void teamSelectInteract(PlayerInteractEntityEvent event) { if (GetState() != GameState.Recruit) return; - + if (event.getRightClicked() == null) return; - + if (!(event.getRightClicked() instanceof Player)) return; @@ -1678,34 +1649,34 @@ public class UHC extends TeamGame return; } - selectTeamMate(player, (Player)event.getRightClicked()); + selectTeamMate(player, (Player) event.getRightClicked()); } - + @EventHandler public void teamSelectCommand(PlayerCommandPreprocessEvent event) { if (GetState() != GameState.Recruit) return; - + if (!event.getMessage().toLowerCase().startsWith("/team ")) return; - + event.setCancelled(true); - + Player target = UtilPlayer.searchOnline(event.getPlayer(), event.getMessage().split(" ")[1], true); if (target == null) return; - + //Observer if (Manager.IsObserver(event.getPlayer())) { UtilPlayer.message(event.getPlayer(), F.main("Game", "Spectators cannot partake in games.")); return; } - + if (event.getPlayer().equals(target)) return; - + selectTeamMate(event.getPlayer(), target); } @@ -1717,28 +1688,28 @@ public class UHC extends TeamGame //Remove Prefs _teamReqs.remove(player); _teamReqs.remove(ally); - + //Inform UtilPlayer.message(player, F.main("Game", "You accepted " + ally.getName() + "'s Team Request!")); UtilPlayer.message(ally, F.main("Game", player.getName() + " accepted your Team Request!")); - + //Leave Old Teams if (GetTeam(player) != null) GetTeam(player).DisbandTeam(); - + if (GetTeam(ally) != null) GetTeam(ally).DisbandTeam(); - + //Get Team GameTeam team = getEmptyTeam(); if (team == null) return; - - team.setDisplayName(player.getName() + " & " + ally.getName()); - + + team.setDisplayName(player.getName() + " & " + ally.getName()); + //Join Team SetPlayerTeam(player, team, true); - SetPlayerTeam(ally, team, true); + SetPlayerTeam(ally, team, true); } //Send Invite else @@ -1747,33 +1718,33 @@ public class UHC extends TeamGame if (GetTeam(player) != null) if (GetTeam(player).HasPlayer(ally)) return; - + //Inform Player UtilPlayer.message(player, F.main("Game", "You sent a Team Request to " + ally.getName() + "!")); - + //Inform Target if (Recharge.Instance.use(player, "Team Req " + ally.getName(), 2000, false, false)) { UtilPlayer.message(ally, F.main("Game", player.getName() + " sent you a Team Request!")); UtilPlayer.message(ally, F.main("Game", "Type " + F.elem("/team " + player.getName()) + " to accept!")); } - + //Add Pref _teamReqs.put(player, ally); } } - + @EventHandler public void teamQuit(PlayerQuitEvent event) { if (GetState() != GameState.Recruit) return; - + Player player = event.getPlayer(); - + if (GetTeam(player) != null) GetTeam(player).DisbandTeam(); - + Iterator teamIter = _teamReqs.keySet().iterator(); while (teamIter.hasNext()) { @@ -1782,7 +1753,7 @@ public class UHC extends TeamGame teamIter.remove(); } } - + public GameTeam getEmptyTeam() { for (GameTeam team : GetTeamList()) @@ -1790,7 +1761,7 @@ public class UHC extends TeamGame if (team.GetPlayers(false).isEmpty()) return team; } - + return null; } } \ No newline at end of file diff --git a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/world/WorldData.java b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/world/WorldData.java index 3117bbcbd..63b45effa 100644 --- a/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/world/WorldData.java +++ b/Plugins/Nautilus.Game.Arcade/src/nautilus/game/arcade/world/WorldData.java @@ -416,6 +416,10 @@ public class WorldData public boolean LoadChunks(long maxMilliseconds) { + if (Host instanceof UHC) + { + return true; + } long startTime = System.currentTimeMillis(); for (; CurX <= MaxX; CurX += 16)