Paper/CraftBukkit-Patches/0001-Spigot-changes.patch
md_5 a65fedb3f8 Mob spawning issues - 'fix'. See below for ideal reasoning from MikePrimm, however until ideal reasoning we must live with the CraftBukkit / Vanilla behaviour since this causes far too many issues.
IIRC, the main item I was driving towards was a consequence of persistent passive mobs - specifically, the fact that allowing a population limit of N (independent of view distance, which is what vanilla does) when your view distance limits actual loaded chunks to a much smaller area than default (say a view of 4, which would be 9 x 9 chunks loaded per player - and spawnable - versus default, which is 8 radius spawn, or 17 x 17 chunks) tends to result in more mobs per chunk. For persistent mobs, this is bad - since they count for server load and for population just by being loaded, versus being despawned beyond 128 blocks (8 chunk radius - unconditional of view distance, as well) - so they can cause the population limit to be reached more easily, cutting off spawning nearer to players. The goal was to make it so that the mobs-per-loaded-chunk was about the same for all view distances, versus having low view distances cause higher mob concentrations.

    Now, all of this assumes that loaded chunks beyond those around players are modest (since they could contain passive mobs that would count towards the limits) - which they should be, except that recent CBs leak chunks like mad, from what I can see (chunk-gc has become more required than optional), and I think there may be some issues with even hostile mobs "lurking" around - possibly even after their chunks are unloaded. Anything that causes more mobs to be in places players don't see them is going to drive population limit issues, and resulting low spawn behaviors. The trick for us, trying to make big servers as practical as possible, is to shift the math the other way - given low view distances, how to best make sure that folks get reasonable spawn behavior while minimizing the time/resources spent on the server on mobs that don't help that. Realistically, I think we need to analyse the mob demographics better - especially as it relates to lower view distances (our changes have no net impact on view distances above 7) - particularly to understand how the proportion of "useful" mobs is working out (ones close enough to players to be considered contributing to game play). One thought is to manage the population limit based on mobs that are 'tickable' - if they aren't close enough to be ticking, they aren't interesting. This is likely a big issue for view distance 5 folks, since mobs cannot spawn closer than 24 (approx 1 chunk radius, given that middle chunk is 0), and don't tick when any of the chunks within a 2 chunk radius aren't loaded) - so, effectively, they "live" within a zone of 7 x 7 with the middle 3 x 3 removed (so, about 40 chunks) out of a total load zone of 11 x 11 (121) - so about 2/3 of the area containing mobs has idle mobs. Normal view distance would result in mobs ticking as far out as they can spawn (radius 8 versus a load radius of 11), so 100% of the mobs that spawn are ticking when they spawn, and all hostile mobs that are loaded are ticking (since they always despawn beyond 128 blocks / 8 chunks from a player). One interesting thought would be to limit the chunks we spawn mobs in to those where they would be ticking initially (that is, view-distance minus 2 or 3).
2013-01-29 16:57:06 +11:00

2493 Zeilen
104 KiB
Diff

From 47c9f07d091d4669fd669569ee924ba083c19b21 Mon Sep 17 00:00:00 2001
From: md_5 <md_5@live.com.au>
Date: Tue, 29 Jan 2013 16:51:35 +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 +-
.../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 ++
39 files changed, 1424 insertions(+), 134 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/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