PaperSpigot-Parent/CraftBukkit-Patches/0001-Spigot-changes.patch
2013-01-28 09:46:19 +11:00

2560 lines
108 KiB
Diff

From 1f766e4a465e4eed65061fd7a908ea22e8c0d6fa Mon Sep 17 00:00:00 2001
From: md_5 <md_5@live.com.au>
Date: Tue, 22 Jan 2013 15:56:54 +1100
Subject: [PATCH] Spigot changes.
---
.gitignore | 2 +
pom.xml | 11 +-
src/main/java/net/minecraft/server/Block.java | 12 +
.../java/net/minecraft/server/BlockCactus.java | 2 +-
src/main/java/net/minecraft/server/BlockCrops.java | 2 +-
src/main/java/net/minecraft/server/BlockGrass.java | 2 +-
.../java/net/minecraft/server/BlockMushroom.java | 2 +-
src/main/java/net/minecraft/server/BlockMycel.java | 2 +-
src/main/java/net/minecraft/server/BlockReed.java | 2 +-
.../java/net/minecraft/server/BlockSapling.java | 2 +-
src/main/java/net/minecraft/server/BlockStem.java | 2 +-
.../net/minecraft/server/ChunkRegionLoader.java | 35 +-
.../java/net/minecraft/server/ChunkSection.java | 31 +-
src/main/java/net/minecraft/server/EntityItem.java | 3 +-
.../java/net/minecraft/server/EntityPlayer.java | 1 +
.../java/net/minecraft/server/EntitySquid.java | 4 -
.../net/minecraft/server/EntityTrackerEntry.java | 2 +
.../java/net/minecraft/server/MinecraftServer.java | 51 +--
.../net/minecraft/server/PlayerConnection.java | 18 +-
src/main/java/net/minecraft/server/PlayerList.java | 10 +-
.../java/net/minecraft/server/SpawnerCreature.java | 23 +-
.../net/minecraft/server/ThreadLoginVerifier.java | 23 +
src/main/java/net/minecraft/server/World.java | 200 ++++++++-
.../java/net/minecraft/server/WorldServer.java | 122 +++++-
.../java/org/bukkit/craftbukkit/CraftServer.java | 98 ++++-
.../java/org/bukkit/craftbukkit/CraftWorld.java | 76 +++-
src/main/java/org/bukkit/craftbukkit/Spigot.java | 25 ++
.../craftbukkit/chunkio/ChunkIOProvider.java | 2 +-
.../bukkit/craftbukkit/command/RestartCommand.java | 24 +
.../craftbukkit/command/TicksPerSecondCommand.java | 35 ++
.../org/bukkit/craftbukkit/entity/CraftPlayer.java | 7 +
.../bukkit/craftbukkit/util/ExceptionHandler.java | 31 ++
.../bukkit/craftbukkit/util/ExceptionReporter.java | 26 ++
.../java/org/bukkit/craftbukkit/util/FlatMap.java | 34 ++
.../org/bukkit/craftbukkit/util/LongHashSet.java | 11 +-
.../bukkit/craftbukkit/util/LongObjectHashMap.java | 5 +
.../java/org/bukkit/craftbukkit/util/Metrics.java | 488 +++++++++++++++++++++
.../org/bukkit/craftbukkit/util/TimedThread.java | 37 ++
.../bukkit/craftbukkit/util/WatchdogThread.java | 88 ++++
src/main/resources/configurations/bukkit.yml | 30 ++
40 files changed, 1444 insertions(+), 137 deletions(-)
create mode 100644 src/main/java/org/bukkit/craftbukkit/Spigot.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/command/RestartCommand.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/command/TicksPerSecondCommand.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/util/ExceptionHandler.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/util/ExceptionReporter.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/util/FlatMap.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/util/Metrics.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/util/TimedThread.java
create mode 100644 src/main/java/org/bukkit/craftbukkit/util/WatchdogThread.java
diff --git a/.gitignore b/.gitignore
index a689360..b97a549 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,5 @@
/src/main/resources/achievement
/src/main/resources/lang
+
+/dependency-reduced-pom.xml
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index f0b0bfb..bd394ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,8 +51,8 @@
<dependencies>
<dependency>
- <groupId>org.bukkit</groupId>
- <artifactId>bukkit</artifactId>
+ <groupId>org.spigotmc</groupId>
+ <artifactId>spigot-api</artifactId>
<version>${project.version}</version>
<type>jar</type>
<scope>compile</scope>
@@ -145,6 +145,11 @@
<version>1.3</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>net.sf.trove4j</groupId>
+ <artifactId>trove4j</artifactId>
+ <version>3.0.2</version>
+ </dependency>
</dependencies>
<!-- This builds a completely 'ready to start' jar with all dependencies inside -->
@@ -156,7 +161,7 @@
<artifactId>gitdescribe-maven-plugin</artifactId>
<version>1.3</version>
<configuration>
- <outputPrefix>git-Bukkit-</outputPrefix>
+ <outputPrefix>git-Spigot-</outputPrefix>
<outputPostfix></outputPostfix>
</configuration>
<executions>
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 db90874..9647bb2 100644
--- a/src/main/java/net/minecraft/server/BlockMushroom.java
+++ b/src/main/java/net/minecraft/server/BlockMushroom.java
@@ -24,7 +24,7 @@ public class BlockMushroom extends BlockFlower {
public void b(World world, int i, int j, int k, Random random) {
final int sourceX = i, sourceY = j, sourceZ = k; // CraftBukkit
- 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<ChunkCoordIntPair, PendingChunkToSave> pendingSaves = new java.util.LinkedHashMap<ChunkCoordIntPair, PendingChunkToSave>(); // Spigot
private Object c = new Object();
private final File d;
@@ -27,15 +26,12 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader {
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
synchronized (this.c) {
- if (this.b.contains(chunkcoordintpair)) {
- for (int k = 0; k < this.a.size(); ++k) {
- if (((PendingChunkToSave) this.a.get(k)).a.equals(chunkcoordintpair)) {
- return true;
- }
- }
+ // Spigot start
+ if (pendingSaves.containsKey(chunkcoordintpair)) {
+ return true;
}
}
-
+ // Spigot end
return RegionFileCache.a(this.d, i, j).chunkExists(i & 31, j & 31);
}
// CraftBukkit end
@@ -60,6 +56,12 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader {
Object object = this.c;
synchronized (this.c) {
+ // Spigot start
+ PendingChunkToSave pendingchunktosave = pendingSaves.get(chunkcoordintpair);
+ if (pendingchunktosave != null) {
+ nbttagcompound = pendingchunktosave.b;
+ }
+ /*
if (this.b.contains(chunkcoordintpair)) {
for (int k = 0; k < this.a.size(); ++k) {
if (((PendingChunkToSave) this.a.get(k)).a.equals(chunkcoordintpair)) {
@@ -68,6 +70,7 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader {
}
}
}
+ */// Spigot end
}
if (nbttagcompound == null) {
@@ -134,6 +137,11 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader {
Object object = this.c;
synchronized (this.c) {
+ // Spigot start
+ if (this.pendingSaves.put(chunkcoordintpair, new PendingChunkToSave(chunkcoordintpair, nbttagcompound)) != null) {
+ return;
+ }
+ /*
if (this.b.contains(chunkcoordintpair)) {
for (int i = 0; i < this.a.size(); ++i) {
if (((PendingChunkToSave) this.a.get(i)).a.equals(chunkcoordintpair)) {
@@ -145,6 +153,7 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader {
this.a.add(new PendingChunkToSave(chunkcoordintpair, nbttagcompound));
this.b.add(chunkcoordintpair);
+ */// Spigot end
FileIOThread.a.a(this);
}
}
@@ -154,12 +163,20 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader {
Object object = this.c;
synchronized (this.c) {
+ // Spigot start
+ if (this.pendingSaves.isEmpty()) {
+ return false;
+ }
+ pendingchunktosave = this.pendingSaves.values().iterator().next();
+ this.pendingSaves.remove(pendingchunktosave.a);
+ /*
if (this.a.isEmpty()) {
return false;
}
pendingchunktosave = (PendingChunkToSave) this.a.remove(0);
this.b.remove(pendingchunktosave.a);
+ */// Spigot end
}
if (pendingchunktosave != null) {
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
index 90e0636..051cf6d 100644
--- a/src/main/java/net/minecraft/server/ChunkSection.java
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
@@ -219,7 +219,7 @@ public class ChunkSection {
}
public void a(byte[] abyte) {
- this.blockIds = abyte;
+ this.blockIds = validateByteArray(abyte); // Spigot - validate
}
public void a(NibbleArray nibblearray) {
@@ -236,19 +236,38 @@ public class ChunkSection {
return;
}
// CraftBukkit end
-
- this.extBlockIds = nibblearray;
+ this.extBlockIds = validateNibbleArray(nibblearray); // Spigot - validate
}
public void b(NibbleArray nibblearray) {
- this.blockData = nibblearray;
+ this.blockData = validateNibbleArray(nibblearray); // Spigot - validate
}
public void c(NibbleArray nibblearray) {
- this.blockLight = nibblearray;
+ this.blockLight = validateNibbleArray(nibblearray); // Spigot - validate
}
public void d(NibbleArray nibblearray) {
- this.skyLight = nibblearray;
+ this.skyLight = validateNibbleArray(nibblearray); // Spigot - validate
+ }
+
+ // Spigot start - validate/correct nibble array
+ private static final NibbleArray validateNibbleArray(NibbleArray na) {
+ if ((na != null) && (na.a.length < 2048)) {
+ NibbleArray newna = new NibbleArray(4096, 4);
+ System.arraycopy(na.a, 0, newna.a, 0, na.a.length);
+ na = newna;
+ }
+ return na;
+ }
+ // Validate/correct byte array
+ private static final byte[] validateByteArray(byte[] ba) {
+ if ((ba != null) && (ba.length < 4096)) {
+ byte[] newba = new byte[4096];
+ System.arraycopy(ba, 0, newba, 0, ba.length);
+ ba = newba;
+ }
+ return ba;
}
+ // Spigot end
}
diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java
index b8b6d52..a7baa0f 100644
--- a/src/main/java/net/minecraft/server/EntityItem.java
+++ b/src/main/java/net/minecraft/server/EntityItem.java
@@ -61,6 +61,7 @@ public class EntityItem extends Entity {
this.lastTick = currentTick;
// CraftBukkit end
+ if (lastTick % 2 == 0) { // Spigot
this.lastX = this.locX;
this.lastY = this.locY;
this.lastZ = this.locZ;
@@ -99,7 +100,7 @@ public class EntityItem extends Entity {
if (this.onGround) {
this.motY *= -0.5D;
}
-
+ } // Spigot
++this.age;
if (!this.world.isStatic && this.age >= 6000) {
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index 8d61ca6..3aed58f 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -49,6 +49,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
public int newTotalExp = 0;
public boolean keepLevel = false;
// CraftBukkit end
+ public java.util.Set<java.util.UUID> sentFrames = new java.util.HashSet<java.util.UUID>(); // Spigot
public EntityPlayer(MinecraftServer minecraftserver, World world, String s, PlayerInteractManager playerinteractmanager) {
super(world);
diff --git a/src/main/java/net/minecraft/server/EntitySquid.java b/src/main/java/net/minecraft/server/EntitySquid.java
index 961d83a..188d477 100644
--- a/src/main/java/net/minecraft/server/EntitySquid.java
+++ b/src/main/java/net/minecraft/server/EntitySquid.java
@@ -63,10 +63,6 @@ public class EntitySquid extends EntityWaterAnimal {
// CraftBukkit end
}
- public boolean H() {
- return this.world.a(this.boundingBox.grow(0.0D, -0.6000000238418579D, 0.0D), Material.WATER, (Entity) this);
- }
-
public void c() {
super.c();
this.e = this.d;
diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
index a026c4c..cb91e30 100644
--- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java
+++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
@@ -84,6 +84,7 @@ public class EntityTrackerEntry {
while (j0.hasNext()) {
EntityHuman j1 = (EntityHuman) j0.next();
EntityPlayer j2 = (EntityPlayer) j1;
+ if (j2.sentFrames.contains(i4.uniqueId)) continue; // Spigot
i7.a(j2, i5);
if (j2.playerConnection.lowPriorityCount() <= 5) {
@@ -91,6 +92,7 @@ public class EntityTrackerEntry {
if (j3 != null) {
j2.playerConnection.sendPacket(j3);
+ j2.sentFrames.add(i4.uniqueId); // Spigot
}
}
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 4bdf8aa..4ee2b8b 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -86,6 +86,11 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
// CraftBukkit end
+ // Spigot start
+ private static final int TPS = 20;
+ private static final int TICK_TIME = 1000000000 / TPS;
+ public static double currentTPS = 0;
+ // Spigot end
public MinecraftServer(OptionSet options) { // CraftBukkit - signature file -> OptionSet
l = this;
@@ -397,39 +402,20 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo
public void run() {
try {
if (this.init()) {
- long i = System.currentTimeMillis();
-
- for (long j = 0L; this.isRunning; this.Q = true) {
- long k = System.currentTimeMillis();
- long l = k - i;
-
- if (l > 2000L && i - this.R >= 15000L) {
- if (this.server.getWarnOnOverload()) // CraftBukkit - Added option to suppress warning messages
- log.warning("Can\'t keep up! Did the system time change, or is the server overloaded?");
- l = 2000L;
- this.R = i;
+ // Spigot start
+ for (long lastTick = 0L; this.isRunning; this.Q = true) {
+ long curTime = System.nanoTime();
+ long wait = TICK_TIME - (curTime - lastTick);
+ if (wait > 0) {
+ Thread.sleep(wait / 1000000);
+ continue;
}
-
- if (l < 0L) {
- log.warning("Time ran backwards! Did the system time change?");
- l = 0L;
- }
-
- j += l;
- i = k;
- if (this.worlds.get(0).everyoneDeeplySleeping()) { // CraftBukkit
- this.q();
- j = 0L;
- } else {
- while (j > 50L) {
- MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
- j -= 50L;
- this.q();
- }
- }
-
- Thread.sleep(1L);
+ currentTPS = (currentTPS * 0.95) + (1E9 / (curTime - lastTick) * 0.05);
+ lastTick = curTime;
+ MinecraftServer.currentTick++;
+ this.q();
}
+ // Spigot end
} else {
this.a((CrashReport) null);
}
@@ -454,6 +440,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo
this.a(crashreport);
} finally {
+ org.bukkit.craftbukkit.util.WatchdogThread.stopping(); // Spigot
try {
this.stop();
this.isStopped = true;
@@ -605,6 +592,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo
}
this.methodProfiler.b();
+ org.bukkit.craftbukkit.util.WatchdogThread.tick(); // Spigot
}
public boolean getAllowNether() {
@@ -708,6 +696,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo
dedicatedserver.an();
}
*/
+ dedicatedserver.primaryThread.setUncaughtExceptionHandler(new org.bukkit.craftbukkit.util.ExceptionHandler()); // Spigot
dedicatedserver.primaryThread.start();
// Runtime.getRuntime().addShutdownHook(new ThreadShutdown(dedicatedserver));
diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java
index fac9ea5..43a24f5 100644
--- a/src/main/java/net/minecraft/server/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
@@ -852,8 +852,19 @@ public class PlayerConnection extends Connection {
this.chat(s, packet3chat.a_());
+ // Spigot start
+ boolean isCounted = true;
+ if (server.spamGuardExclusions != null) {
+ for (String excluded : server.spamGuardExclusions) {
+ if (s.startsWith(excluded)) {
+ isCounted = false;
+ break;
+ }
+ }
+ }
// This section stays because it is only applicable to packets
- if (chatSpamField.addAndGet(this, 20) > 200 && !this.minecraftServer.getPlayerList().isOp(this.player.name)) { // CraftBukkit use thread-safe spam
+ if (isCounted && chatSpamField.addAndGet(this, 20) > 200 && !this.minecraftServer.getPlayerList().isOp(this.player.name)) { // CraftBukkit use thread-safe spam
+ // Spigot end
// CraftBukkit start
if (packet3chat.a_()) {
Waitable waitable = new Waitable() {
@@ -976,7 +987,7 @@ public class PlayerConnection extends Connection {
}
try {
- logger.info(event.getPlayer().getName() + " issued server command: " + event.getMessage()); // CraftBukkit
+ if (server.logCommands) logger.info(event.getPlayer().getName() + " issued server command: " + event.getMessage()); // Spigot
if (this.server.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) {
return;
}
@@ -1353,8 +1364,9 @@ public class PlayerConnection extends Connection {
flag = false;
} else {
for (i = 0; i < packet130updatesign.lines[j].length(); ++i) {
- if (SharedConstants.allowedCharacters.indexOf(packet130updatesign.lines[j].charAt(i)) < 0) {
+ if (!SharedConstants.isAllowedChatCharacter(packet130updatesign.lines[j].charAt(i))) {
flag = false;
+ break;
}
}
}
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index f669a00..dee6579 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -253,7 +253,7 @@ public abstract class PlayerList {
event.disallow(PlayerLoginEvent.Result.KICK_BANNED, s1);
} else if (!this.isWhitelisted(s)) {
- event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, "You are not white-listed on this server!");
+ event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, cserver.whitelistMessage); // Spigot
} else {
String s2 = socketaddress.toString();
@@ -913,7 +913,13 @@ public abstract class PlayerList {
public void r() {
while (!this.players.isEmpty()) {
- ((EntityPlayer) this.players.get(0)).playerConnection.disconnect(this.server.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message
+ // Spigot start
+ EntityPlayer p = (EntityPlayer) this.players.get(0);
+ p.playerConnection.disconnect(this.server.server.getShutdownMessage());
+ if ((!this.players.isEmpty()) && (this.players.get(0) == p)) {
+ this.players.remove(0); // Prevent shutdown hang if already disconnected
+ }
+ // Spigot end
}
}
diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java
index 9b3e262..61721c4 100644
--- a/src/main/java/net/minecraft/server/SpawnerCreature.java
+++ b/src/main/java/net/minecraft/server/SpawnerCreature.java
@@ -16,6 +16,7 @@ public final class SpawnerCreature {
private static LongObjectHashMap<Boolean> b = new LongObjectHashMap<Boolean>(); // CraftBukkit - HashMap -> LongObjectHashMap
protected static final Class[] a = new Class[] { EntitySpider.class, EntityZombie.class, EntitySkeleton.class};
+ private static byte spawnRadius = 0; // Spigot
protected static ChunkPosition getRandomPosition(World world, int i, int j) {
Chunk chunk = world.getChunkAt(i, j);
@@ -34,13 +35,21 @@ public final class SpawnerCreature {
int i;
int j;
+ // Spigot start - limit radius to spawn distance (chunks aren't loaded)
+ if (spawnRadius == 0) {
+ spawnRadius = (byte) worldserver.getServer().getViewDistance();
+ if (spawnRadius > 8) {
+ spawnRadius = 8;
+ }
+ }
+ // Spigot end
for (i = 0; i < worldserver.players.size(); ++i) {
EntityHuman entityhuman = (EntityHuman) worldserver.players.get(i);
int k = MathHelper.floor(entityhuman.locX / 16.0D);
j = MathHelper.floor(entityhuman.locZ / 16.0D);
- byte b0 = 8;
+ byte b0 = spawnRadius; // Spigot - replace 8 with view distance constrained value
for (int l = -b0; l <= b0; ++l) {
for (int i1 = -b0; i1 <= b0; ++i1) {
@@ -88,13 +97,15 @@ public final class SpawnerCreature {
if (limit == 0) {
continue;
}
+ int mobcnt = 0;
// CraftBukkit end
- if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && (!enumcreaturetype.e() || flag2) && worldserver.a(enumcreaturetype.a()) <= limit * b.size() / 256) { // CraftBukkit - use per-world limits
+ if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && (!enumcreaturetype.e() || flag2) && (mobcnt = worldserver.a(enumcreaturetype.a())) <= limit * b.size() / 256) { // CraftBukkit - use per-world limits
Iterator iterator = b.keySet().iterator();
+ int moblimit = (limit * b.size() / 256) - mobcnt + 1; // CraftBukkit - up to 1 more than limit
label110:
- while (iterator.hasNext()) {
+ while (iterator.hasNext() && (moblimit > 0)) { // Spigot - while more allowed
// CraftBukkit start
long key = ((Long) iterator.next()).longValue();
@@ -158,6 +169,12 @@ public final class SpawnerCreature {
a(entityliving, worldserver, f, f1, f2);
worldserver.addEntity(entityliving, SpawnReason.NATURAL);
// CraftBukkit end
+ // Spigot start
+ moblimit--;
+ if (moblimit <= 0) { // If we're past limit, stop spawn
+ continue label110;
+ }
+ // Spigot end
if (j2 >= entityliving.bv()) {
continue label110;
}
diff --git a/src/main/java/net/minecraft/server/ThreadLoginVerifier.java b/src/main/java/net/minecraft/server/ThreadLoginVerifier.java
index 0686ba0..58d30eb 100644
--- a/src/main/java/net/minecraft/server/ThreadLoginVerifier.java
+++ b/src/main/java/net/minecraft/server/ThreadLoginVerifier.java
@@ -28,6 +28,29 @@ class ThreadLoginVerifier extends Thread {
public void run() {
try {
+ // Spigot start
+ if (((CraftServer) org.bukkit.Bukkit.getServer()).ipFilter) {
+ try {
+ String ip = this.pendingConnection.getSocket().getInetAddress().getHostAddress();
+ String[] split = ip.split("\\.");
+ StringBuilder lookup = new StringBuilder();
+ for (int i = split.length - 1; i >= 0; i--) {
+ lookup.append(split[i]);
+ lookup.append(".");
+ }
+ if (!ip.contains("127.0.0.1")) {
+ lookup.append("xbl.spamhaus.org.");
+ if (java.net.InetAddress.getByName(lookup.toString()) != null) {
+ this.pendingConnection.networkManager.queue(new Packet255KickDisconnect("Your IP address (" + ip + ") is flagged as unsafe by spamhaus.org/xbl"));
+ this.pendingConnection.networkManager.d();
+ this.pendingConnection.c = true;
+ return;
+ }
+ }
+ } catch (Exception ex) {
+ }
+ }
+ // Spigot end
String s = (new BigInteger(MinecraftEncryption.a(PendingConnection.a(this.pendingConnection), PendingConnection.b(this.pendingConnection).F().getPublic(), PendingConnection.c(this.pendingConnection)))).toString(16);
URL url = new URL("http://session.minecraft.net/game/checkserver.jsp?user=" + URLEncoder.encode(PendingConnection.d(this.pendingConnection), "UTF-8") + "&serverId=" + URLEncoder.encode(s, "UTF-8"));
BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(url.openStream()));
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index c3dc4a4..9906b9e 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -65,7 +65,7 @@ public abstract class World implements IBlockAccess {
// CraftBukkit start - public, longhashset
public boolean allowMonsters = true;
public boolean allowAnimals = true;
- protected LongHashSet chunkTickList = new LongHashSet();
+ protected gnu.trove.map.hash.TLongShortHashMap chunkTickList; // Spigot
public long ticksPerAnimalSpawns;
public long ticksPerMonsterSpawns;
// CraftBukkit end
@@ -73,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);
@@ -99,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;
@@ -111,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
@@ -123,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) {
@@ -901,6 +922,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<Entity> 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<Entity> 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);
@@ -993,6 +1055,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)) {
@@ -1006,6 +1101,7 @@ public abstract class World implements IBlockAccess {
}
}
}
+ */// Spigot end
double d0 = 0.25D;
List list = this.getEntities(entity, axisalignedbb.grow(d0, d0, d0));
@@ -1313,7 +1409,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<String> report = new ArrayList<String>();
+ 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;
@@ -1894,6 +2020,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");
@@ -1903,25 +2034,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) {
@@ -1929,7 +2077,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;
@@ -1968,9 +2116,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);
@@ -2308,7 +2463,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);
@@ -2317,12 +2475,12 @@ public abstract class World implements IBlockAccess {
for (int i1 = i; i1 <= j; ++i1) {
for (int j1 = k; j1 <= l; ++j1) {
if (this.isChunkLoaded(i1, j1)) {
- this.getChunkAt(i1, j1).a(entity, axisalignedbb, this.O);
+ this.getChunkAt(i1, j1).a(entity, axisalignedbb, entities); // Spigot
}
}
}
- return this.O;
+ return entities; // Spigot
}
public List a(Class oclass, AxisAlignedBB axisalignedbb) {
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 3f73ef9..a42aa1e 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -1,5 +1,7 @@
package net.minecraft.server;
+import gnu.trove.iterator.TLongShortIterator;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -12,6 +14,7 @@ import java.util.TreeSet;
// CraftBukkit start
import org.bukkit.block.BlockState;
import org.bukkit.craftbukkit.util.LongHash;
+import org.bukkit.craftbukkit.util.LongObjectHashMap;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.weather.LightningStrikeEvent;
@@ -24,7 +27,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
private final MinecraftServer server;
public EntityTracker tracker; // CraftBukkit - private final -> public
private final PlayerChunkMap manager;
- private Set L;
+ private LongObjectHashMap<Set<NextTickListEntry>> 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<Set<NextTickListEntry>>(); // CraftBukkit
}
if (this.M == null) {
@@ -157,6 +160,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
SpawnerCreature.spawnEntities(this, this.allowMonsters && (this.ticksPerMonsterSpawns != 0 && time % this.ticksPerMonsterSpawns == 0L), this.allowAnimals && (this.ticksPerAnimalSpawns != 0 && time % this.ticksPerAnimalSpawns == 0L), this.worldData.getTime() % 400L == 0L);
}
// CraftBukkit end
+ this.getWorld().processChunkGC(); // Spigot
this.methodProfiler.c("chunkSource");
this.chunkProvider.unloadChunks();
int j = this.a(1.0F);
@@ -267,15 +271,31 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
}
protected void g() {
+ // Spigot start
+ this.aggregateTicks--;
+ if (this.aggregateTicks != 0) return;
+ aggregateTicks = this.getWorld().aggregateTicks;
+ // Spigot end
super.g();
int i = 0;
int j = 0;
// CraftBukkit start
- // Iterator iterator = this.chunkTickList.iterator();
+ // Iterator iterator = this.chunkTickList.iterator(); // CraftBukkit
- for (long chunkCoord : this.chunkTickList.popAll()) {
- int chunkX = LongHash.msw(chunkCoord);
- int chunkZ = LongHash.lsw(chunkCoord);
+ // CraftBukkit start
+ // Spigot start
+ for (TLongShortIterator iter = chunkTickList.iterator(); iter.hasNext();) {
+ iter.advance();
+ long chunkCoord = iter.key();
+ int chunkX = World.keyToX(chunkCoord);
+ int chunkZ = World.keyToZ(chunkCoord);
+ // If unloaded, or in procedd of being unloaded, drop it
+ if ((!this.isChunkLoaded(chunkX, chunkZ)) || (this.chunkProviderServer.unloadQueue.contains(chunkX, chunkZ))) {
+ iter.remove();
+ continue;
+ }
+ int players = iter.value();
+ // Spigot end
// ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) iterator.next();
int k = chunkX * 16;
int l = chunkZ * 16;
@@ -373,6 +393,14 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
if (block != null && block.isTicking()) {
++i;
+ // Spigot start
+ if (players < 1) {
+ //grow fast if no players are in this chunk
+ this.growthOdds = modifiedOdds;
+ } else {
+ this.growthOdds = 100;
+ }
+ // Spigot end
block.b(this, k2 + k, i3 + chunksection.d(), l2 + l, this.random);
}
}
@@ -413,10 +441,11 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
nextticklistentry.a(j1);
}
- if (!this.L.contains(nextticklistentry)) {
- this.L.add(nextticklistentry);
- this.M.add(nextticklistentry);
- }
+ // if (!this.L.contains(nextticklistentry)) {
+ // this.L.add(nextticklistentry);
+ // this.M.add(nextticklistentry);
+ // }
+ addNextTickIfNeeded(nextticklistentry); // CraftBukkit
}
}
@@ -427,10 +456,11 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
nextticklistentry.a((long) i1 + this.worldData.getTime());
}
- if (!this.L.contains(nextticklistentry)) {
- this.L.add(nextticklistentry);
- this.M.add(nextticklistentry);
- }
+ //if (!this.L.contains(nextticklistentry)) {
+ // this.L.add(nextticklistentry);
+ // this.M.add(nextticklistentry);
+ //}
+ addNextTickIfNeeded(nextticklistentry); // CraftBukkit
}
public void tickEntities() {
@@ -452,9 +482,9 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
public boolean a(boolean flag) {
int i = this.M.size();
- if (i != this.L.size()) {
- throw new IllegalStateException("TickNextTick list out of synch");
- } else {
+ //if (i != this.L.size()) { // Spigot
+ // throw new IllegalStateException("TickNextTick list out of synch"); // Spigot
+ //} else { // Spigot
if (i > 1000) {
// CraftBukkit start - if the server has too much to process over time, try to alleviate that
if (i > 20 * 1000) {
@@ -472,8 +502,11 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
break;
}
- this.M.remove(nextticklistentry);
- this.L.remove(nextticklistentry);
+ // Spigot start
+ //this.M.remove(nextticklistentry);
+ //this.L.remove(nextticklistentry);
+ this.removeNextTickIfNeeded(nextticklistentry);
+ // Spigot end
byte b0 = 8;
if (this.d(nextticklistentry.a - b0, nextticklistentry.b - b0, nextticklistentry.c - b0, nextticklistentry.a + b0, nextticklistentry.b + b0, nextticklistentry.c + b0)) {
@@ -502,10 +535,12 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
}
return !this.M.isEmpty();
- }
+ // } // Spigot
}
public List a(Chunk chunk, boolean flag) {
+ return this.getNextTickEntriesForChunk(chunk, flag); // Spigot
+ /* Spigot start
ArrayList arraylist = null;
ChunkCoordIntPair chunkcoordintpair = chunk.l();
int i = chunkcoordintpair.x << 4;
@@ -532,6 +567,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
}
return arraylist;
+ // Spigot end */
}
public void entityJoinedWorld(Entity entity, boolean flag) {
@@ -610,7 +646,7 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
}
if (this.L == null) {
- this.L = new HashSet();
+ this.L = new LongObjectHashMap<Set<NextTickListEntry>>();
}
if (this.M == null) {
@@ -883,4 +919,48 @@ public class WorldServer extends World implements org.bukkit.BlockChangeDelegate
public PortalTravelAgent s() {
return this.P;
}
+
+ // Spigot start
+ private void addNextTickIfNeeded(NextTickListEntry ent) {
+ long coord = LongHash.toLong(ent.a >> 4, ent.c >> 4);
+ Set<NextTickListEntry> chunkset = L.get(coord);
+ if (chunkset == null) {
+ chunkset = new HashSet<NextTickListEntry>();
+ 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<NextTickListEntry> chunkset = L.get(coord);
+ if (chunkset == null) {
+ return;
+ }
+ if (chunkset.remove(ent)) {
+ M.remove(ent);
+ if (chunkset.isEmpty()) {
+ L.remove(coord);
+ }
+ }
+ }
+
+ private List<NextTickListEntry> getNextTickEntriesForChunk(Chunk chunk, boolean remove) {
+ long coord = LongHash.toLong(chunk.x, chunk.z);
+ Set<NextTickListEntry> chunkset = L.get(coord);
+ if (chunkset == null) {
+ return null;
+ }
+ List<NextTickListEntry> list = new ArrayList<NextTickListEntry>(chunkset);
+ if (remove) {
+ L.remove(coord);
+ M.removeAll(list);
+ chunkset.clear();
+ }
+ return list;
+ }
+ // Spigot end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index e7c0760..a7785b7 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -146,7 +146,7 @@ public final class CraftServer implements Server {
protected final MinecraftServer console;
protected final DedicatedPlayerList playerList;
private final Map<String, World> worlds = new LinkedHashMap<String, World>();
- private YamlConfiguration configuration;
+ protected YamlConfiguration configuration; // Spigot private -> protected
private final Yaml yaml = new Yaml(new SafeConstructor());
private final Map<String, OfflinePlayer> 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<String> spamGuardExclusions;
+ // Spigot end
static {
ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
@@ -208,12 +216,25 @@ public final class CraftServer implements Server {
chunkGCLoadThresh = configuration.getInt("chunk-gc.load-threshold");
updater = new AutoUpdater(new BukkitDLUpdaterService(configuration.getString("auto-updater.host")), getLogger(), configuration.getString("auto-updater.preferred-channel"));
- updater.setEnabled(configuration.getBoolean("auto-updater.enabled"));
+ updater.setEnabled(false);
updater.setSuggestChannels(configuration.getBoolean("auto-updater.suggest-channels"));
updater.getOnBroken().addAll(configuration.getStringList("auto-updater.on-broken"));
updater.getOnUpdate().addAll(configuration.getStringList("auto-updater.on-update"));
updater.check(serverVersion);
+ // Spigot start
+ Spigot.initialize(this, commandMap, configuration);
+
+ try {
+ configuration.save(getConfigFile());
+ } catch (IOException e) {
+ }
+ try {
+ new org.bukkit.craftbukkit.util.Metrics().start();
+ } catch (IOException e) {
+ getLogger().log(Level.SEVERE, "Could not start metrics", e);
+ }
+ // Spigot end
loadPlugins();
enablePlugins(PluginLoadOrder.STARTUP);
}
@@ -222,7 +243,7 @@ public final class CraftServer implements Server {
return (File) console.options.valueOf("bukkit-settings");
}
- private void saveConfig() {
+ public void saveConfig() { // Spigot private -> public
try {
configuration.save(getConfigFile());
} catch (IOException ex) {
@@ -526,6 +547,7 @@ public final class CraftServer implements Server {
((DedicatedServer) console).propertyManager = config;
+ ((SimplePluginManager) pluginManager).useTimings(configuration.getBoolean("settings.plugin-profiling")); // Spigot
boolean animals = config.getBoolean("spawn-animals", console.getSpawnAnimals());
boolean monsters = config.getBoolean("spawn-monsters", console.worlds.get(0).difficulty > 0);
int difficulty = config.getInt("difficulty", console.worlds.get(0).difficulty);
@@ -591,6 +613,7 @@ public final class CraftServer implements Server {
"This plugin is not properly shutting down its async tasks when it is being reloaded. This may cause conflicts with the newly loaded version of the plugin"
));
}
+ Spigot.initialize(this, commandMap, configuration); // Spigot
loadPlugins();
enablePlugins(PluginLoadOrder.STARTUP);
enablePlugins(PluginLoadOrder.POSTWORLD);
@@ -1039,11 +1062,8 @@ public final class CraftServer implements Server {
return count;
}
+ // Spigot start
public OfflinePlayer getOfflinePlayer(String name) {
- return getOfflinePlayer(name, true);
- }
-
- public OfflinePlayer getOfflinePlayer(String name, boolean search) {
OfflinePlayer result = getPlayerExact(name);
String lname = name.toLowerCase();
@@ -1051,17 +1071,7 @@ public final class CraftServer implements Server {
result = offlinePlayers.get(lname);
if (result == null) {
- if (search) {
- WorldNBTStorage storage = (WorldNBTStorage) console.worlds.get(0).getDataManager();
- for (String dat : storage.getPlayerDir().list(new DatFileFilter())) {
- String datName = dat.substring(0, dat.length() - 4);
- if (datName.equalsIgnoreCase(name)) {
- name = datName;
- break;
- }
- }
- }
-
+ // Spigot end
result = new CraftOfflinePlayer(this, name);
offlinePlayers.put(lname, result);
}
@@ -1199,7 +1209,7 @@ public final class CraftServer implements Server {
Set<OfflinePlayer> players = new HashSet<OfflinePlayer>();
for (String file : files) {
- players.add(getOfflinePlayer(file.substring(0, file.length() - 4), false));
+ players.add(getOfflinePlayer(file.substring(0, file.length() - 4))); // Spigot
}
players.addAll(Arrays.asList(getOnlinePlayers()));
@@ -1305,7 +1315,7 @@ public final class CraftServer implements Server {
public List<String> tabCompleteCommand(Player player, String message) {
List<String> completions = null;
try {
- completions = getCommandMap().tabComplete(player, message.substring(1));
+ completions = (commandComplete) ? getCommandMap().tabComplete(player, message.substring(1)) : null; // Spigot
} catch (CommandException ex) {
player.sendMessage(ChatColor.RED + "An internal error occurred while attempting to tab-complete this command");
getLogger().log(Level.SEVERE, "Exception when " + player.getName() + " attempted to tab complete " + message, ex);
@@ -1341,4 +1351,52 @@ public final class CraftServer implements Server {
public CraftItemFactory getItemFactory() {
return CraftItemFactory.instance();
}
+
+ // Spigot start
+ public void restart() {
+ try {
+ String startupScript = configuration.getString("settings.restart-script-location", "");
+ File file = new File(startupScript);
+ if (file.isFile()) {
+ System.out.println("Attempting to restart with " + startupScript);
+
+ // Kick all players
+ for (Player p : this.getOnlinePlayers()) {
+ ((org.bukkit.craftbukkit.entity.CraftPlayer) p).kickPlayer("Server is restarting", true);
+ }
+ // Give the socket a chance to send the packets
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ }
+ // Close the socket so we can rebind with the new process
+ this.getServer().ae().a();
+
+ // Give time for it to kick in
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ }
+
+ // Actually shutdown
+ try {
+ this.getServer().stop();
+ } catch (Throwable t) {
+ }
+
+ String os = System.getProperty("os.name").toLowerCase();
+ if (os.contains("win")) {
+ Runtime.getRuntime().exec("cmd /c start " + file.getPath());
+ } else {
+ Runtime.getRuntime().exec(file.getPath());
+ }
+ System.exit(0);
+ } else {
+ System.out.println("Startup script '" + startupScript + "' does not exist!");
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ // Spigot end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 6e364b1..45217cd 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -75,7 +75,81 @@ public class CraftWorld implements World {
if (server.chunkGCPeriod > 0) {
chunkGCTickCount = rand.nextInt(server.chunkGCPeriod);
}
- }
+ // Spigot Start
+ org.bukkit.configuration.file.YamlConfiguration configuration = server.configuration;
+ String name;
+ if (world.worldData == null || world.worldData.getName() == null) {
+ name = "default";
+ } else {
+ name = world.worldData.getName().replaceAll(" ", "_");
+ }
+
+ //load defaults first
+ growthPerTick = configuration.getInt("world-settings.default.growth-chunks-per-tick", growthPerTick);
+ itemMergeRadius = configuration.getDouble("world-settings.default.item-merge-radius", itemMergeRadius);
+ expMergeRadius = configuration.getDouble("world-settings.default.exp-merge-radius", expMergeRadius);
+ randomLightingUpdates = configuration.getBoolean("world-settings.default.random-light-updates", randomLightingUpdates);
+ mobSpawnRange = configuration.getInt("world-settings.default.mob-spawn-range", mobSpawnRange);
+ aggregateTicks = Math.max(1, configuration.getInt("world-settings.default.aggregate-chunkticks", aggregateTicks));
+
+ wheatGrowthModifier = configuration.getInt("world-settings.default.wheat-growth-modifier", wheatGrowthModifier);
+ cactusGrowthModifier = configuration.getInt("world-settings.default.cactus-growth-modifier", cactusGrowthModifier);
+ melonGrowthModifier = configuration.getInt("world-settings.default.melon-growth-modifier", melonGrowthModifier);
+ pumpkinGrowthModifier = configuration.getInt("world-settings.default.pumpkin-growth-modifier", pumpkinGrowthModifier);
+ sugarGrowthModifier = configuration.getInt("world-settings.default.sugar-growth-modifier", sugarGrowthModifier);
+ treeGrowthModifier = configuration.getInt("world-settings.default.tree-growth-modifier", treeGrowthModifier);
+ mushroomGrowthModifier = configuration.getInt("world-settings.default.mushroom-growth-modifier", mushroomGrowthModifier);
+
+ //override defaults with world specific, if they exist
+ growthPerTick = configuration.getInt("world-settings." + name + ".growth-chunks-per-tick", growthPerTick);
+ itemMergeRadius = configuration.getDouble("world-settings." + name + ".item-merge-radius", itemMergeRadius);
+ expMergeRadius = configuration.getDouble("world-settings." + name + ".exp-merge-radius", expMergeRadius);
+ randomLightingUpdates = configuration.getBoolean("world-settings." + name + ".random-light-updates", randomLightingUpdates);
+ mobSpawnRange = configuration.getInt("world-settings." + name + ".mob-spawn-range", mobSpawnRange);
+ aggregateTicks = Math.max(1, configuration.getInt("world-settings." + name + ".aggregate-chunkticks", aggregateTicks));
+
+ wheatGrowthModifier = configuration.getInt("world-settings." + name + ".wheat-growth-modifier", wheatGrowthModifier);
+ cactusGrowthModifier = configuration.getInt("world-settings." + name + ".cactus-growth-modifier", cactusGrowthModifier);
+ melonGrowthModifier = configuration.getInt("world-settings." + name + ".melon-growth-modifier", melonGrowthModifier);
+ pumpkinGrowthModifier = configuration.getInt("world-settings." + name + ".pumpkin-growth-modifier", pumpkinGrowthModifier);
+ sugarGrowthModifier = configuration.getInt("world-settings." + name + ".sugar-growth-modifier", sugarGrowthModifier);
+ treeGrowthModifier = configuration.getInt("world-settings." + name + ".tree-growth-modifier", treeGrowthModifier);
+ mushroomGrowthModifier = configuration.getInt("world-settings." + name + ".mushroom-growth-modifier", mushroomGrowthModifier);
+
+ server.getLogger().info("-------------- Spigot ----------------");
+ server.getLogger().info("-------- World Settings For [" + name + "] --------");
+ server.getLogger().info("Growth Per Chunk: " + growthPerTick);
+ server.getLogger().info("Item Merge Radius: " + itemMergeRadius);
+ server.getLogger().info("Experience Merge Radius: " + expMergeRadius);
+ server.getLogger().info("Random Lighting Updates: " + randomLightingUpdates);
+ server.getLogger().info("Mob Spawn Range: " + mobSpawnRange);
+ server.getLogger().info("Aggregate Ticks: " + aggregateTicks);
+ server.getLogger().info("Wheat Growth Modifier: " + wheatGrowthModifier);
+ server.getLogger().info("Cactus Growth Modifier: " + cactusGrowthModifier);
+ server.getLogger().info("Melon Growth Modifier: " + melonGrowthModifier);
+ server.getLogger().info("Pumpkin Growth Modifier: " + pumpkinGrowthModifier);
+ server.getLogger().info("Sugar Growth Modifier: " + sugarGrowthModifier);
+ server.getLogger().info("Tree Growth Modifier: " + treeGrowthModifier);
+ server.getLogger().info("Mushroom Growth Modifier: " + mushroomGrowthModifier);
+ server.getLogger().info("-------------------------------------------------");
+ // Spigot end
+ }
+ // Spigot Start
+ public int growthPerTick = 650;
+ public double itemMergeRadius = 3;
+ public double expMergeRadius = 3;
+ public boolean randomLightingUpdates = false;
+ public int mobSpawnRange = 4;
+ public int aggregateTicks = 4;
+ //Crop growth rates:
+ public int wheatGrowthModifier = 100;
+ public int cactusGrowthModifier = 100;
+ public int melonGrowthModifier = 100;
+ public int pumpkinGrowthModifier = 100;
+ public int sugarGrowthModifier = 100;
+ public int treeGrowthModifier = 100;
+ public int mushroomGrowthModifier = 100;
+ // Spigot end
public Block getBlockAt(int x, int y, int z) {
return getChunkAt(x >> 4, z >> 4).getBlock(x & 0xF, y & 0xFF, z & 0xF);
diff --git a/src/main/java/org/bukkit/craftbukkit/Spigot.java b/src/main/java/org/bukkit/craftbukkit/Spigot.java
new file mode 100644
index 0000000..9e8b12e
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/Spigot.java
@@ -0,0 +1,25 @@
+package org.bukkit.craftbukkit;
+
+import org.bukkit.command.SimpleCommandMap;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+public class Spigot {
+ public static void initialize(CraftServer server, SimpleCommandMap commandMap, YamlConfiguration configuration) {
+ commandMap.register("bukkit", new org.bukkit.craftbukkit.command.RestartCommand("restart"));
+ commandMap.register("bukkit", new org.bukkit.craftbukkit.command.TicksPerSecondCommand("tps"));
+
+ org.bukkit.craftbukkit.util.WatchdogThread.startThread(configuration.getInt("settings.timeout-time", 180), configuration.getBoolean("settings.restart-on-crash", false));
+
+ server.whitelistMessage = configuration.getString("settings.whitelist-message", server.whitelistMessage);
+ server.stopMessage = configuration.getString("settings.stop-message", server.stopMessage);
+ server.logCommands = configuration.getBoolean("settings.log-commands", true);
+ server.ipFilter = configuration.getBoolean("settings.filter-unsafe-ips", false);
+ server.commandComplete = configuration.getBoolean("settings.command-complete", true);
+ server.spamGuardExclusions = configuration.getStringList("settings.spam-exclusions");
+
+ if (server.chunkGCPeriod == 0) {
+ server.getLogger().severe("[Spigot] You should not disable chunk-gc. Resetting period-in-ticks to 600 ticks.");
+ server.chunkGCPeriod = 600;
+ }
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
index 48cf5ba..1d4764c 100644
--- a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
+++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
@@ -40,7 +40,7 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider<QueuedChu
// See if someone already loaded this chunk while we were working on it (API, etc)
if (queuedChunk.provider.chunks.containsKey(queuedChunk.coords)) {
// Make sure it isn't queued for unload, we need it
- queuedChunk.provider.unloadQueue.remove(queuedChunk.coords);
+ queuedChunk.provider.unloadQueue.remove(x, z);
return;
}
diff --git a/src/main/java/org/bukkit/craftbukkit/command/RestartCommand.java b/src/main/java/org/bukkit/craftbukkit/command/RestartCommand.java
new file mode 100644
index 0000000..fba4b4a
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/command/RestartCommand.java
@@ -0,0 +1,24 @@
+package org.bukkit.craftbukkit.command;
+
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftServer;
+
+public class RestartCommand extends Command {
+ public RestartCommand(String name) {
+ super(name);
+ this.description = "Restarts the server";
+ this.usageMessage = "/restart";
+ this.setPermission("bukkit.command.restart");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, String currentAlias, String[] args) {
+ if (!testPermission(sender)) return true;
+
+ ((CraftServer)Bukkit.getServer()).restart();
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/bukkit/craftbukkit/command/TicksPerSecondCommand.java b/src/main/java/org/bukkit/craftbukkit/command/TicksPerSecondCommand.java
new file mode 100644
index 0000000..e30615f
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/command/TicksPerSecondCommand.java
@@ -0,0 +1,35 @@
+package org.bukkit.craftbukkit.command;
+
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.ChatColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+public class TicksPerSecondCommand extends Command {
+
+ public TicksPerSecondCommand(String name) {
+ super(name);
+ this.description = "Gets the current ticks per second for the server";
+ this.usageMessage = "/tps";
+ this.setPermission("bukkit.command.tps");
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, String currentAlias, String[] args) {
+ if (!testPermission(sender)) return true;
+
+ double tps = (double) Math.round(MinecraftServer.currentTPS * 10) / 10;
+ ChatColor color;
+ if (tps > 19.2D) {
+ color = ChatColor.GREEN;
+ } else if (tps > 17.4D) {
+ color = ChatColor.YELLOW;
+ } else {
+ color = ChatColor.RED;
+ }
+
+ sender.sendMessage(ChatColor.GOLD + "[TPS] " + color + tps);
+
+ return true;
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index f0e24d2..1c61830 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -212,10 +212,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
}
public void kickPlayer(String message) {
+ // Spigot start
+ kickPlayer(message, false);
+ }
+
+ public void kickPlayer(String message, boolean async){
if (getHandle().playerConnection == null) return;
+ if (!async && !Bukkit.isPrimaryThread()) throw new IllegalStateException("Cannot kick player from asynchronous thread!"); // Spigot
getHandle().playerConnection.disconnect(message == null ? "" : message);
}
+ // Spigot end
public void setCompassTarget(Location loc) {
if (getHandle().playerConnection == null) return;
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ExceptionHandler.java b/src/main/java/org/bukkit/craftbukkit/util/ExceptionHandler.java
new file mode 100644
index 0000000..392155e
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/util/ExceptionHandler.java
@@ -0,0 +1,31 @@
+package org.bukkit.craftbukkit.util;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.CraftServer;
+
+public class ExceptionHandler implements UncaughtExceptionHandler {
+
+ public void uncaughtException(Thread t, Throwable e) {
+ Logger log = ((CraftServer) Bukkit.getServer()).getLogger();
+ log.log(Level.SEVERE, "The server has crashed!");
+ log.log(Level.SEVERE, "Please report this to md_5!");
+ log.log(Level.SEVERE, "Begin Exception Trace:");
+ log.log(Level.SEVERE, "");
+ StackTraceElement[] stack = e.getStackTrace();
+ for (int line = 0; line < stack.length; line++) {
+ log.log(Level.SEVERE, " " + stack[line].toString());
+ }
+ log.log(Level.SEVERE, "End Exception Trace:");
+ log.log(Level.SEVERE, "");
+ log.log(Level.SEVERE, "Begin Thread Stack Trace:");
+ stack = t.getStackTrace();
+ for (int line = 0; line < stack.length; line++) {
+ log.log(Level.SEVERE, " " + stack[line].toString());
+ }
+ log.log(Level.SEVERE, "End Exception Trace:");
+ log.log(Level.SEVERE, "");
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ExceptionReporter.java b/src/main/java/org/bukkit/craftbukkit/util/ExceptionReporter.java
new file mode 100644
index 0000000..1d0e284
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/util/ExceptionReporter.java
@@ -0,0 +1,26 @@
+package org.bukkit.craftbukkit.util;
+
+import org.bukkit.Bukkit;
+
+public final class ExceptionReporter {
+
+ public static void handle(Throwable t, String... messages) {
+ for (String message : messages) {
+ Bukkit.getLogger().severe(message);
+ }
+ Bukkit.getLogger().severe("Spigot recommends you report this to md_5");
+ Bukkit.getLogger().severe("");
+ Bukkit.getLogger().severe("Spigot version: " + Bukkit.getBukkitVersion());
+ Bukkit.getLogger().severe("Exception Trace Begins:");
+ StackTraceElement[] stack = t.getStackTrace();
+ for (int line = 0; line < stack.length; line++) {
+ Bukkit.getLogger().severe(" " + stack[line].toString());
+ }
+ Bukkit.getLogger().severe("Exception Trace Ends.");
+ Bukkit.getLogger().severe("");
+ }
+
+ public static void handle(Throwable t) {
+ handle(t, "Spigot has encountered an unexpected exception!");
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/FlatMap.java b/src/main/java/org/bukkit/craftbukkit/util/FlatMap.java
new file mode 100644
index 0000000..e8a7725
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/util/FlatMap.java
@@ -0,0 +1,34 @@
+package org.bukkit.craftbukkit.util;
+
+public class FlatMap<V> {
+
+ private static final int FLAT_LOOKUP_SIZE = 512;
+ private final Object[][] flatLookup = new Object[FLAT_LOOKUP_SIZE * 2][FLAT_LOOKUP_SIZE * 2];
+
+ public void put(long msw, long lsw, V value) {
+ long acx = Math.abs(msw);
+ long acz = Math.abs(lsw);
+ if (acx < FLAT_LOOKUP_SIZE && acz < FLAT_LOOKUP_SIZE) {
+ flatLookup[(int) (msw + FLAT_LOOKUP_SIZE)][(int) (lsw + FLAT_LOOKUP_SIZE)] = value;
+ }
+ }
+
+ public void put(long key, V value) {
+ put(LongHash.msw(key), LongHash.lsw(key), value);
+
+ }
+
+ public V get(long msw, long lsw) {
+ long acx = Math.abs(msw);
+ long acz = Math.abs(lsw);
+ if (acx < FLAT_LOOKUP_SIZE && acz < FLAT_LOOKUP_SIZE) {
+ return (V) flatLookup[(int) (msw + FLAT_LOOKUP_SIZE)][(int) (lsw + FLAT_LOOKUP_SIZE)];
+ } else {
+ return null;
+ }
+ }
+
+ public V get(long key) {
+ return get(LongHash.msw(key), LongHash.lsw(key));
+ }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java
index 22c96c5..3f1617d 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/LongHashSet.java
@@ -31,6 +31,8 @@ public class LongHashSet {
private int elements;
private long[] values;
private int modCount;
+ private static final Object PRESENT = new Object();
+ private final FlatMap<Object> flat = new FlatMap<Object>();
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<V> implements Cloneable, Serializable {
private transient V[][] values;
private transient int modCount;
private transient int size;
+ private final FlatMap<V> flat = new FlatMap<V>(); // Spigot
public LongObjectHashMap() {
initialize();
@@ -61,6 +62,8 @@ public class LongObjectHashMap<V> 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<V> 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<V> 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;
+
+/**
+ * <p> The metrics class obtains data about a plugin and submits statistics
+ * about it to the metrics backend. </p> <p> Public methods provided by this
+ * class: </p>
+ * <code>
+ * Graph createGraph(String name); <br/>
+ * void addCustomData(Metrics.Plotter plotter); <br/>
+ * void start(); <br/>
+ * </code>
+ */
+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<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
+ /**
+ * 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<Graph> 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<Graph> 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;
+ }
+ }
+
+ /**
+ * <p>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:</p>
+ * <code>
+ * String httpData = encode("guid") + '=' + encode("1234") + encodeDataPair("authors") + "..";
+ * </code>
+ *
+ * @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<Plotter> plotters = new LinkedHashSet<Plotter>();
+
+ 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 <b>unmodifiable</b> set of the plotter objects in the graph
+ *
+ * @return
+ */
+ public Set<Plotter> 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<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
+ Iterator<Entry<Thread, StackTraceElement[]>> i = traces.entrySet().iterator();
+ while (i.hasNext()) {
+ Entry<Thread, StackTraceElement[]> entry = i.next();
+ Thread thread = entry.getKey();
+ if (thread.getState() != State.WAITING) {
+ System.err.println(LINE);
+
+ log.log(Level.SEVERE, "Current Thread: " + thread.getName());
+ log.log(Level.SEVERE, " PID: " + thread.getId() + " | Alive: " + thread.isAlive() + " | State: " + thread.getState());
+ log.log(Level.SEVERE, " Stack:");
+ StackTraceElement[] stack = entry.getValue();
+ for (int line = 0; line < stack.length; line++) {
+ log.log(Level.SEVERE, " " + stack[line].toString());
+ }
+ }
+ }
+ System.err.println(LINE);
+
+ if (this.restart) {
+ ((CraftServer) Bukkit.getServer()).restart();
+ }
+
+ //Give up
+ this.interrupt();
+ }
+ }
+ }
+}
diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml
index 61a95e3..5262ae1 100644
--- a/src/main/resources/configurations/bukkit.yml
+++ b/src/main/resources/configurations/bukkit.yml
@@ -25,6 +25,36 @@ settings:
query-plugins: true
deprecated-verbose: default
shutdown-message: Server closed
+ restart-script-location: start.bat
+ timeout-time: 180
+ restart-on-crash: false
+ filter-unsafe-ips: false
+ whitelist-message: You are not white-listed on this server!
+ log-commands: true
+ command-complete: true
+ spam-exclusions:
+ - /skill
+world-settings:
+ default:
+ growth-chunks-per-tick: 650
+ mob-spawn-range: 4
+ item-merge-radius: 3.5
+ exp-merge-radius: 3.5
+ random-light-updates: false
+ aggregate-chunkticks: 4
+ wheat-growth-modifier: 100
+ cactus-growth-modifier: 100
+ melon-growth-modifier: 100
+ pumpkin-growth-modifier: 100
+ sugar-growth-modifier: 100
+ tree-growth-modifier: 100
+ mushroom-growth-modifier: 100
+ world:
+ growth-chunks-per-tick: 1000
+ world_nether:
+ growth-chunks-per-tick: 0
+ random-light-updates: true
+ water-creatures-per-chunk: 0
spawn-limits:
monsters: 70
animals: 15
--
1.8.1-rc2