From 6b8688ec60bde099d830d49b86211ac90d295af5 Mon Sep 17 00:00:00 2001 From: md_5 Date: Sun, 30 Dec 2012 23:56:05 -0600 Subject: [PATCH 01/15] Spigot Changes. This commit has undergone basic testing and appears to now be safe for careful production usage. Please report any bugs to IRC as soon as you encounter them. Long live Spigot! --- .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 | 202 ++++++++- .../java/net/minecraft/server/WorldServer.java | 133 ++++-- .../java/org/bukkit/craftbukkit/CraftServer.java | 108 ++++- .../java/org/bukkit/craftbukkit/CraftWorld.java | 76 +++- .../craftbukkit/chunkio/ChunkIOProvider.java | 2 +- .../bukkit/craftbukkit/command/RestartCommand.java | 24 + .../craftbukkit/command/TicksPerSecondCommand.java | 35 ++ .../org/bukkit/craftbukkit/entity/CraftPlayer.java | 7 + .../updater/BukkitDLUpdaterService.java | 26 +- .../bukkit/craftbukkit/util/ExceptionHandler.java | 31 ++ .../bukkit/craftbukkit/util/ExceptionReporter.java | 26 ++ .../java/org/bukkit/craftbukkit/util/FlatMap.java | 34 ++ .../craftbukkit/util/LightningSimulator.java | 184 ++++++++ .../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 | 55 +++ 41 files changed, 1658 insertions(+), 156 deletions(-) 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/LightningSimulator.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..4138573 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ /src/main/resources/achievement /src/main/resources/lang + +/dependency-reduced-pom.xml diff --git a/pom.xml b/pom.xml index 6a75d75..f331d53 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 bfc48d4..8fa8302 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 1cb66df..aefc806 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 86e6ea9..76135a2 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -48,6 +48,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 2f20038..b0baa85 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 dd29094..4a213bc 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -851,8 +851,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() { @@ -975,7 +986,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; } @@ -1371,8 +1382,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 c43a6ed..ee241af 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -250,7 +250,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(); @@ -822,7 +822,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 a1ed84c..576cbd3 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) { return 0; } + 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(); @@ -157,6 +168,12 @@ public final class SpawnerCreature { // CraftBukkit - added a reason for spawning this creature worldserver.addEntity(entityliving, SpawnReason.NATURAL); a(entityliving, worldserver, f, f1, f2); + // 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..263cbd3 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -64,7 +64,8 @@ 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 + private org.bukkit.craftbukkit.util.LightningSimulator lightningSim = new org.bukkit.craftbukkit.util.LightningSimulator(this); // Spigot public long ticksPerAnimalSpawns; public long ticksPerMonsterSpawns; // CraftBukkit end @@ -72,7 +73,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 +112,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 +125,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 +144,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 +925,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 +1058,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 +1104,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 +1412,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; @@ -1815,6 +1942,7 @@ public abstract class World implements IBlockAccess { protected void n() { if (!this.worldProvider.f) { + lightningSim.onTick(); // Spigot int i = this.worldData.getThunderDuration(); if (i <= 0) { @@ -1896,6 +2024,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 +2038,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 +2081,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 +2120,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 +2467,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 +2479,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 b426d5d..6de6b12 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; @@ -293,16 +313,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate int k1; int l1; - if (this.random.nextInt(100000) == 0 && this.N() && this.M()) { - this.k = this.k * 3 + 1013904223; - i1 = this.k >> 2; - j1 = k + (i1 & 15); - k1 = l + (i1 >> 8 & 15); - l1 = this.h(j1, k1); - if (this.D(j1, l1, k1)) { - this.strikeLightning(new EntityLightning(this, (double) j1, (double) l1, (double) k1)); - } - } + // Spigot - remove lightning code this.methodProfiler.c("iceandsnow"); int i2; @@ -373,6 +384,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 +432,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 +447,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 +473,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 +493,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 +526,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 +558,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate } return arraylist; + // Spigot end */ } public void entityJoinedWorld(Entity entity, boolean flag) { @@ -610,7 +637,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 +910,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 936cbc6..67593d5 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,37 @@ 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 + 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)); + + whitelistMessage = configuration.getString("settings.whitelist-message", whitelistMessage); + stopMessage = configuration.getString("settings.stop-message", stopMessage); + logCommands = configuration.getBoolean("settings.log-commands", true); + ipFilter = configuration.getBoolean("settings.filter-unsafe-ips", false); + commandComplete = configuration.getBoolean("settings.command-complete", true); + spamGuardExclusions = configuration.getStringList("settings.spam-exclusions"); + + org.bukkit.craftbukkit.util.LightningSimulator.configure(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 +255,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) { @@ -1036,11 +1069,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(); @@ -1048,17 +1078,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); } @@ -1196,7 +1216,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())); @@ -1302,7 +1322,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); @@ -1338,4 +1358,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 cb20066..3544aa3 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -73,7 +73,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/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 90abcce..3e9df39 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/updater/BukkitDLUpdaterService.java b/src/main/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterService.java index f027900..efc7889 100644 --- a/src/main/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterService.java +++ b/src/main/java/org/bukkit/craftbukkit/updater/BukkitDLUpdaterService.java @@ -1,6 +1,6 @@ package org.bukkit.craftbukkit.updater; -import com.google.gson.*; +// import com.google.gson.*; // Spigot import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; @@ -16,7 +16,7 @@ import java.util.logging.Logger; public class BukkitDLUpdaterService { private static final String API_PREFIX_ARTIFACT = "/api/1.0/downloads/projects/craftbukkit/view/"; private static final String API_PREFIX_CHANNEL = "/api/1.0/downloads/channels/"; - private static final DateDeserializer dateDeserializer = new DateDeserializer(); + // private static final DateDeserializer dateDeserializer = new DateDeserializer(); // Spigot private final String host; public BukkitDLUpdaterService(String host) { @@ -47,8 +47,11 @@ public class BukkitDLUpdaterService { URLConnection connection = url.openConnection(); connection.setRequestProperty("User-Agent", getUserAgent()); reader = new InputStreamReader(connection.getInputStream()); - Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, dateDeserializer).setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - return gson.fromJson(reader, ArtifactDetails.class); + // Spigot start + // Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, dateDeserializer).setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + // return gson.fromJson(reader, ArtifactDetails.class); + // Spigot end + return null; } finally { if (reader != null) { reader.close(); @@ -76,10 +79,13 @@ public class BukkitDLUpdaterService { URLConnection connection = url.openConnection(); connection.setRequestProperty("User-Agent", getUserAgent()); reader = new InputStreamReader(connection.getInputStream()); - Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, dateDeserializer).setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - ArtifactDetails.ChannelDetails fromJson = gson.fromJson(reader, ArtifactDetails.ChannelDetails.class); + // Spigot start + // Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, dateDeserializer).setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + // ArtifactDetails.ChannelDetails fromJson = gson.fromJson(reader, ArtifactDetails.ChannelDetails.class); - return fromJson; + //return fromJson; + // Spigot end + return null; } finally { if (reader != null) { reader.close(); @@ -87,7 +93,9 @@ public class BukkitDLUpdaterService { } } - static class DateDeserializer implements JsonDeserializer { + // Spigot start + /* + static class DateDeserializer implements JsonDeserializer { private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public Date deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException { @@ -97,5 +105,5 @@ public class BukkitDLUpdaterService { throw new JsonParseException("Date is not formatted correctly", ex); } } - } + }*/// Spigot end } 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/LightningSimulator.java b/src/main/java/org/bukkit/craftbukkit/util/LightningSimulator.java new file mode 100644 index 0000000..3b12b19 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/util/LightningSimulator.java @@ -0,0 +1,184 @@ +package org.bukkit.craftbukkit.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Random; +import net.minecraft.server.EntityLightning; +import net.minecraft.server.EntityPlayer; +import net.minecraft.server.MathHelper; +import net.minecraft.server.World; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.weather.ThunderChangeEvent; + +public class LightningSimulator { + + private static final int MAX_LIGHTNING_BRANCHES = 5; + final World world; + final HashMap playerCountdown = new HashMap(); + Intensity stormIntensity = null; + boolean canceled = false; + + public LightningSimulator(World world) { + this.world = world; + } + + public static void configure(YamlConfiguration configuration) { + Bukkit.getLogger().info("--------Setting up Storm Configuration--------"); + for (Intensity intensity : Intensity.values()) { + String nameFormatted = intensity.name().toLowerCase().replaceAll("_", "-"); + intensity.chance = configuration.getInt("storm-settings." + nameFormatted + ".chance", intensity.chance); + intensity.baseTicks = configuration.getInt("storm-settings." + nameFormatted + ".lightning-delay", intensity.baseTicks); + intensity.randomTicks = configuration.getInt("storm-settings." + nameFormatted + ".lightning-random-delay", intensity.randomTicks); + Bukkit.getLogger().info(" Storm Type: " + nameFormatted); + Bukkit.getLogger().info(" Chance: " + intensity.chance); + Bukkit.getLogger().info(" Lightning Delay Ticks: " + intensity.baseTicks); + Bukkit.getLogger().info(" Lightning Random Delay Ticks: " + intensity.randomTicks); + } + Bukkit.getLogger().info("--------Finished Storm Configuration--------"); + } + + public void onTick() { + try { + updatePlayerTimers(); + } catch (Exception e) { + System.out.println("Spigot failed to calculate lightning for the server"); + System.out.println("Please report this to md_5"); + System.out.println("Spigot Version: " + Bukkit.getBukkitVersion()); + e.printStackTrace(); + } + } + + public void updatePlayerTimers() { + if (world.getWorld().hasStorm()) { + if (canceled) { + return; + } + if (stormIntensity == null) { + ThunderChangeEvent thunder = new ThunderChangeEvent(world.getWorld(), true); + Bukkit.getPluginManager().callEvent(thunder); + if (thunder.isCancelled()) { + canceled = true; + return; + } + stormIntensity = Intensity.getRandomIntensity(world.random); + System.out.println("Started a storm of type " + stormIntensity.name() + " in world [" + world.worldData.getName() + "]"); + } + List toStrike = new ArrayList(); + for (Object o : world.players) { + if (o instanceof EntityPlayer) { + EntityPlayer player = (EntityPlayer) o; + Integer ticksLeft = playerCountdown.get(player); + if (ticksLeft == null) { + playerCountdown.put(player, getTicksBeforeNextLightning(world.random)); + } else if (ticksLeft == 1) { + //weed out dc'd players + if (!player.playerConnection.disconnected) { + toStrike.add(player); + playerCountdown.put(player, getTicksBeforeNextLightning(world.random)); + } + } else { + playerCountdown.put(player, ticksLeft - 1); + } + } + } + strikePlayers(toStrike); + } else { + stormIntensity = null; + canceled = false; + } + } + + public void strikePlayers(List toStrike) { + for (EntityPlayer player : toStrike) { + final int posX = MathHelper.floor(player.locX); + final int posY = MathHelper.floor(player.locY); + final int posZ = MathHelper.floor(player.locZ); + for (int tries = 0; tries < 10; tries++) { + //pick a random chunk between -4, -4, to 4, 4 relative to the player's position to strike at + int cx = (world.random.nextBoolean() ? -1 : 1) * world.random.nextInt(5); + int cz = (world.random.nextBoolean() ? -1 : 1) * world.random.nextInt(5); + + //pick random coords to try to strike at inside the chunk (0, 0) to (15, 15) + int rx = world.random.nextInt(16); + int rz = world.random.nextInt(16); + + //pick a offset from the player's y position to strike at (-15 - +15) of their position + int offsetY = (world.random.nextBoolean() ? -1 : 1) * world.random.nextInt(15); + + int x = cx * 16 + rx + posX; + int y = posY + offsetY; + int z = cz * 16 + rz + posZ; + + if (isRainingAt(x, y, z)) { + int lightning = 1; + //30% chance of extra lightning at the spot + if (world.random.nextInt(10) < 3) { + lightning += world.random.nextInt(MAX_LIGHTNING_BRANCHES); + } + for (int strikes = 0; strikes < lightning; strikes++) { + double adjustX = 0.5D; + double adjustY = 0.0D; + double adjustZ = 0.5D; + //if there are extra strikes, tweak their placement slightly + if (strikes > 0) { + adjustX += (world.random.nextBoolean() ? -1 : 1) * world.random.nextInt(2); + adjustY += (world.random.nextBoolean() ? -1 : 1) * world.random.nextInt(8); + adjustZ += (world.random.nextBoolean() ? -1 : 1) * world.random.nextInt(2); + } + EntityLightning lightningStrike = new EntityLightning(world, x + adjustX, y + adjustY, z + adjustZ); + world.strikeLightning(lightningStrike); + } + //success, go to the next player + break; + } + } + } + } + + public int getTicksBeforeNextLightning(Random rand) { + return stormIntensity.baseTicks + rand.nextInt(stormIntensity.randomTicks); + } + + public boolean isRainingAt(int x, int y, int z) { + return world.D(x, y, z); + } +} + +enum Intensity { + + STRONG_ELECTRICAL_STORM(5, 10, 20), + ELECTRICAL_STORM(15, 40, 150), + STRONG_THUNDERSTORM(30, 60, 250), + THUNDERSTORM(50, 100, 500), + WEAK_THUNDERSTORM(75, 300, 1000), + RAINSTORM(100, 500, 2000); + int chance, baseTicks, randomTicks; + + Intensity(int chance, int baseTicks, int randomTicks) { + this.chance = chance; + this.baseTicks = baseTicks; + this.randomTicks = randomTicks; + } + + public static Intensity getRandomIntensity(Random rand) { + int r = rand.nextInt(100); + if (r < STRONG_ELECTRICAL_STORM.chance) { + return STRONG_ELECTRICAL_STORM; + } + if (r < ELECTRICAL_STORM.chance) { + return ELECTRICAL_STORM; + } + if (r < STRONG_THUNDERSTORM.chance) { + return STRONG_THUNDERSTORM; + } + if (r < THUNDERSTORM.chance) { + return THUNDERSTORM; + } + if (r < WEAK_THUNDERSTORM.chance) { + return WEAK_THUNDERSTORM; + } + return RAINSTORM; + } +} 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..e192700 100644 --- a/src/main/resources/configurations/bukkit.yml +++ b/src/main/resources/configurations/bukkit.yml @@ -25,6 +25,61 @@ 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 +storm-settings: + strong-electrical-storm: + chance: 5 + lightning-delay: 10 + lightning-random-delay: 20 + electrical-storm: + chance: 15 + lightning-delay: 40 + lightning-random-delay: 150 + strong-thunderstorm: + chance: 30 + lightning-delay: 60 + lightning-random-delay: 250 + thunderstorm: + chance: 50 + lightning-delay: 100 + lightning-random-delay: 500 + weak-thunderstorm: + chance: 75 + lightning-delay: 300 + lightning-random-delay: 1000 + rainstorm: + chance: 100 + lightning-delay: 500 + lightning-random-delay: 2000 spawn-limits: monsters: 70 animals: 15 -- 1.8.1-rc2