From 773d5d2e6c47548c120cb5c33619964136a384e5 Mon Sep 17 00:00:00 2001 From: md_5 Date: Tue, 22 Jan 2013 15:56:54 +1100 Subject: [PATCH] Spigot changes. --- .gitignore | 2 + pom.xml | 11 +- src/main/java/net/minecraft/server/Block.java | 12 + .../java/net/minecraft/server/BlockCactus.java | 2 +- src/main/java/net/minecraft/server/BlockCrops.java | 2 +- src/main/java/net/minecraft/server/BlockGrass.java | 2 +- .../java/net/minecraft/server/BlockMushroom.java | 2 +- src/main/java/net/minecraft/server/BlockMycel.java | 2 +- src/main/java/net/minecraft/server/BlockReed.java | 2 +- .../java/net/minecraft/server/BlockSapling.java | 2 +- src/main/java/net/minecraft/server/BlockStem.java | 2 +- .../net/minecraft/server/ChunkRegionLoader.java | 35 +- .../java/net/minecraft/server/ChunkSection.java | 31 +- src/main/java/net/minecraft/server/EntityItem.java | 3 +- .../java/net/minecraft/server/EntityPlayer.java | 1 + .../java/net/minecraft/server/EntitySquid.java | 4 - .../net/minecraft/server/EntityTrackerEntry.java | 2 + .../java/net/minecraft/server/MinecraftServer.java | 51 +-- .../net/minecraft/server/PlayerConnection.java | 18 +- src/main/java/net/minecraft/server/PlayerList.java | 10 +- .../java/net/minecraft/server/SpawnerCreature.java | 23 +- .../net/minecraft/server/ThreadLoginVerifier.java | 23 + src/main/java/net/minecraft/server/World.java | 200 ++++++++- .../java/net/minecraft/server/WorldServer.java | 122 +++++- .../java/org/bukkit/craftbukkit/CraftServer.java | 98 ++++- .../java/org/bukkit/craftbukkit/CraftWorld.java | 76 +++- src/main/java/org/bukkit/craftbukkit/Spigot.java | 25 ++ .../craftbukkit/chunkio/ChunkIOProvider.java | 2 +- .../bukkit/craftbukkit/command/RestartCommand.java | 24 + .../craftbukkit/command/TicksPerSecondCommand.java | 35 ++ .../org/bukkit/craftbukkit/entity/CraftPlayer.java | 7 + .../bukkit/craftbukkit/util/ExceptionHandler.java | 31 ++ .../bukkit/craftbukkit/util/ExceptionReporter.java | 26 ++ .../java/org/bukkit/craftbukkit/util/FlatMap.java | 34 ++ .../org/bukkit/craftbukkit/util/LongHashSet.java | 11 +- .../bukkit/craftbukkit/util/LongObjectHashMap.java | 5 + .../java/org/bukkit/craftbukkit/util/Metrics.java | 488 +++++++++++++++++++++ .../org/bukkit/craftbukkit/util/TimedThread.java | 37 ++ .../bukkit/craftbukkit/util/WatchdogThread.java | 88 ++++ src/main/resources/configurations/bukkit.yml | 30 ++ 40 files changed, 1444 insertions(+), 137 deletions(-) create mode 100644 src/main/java/org/bukkit/craftbukkit/Spigot.java create mode 100644 src/main/java/org/bukkit/craftbukkit/command/RestartCommand.java create mode 100644 src/main/java/org/bukkit/craftbukkit/command/TicksPerSecondCommand.java create mode 100644 src/main/java/org/bukkit/craftbukkit/util/ExceptionHandler.java create mode 100644 src/main/java/org/bukkit/craftbukkit/util/ExceptionReporter.java create mode 100644 src/main/java/org/bukkit/craftbukkit/util/FlatMap.java create mode 100644 src/main/java/org/bukkit/craftbukkit/util/Metrics.java create mode 100644 src/main/java/org/bukkit/craftbukkit/util/TimedThread.java create mode 100644 src/main/java/org/bukkit/craftbukkit/util/WatchdogThread.java diff --git a/.gitignore b/.gitignore index a689360..b97a549 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ /src/main/resources/achievement /src/main/resources/lang + +/dependency-reduced-pom.xml \ No newline at end of file diff --git a/pom.xml b/pom.xml index f0b0bfb..bd394ae 100644 --- a/pom.xml +++ b/pom.xml @@ -51,8 +51,8 @@ - org.bukkit - bukkit + org.spigotmc + spigot-api ${project.version} jar compile @@ -145,6 +145,11 @@ 1.3 test + + net.sf.trove4j + trove4j + 3.0.2 + @@ -156,7 +161,7 @@ gitdescribe-maven-plugin 1.3 - git-Bukkit- + git-Spigot- diff --git a/src/main/java/net/minecraft/server/Block.java b/src/main/java/net/minecraft/server/Block.java index f29eace..202bd19 100644 --- a/src/main/java/net/minecraft/server/Block.java +++ b/src/main/java/net/minecraft/server/Block.java @@ -753,4 +753,16 @@ public class Block { return 0; } // CraftBukkit end + + // Spigot start + public static float range(float min, float value, float max) { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; + } + // Spigot end } diff --git a/src/main/java/net/minecraft/server/BlockCactus.java b/src/main/java/net/minecraft/server/BlockCactus.java index dd68020..1cb89fa 100644 --- a/src/main/java/net/minecraft/server/BlockCactus.java +++ b/src/main/java/net/minecraft/server/BlockCactus.java @@ -23,7 +23,7 @@ public class BlockCactus extends Block { if (l < 3) { int i1 = world.getData(i, j, k); - if (i1 == 15) { + if (i1 >= (byte) range(3, (world.growthOdds * 100 / world.getWorld().cactusGrowthModifier * 15 / 100F) + 0.5F, 15)) { // Spigot org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, i, j + 1, k, this.id, 0); // CraftBukkit world.setData(i, j, k, 0); } else { diff --git a/src/main/java/net/minecraft/server/BlockCrops.java b/src/main/java/net/minecraft/server/BlockCrops.java index a2ce8f9..4d3b448 100644 --- a/src/main/java/net/minecraft/server/BlockCrops.java +++ b/src/main/java/net/minecraft/server/BlockCrops.java @@ -30,7 +30,7 @@ public class BlockCrops extends BlockFlower { if (l < 7) { float f = this.l(world, i, j, k); - if (random.nextInt((int) (25.0F / f) + 1) == 0) { + if (random.nextInt((int) ((world.growthOdds * 100 / world.getWorld().wheatGrowthModifier / 25.0F) / f) + 1) == 0) { // Spigot org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, i, j, k, this.id, ++l); // CraftBukkit } } diff --git a/src/main/java/net/minecraft/server/BlockGrass.java b/src/main/java/net/minecraft/server/BlockGrass.java index 79a007c..0bc7882 100644 --- a/src/main/java/net/minecraft/server/BlockGrass.java +++ b/src/main/java/net/minecraft/server/BlockGrass.java @@ -37,7 +37,7 @@ public class BlockGrass extends Block { } // CraftBukkit end } else if (world.getLightLevel(i, j + 1, k) >= 9) { - for (int l = 0; l < 4; ++l) { + for (int l = 0; l < Math.max(4, Math.max(20, (int) (4 * 100F / world.growthOdds))); ++l) { // Spigot int i1 = i + random.nextInt(3) - 1; int j1 = j + random.nextInt(5) - 3; int k1 = k + random.nextInt(3) - 1; diff --git a/src/main/java/net/minecraft/server/BlockMushroom.java b/src/main/java/net/minecraft/server/BlockMushroom.java index 38fac4c..4ff3111 100644 --- a/src/main/java/net/minecraft/server/BlockMushroom.java +++ b/src/main/java/net/minecraft/server/BlockMushroom.java @@ -23,7 +23,7 @@ public class BlockMushroom extends BlockFlower { } public void b(World world, int i, int j, int k, Random random) { - if (random.nextInt(25) == 0) { + if (random.nextInt((int) (world.growthOdds * 100 / world.getWorld().mushroomGrowthModifier * 25)) == 0) { // Spigot byte b0 = 4; int l = 5; diff --git a/src/main/java/net/minecraft/server/BlockMycel.java b/src/main/java/net/minecraft/server/BlockMycel.java index 6dbf49f..afef94d 100644 --- a/src/main/java/net/minecraft/server/BlockMycel.java +++ b/src/main/java/net/minecraft/server/BlockMycel.java @@ -37,7 +37,7 @@ public class BlockMycel extends Block { } // CraftBukkit end } else if (world.getLightLevel(i, j + 1, k) >= 9) { - for (int l = 0; l < 4; ++l) { + for (int l = 0; l < Math.max(4, Math.max(20, (int) (4 * 100F / world.growthOdds))); ++l) { // Spigot int i1 = i + random.nextInt(3) - 1; int j1 = j + random.nextInt(5) - 3; int k1 = k + random.nextInt(3) - 1; diff --git a/src/main/java/net/minecraft/server/BlockReed.java b/src/main/java/net/minecraft/server/BlockReed.java index 399050a..66ad508 100644 --- a/src/main/java/net/minecraft/server/BlockReed.java +++ b/src/main/java/net/minecraft/server/BlockReed.java @@ -24,7 +24,7 @@ public class BlockReed extends Block { if (l < 3) { int i1 = world.getData(i, j, k); - if (i1 == 15) { + if (i1 >= (byte) range(3, (world.growthOdds * 100 / world.getWorld().sugarGrowthModifier * 15 / 100F) + 0.5F, 15)) { // Spigot org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, i, j + 1, k, this.id, 0); // CraftBukkit world.setData(i, j, k, 0); } else { diff --git a/src/main/java/net/minecraft/server/BlockSapling.java b/src/main/java/net/minecraft/server/BlockSapling.java index 9c94399..e8b0f96 100644 --- a/src/main/java/net/minecraft/server/BlockSapling.java +++ b/src/main/java/net/minecraft/server/BlockSapling.java @@ -27,7 +27,7 @@ public class BlockSapling extends BlockFlower { if (world.getLightLevel(i, j + 1, k) >= 9 && random.nextInt(7) == 0) { int l = world.getData(i, j, k); - if ((l & 8) == 0) { + if (world.getLightLevel(i, j + 1, k) >= 9 && (random.nextInt(Math.max(2, (int) ((world.growthOdds * 100 / world.getWorld().treeGrowthModifier * 7 / 100F) + 0.5F))) == 0)) { // Spigot world.setData(i, j, k, l | 8); } else { this.grow(world, i, j, k, random, false, null, null); // CraftBukkit - added bonemeal, player and itemstack diff --git a/src/main/java/net/minecraft/server/BlockStem.java b/src/main/java/net/minecraft/server/BlockStem.java index ff1b89f..dfaf45d 100644 --- a/src/main/java/net/minecraft/server/BlockStem.java +++ b/src/main/java/net/minecraft/server/BlockStem.java @@ -27,7 +27,7 @@ public class BlockStem extends BlockFlower { if (world.getLightLevel(i, j + 1, k) >= 9) { float f = this.n(world, i, j, k); - if (random.nextInt((int) (25.0F / f) + 1) == 0) { + if (random.nextInt((int) ((world.growthOdds * 100 / ((this.id == Block.PUMPKIN_STEM.id) ? world.getWorld().pumpkinGrowthModifier : world.getWorld().melonGrowthModifier) / 25.0F) / f) + 1) == 0) { // Spigot int l = world.getData(i, j, k); if (l < 7) { diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java index 88c33d0..e5e60a9 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -13,8 +13,7 @@ import java.util.Set; public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader { - private List a = new ArrayList(); - private Set b = new HashSet(); + private java.util.LinkedHashMap pendingSaves = new java.util.LinkedHashMap(); // Spigot private Object c = new Object(); private final File d; @@ -27,15 +26,12 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader { ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j); synchronized (this.c) { - if (this.b.contains(chunkcoordintpair)) { - for (int k = 0; k < this.a.size(); ++k) { - if (((PendingChunkToSave) this.a.get(k)).a.equals(chunkcoordintpair)) { - return true; - } - } + // Spigot start + if (pendingSaves.containsKey(chunkcoordintpair)) { + return true; } } - + // Spigot end return RegionFileCache.a(this.d, i, j).chunkExists(i & 31, j & 31); } // CraftBukkit end @@ -60,6 +56,12 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader { Object object = this.c; synchronized (this.c) { + // Spigot start + PendingChunkToSave pendingchunktosave = pendingSaves.get(chunkcoordintpair); + if (pendingchunktosave != null) { + nbttagcompound = pendingchunktosave.b; + } + /* if (this.b.contains(chunkcoordintpair)) { for (int k = 0; k < this.a.size(); ++k) { if (((PendingChunkToSave) this.a.get(k)).a.equals(chunkcoordintpair)) { @@ -68,6 +70,7 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader { } } } + */// Spigot end } if (nbttagcompound == null) { @@ -134,6 +137,11 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader { Object object = this.c; synchronized (this.c) { + // Spigot start + if (this.pendingSaves.put(chunkcoordintpair, new PendingChunkToSave(chunkcoordintpair, nbttagcompound)) != null) { + return; + } + /* if (this.b.contains(chunkcoordintpair)) { for (int i = 0; i < this.a.size(); ++i) { if (((PendingChunkToSave) this.a.get(i)).a.equals(chunkcoordintpair)) { @@ -145,6 +153,7 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader { this.a.add(new PendingChunkToSave(chunkcoordintpair, nbttagcompound)); this.b.add(chunkcoordintpair); + */// Spigot end FileIOThread.a.a(this); } } @@ -154,12 +163,20 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader { Object object = this.c; synchronized (this.c) { + // Spigot start + if (this.pendingSaves.isEmpty()) { + return false; + } + pendingchunktosave = this.pendingSaves.values().iterator().next(); + this.pendingSaves.remove(pendingchunktosave.a); + /* if (this.a.isEmpty()) { return false; } pendingchunktosave = (PendingChunkToSave) this.a.remove(0); this.b.remove(pendingchunktosave.a); + */// Spigot end } if (pendingchunktosave != null) { diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java index 90e0636..051cf6d 100644 --- a/src/main/java/net/minecraft/server/ChunkSection.java +++ b/src/main/java/net/minecraft/server/ChunkSection.java @@ -219,7 +219,7 @@ public class ChunkSection { } public void a(byte[] abyte) { - this.blockIds = abyte; + this.blockIds = validateByteArray(abyte); // Spigot - validate } public void a(NibbleArray nibblearray) { @@ -236,19 +236,38 @@ public class ChunkSection { return; } // CraftBukkit end - - this.extBlockIds = nibblearray; + this.extBlockIds = validateNibbleArray(nibblearray); // Spigot - validate } public void b(NibbleArray nibblearray) { - this.blockData = nibblearray; + this.blockData = validateNibbleArray(nibblearray); // Spigot - validate } public void c(NibbleArray nibblearray) { - this.blockLight = nibblearray; + this.blockLight = validateNibbleArray(nibblearray); // Spigot - validate } public void d(NibbleArray nibblearray) { - this.skyLight = nibblearray; + this.skyLight = validateNibbleArray(nibblearray); // Spigot - validate + } + + // Spigot start - validate/correct nibble array + private static final NibbleArray validateNibbleArray(NibbleArray na) { + if ((na != null) && (na.a.length < 2048)) { + NibbleArray newna = new NibbleArray(4096, 4); + System.arraycopy(na.a, 0, newna.a, 0, na.a.length); + na = newna; + } + return na; + } + // Validate/correct byte array + private static final byte[] validateByteArray(byte[] ba) { + if ((ba != null) && (ba.length < 4096)) { + byte[] newba = new byte[4096]; + System.arraycopy(ba, 0, newba, 0, ba.length); + ba = newba; + } + return ba; } + // Spigot end } diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java index b8b6d52..a7baa0f 100644 --- a/src/main/java/net/minecraft/server/EntityItem.java +++ b/src/main/java/net/minecraft/server/EntityItem.java @@ -61,6 +61,7 @@ public class EntityItem extends Entity { this.lastTick = currentTick; // CraftBukkit end + if (lastTick % 2 == 0) { // Spigot this.lastX = this.locX; this.lastY = this.locY; this.lastZ = this.locZ; @@ -99,7 +100,7 @@ public class EntityItem extends Entity { if (this.onGround) { this.motY *= -0.5D; } - + } // Spigot ++this.age; if (!this.world.isStatic && this.age >= 6000) { // CraftBukkit start diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index 8d61ca6..3aed58f 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -49,6 +49,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public int newTotalExp = 0; public boolean keepLevel = false; // CraftBukkit end + public java.util.Set sentFrames = new java.util.HashSet(); // Spigot public EntityPlayer(MinecraftServer minecraftserver, World world, String s, PlayerInteractManager playerinteractmanager) { super(world); diff --git a/src/main/java/net/minecraft/server/EntitySquid.java b/src/main/java/net/minecraft/server/EntitySquid.java index 961d83a..188d477 100644 --- a/src/main/java/net/minecraft/server/EntitySquid.java +++ b/src/main/java/net/minecraft/server/EntitySquid.java @@ -63,10 +63,6 @@ public class EntitySquid extends EntityWaterAnimal { // CraftBukkit end } - public boolean H() { - return this.world.a(this.boundingBox.grow(0.0D, -0.6000000238418579D, 0.0D), Material.WATER, (Entity) this); - } - public void c() { super.c(); this.e = this.d; diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java index a026c4c..cb91e30 100644 --- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -84,6 +84,7 @@ public class EntityTrackerEntry { while (j0.hasNext()) { EntityHuman j1 = (EntityHuman) j0.next(); EntityPlayer j2 = (EntityPlayer) j1; + if (j2.sentFrames.contains(i4.uniqueId)) continue; // Spigot i7.a(j2, i5); if (j2.playerConnection.lowPriorityCount() <= 5) { @@ -91,6 +92,7 @@ public class EntityTrackerEntry { if (j3 != null) { j2.playerConnection.sendPacket(j3); + j2.sentFrames.add(i4.uniqueId); // Spigot } } } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 4bdf8aa..4ee2b8b 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -86,6 +86,11 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo public java.util.Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; // CraftBukkit end + // Spigot start + private static final int TPS = 20; + private static final int TICK_TIME = 1000000000 / TPS; + public static double currentTPS = 0; + // Spigot end public MinecraftServer(OptionSet options) { // CraftBukkit - signature file -> OptionSet l = this; @@ -397,39 +402,20 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo public void run() { try { if (this.init()) { - long i = System.currentTimeMillis(); - - for (long j = 0L; this.isRunning; this.Q = true) { - long k = System.currentTimeMillis(); - long l = k - i; - - if (l > 2000L && i - this.R >= 15000L) { - if (this.server.getWarnOnOverload()) // CraftBukkit - Added option to suppress warning messages - log.warning("Can\'t keep up! Did the system time change, or is the server overloaded?"); - l = 2000L; - this.R = i; + // Spigot start + for (long lastTick = 0L; this.isRunning; this.Q = true) { + long curTime = System.nanoTime(); + long wait = TICK_TIME - (curTime - lastTick); + if (wait > 0) { + Thread.sleep(wait / 1000000); + continue; } - - if (l < 0L) { - log.warning("Time ran backwards! Did the system time change?"); - l = 0L; - } - - j += l; - i = k; - if (this.worlds.get(0).everyoneDeeplySleeping()) { // CraftBukkit - this.q(); - j = 0L; - } else { - while (j > 50L) { - MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit - j -= 50L; - this.q(); - } - } - - Thread.sleep(1L); + currentTPS = (currentTPS * 0.95) + (1E9 / (curTime - lastTick) * 0.05); + lastTick = curTime; + MinecraftServer.currentTick++; + this.q(); } + // Spigot end } else { this.a((CrashReport) null); } @@ -454,6 +440,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.a(crashreport); } finally { + org.bukkit.craftbukkit.util.WatchdogThread.stopping(); // Spigot try { this.stop(); this.isStopped = true; @@ -605,6 +592,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo } this.methodProfiler.b(); + org.bukkit.craftbukkit.util.WatchdogThread.tick(); // Spigot } public boolean getAllowNether() { @@ -708,6 +696,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo dedicatedserver.an(); } */ + dedicatedserver.primaryThread.setUncaughtExceptionHandler(new org.bukkit.craftbukkit.util.ExceptionHandler()); // Spigot dedicatedserver.primaryThread.start(); // Runtime.getRuntime().addShutdownHook(new ThreadShutdown(dedicatedserver)); diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java index fac9ea5..43a24f5 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -852,8 +852,19 @@ public class PlayerConnection extends Connection { this.chat(s, packet3chat.a_()); + // Spigot start + boolean isCounted = true; + if (server.spamGuardExclusions != null) { + for (String excluded : server.spamGuardExclusions) { + if (s.startsWith(excluded)) { + isCounted = false; + break; + } + } + } // This section stays because it is only applicable to packets - if (chatSpamField.addAndGet(this, 20) > 200 && !this.minecraftServer.getPlayerList().isOp(this.player.name)) { // CraftBukkit use thread-safe spam + if (isCounted && chatSpamField.addAndGet(this, 20) > 200 && !this.minecraftServer.getPlayerList().isOp(this.player.name)) { // CraftBukkit use thread-safe spam + // Spigot end // CraftBukkit start if (packet3chat.a_()) { Waitable waitable = new Waitable() { @@ -976,7 +987,7 @@ public class PlayerConnection extends Connection { } try { - logger.info(event.getPlayer().getName() + " issued server command: " + event.getMessage()); // CraftBukkit + if (server.logCommands) logger.info(event.getPlayer().getName() + " issued server command: " + event.getMessage()); // Spigot if (this.server.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) { return; } @@ -1353,8 +1364,9 @@ public class PlayerConnection extends Connection { flag = false; } else { for (i = 0; i < packet130updatesign.lines[j].length(); ++i) { - if (SharedConstants.allowedCharacters.indexOf(packet130updatesign.lines[j].charAt(i)) < 0) { + if (!SharedConstants.isAllowedChatCharacter(packet130updatesign.lines[j].charAt(i))) { flag = false; + break; } } } diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java index f669a00..dee6579 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -253,7 +253,7 @@ public abstract class PlayerList { event.disallow(PlayerLoginEvent.Result.KICK_BANNED, s1); } else if (!this.isWhitelisted(s)) { - event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, "You are not white-listed on this server!"); + event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, cserver.whitelistMessage); // Spigot } else { String s2 = socketaddress.toString(); @@ -913,7 +913,13 @@ public abstract class PlayerList { public void r() { while (!this.players.isEmpty()) { - ((EntityPlayer) this.players.get(0)).playerConnection.disconnect(this.server.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message + // Spigot start + EntityPlayer p = (EntityPlayer) this.players.get(0); + p.playerConnection.disconnect(this.server.server.getShutdownMessage()); + if ((!this.players.isEmpty()) && (this.players.get(0) == p)) { + this.players.remove(0); // Prevent shutdown hang if already disconnected + } + // Spigot end } } diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java index 9b3e262..61721c4 100644 --- a/src/main/java/net/minecraft/server/SpawnerCreature.java +++ b/src/main/java/net/minecraft/server/SpawnerCreature.java @@ -16,6 +16,7 @@ public final class SpawnerCreature { private static LongObjectHashMap b = new LongObjectHashMap(); // CraftBukkit - HashMap -> LongObjectHashMap protected static final Class[] a = new Class[] { EntitySpider.class, EntityZombie.class, EntitySkeleton.class}; + private static byte spawnRadius = 0; // Spigot protected static ChunkPosition getRandomPosition(World world, int i, int j) { Chunk chunk = world.getChunkAt(i, j); @@ -34,13 +35,21 @@ public final class SpawnerCreature { int i; int j; + // Spigot start - limit radius to spawn distance (chunks aren't loaded) + if (spawnRadius == 0) { + spawnRadius = (byte) worldserver.getServer().getViewDistance(); + if (spawnRadius > 8) { + spawnRadius = 8; + } + } + // Spigot end for (i = 0; i < worldserver.players.size(); ++i) { EntityHuman entityhuman = (EntityHuman) worldserver.players.get(i); int k = MathHelper.floor(entityhuman.locX / 16.0D); j = MathHelper.floor(entityhuman.locZ / 16.0D); - byte b0 = 8; + byte b0 = spawnRadius; // Spigot - replace 8 with view distance constrained value for (int l = -b0; l <= b0; ++l) { for (int i1 = -b0; i1 <= b0; ++i1) { @@ -88,13 +97,15 @@ public final class SpawnerCreature { if (limit == 0) { continue; } + int mobcnt = 0; // CraftBukkit end - if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && (!enumcreaturetype.e() || flag2) && worldserver.a(enumcreaturetype.a()) <= limit * b.size() / 256) { // CraftBukkit - use per-world limits + if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && (!enumcreaturetype.e() || flag2) && (mobcnt = worldserver.a(enumcreaturetype.a())) <= limit * b.size() / 256) { // CraftBukkit - use per-world limits Iterator iterator = b.keySet().iterator(); + int moblimit = (limit * b.size() / 256) - mobcnt + 1; // CraftBukkit - up to 1 more than limit label110: - while (iterator.hasNext()) { + while (iterator.hasNext() && (moblimit > 0)) { // Spigot - while more allowed // CraftBukkit start long key = ((Long) iterator.next()).longValue(); @@ -158,6 +169,12 @@ public final class SpawnerCreature { a(entityliving, worldserver, f, f1, f2); worldserver.addEntity(entityliving, SpawnReason.NATURAL); // CraftBukkit end + // Spigot start + moblimit--; + if (moblimit <= 0) { // If we're past limit, stop spawn + continue label110; + } + // Spigot end if (j2 >= entityliving.bv()) { continue label110; } diff --git a/src/main/java/net/minecraft/server/ThreadLoginVerifier.java b/src/main/java/net/minecraft/server/ThreadLoginVerifier.java index 0686ba0..58d30eb 100644 --- a/src/main/java/net/minecraft/server/ThreadLoginVerifier.java +++ b/src/main/java/net/minecraft/server/ThreadLoginVerifier.java @@ -28,6 +28,29 @@ class ThreadLoginVerifier extends Thread { public void run() { try { + // Spigot start + if (((CraftServer) org.bukkit.Bukkit.getServer()).ipFilter) { + try { + String ip = this.pendingConnection.getSocket().getInetAddress().getHostAddress(); + String[] split = ip.split("\\."); + StringBuilder lookup = new StringBuilder(); + for (int i = split.length - 1; i >= 0; i--) { + lookup.append(split[i]); + lookup.append("."); + } + if (!ip.contains("127.0.0.1")) { + lookup.append("xbl.spamhaus.org."); + if (java.net.InetAddress.getByName(lookup.toString()) != null) { + this.pendingConnection.networkManager.queue(new Packet255KickDisconnect("Your IP address (" + ip + ") is flagged as unsafe by spamhaus.org/xbl")); + this.pendingConnection.networkManager.d(); + this.pendingConnection.c = true; + return; + } + } + } catch (Exception ex) { + } + } + // Spigot end String s = (new BigInteger(MinecraftEncryption.a(PendingConnection.a(this.pendingConnection), PendingConnection.b(this.pendingConnection).F().getPublic(), PendingConnection.c(this.pendingConnection)))).toString(16); URL url = new URL("http://session.minecraft.net/game/checkserver.jsp?user=" + URLEncoder.encode(PendingConnection.d(this.pendingConnection), "UTF-8") + "&serverId=" + URLEncoder.encode(s, "UTF-8")); BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(url.openStream())); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index e2fd0df..5a01944 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -64,7 +64,7 @@ public abstract class World implements IBlockAccess { // CraftBukkit start - public, longhashset public boolean allowMonsters = true; public boolean allowAnimals = true; - protected LongHashSet chunkTickList = new LongHashSet(); + protected gnu.trove.map.hash.TLongShortHashMap chunkTickList; // Spigot public long ticksPerAnimalSpawns; public long ticksPerMonsterSpawns; // CraftBukkit end @@ -72,7 +72,20 @@ public abstract class World implements IBlockAccess { int[] H; private List O; public boolean isStatic; + // Spigot start + public static final long chunkToKey(int x, int z) { + long k = ((((long)x) & 0xFFFF0000L) << 16) | ((((long)x) & 0x0000FFFFL) << 0); + k |= ((((long)z) & 0xFFFF0000L) << 32) | ((((long)z) & 0x0000FFFFL) << 16); + return k; + } + public static final int keyToX(long k) { + return (int)(((k >> 16) & 0xFFFF0000) | (k & 0x0000FFFF)); + } + public static final int keyToZ(long k) { + return (int)(((k >> 32) & 0xFFFF0000L) | ((k >> 16) & 0x0000FFFF)); + } + // Spigot end public BiomeBase getBiome(int i, int j) { if (this.isLoaded(i, 0, j)) { Chunk chunk = this.getChunkAtWorldCoords(i, j); @@ -98,6 +111,7 @@ public abstract class World implements IBlockAccess { int lastXAccessed = Integer.MIN_VALUE; int lastZAccessed = Integer.MIN_VALUE; final Object chunkLock = new Object(); + private byte chunkTickRadius; public CraftWorld getWorld() { return this.world; @@ -110,11 +124,18 @@ public abstract class World implements IBlockAccess { // Changed signature public World(IDataManager idatamanager, String s, WorldSettings worldsettings, WorldProvider worldprovider, MethodProfiler methodprofiler, ChunkGenerator gen, org.bukkit.World.Environment env) { this.generator = gen; + this.worldData = idatamanager.getWorldData(); // Spigot this.world = new CraftWorld((WorldServer) this, gen, env); this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit this.ticksPerMonsterSpawns = this.getServer().getTicksPerMonsterSpawns(); // CraftBukkit + this.chunkTickRadius = (byte)((this.getServer().getViewDistance() < 7) ? this.getServer().getViewDistance() : 7); // CraftBukkit - don't tick chunks we don't load for player // CraftBukkit end + // Spigot start + this.chunkTickList = new gnu.trove.map.hash.TLongShortHashMap(getWorld().growthPerTick * 5, 0.7f, Long.MIN_VALUE, Short.MIN_VALUE); + chunkTickList.setAutoCompactionFactor(0.0F); + // Spigot end + this.N = this.random.nextInt(12000); this.H = new int['\u8000']; this.O = new UnsafeList(); // CraftBukkit - ArrayList -> UnsafeList @@ -122,7 +143,7 @@ public abstract class World implements IBlockAccess { this.dataManager = idatamanager; this.methodProfiler = methodprofiler; this.worldMaps = new WorldMapCollection(idatamanager); - this.worldData = idatamanager.getWorldData(); + // this.worldData = idatamanager.getWorldData(); Moved up if (worldprovider != null) { this.worldProvider = worldprovider; } else if (this.worldData != null && this.worldData.j() != 0) { @@ -903,6 +924,47 @@ public abstract class World implements IBlockAccess { event = CraftEventFactory.callCreatureSpawnEvent((EntityLiving) entity, spawnReason); } else if (entity instanceof EntityItem) { event = CraftEventFactory.callItemSpawnEvent((EntityItem) entity); + // Spigot start + ItemStack item = ((EntityItem) entity).getItemStack(); + int maxSize = item.getMaxStackSize(); + if (item.count < maxSize) { + double radius = this.getWorld().itemMergeRadius; + if (radius > 0) { + List entities = this.getEntities(entity, entity.boundingBox.grow(radius, radius, radius)); + for (Entity e : entities) { + if (e instanceof EntityItem) { + EntityItem loopItem = (EntityItem) e; + ItemStack loopStack = loopItem.getItemStack(); + if (!loopItem.dead && loopStack.id == item.id && loopStack.getData() == item.getData()) { + if (loopStack.tag == null || item.tag == null || !loopStack.tag.equals(item.tag)) { + int toAdd = Math.min(loopStack.count, maxSize - item.count); + item.count += toAdd; + loopStack.count -= toAdd; + if (loopStack.count <= 0) { + loopItem.die(); + } + } + } + } + } + } + } + } else if (entity instanceof EntityExperienceOrb) { + EntityExperienceOrb xp = (EntityExperienceOrb) entity; + double radius = this.getWorld().expMergeRadius; + if (radius > 0) { + List entities = this.getEntities(entity, entity.boundingBox.grow(radius, radius, radius)); + for (Entity e : entities) { + if (e instanceof EntityExperienceOrb) { + EntityExperienceOrb loopItem = (EntityExperienceOrb) e; + if (!loopItem.dead) { + xp.value += loopItem.value; + loopItem.die(); + } + } + } + } + // Spigot end } else if (entity.getBukkitEntity() instanceof org.bukkit.entity.Projectile) { // Not all projectiles extend EntityProjectile, so check for Bukkit interface instead event = CraftEventFactory.callProjectileLaunchEvent(entity); @@ -995,6 +1057,39 @@ public abstract class World implements IBlockAccess { int i1 = MathHelper.floor(axisalignedbb.c); int j1 = MathHelper.floor(axisalignedbb.f + 1.0D); + // Spigot start + int ystart = ((k - 1) < 0) ? 0 : (k - 1); + for (int chunkx = (i >> 4); chunkx <= ((j - 1) >> 4); chunkx++) { + int cx = chunkx << 4; + for (int chunkz = (i1 >> 4); chunkz <= ((j1 - 1) >> 4); chunkz++) { + if (!this.isChunkLoaded(chunkx, chunkz)) { + continue; + } + int cz = chunkz << 4; + Chunk chunk = this.getChunkAt(chunkx, chunkz); + // Compute ranges within chunk + int xstart = (i < cx)?cx:i; + int xend = (j < (cx+16))?j:(cx+16); + int zstart = (i1 < cz)?cz:i1; + int zend = (j1 < (cz+16))?j1:(cz+16); + // Loop through blocks within chunk + for (int x = xstart; x < xend; x++) { + for (int z = zstart; z < zend; z++) { + for (int y = ystart; y < l; y++) { + int blkid = chunk.getTypeId(x - cx, y, z - cz); + if (blkid > 0) { + Block block = Block.byId[blkid]; + + if (block != null) { + block.a(this, x, y, z, axisalignedbb, this.L, entity); + } + } + } + } + } + } + } + /* for (int k1 = i; k1 < j; ++k1) { for (int l1 = i1; l1 < j1; ++l1) { if (this.isLoaded(k1, 64, l1)) { @@ -1008,6 +1103,7 @@ public abstract class World implements IBlockAccess { } } } + */// Spigot end double d0 = 0.25D; List list = this.getEntities(entity, axisalignedbb.grow(d0, d0, d0)); @@ -1315,7 +1411,37 @@ public abstract class World implements IBlockAccess { this.entityJoinedWorld(entity, true); } - public void entityJoinedWorld(Entity entity, boolean flag) { + // Spigot start + public int tickEntityExceptions = 0; + public void entityJoinedWorld(final Entity entity, final boolean flag) { + if (entity == null) { + return; + } + try { + tickEntity(entity, flag); + } catch (Exception e) { + try { + tickEntityExceptions++; + List report = new ArrayList(); + report.add("Spigot has detected an unexpected exception while handling"); + if (!(entity instanceof EntityPlayer)) { + report.add("entity " + entity.toString() + " (id: " + entity.id + ")"); + report.add("Spigot will kill the entity from the game instead of crashing your server."); + entity.die(); + } else { + report.add("player '" + ((EntityPlayer) entity).name + "'. They will be kicked instead of crashing your server."); + ((EntityPlayer) entity).getBukkitEntity().kickPlayer("The server experienced and error and was forced to kick you. Please re-login."); + } + org.bukkit.craftbukkit.util.ExceptionReporter.handle(e, report.toArray(new String[0])); + } catch (Throwable t) { + org.bukkit.craftbukkit.util.ExceptionReporter.handle(t, "Spigot has detected an unexpected exception while attempting to handle an exception (yes you read that correctly)."); + Bukkit.shutdown(); + } + } + } + + public void tickEntity(Entity entity, boolean flag) { + // Spigot end int i = MathHelper.floor(entity.locX); int j = MathHelper.floor(entity.locZ); byte b0 = 32; @@ -1896,6 +2022,11 @@ public abstract class World implements IBlockAccess { this.worldData.setWeatherDuration(1); } + // Spigot start + public int aggregateTicks = 1; + protected float modifiedOdds = 100F; + public float growthOdds = 100F; + protected void z() { // this.chunkTickList.clear(); // CraftBukkit - removed this.methodProfiler.a("buildList"); @@ -1905,25 +2036,42 @@ public abstract class World implements IBlockAccess { int j; int k; + final int optimalChunks = this.getWorld().growthPerTick; + + if (optimalChunks <= 0) return; + if (players.size() == 0) return; + //Keep chunks with growth inside of the optimal chunk range + int chunksPerPlayer = Math.min(200, Math.max(1, (int)(((optimalChunks - players.size()) / (double)players.size()) + 0.5))); + int randRange = 3 + chunksPerPlayer / 30; + if(randRange > chunkTickRadius) { // Limit to normal tick radius - including view distance + randRange = chunkTickRadius; + } + //odds of growth happening vs growth happening in vanilla + final float modifiedOdds = Math.max(35, Math.min(100, ((chunksPerPlayer + 1) * 100F) / 15F)); + this.modifiedOdds = modifiedOdds; + this.growthOdds = modifiedOdds; + for (i = 0; i < this.players.size(); ++i) { entityhuman = (EntityHuman) this.players.get(i); - j = MathHelper.floor(entityhuman.locX / 16.0D); - k = MathHelper.floor(entityhuman.locZ / 16.0D); - byte b0 = 7; - - for (int l = -b0; l <= b0; ++l) { - for (int i1 = -b0; i1 <= b0; ++i1) { - // CraftBukkit start - don't tick chunks queued for unload - ChunkProviderServer chunkProviderServer = ((WorldServer) entityhuman.world).chunkProviderServer; - if (chunkProviderServer.unloadQueue.contains(l + j, i1 + k)) { - continue; - } - // CraftBukkit end - - this.chunkTickList.add(org.bukkit.craftbukkit.util.LongHash.toLong(l + j, i1 + k)); // CraftBukkit + int chunkX = MathHelper.floor(entityhuman.locX / 16.0D); + int chunkZ = MathHelper.floor(entityhuman.locZ / 16.0D); + + //Always update the chunk the player is on + long key = chunkToKey(chunkX, chunkZ); + int existingPlayers = Math.max(0, chunkTickList.get(key)); //filter out -1's + chunkTickList.put(key, (short) (existingPlayers + 1)); + + //Check and see if we update the chunks surrounding the player this tick + for (int chunk = 0; chunk < chunksPerPlayer; chunk++) { + int dx = (random.nextBoolean() ? 1 : -1) * random.nextInt(randRange); + int dz = (random.nextBoolean() ? 1 : -1) * random.nextInt(randRange); + long hash = chunkToKey(dx + chunkX, dz + chunkZ); + if (!chunkTickList.contains(hash) && this.isChunkLoaded(dx + chunkX, dz + chunkZ)) { + chunkTickList.put(hash, (short) -1); //no players } } } + // Spigot End this.methodProfiler.b(); if (this.N > 0) { @@ -1931,7 +2079,7 @@ public abstract class World implements IBlockAccess { } this.methodProfiler.a("playerCheckLight"); - if (!this.players.isEmpty()) { + if (!this.players.isEmpty() && this.getWorld().randomLightingUpdates) { // Spigot i = this.random.nextInt(this.players.size()); entityhuman = (EntityHuman) this.players.get(i); j = MathHelper.floor(entityhuman.locX) + this.random.nextInt(11) - 5; @@ -1970,9 +2118,16 @@ public abstract class World implements IBlockAccess { chunk.o(); } + // Spigot start protected void g() { + try { this.z(); } + catch (Exception e) { + org.bukkit.craftbukkit.util.ExceptionReporter.handle(e, "Spigot has detected an unexpected exception while ticking chunks"); + } + } + // Spigot end public boolean w(int i, int j, int k) { return this.c(i, j, k, false); @@ -2310,7 +2465,10 @@ public abstract class World implements IBlockAccess { } public List getEntities(Entity entity, AxisAlignedBB axisalignedbb) { - this.O.clear(); + // Spigot start + // this.O.clear(); + ArrayList entities = new ArrayList(); + // Spigot end int i = MathHelper.floor((axisalignedbb.a - 2.0D) / 16.0D); int j = MathHelper.floor((axisalignedbb.d + 2.0D) / 16.0D); int k = MathHelper.floor((axisalignedbb.c - 2.0D) / 16.0D); @@ -2319,12 +2477,12 @@ public abstract class World implements IBlockAccess { for (int i1 = i; i1 <= j; ++i1) { for (int j1 = k; j1 <= l; ++j1) { if (this.isChunkLoaded(i1, j1)) { - this.getChunkAt(i1, j1).a(entity, axisalignedbb, this.O); + this.getChunkAt(i1, j1).a(entity, axisalignedbb, entities); // Spigot } } } - return this.O; + return entities; // Spigot } public List a(Class oclass, AxisAlignedBB axisalignedbb) { diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java index 3f73ef9..a42aa1e 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -1,5 +1,7 @@ package net.minecraft.server; +import gnu.trove.iterator.TLongShortIterator; + import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -12,6 +14,7 @@ import java.util.TreeSet; // CraftBukkit start import org.bukkit.block.BlockState; import org.bukkit.craftbukkit.util.LongHash; +import org.bukkit.craftbukkit.util.LongObjectHashMap; import org.bukkit.event.block.BlockFormEvent; import org.bukkit.event.weather.LightningStrikeEvent; @@ -24,7 +27,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate private final MinecraftServer server; public EntityTracker tracker; // CraftBukkit - private final -> public private final PlayerChunkMap manager; - private Set L; + private LongObjectHashMap> L; // CraftBukkit - change to something chunk friendly private TreeSet M; public ChunkProviderServer chunkProviderServer; public boolean savingDisabled; @@ -52,7 +55,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate } if (this.L == null) { - this.L = new HashSet(); + this.L = new LongObjectHashMap>(); // CraftBukkit } if (this.M == null) { @@ -157,6 +160,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate SpawnerCreature.spawnEntities(this, this.allowMonsters && (this.ticksPerMonsterSpawns != 0 && time % this.ticksPerMonsterSpawns == 0L), this.allowAnimals && (this.ticksPerAnimalSpawns != 0 && time % this.ticksPerAnimalSpawns == 0L), this.worldData.getTime() % 400L == 0L); } // CraftBukkit end + this.getWorld().processChunkGC(); // Spigot this.methodProfiler.c("chunkSource"); this.chunkProvider.unloadChunks(); int j = this.a(1.0F); @@ -267,15 +271,31 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate } protected void g() { + // Spigot start + this.aggregateTicks--; + if (this.aggregateTicks != 0) return; + aggregateTicks = this.getWorld().aggregateTicks; + // Spigot end super.g(); int i = 0; int j = 0; // CraftBukkit start - // Iterator iterator = this.chunkTickList.iterator(); + // Iterator iterator = this.chunkTickList.iterator(); // CraftBukkit - for (long chunkCoord : this.chunkTickList.popAll()) { - int chunkX = LongHash.msw(chunkCoord); - int chunkZ = LongHash.lsw(chunkCoord); + // CraftBukkit start + // Spigot start + for (TLongShortIterator iter = chunkTickList.iterator(); iter.hasNext();) { + iter.advance(); + long chunkCoord = iter.key(); + int chunkX = World.keyToX(chunkCoord); + int chunkZ = World.keyToZ(chunkCoord); + // If unloaded, or in procedd of being unloaded, drop it + if ((!this.isChunkLoaded(chunkX, chunkZ)) || (this.chunkProviderServer.unloadQueue.contains(chunkX, chunkZ))) { + iter.remove(); + continue; + } + int players = iter.value(); + // Spigot end // ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) iterator.next(); int k = chunkX * 16; int l = chunkZ * 16; @@ -373,6 +393,14 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate if (block != null && block.isTicking()) { ++i; + // Spigot start + if (players < 1) { + //grow fast if no players are in this chunk + this.growthOdds = modifiedOdds; + } else { + this.growthOdds = 100; + } + // Spigot end block.b(this, k2 + k, i3 + chunksection.d(), l2 + l, this.random); } } @@ -413,10 +441,11 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate nextticklistentry.a(j1); } - if (!this.L.contains(nextticklistentry)) { - this.L.add(nextticklistentry); - this.M.add(nextticklistentry); - } + // if (!this.L.contains(nextticklistentry)) { + // this.L.add(nextticklistentry); + // this.M.add(nextticklistentry); + // } + addNextTickIfNeeded(nextticklistentry); // CraftBukkit } } @@ -427,10 +456,11 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate nextticklistentry.a((long) i1 + this.worldData.getTime()); } - if (!this.L.contains(nextticklistentry)) { - this.L.add(nextticklistentry); - this.M.add(nextticklistentry); - } + //if (!this.L.contains(nextticklistentry)) { + // this.L.add(nextticklistentry); + // this.M.add(nextticklistentry); + //} + addNextTickIfNeeded(nextticklistentry); // CraftBukkit } public void tickEntities() { @@ -452,9 +482,9 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate public boolean a(boolean flag) { int i = this.M.size(); - if (i != this.L.size()) { - throw new IllegalStateException("TickNextTick list out of synch"); - } else { + //if (i != this.L.size()) { // Spigot + // throw new IllegalStateException("TickNextTick list out of synch"); // Spigot + //} else { // Spigot if (i > 1000) { // CraftBukkit start - if the server has too much to process over time, try to alleviate that if (i > 20 * 1000) { @@ -472,8 +502,11 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate break; } - this.M.remove(nextticklistentry); - this.L.remove(nextticklistentry); + // Spigot start + //this.M.remove(nextticklistentry); + //this.L.remove(nextticklistentry); + this.removeNextTickIfNeeded(nextticklistentry); + // Spigot end byte b0 = 8; if (this.d(nextticklistentry.a - b0, nextticklistentry.b - b0, nextticklistentry.c - b0, nextticklistentry.a + b0, nextticklistentry.b + b0, nextticklistentry.c + b0)) { @@ -502,10 +535,12 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate } return !this.M.isEmpty(); - } + // } // Spigot } public List a(Chunk chunk, boolean flag) { + return this.getNextTickEntriesForChunk(chunk, flag); // Spigot + /* Spigot start ArrayList arraylist = null; ChunkCoordIntPair chunkcoordintpair = chunk.l(); int i = chunkcoordintpair.x << 4; @@ -532,6 +567,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate } return arraylist; + // Spigot end */ } public void entityJoinedWorld(Entity entity, boolean flag) { @@ -610,7 +646,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate } if (this.L == null) { - this.L = new HashSet(); + this.L = new LongObjectHashMap>(); } if (this.M == null) { @@ -883,4 +919,48 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate public PortalTravelAgent s() { return this.P; } + + // Spigot start + private void addNextTickIfNeeded(NextTickListEntry ent) { + long coord = LongHash.toLong(ent.a >> 4, ent.c >> 4); + Set chunkset = L.get(coord); + if (chunkset == null) { + chunkset = new HashSet(); + L.put(coord, chunkset); + } else if (chunkset.contains(ent)) { + return; + } + chunkset.add(ent); + M.add(ent); + } + + private void removeNextTickIfNeeded(NextTickListEntry ent) { + long coord = LongHash.toLong(ent.a >> 4, ent.c >> 4); + Set chunkset = L.get(coord); + if (chunkset == null) { + return; + } + if (chunkset.remove(ent)) { + M.remove(ent); + if (chunkset.isEmpty()) { + L.remove(coord); + } + } + } + + private List getNextTickEntriesForChunk(Chunk chunk, boolean remove) { + long coord = LongHash.toLong(chunk.x, chunk.z); + Set chunkset = L.get(coord); + if (chunkset == null) { + return null; + } + List list = new ArrayList(chunkset); + if (remove) { + L.remove(coord); + M.removeAll(list); + chunkset.clear(); + } + return list; + } + // Spigot end } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index e7c0760..a7785b7 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -146,7 +146,7 @@ public final class CraftServer implements Server { protected final MinecraftServer console; protected final DedicatedPlayerList playerList; private final Map worlds = new LinkedHashMap(); - private YamlConfiguration configuration; + protected YamlConfiguration configuration; // Spigot private -> protected private final Yaml yaml = new Yaml(new SafeConstructor()); private final Map offlinePlayers = new MapMaker().softValues().makeMap(); private final AutoUpdater updater; @@ -166,6 +166,14 @@ public final class CraftServer implements Server { private final class BooleanWrapper { private boolean value = true; } + // Spigot start + public String whitelistMessage = "You are not white-listed on this server!"; + public String stopMessage = "Server restarting. Brb"; + public boolean logCommands = true; + public boolean ipFilter = false; + public boolean commandComplete = true; + public List spamGuardExclusions; + // Spigot end static { ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); @@ -208,12 +216,25 @@ public final class CraftServer implements Server { chunkGCLoadThresh = configuration.getInt("chunk-gc.load-threshold"); updater = new AutoUpdater(new BukkitDLUpdaterService(configuration.getString("auto-updater.host")), getLogger(), configuration.getString("auto-updater.preferred-channel")); - updater.setEnabled(configuration.getBoolean("auto-updater.enabled")); + updater.setEnabled(false); updater.setSuggestChannels(configuration.getBoolean("auto-updater.suggest-channels")); updater.getOnBroken().addAll(configuration.getStringList("auto-updater.on-broken")); updater.getOnUpdate().addAll(configuration.getStringList("auto-updater.on-update")); updater.check(serverVersion); + // Spigot start + Spigot.initialize(this, commandMap, configuration); + + try { + configuration.save(getConfigFile()); + } catch (IOException e) { + } + try { + new org.bukkit.craftbukkit.util.Metrics().start(); + } catch (IOException e) { + getLogger().log(Level.SEVERE, "Could not start metrics", e); + } + // Spigot end loadPlugins(); enablePlugins(PluginLoadOrder.STARTUP); } @@ -222,7 +243,7 @@ public final class CraftServer implements Server { return (File) console.options.valueOf("bukkit-settings"); } - private void saveConfig() { + public void saveConfig() { // Spigot private -> public try { configuration.save(getConfigFile()); } catch (IOException ex) { @@ -526,6 +547,7 @@ public final class CraftServer implements Server { ((DedicatedServer) console).propertyManager = config; + ((SimplePluginManager) pluginManager).useTimings(configuration.getBoolean("settings.plugin-profiling")); // Spigot boolean animals = config.getBoolean("spawn-animals", console.getSpawnAnimals()); boolean monsters = config.getBoolean("spawn-monsters", console.worlds.get(0).difficulty > 0); int difficulty = config.getInt("difficulty", console.worlds.get(0).difficulty); @@ -591,6 +613,7 @@ public final class CraftServer implements Server { "This plugin is not properly shutting down its async tasks when it is being reloaded. This may cause conflicts with the newly loaded version of the plugin" )); } + Spigot.initialize(this, commandMap, configuration); // Spigot loadPlugins(); enablePlugins(PluginLoadOrder.STARTUP); enablePlugins(PluginLoadOrder.POSTWORLD); @@ -1039,11 +1062,8 @@ public final class CraftServer implements Server { return count; } + // Spigot start public OfflinePlayer getOfflinePlayer(String name) { - return getOfflinePlayer(name, true); - } - - public OfflinePlayer getOfflinePlayer(String name, boolean search) { OfflinePlayer result = getPlayerExact(name); String lname = name.toLowerCase(); @@ -1051,17 +1071,7 @@ public final class CraftServer implements Server { result = offlinePlayers.get(lname); if (result == null) { - if (search) { - WorldNBTStorage storage = (WorldNBTStorage) console.worlds.get(0).getDataManager(); - for (String dat : storage.getPlayerDir().list(new DatFileFilter())) { - String datName = dat.substring(0, dat.length() - 4); - if (datName.equalsIgnoreCase(name)) { - name = datName; - break; - } - } - } - + // Spigot end result = new CraftOfflinePlayer(this, name); offlinePlayers.put(lname, result); } @@ -1199,7 +1209,7 @@ public final class CraftServer implements Server { Set players = new HashSet(); for (String file : files) { - players.add(getOfflinePlayer(file.substring(0, file.length() - 4), false)); + players.add(getOfflinePlayer(file.substring(0, file.length() - 4))); // Spigot } players.addAll(Arrays.asList(getOnlinePlayers())); @@ -1305,7 +1315,7 @@ public final class CraftServer implements Server { public List tabCompleteCommand(Player player, String message) { List completions = null; try { - completions = getCommandMap().tabComplete(player, message.substring(1)); + completions = (commandComplete) ? getCommandMap().tabComplete(player, message.substring(1)) : null; // Spigot } catch (CommandException ex) { player.sendMessage(ChatColor.RED + "An internal error occurred while attempting to tab-complete this command"); getLogger().log(Level.SEVERE, "Exception when " + player.getName() + " attempted to tab complete " + message, ex); @@ -1341,4 +1351,52 @@ public final class CraftServer implements Server { public CraftItemFactory getItemFactory() { return CraftItemFactory.instance(); } + + // Spigot start + public void restart() { + try { + String startupScript = configuration.getString("settings.restart-script-location", ""); + File file = new File(startupScript); + if (file.isFile()) { + System.out.println("Attempting to restart with " + startupScript); + + // Kick all players + for (Player p : this.getOnlinePlayers()) { + ((org.bukkit.craftbukkit.entity.CraftPlayer) p).kickPlayer("Server is restarting", true); + } + // Give the socket a chance to send the packets + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + } + // Close the socket so we can rebind with the new process + this.getServer().ae().a(); + + // Give time for it to kick in + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + } + + // Actually shutdown + try { + this.getServer().stop(); + } catch (Throwable t) { + } + + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + Runtime.getRuntime().exec("cmd /c start " + file.getPath()); + } else { + Runtime.getRuntime().exec(file.getPath()); + } + System.exit(0); + } else { + System.out.println("Startup script '" + startupScript + "' does not exist!"); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + // Spigot end } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 6e364b1..45217cd 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -75,7 +75,81 @@ public class CraftWorld implements World { if (server.chunkGCPeriod > 0) { chunkGCTickCount = rand.nextInt(server.chunkGCPeriod); } - } + // Spigot Start + org.bukkit.configuration.file.YamlConfiguration configuration = server.configuration; + String name; + if (world.worldData == null || world.worldData.getName() == null) { + name = "default"; + } else { + name = world.worldData.getName().replaceAll(" ", "_"); + } + + //load defaults first + growthPerTick = configuration.getInt("world-settings.default.growth-chunks-per-tick", growthPerTick); + itemMergeRadius = configuration.getDouble("world-settings.default.item-merge-radius", itemMergeRadius); + expMergeRadius = configuration.getDouble("world-settings.default.exp-merge-radius", expMergeRadius); + randomLightingUpdates = configuration.getBoolean("world-settings.default.random-light-updates", randomLightingUpdates); + mobSpawnRange = configuration.getInt("world-settings.default.mob-spawn-range", mobSpawnRange); + aggregateTicks = Math.max(1, configuration.getInt("world-settings.default.aggregate-chunkticks", aggregateTicks)); + + wheatGrowthModifier = configuration.getInt("world-settings.default.wheat-growth-modifier", wheatGrowthModifier); + cactusGrowthModifier = configuration.getInt("world-settings.default.cactus-growth-modifier", cactusGrowthModifier); + melonGrowthModifier = configuration.getInt("world-settings.default.melon-growth-modifier", melonGrowthModifier); + pumpkinGrowthModifier = configuration.getInt("world-settings.default.pumpkin-growth-modifier", pumpkinGrowthModifier); + sugarGrowthModifier = configuration.getInt("world-settings.default.sugar-growth-modifier", sugarGrowthModifier); + treeGrowthModifier = configuration.getInt("world-settings.default.tree-growth-modifier", treeGrowthModifier); + mushroomGrowthModifier = configuration.getInt("world-settings.default.mushroom-growth-modifier", mushroomGrowthModifier); + + //override defaults with world specific, if they exist + growthPerTick = configuration.getInt("world-settings." + name + ".growth-chunks-per-tick", growthPerTick); + itemMergeRadius = configuration.getDouble("world-settings." + name + ".item-merge-radius", itemMergeRadius); + expMergeRadius = configuration.getDouble("world-settings." + name + ".exp-merge-radius", expMergeRadius); + randomLightingUpdates = configuration.getBoolean("world-settings." + name + ".random-light-updates", randomLightingUpdates); + mobSpawnRange = configuration.getInt("world-settings." + name + ".mob-spawn-range", mobSpawnRange); + aggregateTicks = Math.max(1, configuration.getInt("world-settings." + name + ".aggregate-chunkticks", aggregateTicks)); + + wheatGrowthModifier = configuration.getInt("world-settings." + name + ".wheat-growth-modifier", wheatGrowthModifier); + cactusGrowthModifier = configuration.getInt("world-settings." + name + ".cactus-growth-modifier", cactusGrowthModifier); + melonGrowthModifier = configuration.getInt("world-settings." + name + ".melon-growth-modifier", melonGrowthModifier); + pumpkinGrowthModifier = configuration.getInt("world-settings." + name + ".pumpkin-growth-modifier", pumpkinGrowthModifier); + sugarGrowthModifier = configuration.getInt("world-settings." + name + ".sugar-growth-modifier", sugarGrowthModifier); + treeGrowthModifier = configuration.getInt("world-settings." + name + ".tree-growth-modifier", treeGrowthModifier); + mushroomGrowthModifier = configuration.getInt("world-settings." + name + ".mushroom-growth-modifier", mushroomGrowthModifier); + + server.getLogger().info("-------------- Spigot ----------------"); + server.getLogger().info("-------- World Settings For [" + name + "] --------"); + server.getLogger().info("Growth Per Chunk: " + growthPerTick); + server.getLogger().info("Item Merge Radius: " + itemMergeRadius); + server.getLogger().info("Experience Merge Radius: " + expMergeRadius); + server.getLogger().info("Random Lighting Updates: " + randomLightingUpdates); + server.getLogger().info("Mob Spawn Range: " + mobSpawnRange); + server.getLogger().info("Aggregate Ticks: " + aggregateTicks); + server.getLogger().info("Wheat Growth Modifier: " + wheatGrowthModifier); + server.getLogger().info("Cactus Growth Modifier: " + cactusGrowthModifier); + server.getLogger().info("Melon Growth Modifier: " + melonGrowthModifier); + server.getLogger().info("Pumpkin Growth Modifier: " + pumpkinGrowthModifier); + server.getLogger().info("Sugar Growth Modifier: " + sugarGrowthModifier); + server.getLogger().info("Tree Growth Modifier: " + treeGrowthModifier); + server.getLogger().info("Mushroom Growth Modifier: " + mushroomGrowthModifier); + server.getLogger().info("-------------------------------------------------"); + // Spigot end + } + // Spigot Start + public int growthPerTick = 650; + public double itemMergeRadius = 3; + public double expMergeRadius = 3; + public boolean randomLightingUpdates = false; + public int mobSpawnRange = 4; + public int aggregateTicks = 4; + //Crop growth rates: + public int wheatGrowthModifier = 100; + public int cactusGrowthModifier = 100; + public int melonGrowthModifier = 100; + public int pumpkinGrowthModifier = 100; + public int sugarGrowthModifier = 100; + public int treeGrowthModifier = 100; + public int mushroomGrowthModifier = 100; + // Spigot end public Block getBlockAt(int x, int y, int z) { return getChunkAt(x >> 4, z >> 4).getBlock(x & 0xF, y & 0xFF, z & 0xF); diff --git a/src/main/java/org/bukkit/craftbukkit/Spigot.java b/src/main/java/org/bukkit/craftbukkit/Spigot.java new file mode 100644 index 0000000..9e8b12e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/Spigot.java @@ -0,0 +1,25 @@ +package org.bukkit.craftbukkit; + +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.configuration.file.YamlConfiguration; + +public class Spigot { + public static void initialize(CraftServer server, SimpleCommandMap commandMap, YamlConfiguration configuration) { + commandMap.register("bukkit", new org.bukkit.craftbukkit.command.RestartCommand("restart")); + commandMap.register("bukkit", new org.bukkit.craftbukkit.command.TicksPerSecondCommand("tps")); + + org.bukkit.craftbukkit.util.WatchdogThread.startThread(configuration.getInt("settings.timeout-time", 180), configuration.getBoolean("settings.restart-on-crash", false)); + + server.whitelistMessage = configuration.getString("settings.whitelist-message", server.whitelistMessage); + server.stopMessage = configuration.getString("settings.stop-message", server.stopMessage); + server.logCommands = configuration.getBoolean("settings.log-commands", true); + server.ipFilter = configuration.getBoolean("settings.filter-unsafe-ips", false); + server.commandComplete = configuration.getBoolean("settings.command-complete", true); + server.spamGuardExclusions = configuration.getStringList("settings.spam-exclusions"); + + if (server.chunkGCPeriod == 0) { + server.getLogger().severe("[Spigot] You should not disable chunk-gc. Resetting period-in-ticks to 600 ticks."); + server.chunkGCPeriod = 600; + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java index 48cf5ba..1d4764c 100644 --- a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java +++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java @@ -40,7 +40,7 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider 19.2D) { + color = ChatColor.GREEN; + } else if (tps > 17.4D) { + color = ChatColor.YELLOW; + } else { + color = ChatColor.RED; + } + + sender.sendMessage(ChatColor.GOLD + "[TPS] " + color + tps); + + return true; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index f0e24d2..1c61830 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -212,10 +212,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } public void kickPlayer(String message) { + // Spigot start + kickPlayer(message, false); + } + + public void kickPlayer(String message, boolean async){ if (getHandle().playerConnection == null) return; + if (!async && !Bukkit.isPrimaryThread()) throw new IllegalStateException("Cannot kick player from asynchronous thread!"); // Spigot getHandle().playerConnection.disconnect(message == null ? "" : message); } + // Spigot end public void setCompassTarget(Location loc) { if (getHandle().playerConnection == null) return; diff --git a/src/main/java/org/bukkit/craftbukkit/util/ExceptionHandler.java b/src/main/java/org/bukkit/craftbukkit/util/ExceptionHandler.java new file mode 100644 index 0000000..392155e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/ExceptionHandler.java @@ -0,0 +1,31 @@ +package org.bukkit.craftbukkit.util; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; + +public class ExceptionHandler implements UncaughtExceptionHandler { + + public void uncaughtException(Thread t, Throwable e) { + Logger log = ((CraftServer) Bukkit.getServer()).getLogger(); + log.log(Level.SEVERE, "The server has crashed!"); + log.log(Level.SEVERE, "Please report this to md_5!"); + log.log(Level.SEVERE, "Begin Exception Trace:"); + log.log(Level.SEVERE, ""); + StackTraceElement[] stack = e.getStackTrace(); + for (int line = 0; line < stack.length; line++) { + log.log(Level.SEVERE, " " + stack[line].toString()); + } + log.log(Level.SEVERE, "End Exception Trace:"); + log.log(Level.SEVERE, ""); + log.log(Level.SEVERE, "Begin Thread Stack Trace:"); + stack = t.getStackTrace(); + for (int line = 0; line < stack.length; line++) { + log.log(Level.SEVERE, " " + stack[line].toString()); + } + log.log(Level.SEVERE, "End Exception Trace:"); + log.log(Level.SEVERE, ""); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/ExceptionReporter.java b/src/main/java/org/bukkit/craftbukkit/util/ExceptionReporter.java new file mode 100644 index 0000000..1d0e284 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/ExceptionReporter.java @@ -0,0 +1,26 @@ +package org.bukkit.craftbukkit.util; + +import org.bukkit.Bukkit; + +public final class ExceptionReporter { + + public static void handle(Throwable t, String... messages) { + for (String message : messages) { + Bukkit.getLogger().severe(message); + } + Bukkit.getLogger().severe("Spigot recommends you report this to md_5"); + Bukkit.getLogger().severe(""); + Bukkit.getLogger().severe("Spigot version: " + Bukkit.getBukkitVersion()); + Bukkit.getLogger().severe("Exception Trace Begins:"); + StackTraceElement[] stack = t.getStackTrace(); + for (int line = 0; line < stack.length; line++) { + Bukkit.getLogger().severe(" " + stack[line].toString()); + } + Bukkit.getLogger().severe("Exception Trace Ends."); + Bukkit.getLogger().severe(""); + } + + public static void handle(Throwable t) { + handle(t, "Spigot has encountered an unexpected exception!"); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/FlatMap.java b/src/main/java/org/bukkit/craftbukkit/util/FlatMap.java new file mode 100644 index 0000000..e8a7725 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/FlatMap.java @@ -0,0 +1,34 @@ +package org.bukkit.craftbukkit.util; + +public class FlatMap { + + private static final int FLAT_LOOKUP_SIZE = 512; + private final Object[][] flatLookup = new Object[FLAT_LOOKUP_SIZE * 2][FLAT_LOOKUP_SIZE * 2]; + + public void put(long msw, long lsw, V value) { + long acx = Math.abs(msw); + long acz = Math.abs(lsw); + if (acx < FLAT_LOOKUP_SIZE && acz < FLAT_LOOKUP_SIZE) { + flatLookup[(int) (msw + FLAT_LOOKUP_SIZE)][(int) (lsw + FLAT_LOOKUP_SIZE)] = value; + } + } + + public void put(long key, V value) { + put(LongHash.msw(key), LongHash.lsw(key), value); + + } + + public V get(long msw, long lsw) { + long acx = Math.abs(msw); + long acz = Math.abs(lsw); + if (acx < FLAT_LOOKUP_SIZE && acz < FLAT_LOOKUP_SIZE) { + return (V) flatLookup[(int) (msw + FLAT_LOOKUP_SIZE)][(int) (lsw + FLAT_LOOKUP_SIZE)]; + } else { + return null; + } + } + + public V get(long key) { + return get(LongHash.msw(key), LongHash.lsw(key)); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java index 22c96c5..3f1617d 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java +++ b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java @@ -31,6 +31,8 @@ public class LongHashSet { private int elements; private long[] values; private int modCount; + private static final Object PRESENT = new Object(); + private final FlatMap flat = new FlatMap(); public LongHashSet() { this(INITIAL_SIZE); @@ -56,10 +58,11 @@ public class LongHashSet { } public boolean contains(int msw, int lsw) { + if (flat.get(msw, lsw) != null) return true; // Spigot return contains(LongHash.toLong(msw, lsw)); } - public boolean contains(long value) { + private boolean contains(long value) { // Spigot int hash = hash(value); int index = (hash & 0x7FFFFFFF) % values.length; int offset = 1; @@ -78,10 +81,11 @@ public class LongHashSet { } public boolean add(int msw, int lsw) { + flat.put(msw, lsw, PRESENT); // Spigot return add(LongHash.toLong(msw, lsw)); } - public boolean add(long value) { + private boolean add(long value) { // Spigot int hash = hash(value); int index = (hash & 0x7FFFFFFF) % values.length; int offset = 1; @@ -125,10 +129,11 @@ public class LongHashSet { } public void remove(int msw, int lsw) { + flat.put(msw, lsw, null); // Spigot remove(LongHash.toLong(msw, lsw)); } - public boolean remove(long value) { + private boolean remove(long value) { // Spigot int hash = hash(value); int index = (hash & 0x7FFFFFFF) % values.length; int offset = 1; diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java b/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java index 01861cc..dbd33fa 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java +++ b/src/main/java/org/bukkit/craftbukkit/util/LongObjectHashMap.java @@ -28,6 +28,7 @@ public class LongObjectHashMap implements Cloneable, Serializable { private transient V[][] values; private transient int modCount; private transient int size; + private final FlatMap flat = new FlatMap(); // Spigot public LongObjectHashMap() { initialize(); @@ -61,6 +62,8 @@ public class LongObjectHashMap implements Cloneable, Serializable { } public V get(long key) { + V val = flat.get(key); // Spigot + if (val != null) return val; // Spigot int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); long[] inner = keys[index]; if (inner == null) return null; @@ -78,6 +81,7 @@ public class LongObjectHashMap implements Cloneable, Serializable { } public V put(long key, V value) { + flat.put(key, value); // Spigot int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); long[] innerKeys = keys[index]; V[] innerValues = values[index]; @@ -124,6 +128,7 @@ public class LongObjectHashMap implements Cloneable, Serializable { } public V remove(long key) { + flat.put(key, null); // Spigot int index = (int) (keyIndex(key) & (BUCKET_SIZE - 1)); long[] inner = keys[index]; if (inner == null) { diff --git a/src/main/java/org/bukkit/craftbukkit/util/Metrics.java b/src/main/java/org/bukkit/craftbukkit/util/Metrics.java new file mode 100644 index 0000000..da05b80 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/Metrics.java @@ -0,0 +1,488 @@ +package org.bukkit.craftbukkit.util; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; + +/** + *

The metrics class obtains data about a plugin and submits statistics + * about it to the metrics backend.

Public methods provided by this + * class:

+ * + * Graph createGraph(String name);
+ * void addCustomData(Metrics.Plotter plotter);
+ * void start();
+ *
+ */ +public class Metrics { + + /** + * The current revision number + */ + private final static int REVISION = 5; + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://mcstats.org"; + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/report/%s"; + /** + * The file where guid and opt out is stored in + */ + private static final String CONFIG_FILE = "plugins/PluginMetrics/config.yml"; + /** + * The separator to use for custom data. This MUST NOT change unless you are + * hosting your own version of metrics and want to change it. + */ + private static final String CUSTOM_DATA_SEPARATOR = "~~"; + /** + * Interval of time to ping (in minutes) + */ + private final static int PING_INTERVAL = 5; + /** + * All of the custom graphs to submit to metrics + */ + private final Set graphs = Collections.synchronizedSet(new HashSet()); + /** + * The default graph, used for addCustomData when you don't want a specific + * graph + */ + private final Graph defaultGraph = new Graph("Default"); + /** + * The plugin configuration file + */ + private final YamlConfiguration configuration; + /** + * Unique server id + */ + private final String guid; + + public Metrics() throws IOException { + // load the config + File file = new File(CONFIG_FILE); + configuration = YamlConfiguration.loadConfiguration(file); + + // add some defaults + configuration.addDefault("opt-out", false); + configuration.addDefault("guid", UUID.randomUUID().toString()); + + // Do we need to create the file? + if (configuration.get("guid", null) == null) { + configuration.options().header("http://metrics.griefcraft.com").copyDefaults(true); + configuration.save(file); + } + + // Load the guid then + guid = configuration.getString("guid"); + + Graph graph = createGraph("Operating System"); + // Plot the total amount of protections + graph.addPlotter(new Metrics.Plotter(System.getProperty("os.name")) { + @Override + public int getValue() { + return 1; + } + }); + + graph = createGraph("System Cores"); + // Plot the total amount of protections + graph.addPlotter(new Metrics.Plotter(Integer.toString(Runtime.getRuntime().availableProcessors())) { + @Override + public int getValue() { + return 1; + } + }); + + graph = createGraph("System RAM"); + long RAM = Runtime.getRuntime().maxMemory() / 1024L / 1024L; + String plotName; + if (RAM < 1024) { + plotName = "< 1024mb"; + } else if (RAM < 2048) { + plotName = "1024-2048mb"; + } else if (RAM < 4096) { + plotName = "2048-4096mb"; + } else if (RAM < 8192) { + plotName = "4096-8192mb"; + } else if (RAM < 16384) { + plotName = "8192-16384mb"; + } else { + plotName = "16384+ mb"; + } + + // Plot the total amount of protections + graph.addPlotter(new Metrics.Plotter(plotName) { + @Override + public int getValue() { + return 1; + } + }); + } + + /** + * Construct and create a Graph that can be used to separate specific + * plotters to their own graphs on the metrics website. Plotters can be + * added to the graph object returned. + * + * @param name + * @return Graph object created. Will never return NULL under normal + * circumstances unless bad parameters are given + */ + public Graph createGraph(String name) { + if (name == null) { + throw new IllegalArgumentException("Graph name cannot be null"); + } + + // Construct the graph object + Graph graph = new Graph(name); + + // Now we can add our graph + graphs.add(graph); + + // and return back + return graph; + } + + /** + * Adds a custom data plotter to the default graph + * + * @param plotter + */ + public void addCustomData(Plotter plotter) { + if (plotter == null) { + throw new IllegalArgumentException("Plotter cannot be null"); + } + + // Add the plotter to the graph o/ + defaultGraph.addPlotter(plotter); + + // Ensure the default graph is included in the submitted graphs + graphs.add(defaultGraph); + } + + /** + * Start measuring statistics. This will immediately create an async + * repeating task as the plugin and send the initial data to the metrics + * backend, and then after that it will post in increments of PING_INTERVAL + * * 1200 ticks. + */ + public void start() { + // Did we opt out? + if (configuration.getBoolean("opt-out", false)) { + return; + } + + // Begin hitting the server with glorious data + new TimedThread(new Runnable() { + private boolean firstPost = true; + + public void run() { + try { + // We use the inverse of firstPost because if it is the first time we are posting, + // it is not a interval ping, so it evaluates to FALSE + // Each time thereafter it will evaluate to TRUE, i.e PING! + postPlugin(!firstPost); + + // After the first post we set firstPost to false + // Each post thereafter will be a ping + firstPost = false; + } catch (IOException e) { + System.err.println("[Metrics] " + e.getMessage()); + } + } + }, PING_INTERVAL * 60000).start(); + } + + /** + * Generic method that posts a plugin to the metrics website + */ + private void postPlugin(boolean isPing) throws IOException { + // Construct the post data + String data = encode("guid") + '=' + encode(guid) + + encodeDataPair("version", "Spigot 1.4") + + encodeDataPair("server", Bukkit.getVersion()) + + encodeDataPair("players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length)) + + encodeDataPair("revision", String.valueOf(REVISION)); + + // If we're pinging, append it + if (isPing) { + data += encodeDataPair("ping", "true"); + } + + // Acquire a lock on the graphs, which lets us make the assumption we also lock everything + // inside of the graph (e.g plotters) + synchronized (graphs) { + Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + Graph graph = iter.next(); + + //System.out.println("Sending data for " + graph.getName()); + + // Because we have a lock on the graphs set already, it is reasonable to assume + // that our lock transcends down to the individual plotters in the graphs also. + // Because our methods are private, no one but us can reasonably access this list + // without reflection so this is a safe assumption without adding more code. + for (Plotter plotter : graph.getPlotters()) { + // The key name to send to the metrics server + // The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top + // Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME + String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName()); + + // The value to send, which for the foreseeable future is just the string + // value of plotter.getValue() + String value = Integer.toString(plotter.getValue()); + + //System.out.println("Plotter data for " + plotter.getColumnName() + " is " + plotter.getValue()); + + // Add it to the http post data :) + data += encodeDataPair(key, value); + } + } + } + + // Create the url + URL url = new URL(BASE_URL + String.format(REPORT_URL, "Spigot")); + + // Connect to the website + URLConnection connection; + + // Mineshafter creates a socks proxy, so we can safely bypass it + // It does not reroute POST requests so we need to go around it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + + connection.setDoOutput(true); + + // Write the data + OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); + writer.write(data); + writer.flush(); + + // System.out.println(data); + + // Now read the response + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String response = reader.readLine(); + + // close resources + writer.close(); + reader.close(); + + if (response.startsWith("ERR")) { + throw new IOException(response); //Throw the exception + } else { + // Is this the first update this hour? + if (response.contains("OK This is your first update this hour")) { + synchronized (graphs) { + Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + Graph graph = iter.next(); + + for (Plotter plotter : graph.getPlotters()) { + plotter.reset(); + } + } + } + } + } + //if (response.startsWith("OK")) - We should get "OK" followed by an optional description if everything goes right + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send + * POST requests + * + * @return + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (Exception e) { + return false; + } + } + + /** + *

Encode a key/value data pair to be used in a HTTP post request. This + * INCLUDES a & so the first key/value pair MUST be included manually, + * e.g:

+ * + * String httpData = encode("guid") + '=' + encode("1234") + encodeDataPair("authors") + ".."; + * + * + * @param key + * @param value + * @return + */ + private static String encodeDataPair(String key, String value) throws UnsupportedEncodingException { + return '&' + encode(key) + '=' + encode(value); + } + + /** + * Encode text as UTF-8 + * + * @param text + * @return + */ + private static String encode(String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + + /** + * Represents a custom graph on the website + */ + public static class Graph { + + /** + * The graph's name, alphanumeric and spaces only :) If it does not + * comply to the above when submitted, it is rejected + */ + private final String name; + /** + * The set of plotters that are contained within this graph + */ + private final Set plotters = new LinkedHashSet(); + + public Graph(String name) { + this.name = name; + } + + /** + * Gets the graph's name + * + * @return + */ + public String getName() { + return name; + } + + /** + * Add a plotter to the graph, which will be used to plot entries + * + * @param plotter + */ + public void addPlotter(Plotter plotter) { + plotters.add(plotter); + } + + /** + * Remove a plotter from the graph + * + * @param plotter + */ + public void removePlotter(Plotter plotter) { + plotters.remove(plotter); + } + + /** + * Gets an unmodifiable set of the plotter objects in the graph + * + * @return + */ + public Set getPlotters() { + return Collections.unmodifiableSet(plotters); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Graph)) { + return false; + } + + Graph graph = (Graph) object; + return graph.name.equals(name); + } + } + + /** + * Interface used to collect custom data for a plugin + */ + public static abstract class Plotter { + + /** + * The plot's name + */ + private final String name; + + /** + * Construct a plotter with the default plot name + */ + public Plotter() { + this("Default"); + } + + /** + * Construct a plotter with a specific plot name + * + * @param name + */ + public Plotter(String name) { + this.name = name; + } + + /** + * Get the current value for the plotted point + * + * @return + */ + public abstract int getValue(); + + /** + * Get the column name for the plotted point + * + * @return the plotted point's column name + */ + public String getColumnName() { + return name; + } + + /** + * Called after the website graphs have been updated + */ + public void reset() { + } + + @Override + public int hashCode() { + return getColumnName().hashCode() + getValue(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Plotter)) { + return false; + } + + Plotter plotter = (Plotter) object; + return plotter.name.equals(name) && plotter.getValue() == getValue(); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/TimedThread.java b/src/main/java/org/bukkit/craftbukkit/util/TimedThread.java new file mode 100644 index 0000000..d8d2c7c --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/TimedThread.java @@ -0,0 +1,37 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.bukkit.craftbukkit.util; + +public class TimedThread extends Thread { + + final Runnable runnable; + final long time; + + public TimedThread(Runnable runnable, long time) { + super("Spigot Metrics Gathering Thread"); + setDaemon(true); + this.runnable = runnable; + this.time = time; + } + + @Override + public void run() { + try { + sleep(60000); + } catch (InterruptedException ie) { + } + + while (!isInterrupted()) { + try { + runnable.run(); + sleep(time); + } catch (InterruptedException ie) { + } catch (Exception ex) { + ex.printStackTrace(); + interrupt(); + } + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/util/WatchdogThread.java b/src/main/java/org/bukkit/craftbukkit/util/WatchdogThread.java new file mode 100644 index 0000000..9e92ea2 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/WatchdogThread.java @@ -0,0 +1,88 @@ +package org.bukkit.craftbukkit.util; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; + +public class WatchdogThread extends Thread { + + private static WatchdogThread instance; + private static final String LINE = "------------------------------"; + private AtomicLong lastTick = new AtomicLong(System.currentTimeMillis()); + private final long timeoutTime; + private final boolean restart; + private boolean stopping; + + private WatchdogThread(long timeoutTime, boolean restart) { + super("Spigot Watchdog Thread"); + this.timeoutTime = timeoutTime; + this.restart = restart; + } + + public static void startThread(int timeoutTime, boolean restart) { + if (instance == null) { + instance = new WatchdogThread(timeoutTime * 1000L, restart); + instance.start(); + } + instance.stopping = false; + } + + public static void tick() { + instance.lastTick.set(System.currentTimeMillis()); + } + + public static void stopping() { + if (instance != null) { + instance.stopping = true; + } + } + + @Override + public void run() { + while (!this.isInterrupted()) { + try { + sleep(10000); + } catch (InterruptedException ignore) { + } + if (stopping) + continue; + if (System.currentTimeMillis() > (lastTick.get() + timeoutTime)) { + Logger log = ((CraftServer) Bukkit.getServer()).getLogger(); + log.log(Level.SEVERE, "The server has stopped responding!"); + log.log(Level.SEVERE, "Please report this to md_5!"); + log.log(Level.SEVERE, "Spigot version: " + Bukkit.getBukkitVersion()); + log.log(Level.SEVERE, "Begin Exception Trace For All Threads:"); + Map traces = Thread.getAllStackTraces(); + Iterator> i = traces.entrySet().iterator(); + while (i.hasNext()) { + Entry entry = i.next(); + Thread thread = entry.getKey(); + if (thread.getState() != State.WAITING) { + System.err.println(LINE); + + log.log(Level.SEVERE, "Current Thread: " + thread.getName()); + log.log(Level.SEVERE, " PID: " + thread.getId() + " | Alive: " + thread.isAlive() + " | State: " + thread.getState()); + log.log(Level.SEVERE, " Stack:"); + StackTraceElement[] stack = entry.getValue(); + for (int line = 0; line < stack.length; line++) { + log.log(Level.SEVERE, " " + stack[line].toString()); + } + } + } + System.err.println(LINE); + + if (this.restart) { + ((CraftServer) Bukkit.getServer()).restart(); + } + + //Give up + this.interrupt(); + } + } + } +} diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml index 61a95e3..5262ae1 100644 --- a/src/main/resources/configurations/bukkit.yml +++ b/src/main/resources/configurations/bukkit.yml @@ -25,6 +25,36 @@ settings: query-plugins: true deprecated-verbose: default shutdown-message: Server closed + restart-script-location: start.bat + timeout-time: 180 + restart-on-crash: false + filter-unsafe-ips: false + whitelist-message: You are not white-listed on this server! + log-commands: true + command-complete: true + spam-exclusions: + - /skill +world-settings: + default: + growth-chunks-per-tick: 650 + mob-spawn-range: 4 + item-merge-radius: 3.5 + exp-merge-radius: 3.5 + random-light-updates: false + aggregate-chunkticks: 4 + wheat-growth-modifier: 100 + cactus-growth-modifier: 100 + melon-growth-modifier: 100 + pumpkin-growth-modifier: 100 + sugar-growth-modifier: 100 + tree-growth-modifier: 100 + mushroom-growth-modifier: 100 + world: + growth-chunks-per-tick: 1000 + world_nether: + growth-chunks-per-tick: 0 + random-light-updates: true + water-creatures-per-chunk: 0 spawn-limits: monsters: 70 animals: 15 -- 1.8.1-rc2