commit 017a98eb6895532d665cc9068da13e9b4ce9f1c7 Author: md_5 Date: Tue Jan 15 12:18:40 2013 +1100 Initial commit - hello future of Spigot! diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..7c741aaf34 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "Bukkit"] + path = Bukkit + url = https://github.com/Bukkit/Bukkit.git +[submodule "CraftBukkit"] + path = CraftBukkit + url = https://github.com/Bukkit/CraftBukkit.git diff --git a/Bukkit b/Bukkit new file mode 160000 index 0000000000..2877472d92 --- /dev/null +++ b/Bukkit @@ -0,0 +1 @@ +Subproject commit 2877472d921d414b9c296c8997f30e838f08d351 diff --git a/Bukkit-Patches/0001-Spigot-POM-Changes.patch b/Bukkit-Patches/0001-Spigot-POM-Changes.patch new file mode 100644 index 0000000000..e28c441b0f --- /dev/null +++ b/Bukkit-Patches/0001-Spigot-POM-Changes.patch @@ -0,0 +1,64 @@ +From 3359843a528eeada4d61ef1d4e7c0c30ad4ca982 Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Sat, 5 Jan 2013 15:34:12 +1100 +Subject: [PATCH] Spigot POM Changes + +--- + pom.xml | 36 ++++-------------------------------- + 1 file changed, 4 insertions(+), 32 deletions(-) + +diff --git a/pom.xml b/pom.xml +index 1847285..b29f810 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -1,43 +1,15 @@ + + 4.0.0 +- org.bukkit +- bukkit ++ org.spigotmc ++ spigot-api + 1.4.6-R0.4-SNAPSHOT +- Bukkit +- http://www.bukkit.org ++ Spigot-API ++ http://www.spigotmc.org + + + UTF-8 + + +- +- scm:git:git@github.com:Bukkit/Bukkit.git +- scm:git:git://github.com/Bukkit/Bukkit.git +- https://github.com/Bukkit/Bukkit/tree/master/ +- +- +- +- jenkins +- http://ci.bukkit.org +- +- +- +- +- jd.bukkit.org +- file:///home/javadocs/public_html/ +- +- +- repobo-rel +- repo.bukkit.org Releases +- http://repo.bukkit.org/content/repositories/releases/ +- +- +- repobo-snap +- repo.bukkit.org Snapshots +- http://repo.bukkit.org/content/repositories/snapshots/ +- +- +- + + + +-- +1.8.1-rc2 + diff --git a/CraftBukkit b/CraftBukkit new file mode 160000 index 0000000000..c056293b38 --- /dev/null +++ b/CraftBukkit @@ -0,0 +1 @@ +Subproject commit c056293b38cb9a1296937d91746b175252be044a diff --git a/CraftBukkit-Patches/0001-Spigot-Changes.-This-commit-has-undergone-basic-test.patch b/CraftBukkit-Patches/0001-Spigot-Changes.-This-commit-has-undergone-basic-test.patch new file mode 100644 index 0000000000..a267de42bd --- /dev/null +++ b/CraftBukkit-Patches/0001-Spigot-Changes.-This-commit-has-undergone-basic-test.patch @@ -0,0 +1,2855 @@ +From 181e716d4433066069208693b6b494d5944cb0b6 Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Sun, 30 Dec 2012 23:56:05 -0600 +Subject: [PATCH 01/13] 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 | 206 ++++++++- + .../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, 1660 insertions(+), 158 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 47e7e52..c923f94 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..c50b814 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) { +@@ -276,14 +298,14 @@ public abstract class World implements IBlockAccess { + // CraftBukkit start + public Chunk getChunkAt(int i, int j) { + Chunk result = null; +- synchronized (this.chunkLock) { ++ // Spigot start - remove sync + if (this.lastChunkAccessed == null || this.lastXAccessed != i || this.lastZAccessed != j) { + this.lastChunkAccessed = this.chunkProvider.getOrCreateChunk(i, j); + this.lastXAccessed = i; + this.lastZAccessed = j; + } + result = this.lastChunkAccessed; +- } ++ // Spigot end + return result; + } + // CraftBukkit end +@@ -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 + diff --git a/CraftBukkit-Patches/0001-Spigot-Changes.patch b/CraftBukkit-Patches/0001-Spigot-Changes.patch new file mode 100644 index 0000000000..b5b115e82b --- /dev/null +++ b/CraftBukkit-Patches/0001-Spigot-Changes.patch @@ -0,0 +1,2856 @@ +From a4a917da13d09fe9b5046339457f4e811dcb22a9 Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Sun, 30 Dec 2012 23:56:05 -0600 +Subject: [PATCH 01/13] 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 | 206 ++++++++- + .../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, 1660 insertions(+), 158 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..c50b814 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) { +@@ -276,14 +298,14 @@ public abstract class World implements IBlockAccess { + // CraftBukkit start + public Chunk getChunkAt(int i, int j) { + Chunk result = null; +- synchronized (this.chunkLock) { ++ // Spigot start - remove sync + if (this.lastChunkAccessed == null || this.lastXAccessed != i || this.lastZAccessed != j) { + this.lastChunkAccessed = this.chunkProvider.getOrCreateChunk(i, j); + this.lastXAccessed = i; + this.lastZAccessed = j; + } + result = this.lastChunkAccessed; +- } ++ // Spigot end + return result; + } + // CraftBukkit end +@@ -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.7.10.4 + diff --git a/CraftBukkit-Patches/0002-Update-Maven-Shade-Plugin-to-version-2.0.-Fixes-BUKK.patch b/CraftBukkit-Patches/0002-Update-Maven-Shade-Plugin-to-version-2.0.-Fixes-BUKK.patch new file mode 100644 index 0000000000..79dc14329b --- /dev/null +++ b/CraftBukkit-Patches/0002-Update-Maven-Shade-Plugin-to-version-2.0.-Fixes-BUKK.patch @@ -0,0 +1,26 @@ +From c17687209fb0774502789624c38b5e8879fc737a Mon Sep 17 00:00:00 2001 +From: Agaricus +Date: Wed, 19 Dec 2012 19:13:17 -0800 +Subject: [PATCH 02/13] Update Maven Shade Plugin to version 2.0. Fixes + BUKKIT-3213 + +--- + pom.xml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pom.xml b/pom.xml +index c923f94..b1c566a 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -215,7 +215,7 @@ + + org.apache.maven.plugins + maven-shade-plugin +- 1.4 ++ 2.0 + + + package +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0003-Address-BUKKIT-3286-by-firing-the-inventory-close-ev.patch b/CraftBukkit-Patches/0003-Address-BUKKIT-3286-by-firing-the-inventory-close-ev.patch new file mode 100644 index 0000000000..36028bb157 --- /dev/null +++ b/CraftBukkit-Patches/0003-Address-BUKKIT-3286-by-firing-the-inventory-close-ev.patch @@ -0,0 +1,43 @@ +From dabb132cd8dd0b5940ee7f2a1c2ff37c5239e037 Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Sun, 23 Dec 2012 17:09:40 +1100 +Subject: [PATCH 03/13] Address BUKKIT-3286 by firing the inventory close event + when a secondary container is closed due to the player entity being + destroyed. This covers all edge cases such as server stop / player kick / + player quit. + +--- + src/main/java/net/minecraft/server/EntityHuman.java | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java +index 8566391..0b0c351 100644 +--- a/src/main/java/net/minecraft/server/EntityHuman.java ++++ b/src/main/java/net/minecraft/server/EntityHuman.java +@@ -4,10 +4,12 @@ import java.util.Iterator; + import java.util.List; + + // CraftBukkit start ++import org.bukkit.Bukkit; + import org.bukkit.craftbukkit.entity.CraftItem; + import org.bukkit.entity.HumanEntity; + import org.bukkit.entity.Player; + import org.bukkit.event.entity.EntityCombustByEntityEvent; ++import org.bukkit.event.inventory.InventoryCloseEvent; + import org.bukkit.event.player.PlayerBedEnterEvent; + import org.bukkit.event.player.PlayerBedLeaveEvent; + import org.bukkit.event.player.PlayerDropItemEvent; +@@ -882,6 +884,10 @@ public abstract class EntityHuman extends EntityLiving implements ICommandListen + super.die(); + this.defaultContainer.b(this); + if (this.activeContainer != null) { ++ // CraftBukkit start ++ InventoryCloseEvent event = new InventoryCloseEvent(this.activeContainer.getBukkitView()); ++ Bukkit.getServer().getPluginManager().callEvent(event); ++ // CraftBukkit end + this.activeContainer.b(this); + } + } +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0004-Update-item-merge-logic.patch b/CraftBukkit-Patches/0004-Update-item-merge-logic.patch new file mode 100644 index 0000000000..a7e442a33d --- /dev/null +++ b/CraftBukkit-Patches/0004-Update-item-merge-logic.patch @@ -0,0 +1,45 @@ +From f1b9d9c2a5d620dc7aee11722fd329cd34dcb507 Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Sun, 23 Dec 2012 17:51:07 +1100 +Subject: [PATCH 04/13] Update item merge logic + +--- + src/main/java/net/minecraft/server/World.java | 15 +++++++-------- + 1 file changed, 7 insertions(+), 8 deletions(-) + +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index c50b814..29ccbef 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -927,6 +927,7 @@ public abstract class World implements IBlockAccess { + event = CraftEventFactory.callItemSpawnEvent((EntityItem) entity); + // Spigot start + ItemStack item = ((EntityItem) entity).getItemStack(); ++ org.bukkit.craftbukkit.inventory.CraftItemStack craft = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); + int maxSize = item.getMaxStackSize(); + if (item.count < maxSize) { + double radius = this.getWorld().itemMergeRadius; +@@ -936,14 +937,12 @@ public abstract class World implements IBlockAccess { + 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(); +- } ++ if (!loopItem.dead && craft.isSimilar(org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(loopStack))) { ++ int toAdd = Math.min(loopStack.count, maxSize - item.count); ++ item.count += toAdd; ++ loopStack.count -= toAdd; ++ if (loopStack.count <= 0) { ++ loopItem.die(); + } + } + } +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0005-Include-NibbleArray-from-mc-dev-for-diff-visibility.patch b/CraftBukkit-Patches/0005-Include-NibbleArray-from-mc-dev-for-diff-visibility.patch new file mode 100644 index 0000000000..347f5c6e3f --- /dev/null +++ b/CraftBukkit-Patches/0005-Include-NibbleArray-from-mc-dev-for-diff-visibility.patch @@ -0,0 +1,59 @@ +From 1971336996f8ac3eccdbc631a3d271890d97fc83 Mon Sep 17 00:00:00 2001 +From: Mike Primm +Date: Fri, 14 Dec 2012 21:51:06 -0600 +Subject: [PATCH 05/13] Include NibbleArray from mc-dev for diff visibility + +--- + .../java/net/minecraft/server/NibbleArray.java | 40 ++++++++++++++++++++++ + 1 file changed, 40 insertions(+) + create mode 100644 src/main/java/net/minecraft/server/NibbleArray.java + +diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java +new file mode 100644 +index 0000000..5d75a54 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/NibbleArray.java +@@ -0,0 +1,40 @@ ++package net.minecraft.server; ++ ++public class NibbleArray { ++ ++ public final byte[] a; ++ private final int b; ++ private final int c; ++ ++ public NibbleArray(int i, int j) { ++ this.a = new byte[i >> 1]; ++ this.b = j; ++ this.c = j + 4; ++ } ++ ++ public NibbleArray(byte[] abyte, int i) { ++ this.a = abyte; ++ this.b = i; ++ this.c = i + 4; ++ } ++ ++ public int a(int i, int j, int k) { ++ int l = j << this.c | k << this.b | i; ++ int i1 = l >> 1; ++ int j1 = l & 1; ++ ++ return j1 == 0 ? this.a[i1] & 15 : this.a[i1] >> 4 & 15; ++ } ++ ++ public void a(int i, int j, int k, int l) { ++ int i1 = j << this.c | k << this.b | i; ++ int j1 = i1 >> 1; ++ int k1 = i1 & 1; ++ ++ if (k1 == 0) { ++ this.a[j1] = (byte) (this.a[j1] & 240 | l & 15); ++ } else { ++ this.a[j1] = (byte) (this.a[j1] & 15 | (l & 15) << 4); ++ } ++ } ++} +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0006-Implement-lightening-of-NibbleArrays-only-allocate-b.patch b/CraftBukkit-Patches/0006-Implement-lightening-of-NibbleArrays-only-allocate-b.patch new file mode 100644 index 0000000000..d5938effe0 --- /dev/null +++ b/CraftBukkit-Patches/0006-Implement-lightening-of-NibbleArrays-only-allocate-b.patch @@ -0,0 +1,389 @@ +From ccb92789b659c2610a7b8f23740197ed784b749a Mon Sep 17 00:00:00 2001 +From: Mike Primm +Date: Sun, 23 Dec 2012 14:46:23 -0600 +Subject: [PATCH 06/13] Implement 'lightening' of NibbleArrays - only allocate + buffers when non-trivial value Saving from 40-45% of memory use by chunk + section data. + +Finish up NibbleArray lightening work - use for Snapshots, reduce copies + +Fix nibble handling with NBT - arrays aren't copied by NBTByteArray +--- + .../net/minecraft/server/ChunkRegionLoader.java | 10 +- + .../java/net/minecraft/server/ChunkSection.java | 24 ++-- + .../java/net/minecraft/server/NibbleArray.java | 122 ++++++++++++++++++++- + .../net/minecraft/server/Packet51MapChunk.java | 28 +++-- + .../java/org/bukkit/craftbukkit/CraftChunk.java | 44 +++++++- + 5 files changed, 197 insertions(+), 31 deletions(-) + +diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +index e5e60a9..2e72ab5 100644 +--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java ++++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java +@@ -225,15 +225,15 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader { + nbttagcompound1.setByte("Y", (byte) (chunksection.d() >> 4 & 255)); + nbttagcompound1.setByteArray("Blocks", chunksection.g()); + if (chunksection.i() != null) { +- nbttagcompound1.setByteArray("Add", chunksection.i().a); ++ nbttagcompound1.setByteArray("Add", chunksection.i().getValueArray()); // Spigot + } + +- nbttagcompound1.setByteArray("Data", chunksection.j().a); +- nbttagcompound1.setByteArray("BlockLight", chunksection.k().a); ++ nbttagcompound1.setByteArray("Data", chunksection.j().getValueArray()); // Spigot ++ nbttagcompound1.setByteArray("BlockLight", chunksection.k().getValueArray()); // Spigot + if (flag) { +- nbttagcompound1.setByteArray("SkyLight", chunksection.l().a); ++ nbttagcompound1.setByteArray("SkyLight", chunksection.l().getValueArray()); // Spigot + } else { +- nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.k().a.length]); ++ nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.k().getValueArray().length]); // Spigot + } + + nbttaglist.add(nbttagcompound1); +diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java +index 051cf6d..42e669c 100644 +--- a/src/main/java/net/minecraft/server/ChunkSection.java ++++ b/src/main/java/net/minecraft/server/ChunkSection.java +@@ -134,7 +134,8 @@ public class ChunkSection { + } + } + } else { +- byte[] ext = this.extBlockIds.a; ++ this.extBlockIds.forceToNonTrivialArray(); // Spigot ++ byte[] ext = this.extBlockIds.getValueArray(); + for (int off = 0, off2 = 0; off < blkIds.length;) { + byte extid = ext[off2]; + int l = (blkIds[off] & 0xFF) | ((extid & 0xF) << 8); // Even data +@@ -165,6 +166,12 @@ public class ChunkSection { + off++; + off2++; + } ++ // Spigot start ++ this.extBlockIds.detectAndProcessTrivialArray(); ++ if (this.extBlockIds.isTrivialArray() && (this.extBlockIds.getTrivialArrayValue() == 0)) { ++ this.extBlockIds = null; ++ } ++ // Spigot end + } + this.nonEmptyBlockCount = cntNonEmpty; + this.tickingBlockCount = cntTicking; +@@ -225,12 +232,11 @@ public class ChunkSection { + public void a(NibbleArray nibblearray) { + // CraftBukkit start - don't hang on to an empty nibble array + boolean empty = true; +- for (int i = 0; i < nibblearray.a.length; i++) { +- if (nibblearray.a[i] != 0) { +- empty = false; +- break; +- } ++ // Spigot start ++ if ((!nibblearray.isTrivialArray()) || (nibblearray.getTrivialArrayValue() != 0)) { ++ empty = false; + } ++ // Spigot end + + if (empty) { + return; +@@ -253,10 +259,8 @@ public class ChunkSection { + + // 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; ++ if ((na != null) && (na.getByteLength() < 2048)) { ++ na.resizeArray(2048); + } + return na; + } +diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java +index 5d75a54..c9bc20c 100644 +--- a/src/main/java/net/minecraft/server/NibbleArray.java ++++ b/src/main/java/net/minecraft/server/NibbleArray.java +@@ -1,13 +1,117 @@ + package net.minecraft.server; + ++import java.util.Arrays; // Spigot ++ + public class NibbleArray { + +- public final byte[] a; ++ private byte[] a; // Spigot - remove final, make private (anyone directly accessing this is broken already) + private final int b; + private final int c; ++ // Spigot start ++ private byte trivialValue; ++ private byte trivialByte; ++ private int length; ++ private static final int LEN2K = 2048; // Universal length used right now - optimize around this ++ private static final byte[][] TrivLen2k; ++ ++ static { ++ TrivLen2k = new byte[16][]; ++ for (int i = 0; i < 16; i++) { ++ TrivLen2k[i] = new byte[LEN2K]; ++ Arrays.fill(TrivLen2k[i], (byte) (i | (i << 4))); ++ } ++ } ++ ++ // Try to convert array to trivial array ++ public void detectAndProcessTrivialArray() { ++ trivialValue = (byte) (a[0] & 0xF); ++ trivialByte = (byte) (trivialValue | (trivialValue << 4)); ++ for (int i = 0; i < a.length; i++) { ++ if (a[i] != trivialByte) return; ++ } ++ // All values matches, so array is trivial ++ this.length = a.length; ++ this.a = null; ++ } ++ ++ // Force array to non-trivial state ++ public void forceToNonTrivialArray() { ++ if (this.a == null) { ++ this.a = new byte[this.length]; ++ if (this.trivialByte != 0) { ++ Arrays.fill(this.a, this.trivialByte); ++ } ++ } ++ } ++ ++ // Test if array is in trivial state ++ public boolean isTrivialArray() { ++ return (this.a == null); ++ } ++ ++ // Get value of all elements (only valid if array is in trivial state) ++ public int getTrivialArrayValue() { ++ return this.trivialValue; ++ } ++ ++ // Get logical length of byte array for nibble data (whether trivial or non-trivial) ++ public int getByteLength() { ++ if (this.a == null) { ++ return this.length; ++ } else { ++ return this.a.length; ++ } ++ } ++ ++ // Return byte encoding of array (whether trivial or non-trivial) - returns read-only array if trivial (do not modify!) ++ public byte[] getValueArray() { ++ if (this.a != null) { ++ return this.a; ++ } else { ++ byte[] rslt; ++ ++ if (this.length == LEN2K) { // All current uses are 2k long, but be safe ++ rslt = TrivLen2k[this.trivialValue]; ++ } else { ++ rslt = new byte[this.length]; ++ if (this.trivialByte != 0) { ++ Arrays.fill(rslt, this.trivialByte); ++ } ++ } ++ return rslt; ++ } ++ } ++ ++ // Copy byte representation of array to given offset in given byte array ++ public int copyToByteArray(byte[] dest, int off) { ++ if (this.a == null) { ++ Arrays.fill(dest, off, off + this.length, this.trivialByte); ++ return off + this.length; ++ } else { ++ System.arraycopy(this.a, 0, dest, off, this.a.length); ++ return off + this.a.length; ++ } ++ } ++ ++ // Resize array to given byte length ++ public void resizeArray(int len) { ++ if (this.a == null) { ++ this.length = len; ++ } else if (this.a.length != len) { ++ byte[] newa = new byte[len]; ++ System.arraycopy(this.a, 0, newa, 0, ((this.a.length > len) ? len : this.a.length)); ++ this.a = newa; ++ } ++ } ++ // Spigot end + + public NibbleArray(int i, int j) { +- this.a = new byte[i >> 1]; ++ // Spigot start ++ //this.a = new byte[i >> 1]; ++ this.a = null; // Start off as trivial value (all same zero value) ++ this.length = i >> 1; ++ this.trivialByte = this.trivialValue = 0; ++ // Spigot end + this.b = j; + this.c = j + 4; + } +@@ -16,9 +120,11 @@ public class NibbleArray { + this.a = abyte; + this.b = i; + this.c = i + 4; ++ detectAndProcessTrivialArray(); // Spigot + } + + public int a(int i, int j, int k) { ++ if (this.a == null) return this.trivialValue; // Spigot + int l = j << this.c | k << this.b | i; + int i1 = l >> 1; + int j1 = l & 1; +@@ -27,6 +133,18 @@ public class NibbleArray { + } + + public void a(int i, int j, int k, int l) { ++ // Spigot start ++ if (this.a == null) { ++ if (l != this.trivialValue) { // Not same as trivial value, array no longer trivial ++ this.a = new byte[this.length]; ++ if (this.trivialByte != 0) { ++ Arrays.fill(this.a, this.trivialByte); ++ } ++ } else { ++ return; ++ } ++ } ++ // Spigot end + int i1 = j << this.c | k << this.b | i; + int j1 = i1 >> 1; + int k1 = i1 & 1; +diff --git a/src/main/java/net/minecraft/server/Packet51MapChunk.java b/src/main/java/net/minecraft/server/Packet51MapChunk.java +index 14a6245..ee179be 100644 +--- a/src/main/java/net/minecraft/server/Packet51MapChunk.java ++++ b/src/main/java/net/minecraft/server/Packet51MapChunk.java +@@ -139,16 +139,22 @@ public class Packet51MapChunk extends Packet { + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].a()) && (i & 1 << l) != 0) { + nibblearray = achunksection[l].j(); +- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length); +- j += nibblearray.a.length; ++ // Spigot start ++ // System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length); ++ // j += nibblearray.a.length; ++ j = nibblearray.copyToByteArray(abyte, j); ++ // Spigot end + } + } + + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].a()) && (i & 1 << l) != 0) { + nibblearray = achunksection[l].k(); +- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length); +- j += nibblearray.a.length; ++ // Spigot start ++ // System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length); ++ // j += nibblearray.a.length; ++ j = nibblearray.copyToByteArray(abyte, j); ++ // Spigot end + } + } + +@@ -156,8 +162,11 @@ public class Packet51MapChunk extends Packet { + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].a()) && (i & 1 << l) != 0) { + nibblearray = achunksection[l].l(); +- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length); +- j += nibblearray.a.length; ++ // Spigot start ++ // System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length); ++ // j += nibblearray.a.length; ++ j = nibblearray.copyToByteArray(abyte, j); ++ // Spigot end + } + } + } +@@ -166,8 +175,11 @@ public class Packet51MapChunk extends Packet { + for (l = 0; l < achunksection.length; ++l) { + if (achunksection[l] != null && (!flag || !achunksection[l].a()) && achunksection[l].i() != null && (i & 1 << l) != 0) { + nibblearray = achunksection[l].i(); +- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length); +- j += nibblearray.a.length; ++ // Spigot start ++ //System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length); ++ //j += nibblearray.a.length; ++ j = nibblearray.copyToByteArray(abyte, j); ++ // Spigot end + } + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +index c3b9113..e34e781 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +@@ -174,7 +174,18 @@ public class CraftChunk implements Chunk { + } + + if (cs[i].i() != null) { /* If we've got extended IDs */ +- byte[] extids = cs[i].i().a; ++ // Spigot start ++ if (cs[i].i().isTrivialArray()) { ++ int tval = cs[i].i().getTrivialArrayValue(); ++ if (tval != 0) { ++ tval = tval << 8; ++ for (int j = 0; j < 4096; j++) { ++ blockids[j << 1] |= tval; ++ } ++ } ++ } else { ++ byte[] extids = cs[i].i().getValueArray(); ++ // Spigot end + + for (int j = 0; j < 2048; j++) { + short b = (short) (extids[j] & 0xFF); +@@ -186,21 +197,42 @@ public class CraftChunk implements Chunk { + blockids[j<<1] |= (b & 0x0F) << 8; + blockids[(j<<1)+1] |= (b & 0xF0) << 4; + } ++ } // Spigot + } + + sectionBlockIDs[i] = blockids; + + /* Get block data nibbles */ +- sectionBlockData[i] = new byte[2048]; +- System.arraycopy(cs[i].j().a, 0, sectionBlockData[i], 0, 2048); // Should be getData ++ // Spigot start ++ if (cs[i].j().isTrivialArray() && (cs[i].j().getTrivialArrayValue() == 0)) { ++ sectionBlockData[i] = emptyData; ++ } else { ++ sectionBlockData[i] = new byte[2048]; ++ cs[i].j().copyToByteArray(sectionBlockData[i], 0); ++ } + if (cs[i].l() == null) { + sectionSkyLights[i] = emptyData; ++ } ++ else if (cs[i].l().isTrivialArray()) { ++ if (cs[i].l().getTrivialArrayValue() == 0) { ++ sectionSkyLights[i] = emptyData; ++ } else if (cs[i].l().getTrivialArrayValue() == 15) { ++ sectionSkyLights[i] = emptySkyLight; ++ } else { ++ sectionSkyLights[i] = new byte[2048]; ++ cs[i].l().copyToByteArray(sectionSkyLights[i], 0); ++ } + } else { + sectionSkyLights[i] = new byte[2048]; +- System.arraycopy(cs[i].l().a, 0, sectionSkyLights[i], 0, 2048); // Should be getSkyLight ++ cs[i].l().copyToByteArray(sectionSkyLights[i], 0); ++ } ++ if (cs[i].k().isTrivialArray() && (cs[i].k().getTrivialArrayValue() == 0)) { ++ sectionEmitLights[i] = emptyData; ++ } else { ++ sectionEmitLights[i] = new byte[2048]; ++ cs[i].k().copyToByteArray(sectionEmitLights[i], 0); + } +- sectionEmitLights[i] = new byte[2048]; +- System.arraycopy(cs[i].k().a, 0, sectionEmitLights[i], 0, 2048); // Should be getBlockLight ++ // Spigot end + } + } + +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0007-Return-LOHS-to-full-operation.patch b/CraftBukkit-Patches/0007-Return-LOHS-to-full-operation.patch new file mode 100644 index 0000000000..43df135f79 --- /dev/null +++ b/CraftBukkit-Patches/0007-Return-LOHS-to-full-operation.patch @@ -0,0 +1,30 @@ +From cb844f20abeb6fc8a6a9f6492307c7b5ed62b765 Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Mon, 7 Jan 2013 09:57:30 +1100 +Subject: [PATCH 07/13] Return LOHS to full operation. + +--- + src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java +index 3f1617d..a1250d3 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java +@@ -81,11 +81,11 @@ public class LongHashSet { + } + + public boolean add(int msw, int lsw) { +- flat.put(msw, lsw, PRESENT); // Spigot + return add(LongHash.toLong(msw, lsw)); + } + +- private boolean add(long value) { // Spigot ++ public boolean add(long value) { ++ flat.put(value, PRESENT); // Spigot + int hash = hash(value); + int index = (hash & 0x7FFFFFFF) % values.length; + int offset = 1; +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0008-Update-timeout-time-to-new-default-value.patch b/CraftBukkit-Patches/0008-Update-timeout-time-to-new-default-value.patch new file mode 100644 index 0000000000..0adcc7de34 --- /dev/null +++ b/CraftBukkit-Patches/0008-Update-timeout-time-to-new-default-value.patch @@ -0,0 +1,46 @@ +From ac06044ae6bcc052df0c71fe3c7fcacef84467bc Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Thu, 10 Jan 2013 12:33:37 +1100 +Subject: [PATCH 08/13] Update timeout time to new default value. + +--- + src/main/java/org/bukkit/craftbukkit/CraftServer.java | 9 ++++++++- + src/main/resources/configurations/bukkit.yml | 2 +- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 67593d5..047378a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -226,7 +226,14 @@ public final class CraftServer implements Server { + 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)); ++ int timeout = configuration.getInt("settings.timeout-time", 300); ++ if (timeout == 180) { ++ timeout = 300; ++ getLogger().info("Migrating to new timeout time of 300"); ++ configuration.set("settings.timeout-time", timeout); ++ saveConfig(); ++ } ++ org.bukkit.craftbukkit.util.WatchdogThread.startThread(timeout, configuration.getBoolean("settings.restart-on-crash", false)); + + whitelistMessage = configuration.getString("settings.whitelist-message", whitelistMessage); + stopMessage = configuration.getString("settings.stop-message", stopMessage); +diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml +index e192700..78134f9 100644 +--- a/src/main/resources/configurations/bukkit.yml ++++ b/src/main/resources/configurations/bukkit.yml +@@ -26,7 +26,7 @@ settings: + deprecated-verbose: default + shutdown-message: Server closed + restart-script-location: start.bat +- timeout-time: 180 ++ timeout-time: 300 + restart-on-crash: false + filter-unsafe-ips: false + whitelist-message: You are not white-listed on this server! +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0009-Per-world-view-distance.patch b/CraftBukkit-Patches/0009-Per-world-view-distance.patch new file mode 100644 index 0000000000..332545b76e --- /dev/null +++ b/CraftBukkit-Patches/0009-Per-world-view-distance.patch @@ -0,0 +1,69 @@ +From 3118b5b6884ef2b1e21e87e00e14a984ef738776 Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Sat, 12 Jan 2013 19:57:45 +1100 +Subject: [PATCH 09/13] Per world view distance. + +--- + src/main/java/net/minecraft/server/WorldServer.java | 2 +- + src/main/java/org/bukkit/craftbukkit/CraftWorld.java | 5 +++++ + src/main/resources/configurations/bukkit.yml | 1 + + 3 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index 6de6b12..6e5434b 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -49,7 +49,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate + // CraftBukkit end + this.server = minecraftserver; + this.tracker = new EntityTracker(this); +- this.manager = new PlayerChunkMap(this, minecraftserver.getPlayerList().o()); ++ this.manager = new PlayerChunkMap(this, getWorld().viewDistance); // Spigot + if (this.entitiesById == null) { + this.entitiesById = new IntHashMap(); + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 3544aa3..856307f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -114,6 +114,9 @@ public class CraftWorld implements World { + treeGrowthModifier = configuration.getInt("world-settings." + name + ".tree-growth-modifier", treeGrowthModifier); + mushroomGrowthModifier = configuration.getInt("world-settings." + name + ".mushroom-growth-modifier", mushroomGrowthModifier); + ++ viewDistance = Bukkit.getServer().getViewDistance(); ++ viewDistance = configuration.getInt("world-settings." + name + ".view-distance", viewDistance); ++ + server.getLogger().info("-------------- Spigot ----------------"); + server.getLogger().info("-------- World Settings For [" + name + "] --------"); + server.getLogger().info("Growth Per Chunk: " + growthPerTick); +@@ -129,6 +132,7 @@ public class CraftWorld implements World { + server.getLogger().info("Sugar Growth Modifier: " + sugarGrowthModifier); + server.getLogger().info("Tree Growth Modifier: " + treeGrowthModifier); + server.getLogger().info("Mushroom Growth Modifier: " + mushroomGrowthModifier); ++ server.getLogger().info("View distance: " + viewDistance); + server.getLogger().info("-------------------------------------------------"); + // Spigot end + } +@@ -139,6 +143,7 @@ public class CraftWorld implements World { + public boolean randomLightingUpdates = false; + public int mobSpawnRange = 4; + public int aggregateTicks = 4; ++ public int viewDistance; + //Crop growth rates: + public int wheatGrowthModifier = 100; + public int cactusGrowthModifier = 100; +diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml +index 78134f9..c41fac1 100644 +--- a/src/main/resources/configurations/bukkit.yml ++++ b/src/main/resources/configurations/bukkit.yml +@@ -52,6 +52,7 @@ world-settings: + world: + growth-chunks-per-tick: 1000 + world_nether: ++ view-distance: 5 + growth-chunks-per-tick: 0 + random-light-updates: true + water-creatures-per-chunk: 0 +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0010-Fix-various-crop-growth-modifier-formula-issues.patch b/CraftBukkit-Patches/0010-Fix-various-crop-growth-modifier-formula-issues.patch new file mode 100644 index 0000000000..547a493f82 --- /dev/null +++ b/CraftBukkit-Patches/0010-Fix-various-crop-growth-modifier-formula-issues.patch @@ -0,0 +1,125 @@ +From c496c06761b4bda4b215743b3cd33039aef8d5ac Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 11 Jan 2013 11:08:45 -0500 +Subject: [PATCH 10/13] Fix various crop growth modifier formula issues + +--- + src/main/java/net/minecraft/server/BlockCactus.java | 2 +- + src/main/java/net/minecraft/server/BlockCrops.java | 2 +- + src/main/java/net/minecraft/server/BlockGrass.java | 3 ++- + src/main/java/net/minecraft/server/BlockMushroom.java | 2 +- + src/main/java/net/minecraft/server/BlockMycel.java | 3 ++- + src/main/java/net/minecraft/server/BlockReed.java | 2 +- + src/main/java/net/minecraft/server/BlockSapling.java | 2 +- + src/main/java/net/minecraft/server/BlockStem.java | 2 +- + 8 files changed, 10 insertions(+), 8 deletions(-) + +diff --git a/src/main/java/net/minecraft/server/BlockCactus.java b/src/main/java/net/minecraft/server/BlockCactus.java +index 1cb89fa..57bedfc 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 >= (byte) range(3, (world.growthOdds * 100 / world.getWorld().cactusGrowthModifier * 15 / 100F) + 0.5F, 15)) { // Spigot ++ if (i1 >= (byte) range(3, (world.growthOdds / world.getWorld().cactusGrowthModifier * 15) + 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 4d3b448..815e050 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) ((world.growthOdds * 100 / world.getWorld().wheatGrowthModifier / 25.0F) / f) + 1) == 0) { // Spigot ++ if (random.nextInt((int) (world.growthOdds / 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 0bc7882..bf117bc 100644 +--- a/src/main/java/net/minecraft/server/BlockGrass.java ++++ b/src/main/java/net/minecraft/server/BlockGrass.java +@@ -37,7 +37,8 @@ public class BlockGrass extends Block { + } + // CraftBukkit end + } else if (world.getLightLevel(i, j + 1, k) >= 9) { +- for (int l = 0; l < Math.max(4, Math.max(20, (int) (4 * 100F / world.growthOdds))); ++l) { // Spigot ++ int numGrowth = Math.min(4, Math.max(20, (int) (4 * 100F / world.growthOdds))); // Spigot ++ for (int l = 0; l < numGrowth; ++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 8fa8302..f66ea7a 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((int) (world.growthOdds * 100 / world.getWorld().mushroomGrowthModifier * 25)) == 0) { // Spigot ++ if (random.nextInt((int) (world.growthOdds / 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 afef94d..b7cfa69 100644 +--- a/src/main/java/net/minecraft/server/BlockMycel.java ++++ b/src/main/java/net/minecraft/server/BlockMycel.java +@@ -37,7 +37,8 @@ public class BlockMycel extends Block { + } + // CraftBukkit end + } else if (world.getLightLevel(i, j + 1, k) >= 9) { +- for (int l = 0; l < Math.max(4, Math.max(20, (int) (4 * 100F / world.growthOdds))); ++l) { // Spigot ++ int numGrowth = Math.min(4, Math.max(20, (int) (4 * 100F / world.growthOdds))); // Spigot ++ for (int l = 0; l < numGrowth; ++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 66ad508..c5a8d8a 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 >= (byte) range(3, (world.growthOdds * 100 / world.getWorld().sugarGrowthModifier * 15 / 100F) + 0.5F, 15)) { // Spigot ++ if (i1 >= (byte) range(3, (world.growthOdds / world.getWorld().sugarGrowthModifier * 15) + 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 e8b0f96..d30fe65 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 (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 ++ if (random.nextInt(Math.max(2, (int) ((world.growthOdds / world.getWorld().treeGrowthModifier * 7) + 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 dfaf45d..82be68e 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) ((world.growthOdds * 100 / ((this.id == Block.PUMPKIN_STEM.id) ? world.getWorld().pumpkinGrowthModifier : world.getWorld().melonGrowthModifier) / 25.0F) / f) + 1) == 0) { // Spigot ++ if (random.nextInt((int) (world.growthOdds / (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) { +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0011-Fix-mob-spawn-radius-setting-so-that-its-actually-us.patch b/CraftBukkit-Patches/0011-Fix-mob-spawn-radius-setting-so-that-its-actually-us.patch new file mode 100644 index 0000000000..9b37778436 --- /dev/null +++ b/CraftBukkit-Patches/0011-Fix-mob-spawn-radius-setting-so-that-its-actually-us.patch @@ -0,0 +1,29 @@ +From 2600250e5c8ad99a131ef58f3180fc5c288882ed Mon Sep 17 00:00:00 2001 +From: Aikar +Date: Fri, 11 Jan 2013 14:54:51 -0500 +Subject: [PATCH 11/13] Fix mob-spawn-radius setting so that its actually + used... + +--- + src/main/java/net/minecraft/server/SpawnerCreature.java | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java +index 576cbd3..1e4a1a5 100644 +--- a/src/main/java/net/minecraft/server/SpawnerCreature.java ++++ b/src/main/java/net/minecraft/server/SpawnerCreature.java +@@ -37,7 +37,10 @@ public final class SpawnerCreature { + int j; + // Spigot start - limit radius to spawn distance (chunks aren't loaded) + if (spawnRadius == 0) { +- spawnRadius = (byte) worldserver.getServer().getViewDistance(); ++ spawnRadius = (byte) worldserver.getWorld().mobSpawnRange; ++ if (spawnRadius > (byte) worldserver.getServer().getViewDistance()) { ++ spawnRadius = (byte) worldserver.getServer().getViewDistance(); ++ } + if (spawnRadius > 8) { + spawnRadius = 8; + } +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0012-Add-OldChunkLoader-from-mc-dev-for-diff-visibility.patch b/CraftBukkit-Patches/0012-Add-OldChunkLoader-from-mc-dev-for-diff-visibility.patch new file mode 100644 index 0000000000..9073357f1e --- /dev/null +++ b/CraftBukkit-Patches/0012-Add-OldChunkLoader-from-mc-dev-for-diff-visibility.patch @@ -0,0 +1,140 @@ +From 7fd81ee8ece682794b2cc214b9be16506514a1e6 Mon Sep 17 00:00:00 2001 +From: Agaricus +Date: Sun, 13 Jan 2013 03:41:38 -0800 +Subject: [PATCH 12/13] Add OldChunkLoader from mc-dev for diff visibility + +--- + .../java/net/minecraft/server/OldChunkLoader.java | 120 +++++++++++++++++++++ + 1 file changed, 120 insertions(+) + create mode 100644 src/main/java/net/minecraft/server/OldChunkLoader.java + +diff --git a/src/main/java/net/minecraft/server/OldChunkLoader.java b/src/main/java/net/minecraft/server/OldChunkLoader.java +new file mode 100644 +index 0000000..65b0b70 +--- /dev/null ++++ b/src/main/java/net/minecraft/server/OldChunkLoader.java +@@ -0,0 +1,120 @@ ++package net.minecraft.server; ++ ++public class OldChunkLoader { ++ ++ public static OldChunk a(NBTTagCompound nbttagcompound) { ++ int i = nbttagcompound.getInt("xPos"); ++ int j = nbttagcompound.getInt("zPos"); ++ OldChunk oldchunk = new OldChunk(i, j); ++ ++ oldchunk.g = nbttagcompound.getByteArray("Blocks"); ++ oldchunk.f = new OldNibbleArray(nbttagcompound.getByteArray("Data"), 7); ++ oldchunk.e = new OldNibbleArray(nbttagcompound.getByteArray("SkyLight"), 7); ++ oldchunk.d = new OldNibbleArray(nbttagcompound.getByteArray("BlockLight"), 7); ++ oldchunk.c = nbttagcompound.getByteArray("HeightMap"); ++ oldchunk.b = nbttagcompound.getBoolean("TerrainPopulated"); ++ oldchunk.h = nbttagcompound.getList("Entities"); ++ oldchunk.i = nbttagcompound.getList("TileEntities"); ++ oldchunk.j = nbttagcompound.getList("TileTicks"); ++ ++ try { ++ oldchunk.a = nbttagcompound.getLong("LastUpdate"); ++ } catch (ClassCastException classcastexception) { ++ oldchunk.a = (long) nbttagcompound.getInt("LastUpdate"); ++ } ++ ++ return oldchunk; ++ } ++ ++ public static void a(OldChunk oldchunk, NBTTagCompound nbttagcompound, WorldChunkManager worldchunkmanager) { ++ nbttagcompound.setInt("xPos", oldchunk.k); ++ nbttagcompound.setInt("zPos", oldchunk.l); ++ nbttagcompound.setLong("LastUpdate", oldchunk.a); ++ int[] aint = new int[oldchunk.c.length]; ++ ++ for (int i = 0; i < oldchunk.c.length; ++i) { ++ aint[i] = oldchunk.c[i]; ++ } ++ ++ nbttagcompound.setIntArray("HeightMap", aint); ++ nbttagcompound.setBoolean("TerrainPopulated", oldchunk.b); ++ NBTTagList nbttaglist = new NBTTagList("Sections"); ++ ++ int j; ++ ++ for (int k = 0; k < 8; ++k) { ++ boolean flag = true; ++ ++ for (j = 0; j < 16 && flag; ++j) { ++ int l = 0; ++ ++ while (l < 16 && flag) { ++ int i1 = 0; ++ ++ while (true) { ++ if (i1 < 16) { ++ int j1 = j << 11 | i1 << 7 | l + (k << 4); ++ byte b0 = oldchunk.g[j1]; ++ ++ if (b0 == 0) { ++ ++i1; ++ continue; ++ } ++ ++ flag = false; ++ } ++ ++ ++l; ++ break; ++ } ++ } ++ } ++ ++ if (!flag) { ++ byte[] abyte = new byte[4096]; ++ NibbleArray nibblearray = new NibbleArray(abyte.length, 4); ++ NibbleArray nibblearray1 = new NibbleArray(abyte.length, 4); ++ NibbleArray nibblearray2 = new NibbleArray(abyte.length, 4); ++ ++ for (int k1 = 0; k1 < 16; ++k1) { ++ for (int l1 = 0; l1 < 16; ++l1) { ++ for (int i2 = 0; i2 < 16; ++i2) { ++ int j2 = k1 << 11 | i2 << 7 | l1 + (k << 4); ++ byte b1 = oldchunk.g[j2]; ++ ++ abyte[l1 << 8 | i2 << 4 | k1] = (byte) (b1 & 255); ++ nibblearray.a(k1, l1, i2, oldchunk.f.a(k1, l1 + (k << 4), i2)); ++ nibblearray1.a(k1, l1, i2, oldchunk.e.a(k1, l1 + (k << 4), i2)); ++ nibblearray2.a(k1, l1, i2, oldchunk.d.a(k1, l1 + (k << 4), i2)); ++ } ++ } ++ } ++ ++ NBTTagCompound nbttagcompound1 = new NBTTagCompound(); ++ ++ nbttagcompound1.setByte("Y", (byte) (k & 255)); ++ nbttagcompound1.setByteArray("Blocks", abyte); ++ nbttagcompound1.setByteArray("Data", nibblearray.a); ++ nbttagcompound1.setByteArray("SkyLight", nibblearray1.a); ++ nbttagcompound1.setByteArray("BlockLight", nibblearray2.a); ++ nbttaglist.add(nbttagcompound1); ++ } ++ } ++ ++ nbttagcompound.set("Sections", nbttaglist); ++ byte[] abyte1 = new byte[256]; ++ ++ for (int k2 = 0; k2 < 16; ++k2) { ++ for (j = 0; j < 16; ++j) { ++ abyte1[j << 4 | k2] = (byte) (worldchunkmanager.getBiome(oldchunk.k << 4 | k2, oldchunk.l << 4 | j).id & 255); ++ } ++ } ++ ++ nbttagcompound.setByteArray("Biomes", abyte1); ++ nbttagcompound.set("Entities", oldchunk.h); ++ nbttagcompound.set("TileEntities", oldchunk.i); ++ if (oldchunk.j != null) { ++ nbttagcompound.set("TileTicks", oldchunk.j); ++ } ++ } ++} +\ No newline at end of file +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0013-Fix-mcRegion-to-Anvil-conversion.patch b/CraftBukkit-Patches/0013-Fix-mcRegion-to-Anvil-conversion.patch new file mode 100644 index 0000000000..9a7eb618c1 --- /dev/null +++ b/CraftBukkit-Patches/0013-Fix-mcRegion-to-Anvil-conversion.patch @@ -0,0 +1,31 @@ +From 6d3d278b13d8909db71e87a24af672d0227a0c4c Mon Sep 17 00:00:00 2001 +From: Agaricus +Date: Sun, 13 Jan 2013 03:49:07 -0800 +Subject: [PATCH 13/13] Fix mcRegion-to-Anvil conversion + +--- + src/main/java/net/minecraft/server/OldChunkLoader.java | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/src/main/java/net/minecraft/server/OldChunkLoader.java b/src/main/java/net/minecraft/server/OldChunkLoader.java +index 65b0b70..0b3bd0d 100644 +--- a/src/main/java/net/minecraft/server/OldChunkLoader.java ++++ b/src/main/java/net/minecraft/server/OldChunkLoader.java +@@ -94,9 +94,11 @@ public class OldChunkLoader { + + nbttagcompound1.setByte("Y", (byte) (k & 255)); + nbttagcompound1.setByteArray("Blocks", abyte); +- nbttagcompound1.setByteArray("Data", nibblearray.a); +- nbttagcompound1.setByteArray("SkyLight", nibblearray1.a); +- nbttagcompound1.setByteArray("BlockLight", nibblearray2.a); ++ // Spigot start - a -> getValueArray() accessor ++ nbttagcompound1.setByteArray("Data", nibblearray.getValueArray()); ++ nbttagcompound1.setByteArray("SkyLight", nibblearray1.getValueArray()); ++ nbttagcompound1.setByteArray("BlockLight", nibblearray2.getValueArray()); ++ // Spigot end + nbttaglist.add(nbttagcompound1); + } + } +-- +1.8.1-rc2 + diff --git a/CraftBukkit-Patches/0014-Update-pom-with-Spigot-specific-info.patch b/CraftBukkit-Patches/0014-Update-pom-with-Spigot-specific-info.patch new file mode 100644 index 0000000000..5559aa69fd --- /dev/null +++ b/CraftBukkit-Patches/0014-Update-pom-with-Spigot-specific-info.patch @@ -0,0 +1,73 @@ +From 65bee98f2dc2243a21f93902ddc95b371e5f9baa Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Tue, 15 Jan 2013 11:48:54 +1100 +Subject: [PATCH 14/14] Update pom with Spigot specific info. + +--- + pom.xml | 34 ++++------------------------------ + 1 file changed, 4 insertions(+), 30 deletions(-) + +diff --git a/pom.xml b/pom.xml +index 4540fe2..ea5435d 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -1,12 +1,12 @@ + + 4.0.0 +- org.bukkit +- craftbukkit ++ org.spigotmc ++ spigot + jar + 1.4.6-R0.4-SNAPSHOT +- CraftBukkit +- http://www.bukkit.org ++ Spigot ++ http://www.spigotmc.org + + + UTF-8 +@@ -16,25 +16,6 @@ + 1_4_6 + + +- +- scm:git:git://github.com/Bukkit/CraftBukkit.git +- scm:git:ssh://git@github.com/Bukkit/CraftBukkit.git +- https://github.com/Bukkit/CraftBukkit +- +- +- +- +- repobo-rel +- repo.bukkit.org Releases +- http://repo.bukkit.org/content/repositories/releases/ +- +- +- repobo-snap +- repo.bukkit.org Snapshots +- http://repo.bukkit.org/content/repositories/snapshots/ +- +- +- + + + repobo-snap +@@ -42,13 +23,6 @@ + + + +- +- +- bukkit-plugins +- http://repo.bukkit.org/content/groups/public +- +- +- + + + org.spigotmc +-- +1.8.1-rc2 + diff --git a/README.md b/README.md new file mode 100644 index 0000000000..7fb4780ac7 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Spigot +=========== + +High performance Minecraft server implementation diff --git a/applyPatches.sh b/applyPatches.sh new file mode 100755 index 0000000000..30dfe4fbe3 --- /dev/null +++ b/applyPatches.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +basedir=$(dirname $(readlink -f $0)) +echo "Rebuilding Forked projects.... " + +function applyPatch { + what=$1 + target=$2 + cd $basedir + if [ ! -d "$basedir/$target" ]; then + git clone $1 $target + fi + cd "$basedir/$target" + echo "Resetting $target to $what..." + git remote rm upstream 2>/dev/null + git remote add upstream ../$what + git checkout master + git fetch upstream + git reset --hard upstream/master + echo " Applying patches to $target..." + git am --3way $basedir/${what}-Patches/*.patch + if [ "$?" != "0" ]; then + echo " Something did not apply cleanly to $target." + echo " Please review above details and finish the apply then" + echo " save the changes with rebuildPatches.sh" + else + echo " Patches applied cleanly to $target" + fi +} + +applyPatch Bukkit Spigot-API +applyPatch CraftBukkit Spigot diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..75eafef573 --- /dev/null +++ b/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + org.spigotmc + spigot-parent + dev-SNAPSHOT + pom + + Spigot-Parent + Parent project for all Spigot modules. + https://github.com/EcoCityCraft/Spigot + + + Spigot + Spigot-API + + + + UTF-8 + + diff --git a/rebuildPatches.sh b/rebuildPatches.sh new file mode 100755 index 0000000000..354c289791 --- /dev/null +++ b/rebuildPatches.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +basedir=$(dirname $(readlink -f $0)) +echo "Rebuilding patch files from current fork state..." + +function savePatches { + what=$1 + target=$2 + cd $basedir/$target/ + git format-patch -o $basedir/${what}-Patches/ upstream/master + cd $basedir + git add $basedir/${what}-Patches + echo " Patches saved for $what to $what-Patches/" +} + +savePatches Bukkit Spigot-API +savePatches CraftBukkit Spigot diff --git a/upstreamMerge.sh b/upstreamMerge.sh new file mode 100755 index 0000000000..642527c95f --- /dev/null +++ b/upstreamMerge.sh @@ -0,0 +1,10 @@ +#!/bin/bash +basedir=$(dirname $(readlink -f $0)) +function update { + cd $basedir/$1 + git fetch && git reset --hard origin/master + cd ../ + git add $1 +} +update Bukkit +update CraftBukkit